启动仿真器
简介
下面是一个硬件定义+测试平台的示例:
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 终端将显示仿真结果到标准输出。