对于仿真信号的驱动,在SpinalHDL里通过“#=”方法实现:
值得注意的是当我们的设计里有一个输入信号为:
val a=in UInt(32 bits)
在进行仿真信号驱动时,下面的写法会报错的:
a
纠其原因,Scala中默认数据类型为Int,而且像Java一样没有无符号数的概念,因此这里0xffffffff会被当成Int变量,而此时为负数,因而无法直接赋值。此处当如此写:
a
采用Long型进行赋值,以避免这种现象发生。
toXXX
对于信号的读取,SpinalHDL中提供了“toXXX”函数:
和在写驱动时一样,对于信号:
val b=out UInt(32 bits)
在仿真中读取信号值时应:
b.toLong
而非:
b.toInt
采用toInt时当信号为0xffffffff时会被当成负数。
simPublic
在进行仿真时,我们的仿真程序是一个单独的进程、而DUT是另外一个进程交由仿真器执行,两者通过进程间通信来进行信号驱动,因而对于DUT内部的信号,我们在仿真时测试程序是无法访问的,如果需要对DUT内部信号进行访问,需要在我们的DUT里对待抓取的内部信号添加simPublic声明:
class TopLevel extends Component {
val counter = Reg(UInt(8 bits)) init(0) simPublic()
counter := counter + 1
}
如此,我们即可在仿真代码里访问counter:
println(dut.counter.toInt)
阻塞?非阻塞?
在SpinalHDL的仿真代码里,对于阻塞和非阻塞并没有明确的界定,而至于仿真器内部的时隙调度,个人一直是看过明白,看后即忘。为了抛开这个烦恼,个人使用下来的体验就是SpinalHDL中的测试代码对于信号的驱动都是立即生效的,类似于阻塞赋值。和waitSampling()搭配使用产生类似“非阻塞”效果。
像下面这段代码:
dut.clockDomain.forkStimulus(10)
dut.clockDomain.waitSampling()
a#=3
dut.clockDomain.waitSampling()
a#=4
这里第三行代码执行时有效沿刚刚过去,此时立即对信号a赋值为3,然后等待下一个时钟有效沿到来(第四行),第四行代码执行完毕后时钟有效沿刚刚过去,此时对信号a赋值为4,依次往复,信号总是在时钟沿到来后的时刻送达,实现类似“非阻塞赋值”的效果(对仿真器调度机制比较熟悉的小伙伴可以深入研究下)。
总结起来,假定我们的时钟偏移为0,这里我们相当于为两个寄存器之间的数据链路引入了T_dataDelay=0+。
正因为该延迟的引入,假定输出信号b有效时valid为高电平,那么我们对信号b的仿真读取可以这么写:
dut.clockDomain.waitSamplingWhere(valid.toBoolean)
dut.b.toInt
在执行dut.b.toInt诚然时钟有效沿刚刚过去,但由于T_dataDelay=0+的缘故,因此在这一时刻信号b还未来得及变化,因而在执行第二行读取信号b时仍可正常读取。
一家之言,底层调度及仿真器的调度原理超过个人的能力范围,有对此道精通的小伙伴欢迎多多交流。
小结
至此,对于SpinalHDL中有关仿真的总结梳理基本完毕,下文将以一个小的例子为demo构建一个完整的个人觉得在不错的在SpinalHDL中仿真环境书写方式。
原作者:玉骐
|