{josuah.net} | {panoramix-labs.fr} (DIR) • {josuah.net} (DIR) • {panoramix-labs.fr} {git} | {cv} | {links} | {quotes} | {ascii} | {tgtimes} | {gopher} | {mail} (DIR) • {git} (BIN) • {cv} (DIR) • {links} (DIR) • {quotes} (DIR) • {ascii} (HTM) • {tgtimes} (DIR) • {gopher} ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ Different Clock Domains With Verilator ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ (HTM) Clock Domain Crossing ({CDC}) can be difficult to get right. in particular for a beginner like me. Simulating it seemed essential to make sure there were no Verilog bug before testing on hardware. (HTM) I currently use {Verilator} for simulation. It produces a `simulation.vcd` file (HTM) to open in something like {gtkwave}. (HTM) ZipCPU {solved this problem with great art} already. But I did not want to increase the complexity of my small `simulation.cpp` nor use as many abstractions. So here is my approach: Conventions ─────────── I am more of a C developer than C++ one, so I use structs intead of classes. (HTM) Each {cosimulator} such as UART or SPI has an associated struct holding all its belongings. ┊ Vsimulation *vsim; // Verilator global simulation state ┊ VerilatedVCD *vcd; // used for dumping state to a VCD file ┊ ┊ // setup Verilator with "simulation.vcd" as dump file ┊ void ┊ simulation_init(int argc, char **argv) ┊ { ┊ Verilated::commandArgs(argc, argv); ┊ Verilated::traceEverOn(true); ┊ ┊ vsim = new Vsimulation; ┊ vcd = new VerilatedVcdC; ┊ ┊ vsim->trace(vcd, 99); ┊ vcd->open("simulation.vcd"); ┊ } `_tick_posedge()` and `_tick_negedge()` ─────────────────────────────────────── Every clock domain will have its clock, that we will want to toggle up and down as clocks do. Each of the toggle-up and toggle down action can change the state of combinational logics in the design. This is a good opportunity to capture the signals and dump them in the `simulation.vcd` file: ┊ typedef uint64_t nanosecond_t; ┊ ┊ void ┊ simulation_eval(nanosecond_t ns) ┊ { ┊ vsim->eval(); ┊ vcd->dump(ns); ┊ } This means that every time we update the combinational logic, we dump all states on the VCD file. Sounds good! Now, to update a main clock for instance named `clk`: ┊ void ┊ simulation_tick_posedge(nanoseond_t ns) ┊ { ┊ vsim->clk = 1; ┊ simulation_eval(ns); ┊ } ┊ ┊ void ┊ simulation_tick_negedge(nanosecond_t ns) ┊ { ┊ vsim->clk = 0; ┊ simulation_eval(ns); ┊ } All that is left is call each of these function in turn with the time `ns` that increases. An extra pair of function like these is to be written for every clock domain to maintain. Thankfully these functions are very small and easy to write. The Big Picture ─────────────── How to simulate clocks out of sync? The easiest I have found is a variable `ns` holding the time increasing in a loop, and react upon it with `if (ns % PERIOD == PHASE)`. This is wrapped in `POSEDGE()` and `NEGEDGE()` macros for convenience. That way, `PERIOD` and `PHASE` can be chosen to any arbitrary value to have any kind of clock frequency and phase, to test all combination we want. ┊ #define POSEDGE(ns, period, phase) \ ┊ ((ns) % (period) == (phase)) ┊ ┊ #define NEGEDGE(ns, period, phase) \ ┊ ((ns) % (period) == ((phase) + (period)) / 2 % (period)) ┊ ┊ #define CLK_SYS_PERIOD 30 ┊ #define CLK_SYS_PHASE 3 ┊ ┊ #define CLK_SPI_PERIOD 31 ┊ #define CLK_SPI_PHASE 0 ┊ ┊ int ┊ main(int argc, char **argv) ┊ { ┊ struct spi spi; ┊ ┊ simulation_init(argc, argv); ┊ spi_init(&spi); ┊ ┊ vsim->spi_csn = 0; ┊ ┊ for (nanosecond_t ns = 0; ns < 20000; ns++) { ┊ // SYS clock domain ┊ if (POSEDGE(ns, CLK_SYS_PERIOD, CLK_SYS_PHASE)) ┊ simulation_tick_posedge(ns); ┊ if (NEGEDGE(ns, CLK_SYS_PERIOD, CLK_SYS_PHASE)) ┊ simulation_tick_negedge(ns); ┊ ┊ // SPI clock domain ┊ if (POSEDGE(ns, CLK_SPI_PERIOD, CLK_SPI_PHASE)) ┊ spi_tick_posedge(&spi, ns); ┊ if (NEGEDGE(ns, CLK_SPI_PERIOD, CLK_SPI_PHASE)) ┊ spi_tick_negedge(&spi, ns); ┊ } ┊ ┊ simulation_finish(); ┊ return 0; ┊ } In that example, we have two clocks slightly drifting by 1ns, starting at a small phase offset of 3ns. It might even be possible to change the period and phase in the middle of the simulation to simulate clock drift or reconfiguration. Links ───── (HTM) • {https://zipcpu.com/blog/2018/07/06/afifo.html} (HTM) • {https://www.veripool.org/verilator/} (HTM) • {http://gtkwave.sourceforge.net/} (HTM) • {https://zipcpu.com/blog/2018/09/06/tbclock.html} (HTM) • {https://zipcpu.com/tutorial/lsn-05-serialtx.pdf}