joe 发表于 2021-8-16 09:50:31

chisel的测试

chisel的测试有二种,一种是利用scalas的测试(assert)来验证chisel级别的代码逻辑有没有错误,另一种就是利用chisel库中的peek和poke,给module的端口加激励,查看信号值,并交给下游的verilator来仿真,产生波形,
这种方式比较简单,类似于verilog中的testbench,适合小型电路仿真,对于大型项目,还是生成verilog次给专业的EDA工具用UVM进行验证较好。结合上一帖的例子,我在此介绍第二种方法
先编写一个类AdderTests,它要接收一个待测试模块类型(c:Adder)的参数,并且extends PeekPokeTester(c),将AdderTests类的构造器参数c传给父类peekPokeTester。
查看源码:abstract class PeekPokeTester[+T <: MultiIOModule]{
...
def step(n:Int)...
def poke(signal:T,value:Int)....
def peek(singal:T):BigInt...
def expect(signal:T,expected:BigInt,msg:=>String=""):Boolean...
...
可以看到PeekPokeTester类中有很多测试方法,但常用的是step/poke/peek/expect这四个。
1)poke(端口,激励值)的语法形式进行端口测试,比如帖子中的例子poke(c.io.A,rnd0),从源码中def poke的signal:T所属的参型限制T<:Element:Pokeable来看,val A = Input(UInt(n.W))的类型是UInt,UInt是Element的子类,
另外,在同一个chisel3.iotesters包下,object Pokeable中有隐式对象:implicit object RuntimePokeable extends Pokeable,所以poke(c.io.A,rnd0)语法是正确的,功能是激励值为rnd0:Int,待测试端口是io.c.A,通过poke(c.io.A,rnd0)就将端口仿真数据设置成功。
2)step(n:Int)循环进行n步, 在每一步,寄存器和存储器都被推进,所有其他元素都重新计算(step最终还是InterpretiveTester的step循环执行FirrtlTerp的cycle方法。可以猜想出来:FirrtlTerp中cycle肯定是按照.fir文件进行执行,因为.fir文件最后还是由firrtl编译器编译出.v文件,所以从逻辑上来说就是对.v中代码进行仿真,至于说是不是一对一的step多少步,估计应该不是完全的一对一的关系)
3)expect方法在PeekPokeTester.scala中的源码是:def expect(signal:T,expected:BigInt,msg: =>String=""):Boolean...,它最终执行的是FirrtlTerpBackend中的expect(看源码可以发现采用的是match模式匹配的类型模式case port:Element,匹配成功之后会通过InterperetiveTester的peek(name)获取这个io端口的值,然后再判断与期待值expect是否一致,不一致的话则将msg显示出来),也就是说expect对一个Element类型的信号,期待其值与某个给定的BigInt类型的参考值是一样的,如果不一致则显示msg信息。
4)peek 获取某个信号的值:
最终也是执行InterpretiveTester中的peek:
def peek(name: String): BigInt = {
    if(interpreter.checkStopped(s"peek($name)")) return 0

    interpreter.getValue(name) match {
      case ConcreteUInt(value, _, _) => value
      case ConcreteSInt(value, _, _) => value
      case _ =>
      fail(new InterpreterException(s"Error:peek($name) value not found"))
    }
}
通过模式匹配中的构造模式获取待测试信号的值。








页: [1]
查看完整版本: chisel的测试