## 启动仿真(Boot a Simulation) ### 一、介绍(Introduction) 这是一个硬件定义+testbench的例子: ```Scala //你的硬件顶层 import spinal.core._ class TopLevel extends Component { ... } //你的测试顶层 import spinal.sim._ import spinal.core.sim._ object DutTests { def main(args: Array[String]): Unit = { SimConfig.withWave.compile(new TopLevel).doSim{ dut => //仿真代码 } } } ``` ### 二、配置(Configuration) `SimConfig`会返回默认的仿真配置实例, 通过该实例能调用各种函数对仿真进行配置: | 语句 | 描述 | | :------------------------: | :-------------------------------------------------: | | `withWave` | 启用仿真波形捕获(默认模式) | | `withVcdWave` | 启用仿真波形捕获(VCD文本模式) | | `withFstWave` | 启用仿真波形捕获(二进制FST模式) | | `withConfig(SpinalConfig)` | 指定用来产生硬件的`SpinalConfig` | | `allOptimisation` | 启用所有的RTL编译优化来减少仿真时间(会增加编译时间) | | `workspacePath(path)` | 改变生成的sim文件的文件夹位置 | | `withVerilator` | 用Verilator作为仿真后端(默认) | | `withGhdl` | 用withGhdl作为仿真后端 | | `withIVerilog` | 用IVerilog作为仿真后端 | | `withVCS` | 用VCS作为仿真后端 | 之后你可以调用`compile(rtl)`函数对硬件编译并为仿真器做准备。这个函数会返回`SimCompiled`实例。 在这个`SimCompiled`实例中你可以用以下函数运行你的仿真: | 语句 | 描述 | | :---------------------------------------------: | :----------------------------------------------------------: | | `doSim[(simName[, seed])]{dut => ...}` | 一直运行仿真指导主线程完成(不等待分支线程)或直到所有线程卡住 | | `doSimUntilVoid[(simName[, seed])]{dut => ...}` | 一直运行仿真知道所有线程完成或卡住 | 例如: ```Scala val spinalConfig = SpinalConfig(defaultClockDomainFrequency = FixedFrequency(10 MHz)) SimConfig .withConfig(spinalConfig) .withWave .allOptimisation .workspacePath("~/tmp") .compile(new TopLevel) .doSim { dut => //仿真代码 } ``` 注意默认情况下, 仿真文件会被替换成`simWorkspace/xxx`文件夹。你可以通过设置`SPINALSIM_WROKSPACE`环境变量重写simWorkspace的位置。 ### 三、在同一硬件上运行多个测试用例(Running multiple tests on the same hardware) ```Scala val compiled = SimConfig.withWave.compile(new Dut) compiled.doSim("testA") { dut => //仿真代码 } compiled.doSim("testB") { dut => //仿真代码 } ``` ### 四、从线程中报告仿真的成功或失败(Throw Success or Failure of the simulation from a thread) 在仿真的任何一个时刻你都可以调用`simSuccess`或`simFailure`来终止它。 如果仿真太大, 很可能会产生仿真失败, 例如因为testbench在等待从未发生的条件的判断。为此, 调用`SimTimeout(maxDuration)`其中`maxDuration`所仿真应该会发生失败的时间(以仿真时间为单位)。 例如, 在1000个时钟周期后终止仿真: ```Scala val period = 10 dut.clockDomain.forkStimulus(period) SimTimeout(1000 * period) ``` ## 访问仿真信号(Accessing signals of the simulation) ### 一、读写信号(Read and write signals) 每个顶层的接口信号都可以通过Scala读写: | 语句 | 描述 | | :------------------------------------: | :---------------------------------------------------------: | | `Bool.toBoolean` | 读出硬件`Bool`信号作为Scala`Boolean`值 | | `Bits`/`UInt`/`SInt.toInt` | 读出硬件`BitVector`信号作为Scala`Int`值 | | `Bits`/`UInt`/`SInt.toLong` | 读出硬件`BitVector`信号作为Scala`Long`值 | | `Bits`/`UInt`/`SInt.toBigInt` | 读出硬件`BitVector`信号作为Scala`BigInt`值 | | `SpinalEnumCraft.toEnum` | 读出硬件`SpinalEnumCraft`信号作为Scala`SpinalEnumElement`值 | | `Bool #= Boolean` | 用Scala`Boolean`值赋值给硬件`Bool`信号 | | `Bits`/`UInt`/`SInt #= Int` | 用Scala`Int`值赋值给硬件`BitVector`信号 | | `Bits`/`UInt`/`SInt #= Long` | 用Scala`Long`值赋值给硬件`BitVector`信号 | | `Bits`/`UInt`/`SInt #= BigInt` | 用Scala`BigInt`值赋值给硬件`BitVector`信号 | | `SpinalEnumCraft #= SpinalEnumElement` | 用Scala`SpinalEnumElement`值赋值给硬件`SpinalEnumCraft`信号 | | `Data.randomize()` | 给SpinalHDL值赋随机值 | ```Scala dut.io.a #= 42 dut.io.a #= 42l dut.io.a #= BigInt("101010", 2) dut.io.a #= BigInt("0123456789ABCDEF", 16) println(dut.io.b.toInt) ``` ### 二、在模块层次访问信号(Accessing signals inside the component's hierarchy) 为了访问在模块层次内部的信号, 你应该先把信号设置成`simPublic`。 你可以直接在硬件描述中增加`simPublic`标签: ```Scala object SimAccessSubSignal { import spinal.core.sim._ class TopLevel extends Component { val counter = Reg(UInt(8 bits)) init(0) simPublic() //这里给counter寄存器增加simPublic标签让其可被访问 counter := counter + 1 } def main(args: Array[String]) { SimConfig.compile(new TopLevel).doSim{dut => dut.clockDomain.forkStimulus(10) for(i <- 0 to 3) { dut.clockDomain.waitSampling() println(dut.counter.toInt) } } } } ``` 或者你可以在完成对顶层例化后, 在仿真时增加标签 ```Scala object SimAccessSubSignal { import spinal.core.sim._ class TopLevel extends Component { val counter = Reg(UInt(8 bits)) init(0) counter := counter + 1 } def main(args: Array[String]) { SimConfig.compile { val dut = new TopLevel dut.counter.simPublic() dut }.doSim{dut => dut.clockDomain.forkStimulus(10) for(i <- 0 to 3) { dut.clockDomain.waitSampling() println(dut.counter.toInt) } } } } ```