十二 · 2021年11月05日

SpinalHDL—仿真信号读写

本文就SpinalHDL中仿真中最后一部分信号读写做梳理。

#=

对于仿真信号的驱动,在SpinalHDL里通过“#=”方法实现:

image.png

值得注意的是当我们的设计里有一个输入信号为:

val a=in UInt(32 bits)

在进行仿真信号驱动时,下面的写法会报错的:

a#=0xffffffff

纠其原因,Scala中默认数据类型为Int,而且像Java一样没有无符号数的概念,因此这里0xffffffff会被当成Int变量,而此时为负数,因而无法直接赋值。此处当如此写:

a#=0xffffffffL

采用Long型进行赋值,以避免这种现象发生。

toXXX

对于信号的读取,SpinalHDL中提供了“toXXX”函数:

image.png

和在写驱动时一样,对于信号:

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() // Here we add the simPublic tag on the counter register to make it visible
    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中仿真环境书写方式。

END

作者:玉骐
原文链接:https://mp.weixin.qq.com/s/KBEU8Ht9pkZH9wURriw6UQ
微信公众号:
 title=

推荐阅读

更多SpinalHDL技术干货请关注Spinal FPGA专栏。
推荐阅读
关注数
1581
内容数
133
用SpinalHDL提升生产力
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息