{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}