SpinalHDL自定义ClockDomain仿真报错 如何正确驱动配置时钟域
SpinalHDL中ClockDomainConfig相关问题解答
报错根因
这个错误是时钟域对象和实际硬件信号不匹配导致的:
- 顶层组件
dut自带的clockDomain是SpinalHDL自动生成的默认时钟域,你的代码中没有任何逻辑绑定到这个默认时钟域,反而手动定义了myClockDomain接在自定义的io.clk、io.rst端口上。 - 仿真时直接调用
dut.clockDomain.forkStimulus(),框架会查找默认时钟域对应的时钟、复位硬件信号,找不到对应键值就会抛出NoSuchElementException。
另外你的仿真代码开头还有笔误:第一行ackage mytest缺少首字母p,应为package mytest。
解决方法
根据你是否需要将时钟、复位作为自定义顶层端口,分两种实现方式:
方式1:保留自定义clk/rst端口(匹配你当前的代码结构)
- 首先给内部定义的自定义时钟域加
simPublic()标记,让仿真层可以访问到这个内部对象:
package mytest import spinal.core._ import spinal.core.sim._ import spinal.lib._ class MyCounter extends Component { val io = new Bundle { val clk = in Bool() simPublic() val rst = in Bool() simPublic() val num = out UInt (16 bits) } val myClockDomain = ClockDomain( clock = io.clk, reset = io.rst, config = ClockDomainConfig( clockEdge = FALLING, resetKind = ASYNC, resetActiveLevel = HIGH ) ).simPublic() // 新增:暴露自定义时钟域给仿真层 val counterArea = new ClockingArea(myClockDomain) { val counter = RegInit(U"16'xffff") counter := counter + 1 io.num := counter } } object MyCounterVerilog { def main(args: Array[String]): Unit = { SpinalVerilog(new MyCounter) } }
- 修改仿真代码,不要操作默认的
dut.clockDomain,改为操作你自定义的dut.myClockDomain,去掉冗余的手动clk/rst赋值,补充必要的仿真延时和复位控制逻辑:
package mytest import spinal.core._ import spinal.sim._ import spinal.core.sim._ //MyCounter's testbench object MyCounterSim { def main(args: Array[String]) { SimConfig.withWave.compile(new MyCounter).doSim { dut => // 驱动自定义时钟域,周期10ps dut.myClockDomain.forkStimulus(period = 10) SimTimeout(1000) // 等待几个周期后触发复位 dut.myClockDomain.waitSampling(50) dut.myClockDomain.assertReset() dut.myClockDomain.waitSampling(10) // 复位保持10个周期 dut.myClockDomain.deassertReset() // 持续运行仿真直到超时 while(true) { dut.myClockDomain.waitSampling() } } } }
方式2:使用全局默认时钟域(代码更简洁)
如果你不需要特殊命名clk/rst端口,可以直接修改组件默认时钟域的配置,不需要手动例化ClockDomain、绑定端口,仿真时直接用dut.clockDomain即可:
package mytest import spinal.core._ import spinal.core.sim._ import spinal.lib._ class MyCounter extends Component { val io = new Bundle { val num = out UInt (16 bits) } // 直接修改当前默认时钟域的配置 ClockDomain.current.setConfig(ClockDomainConfig( clockEdge = FALLING, resetKind = ASYNC, resetActiveLevel = HIGH )) // 寄存器自动使用配置后的默认时钟域 val counter = RegInit(U"16'xffff") counter := counter + 1 io.num := counter } object MyCounterVerilog { def main(args: Array[String]): Unit = { SpinalVerilog(new MyCounter) } }
这种写法的仿真代码不需要做特殊修改,直接调用dut.clockDomain.forkStimulus()就能正常运行。
注意事项
- 所有需要在仿真阶段访问的内部信号、时钟域对象,都要加
simPublic()标记,否则仿真框架无法获取对应硬件句柄,会报键不存在的错误。 - 手动例化的自定义ClockDomain和组件默认
clockDomain是完全独立的对象,仿真时要对应操作,不能混用。 - 仿真代码中必须加入采样等待(如
waitSampling()),否则所有代码会在0时刻瞬间执行,无法得到正确的时序行为。
内容的提问来源于stack exchange,提问作者BossWangST
相关产品推荐
相关产品推荐

