SpinalHDL ConfigClockDomain not work, how to drive configured clock domain when simulating?

173 Views Asked by At

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

1

There are 1 best solutions below

0
On

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 by simTime()) 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), inside dut.clockDomain.waitSampling(count=1) and after dut.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 your MyCounterVerilog main and create an independent testbench (that is not SpinalSim), that would drive the io_clk and io_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 to this.clockDomain.readClockWire as the master clock.

If you examine the generated verilog from suggestion 1 above, it has no toplevel signals called clk or reset. Even if you attempt to rename your signal with setName("clk") or use noIoPrefix() I think you will run into problems because the implicit signal just gets renamed (maybe to clk_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:

var myClock = false  // Consider using AtomicBoolean
var myReset = false  // Consider using AtomicBoolean
dut.clockDomain.onSamplings({
  myClock = !myClock
  dut.io.clk #= myClock
  dut.io.reset #= myReset
})

Here you are taking control of the clock signal directly, not relying on forkStimulus() to assist. So remove the forkStimulus() 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 your MyCounter 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 and reset.

4) Remove the 2 signals clk and reset 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:

  clock = clockDomain.readClockWire,
  reset = clockDomain.readResetWire,

Keep forkStimulus() because now you have a conforming toplevel Componment that regular clk and reset signals, which the SpinalSim expects to work with. This can be confirmed if you examine the elaborated/generated MyCounter.v file, you will see, even after deleting the 2 signals you get the signals emitted by using the normal name, not io_clk and not io_reset.

...

Use of dut.clockDomain.assertReset() maybe problematic while using forkStimulus() 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. The forkStimulus() call will generate reset line edges early in simulation but that should all be over by simulation step>100.