启动仿真(Boot a Simulation)
一、介绍(Introduction)
这是一个硬件定义+testbench的例子:
//你的硬件顶层
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 => ...} |
一直运行仿真知道所有线程完成或卡住 |
例如:
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)
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个时钟周期后终止仿真:
val period = 10
dut.clockDomain.forkStimulus(period)
SimTimeout(1000 * period)
访问仿真信号(Accessing signals of the simulation)
一、读写信号(Read and write signals)
每个顶层的接口信号都可以通过Scala读写:
语句 | 描述 |
---|---|
Bool.toBoolean |
读出硬件Bool 信号作为ScalaBoolean 值 |
Bits /UInt /SInt.toInt |
读出硬件BitVector 信号作为ScalaInt 值 |
Bits /UInt /SInt.toLong |
读出硬件BitVector 信号作为ScalaLong 值 |
Bits /UInt /SInt.toBigInt |
读出硬件BitVector 信号作为ScalaBigInt 值 |
SpinalEnumCraft.toEnum |
读出硬件SpinalEnumCraft 信号作为ScalaSpinalEnumElement 值 |
Bool #= Boolean |
用ScalaBoolean 值赋值给硬件Bool 信号 |
Bits /UInt /SInt #= Int |
用ScalaInt 值赋值给硬件BitVector 信号 |
Bits /UInt /SInt #= Long |
用ScalaLong 值赋值给硬件BitVector 信号 |
Bits /UInt /SInt #= BigInt |
用ScalaBigInt 值赋值给硬件BitVector 信号 |
SpinalEnumCraft #= SpinalEnumElement |
用ScalaSpinalEnumElement 值赋值给硬件SpinalEnumCraft 信号 |
Data.randomize() |
给SpinalHDL值赋随机值 |
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
标签:
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)
}
}
}
}
或者你可以在完成对顶层例化后, 在仿真时增加标签
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)
}
}
}
}