ClockDomainConfig Problem in SpinalHDL
I tried to write a simple spinal HDL demo according to ClockDomainConfig Example to test a configured clock domain and an Area which use the clock domain.
Here is my simple counter:
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
)
)
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)
}
}
Also here is my simulate object:
ackage 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 =>
//get clock
dut.clockDomain.forkStimulus(period = 10) //10 ps
SimTimeout(1000)
dut.io.clk #= dut.clockDomain.clock.toBoolean
dut.io.rst #= dut.clockDomain.reset.toBoolean
for (i <- 0 until 100) {
if (i == 50)
dut.clockDomain.assertReset()
}
}
}
}
But when I attempt to simulate, the JAVA reports such an Error:
Exception in thread "main" java.util.NoSuchElementException: key not found: (clk : Bool)
at scala.collection.MapLike$class.default(MapLike.scala:228)
at scala.collection.AbstractMap.default(Map.scala:59)
at scala.collection.mutable.HashMap.apply(HashMap.scala:65)
at spinal.core.sim.package$SimClockDomainPimper.getBool(package.scala:449)
at spinal.core.sim.package$SimClockDomainPimper.getSignal(package.scala:454)
at spinal.core.sim.package$SimClockDomainPimper.fallingEdge(package.scala:471)
at spinal.core.sim.package$SimClockDomainPimper.forkStimulus(package.scala:663)
at mytest.MyCounterSim$$anonfun$main$2.apply(MyCounterSim.scala:12)
at mytest.MyCounterSim$$anonfun$main$2.apply(MyCounterSim.scala:10)
at spinal.core.sim.SimCompiled$$anonfun$doSimApi$2.apply$mcV$sp(SimBootstraps.scala:538)
at spinal.sim.SimManager.spinal$sim$SimManager$$threadBody$1(SimManager.scala:222)
at spinal.sim.SimManager$$anonfun$2.apply$mcV$sp(SimManager.scala:225)
at spinal.sim.SimThread$$anonfun$1.apply$mcV$sp(SimThread.scala:93)
at spinal.sim.JvmThread.run(SimManager.scala:51)
I do not know how does the problem pop up. When I test the MyCounter without an configured clock domain, everything works well. But once I add the myClockDomain
, the error occurs.
My JDK version is OpenJDK 17.0.2 2022-01-18
sbt version is 1.6.2
verilator version is 4.222 2022-05-02
IDE is IntelliJ IDEA
A few things to point out.
While the design is a copy from documentation, it doesn't really have multiple ClockDomains being used. There is an implied toplevel ClockDomain that already exists can can be accessed with
this.clockDomain
from the Component.Because all the active logic of the counter is tied to one ClockDomain (the one you created) and nothing is attached to the implicit clock domain the signals relating to the implicit ClockDomain may have got pruned from the Verilog.
But conceptually with SpinalHDL a
Component
always has a superior clock domain above it that it is attached to. So the superior clock domain does not go away, even if signals do not appear to exist for it....
You do not call
dut.clockDomain.waitSampling()
at any time to provide control to the simulator to do work. Because of this simulation time (as reported bysimTime()
) is always zero. This simTime=0 is an indicator the simulator is not getting to simulate anything.Maybe insert calls before
dut.clockDomain.waitSampling(count=50)
, insidedut.clockDomain.waitSampling(count=1)
and afterdut.clockDomain.waitSampling(count=50)
the for() loop.Due to this you might hit the simTimeout() limit that has been set low at 1000, maybe increase this to 1000000.
...
One issue if you are using the MyCounter as a toplevel module for SpinalSim and attempting to use APIs like forkStimulus() that assume the 'clk' and 'reset' implicit signals at the top level.
A few solutions:
1) Use SpinalHDL to elaborate the
MyCounter.v
file generated from yourMyCounterVerilog
main and create an independent testbench (that is not SpinalSim), that would drive theio_clk
andio_reset
as you expect.This could be Verilator or cocotb or something-else, etc... a simulator that works with a Verilog file, this will demonstrate the Verilog design does work as generated by SpinalHDL.
2) If you wish to use SpinalSim, don't expect any assistance from
forkStimulus()
API in your SpinalSim to manage a toplevel clk/reset signals if your toplevel design does not conform to what it expects. That the toplevel module is attached tothis.clockDomain.readClockWire
as the master clock.If you examine the generated verilog from suggestion 1 above, it has no toplevel signals called
clk
orreset
. Even if you attempt to rename your signal withsetName("clk")
or usenoIoPrefix()
I think you will run into problems because the implicit signal just gets renamed (maybe toclk_1
) and still doesn't become joined to your signal.Instead to simulate this module you need to directly manage your independant clock in your simulation code. From your code the lines, try a sequence like:
Here you are taking control of the clock signal directly, not relying on
forkStimulus()
to assist. So remove theforkStimulus()
line of code.Due to this, consider the removal of
assertReset()
if you need to achieve this change the myReset state. Again use of the API assume you are using the implicit toplevel ClockDomain.3) Insert a
Toplevel extends Component
module that is the toplevel for the Sim. Place yourMyCounter extends Component
module, inside Topleve, and wire through. This inserted Component would have regular ClockDomain inherited and the MyCounter would be a child module inside. This may better emphasis what is already happening with regards to the superior ClockDomain of the Component hierarchy.You can wire thru the 3 signals. When you inspect the new Toplevel module it will now have 5 signals.
clk, reset, io_clk, io_reset, io_num
Again you still need to manually stimulate your io_* signals, but you will forkStimulus() is managing the other 2 signals
clk
andreset
.4) Remove the 2 signals
clk
andreset
from the io Bundle (delete the lines of code). Yes this isn't exactly as the demonstration code in the SpinalHDL documentation suggests, but the other options exist above if you really need that.Then modify the 2 lines for the ClockDomain setup to read:
Keep
forkStimulus()
because now you have a conforming toplevel Componment that regularclk
andreset
signals, which the SpinalSim expects to work with. This can be confirmed if you examine the elaborated/generatedMyCounter.v
file, you will see, even after deleting the 2 signals you get the signals emitted by using the normal name, notio_clk
and notio_reset
....
Use of
dut.clockDomain.assertReset()
maybe problematic while usingforkStimulus()
that will also be manipulating the reset status early on after simulation startup.Maybe this ok, when
simTime()
is greater than a number, lets say 100. TheforkStimulus()
call will generate reset line edges early in simulation but that should all be over by simulation step>100.