Spinal 仿真相关
toBoolean() 函数
waitUntil(cond), cond不能是函数的返回值的toBoolean
1 | waitUntil(axi4.aw.fire.toBoolean) // Wrong |
亦或者说,由于fire的定义如下:
1 | def fire: Bool = aw.valid & aw.ready |
因此,不能对这一无括号的函数使用toBoolean函数。
软件的归软件,硬件的归硬件
在Testbench里,不能调用那些用于做硬件描述的函数,比如上面的 fire()
函数,这个函数的本质是一段硬件描述,只能出现在component里面。再比如对Bits,UInt类等等,索引它的某一位,这一段依旧是硬件描述,不能出现在component之外的地方,比如testbench中。
我们只能对已经定义好的变量,使用 toBoolean()
这样的函数将其代表的数值结果读取到软件域。这一点是它和Verilog这类HVL所完全不同的特点。
waitUnitl()
类似于sv中的 @(event)
等待某个事件的触发。
大部分时候并不需要使用到这个函数。通常使用的是
1 | clockDomain.waitActiveEdgeWhere(cond :=> Boolean) |
这个函数的语义:一直等待到cond成立,并在成立后的第一个active edge退出等待。
所有的wait函数,只能在thread中使用,不能再callback中使用。
1 | // right |
等待某个延时 #10ps
可以使用函数sleep()
1 | sleep(10) |
fork
尽量不在 clockDomain.onsampling {…} 内部使用 fork
使用toX()函数,将硬件域的数据转换至软件域
做定点数相关的转换时,注意数据类型本身的位宽问题。比如
1 | val a_sint = 1 << bitCounts // when bit counts < 32 |
性能问题
SpinalSim使用Verilator仿真,分为三个阶段:
- verilate阶段:将Verilog文件翻译为cpp
- compile阶段:将cpp文件加上testbench的cpp文件编译为可执行文件
- simulation阶段:运行可执行文件,开始仿真过程
对于大设计,会导致以上三个阶段的运行速度不合理的慢,因此需要合理优化
1 Verilate阶段
数量巨大的register,以及Mux的sel信号的位宽太大(两位数)以及其他的一些问题可能会导致这一阶段所消耗的时间太长。
生成的Verilog网表大小太大也会导致这一阶段时间冗长。
解决办法:1. 改变你的电路结构,规避上面的问题。这些问题往往意味设计的不合理性。2. 使用层次化的verilate流程,但是spinalsim对此没有支持,不好做。
2 Compile阶段
默认情况下,spinalsim使用g++进行多线程编译,因此这一阶段的运行时间与工作站的CPU核数密切相关。尽量在多核系统上跑。
对于可能需要重复多次编译同样设计的问题,安装ccache,并将环境变量OBJCACHE指向ccache。这样可以缓存上次编译过的cpp内容,有效避免重复编译。
3 simulation阶段
默认情况下,spinalsim不会使用verilator的多线程仿真。需要使用simulation flag: --threads <cpu_num> --trace-threads <cpu_num>
。具体设置如下:
1 | SimConfig.addSimulationFlag("--threads 32 --trace-threads 32") |
这样可以使仿真可执行文件以多线程的形式运行。