启动仿真器
简介
下面是一个硬件定义+测试平台的示例:
import spinal.core._
// Identity takes n bits in a and gives them back in z
class Identity(n: Int) extends Component {
val io = new Bundle {
val a = in Bits(n bits)
val z = out Bits(n bits)
}
io.z := io.a
}
import spinal.core.sim._
object TestIdentity extends App {
// Use the component with n = 3 bits as "dut" (device under test)
SimConfig.withWave.compile(new Identity(3)).doSim{ dut =>
// For each number from 3'b000 to 3'b111 included
for (a <- 0 to 7) {
// Apply input
dut.io.a #= a
// Wait for a simulation time unit
sleep(1)
// Read output
val z = dut.io.z.toInt
// Check result
assert(z == a, s"Got $z, expected $a")
}
}
}
配置
SimConfig
将返回一个默认的仿真配置实例,您可以在该实例上调用多个函数来配置您的仿真过程:
语法 |
描述 |
---|---|
|
打开仿真波形捕获与存储(默认格式) |
|
打开仿真波形捕获与存储(VCD格式) |
|
打开仿真波形捕获与存储(FST 二进制格式) |
|
指定用于生成硬件的 |
|
启用所有 RTL 编译优化以减少仿真时间(会增加编译时间) |
|
更改生成的仿真文件存放的文件夹 |
|
使用 Verilator 作为后台仿真器(默认) |
|
使用GHDL作为后台仿真器 |
|
使用 Icarus Verilog 作为后台仿真器 |
|
使用Synopsys VCS作为后台仿真器 |
然后你可以调用 compile(rtl)
函数来编译硬件并预热仿真器。该函数将返回一个 SimCompiled
实例。
在此 SimCompiled
实例上,您可以使用以下函数进行仿真:
doSim[(simName[, seed])]{dut => /* main stimulus code */}
运行仿真,直到主线程运行完成并退出/返回。如果仿真完全卡住,它将检测并报告错误。只要例如时钟正在运行,仿真过程可以永远持续下去,因此建议使用
SimTimeout(cycles)
来限制可能的运行时间。doSimUntilVoid[(simName[, seed])]{dut => ...}
运行仿真,直到通过调用
simSuccess()
或simFailure()
结束。主激励线程可以继续或提前退出。只要有事件需要处理,仿真就会继续。如果完全卡住,仿真器将报告错误。
以下测试平台模板将使用以下顶层设计:
class TopLevel extends Component {
val counter = out(Reg(UInt(8 bits)) init (0))
counter := counter + 1
}
这是一个包含多个仿真配置的模板:
val spinalConfig = SpinalConfig(defaultClockDomainFrequency = FixedFrequency(10 MHz))
SimConfig
.withConfig(spinalConfig)
.withWave
.allOptimisation
.workspacePath("~/tmp")
.compile(new TopLevel)
.doSim { dut =>
SimTimeout(1000)
// Simulation code here
}
这是一个模板,其中仿真过程在主仿真线程执行完成后结束:
SimConfig.compile(new TopLevel).doSim { dut =>
SimTimeout(1000)
dut.clockDomain.forkStimulus(10)
dut.clockDomain.waitSamplingWhere(dut.counter.toInt == 20)
println("done")
}
这是一个模板,其中仿真过程通过显式调用 simSuccess() 结束:
SimConfig.compile(new TopLevel).doSimUntilVoid{ dut =>
SimTimeout(1000)
dut.clockDomain.forkStimulus(10)
fork {
dut.clockDomain.waitSamplingWhere(dut.counter.toInt == 20)
println("done")
simSuccess()
}
}
注意它是否等同于:
SimConfig.compile(new TopLevel).doSim{ dut =>
SimTimeout(1000)
dut.clockDomain.forkStimulus(10)
fork {
dut.clockDomain.waitSamplingWhere(dut.counter.toInt == 20)
println("done")
simSuccess()
}
simThread.suspend() // Avoid the "doSim" completion
}
默认情况下,仿真文件应存放在 $WORKSPACE/$COMPILED 指定的目录中。
$WORKSPACE 的默认值是
simWorkspace
,但你可以利用SPINALSIM_WORKSPACE
环境变量来指定一个不同的工作空间路径。$COMPILED 变量代表的是当前进行仿真的顶层模块的名称。
波形文件的存放路径会根据所选用的后端工具有所变化。在 Verilator 的情况下,它通常会被保存在默认的路径
$WORKSPACE/$COMPILED/$TEST
下。对于 Verilator 后端,可以使用
SimConfig.setTestPath(path)
函数设置测试文件夹位置。You can retrieve the location of the test path during simulation by calling the currentTestPath() function.
在同一硬件上运行多个测试
val compiled = SimConfig.withWave.compile(new Dut)
compiled.doSim("testA") { dut =>
// Simulation code here
}
compiled.doSim("testB") { dut =>
// Simulation code here
}
从线程中抛出仿真成功或失败结果
在仿真过程中的任何时刻,您都可以调用 simSuccess
或 simFailure
来结束它。
当仿真时间太长时,可能会导致仿真失败,例如,因为测试平台正在等待从未发生的条件。为此,请调用 SimTimeout(maxDuration)
函数设置超时时间,其中 maxDuration
是仿真应被视为失败的时间(以仿真时间单位表示)。
例如,要使仿真在持续 1000 个时钟周期后失败:
val period = 10
dut.clockDomain.forkStimulus(period)
SimTimeout(1000 * period)
在失败之前捕获给定时间窗内的波形
如果您有一个很长时间的仿真,并且您不想捕获所有波形(太多错误,太慢),那么您主要有两种方法可以做到这一点。
要么您已经知道模拟在哪个 simTime
失败,在这种情况下您可以在测试平台中执行以下操作:
disableSimWave()
delayed(timeFromWhichIWantToCapture)(enableSimWave())
或者,您可以运行双步锁仿真,其中一个仿真的运行比另一个仿真的运行稍有延迟,一旦领先的模拟出现故障,它将开始记录波形。
为此,您可以使用 DualSimTracer 实用程序,其中包含已编译硬件的参数、故障前要捕获的时间窗大小以及随机种子。
这是一个例子:
package spinaldoc.libraries.sim
import spinal.core._
import spinal.core.sim._
import spinal.lib.misc.test.DualSimTracer
class Toplevel extends Component {
val counter = out(Reg(UInt(16 bits))) init(0)
counter := counter + 1
}
object Example extends App {
val compiled = SimConfig.withFstWave.compile(new Toplevel())
DualSimTracer(compiled, window = 10000, seed = 42){dut=>
dut.clockDomain.forkStimulus(10)
dut.clockDomain.onSamplings {
val value = dut.counter.toInt
if(value % 0x1000 == 0) {
println(f"Value=0x$value%x at ${simTime()}")
}
// Throw a simulation failure after 64K cycles
if(value == 0xFFFF) {
simFailure()
}
}
}
}
这将生成以下文件结构:
simWorkspace/Toplevel/explorer/stdout.log : stdout of the simulation which is ahead
simWorkspace/Toplevel/tracer/stdout.log : stdout of the simulation doing the wave tracing
simWorkspace/Toplevel/tracer.fst : Waveform of the failure
scala 终端将显示仿真结果到标准输出。