Skip to content

Latest commit

 

History

History
143 lines (114 loc) · 6.97 KB

0053-2020-08-06.md

File metadata and controls

143 lines (114 loc) · 6.97 KB

6 Aug 2020

Previous journal: Next journal:
0052-2020-08-05.md 0054-2020-08-07.md

Yet more Verilator

I made t01d:

  • Uses a Makefile to build/run the thing now. Just a simple example so far. make clean will delete any existing obj_dir. make is the same as make all which does clean, then builds the project. make run will just do the general (initial 0) run, building if needed. make run1 and make run2 will run, respectively, with: (1) all unassigned bits set; (2) unassigned bits randomised.
  • .gitignore excludes obj_dir now, since it is platform-specific, and all of its contents (for now) can just be rebuilt by Verilator.
  • Actual project is a sawtooth counter (i.e. given a clock, it counts up and then down again). It supports a SIZE parameter (defaults to 8-bit output), but I'm not yet sure how to override this in Verilator.
  • Test bench inspects the high bit of saw_count__DOT__counter to look inside the module and see whether it's currently counting up or down.

This example:

  • Shows the state of all signals as it runs.
  • Runs an initial 5 clock cycles from cold start (which might include unassigned/random signal states).
  • Does a synchronous reset.
  • Runs for 600 clock cycles.

Run make initially, which will delete obj_dir and then build it again. Then we can do:

$ make run | head -15
Before first eval()...
[00000000] clk=0 reset=0 q=0 (counter)=00000000 (up)
After first eval()...
[00000000] clk=0 reset=0 q=0 (counter)=00000000 (up)
Cold start...
[00000001] clk=0 reset=0 q=1 (counter)=00000001 (up)
[00000002] clk=0 reset=0 q=2 (counter)=00000002 (up)
[00000003] clk=0 reset=0 q=3 (counter)=00000003 (up)
[00000004] clk=0 reset=0 q=4 (counter)=00000004 (up)
[00000005] clk=0 reset=0 q=5 (counter)=00000005 (up)
Synchronous reset...
[00000006] clk=0 reset=1 q=0 (counter)=00000000 (up)
Main loop...
[00000007] clk=0 reset=0 q=1 (counter)=00000001 (up)
[00000008] clk=0 reset=0 q=2 (counter)=00000002 (up)

This starts all unassigned signals as cleared.

Likewise, start with all unassigned signals set:

$ make run1 | head -15
Before first eval()...
[00000000] clk=1 reset=1 q=255 (counter)=000001FF (down)
After first eval()...
[00000000] clk=1 reset=1 q=0 (counter)=000001FF (down)
Cold start...
[00000001] clk=0 reset=1 q=0 (counter)=00000000 (up)
[00000002] clk=0 reset=1 q=0 (counter)=00000000 (up)
[00000003] clk=0 reset=1 q=0 (counter)=00000000 (up)
[00000004] clk=0 reset=1 q=0 (counter)=00000000 (up)
[00000005] clk=0 reset=1 q=0 (counter)=00000000 (up)
Synchronous reset...
[00000006] clk=0 reset=1 q=0 (counter)=00000000 (up)
Main loop...
[00000007] clk=0 reset=0 q=1 (counter)=00000001 (up)
[00000008] clk=0 reset=0 q=2 (counter)=00000002 (up)

Then this is how it looks with random values. Note how q needs the first eval() in order to "settle", even though it is an assigned (rather than registered) value:

$ make run2 SEED=991 | head -15
Before first eval()...
[00000000] clk=0 reset=0 q=121 (counter)=000001B1 (down)
After first eval()...
[00000000] clk=0 reset=0 q=78 (counter)=000001B1 (down)
Cold start...
[00000001] clk=0 reset=0 q=77 (counter)=000001B2 (down)
[00000002] clk=0 reset=0 q=76 (counter)=000001B3 (down)
[00000003] clk=0 reset=0 q=75 (counter)=000001B4 (down)
[00000004] clk=0 reset=0 q=74 (counter)=000001B5 (down)
[00000005] clk=0 reset=0 q=73 (counter)=000001B6 (down)
Synchronous reset...
[00000006] clk=0 reset=1 q=0 (counter)=00000000 (up)
Main loop...
[00000007] clk=0 reset=0 q=1 (counter)=00000001 (up)
[00000008] clk=0 reset=0 q=2 (counter)=00000002 (up)

When doing something like this:

reg [SIZE:0] counter /* verilator public */;

...noting placement of the semicolon relative to the comment... it exposes counter as a variable you can directly access in the C++ code. Just note that whichever .h file defines it, that header must be included in your main .cpp file, so you can do this, for example:

    printf("counter=%08X\n", m_core->saw_count->counter);

(For more info on the headers and how this all works, see this but note that I think where it mentions ->v, this only applies for SystemC stuff, which we're not using).

In my example I made this function, to get the status of the internal up/down bit:

`ifdef verilator
  function get_up_down;
    // verilator public
    get_up_down = counter[SIZE];
  endfunction // get_up_down
`endif // verilator

Note that the placement of the // verilator public comment is vital. If you want, a function like this can also be declared as a bit range such as function [4:0] whatever;. In any case, I can easily call the function above in my C++ code like this:

    printf("direction=%s\n", (m_core->saw_count->get_up_down()) ? "down" : "up");

Examples of Verilator functions in Verilog code can be found here.

Verilog defines can be set on the verilator command-line using +define+myvar=somevalue. Note that the =somevalue part is optional.

I modified my makefile so it can be used like this:

make DEF='myvar=somevalue whatever'

...which in this case is equivalent to including the following in the Verilog code:

`define myvar somevalue
`define whatever

Other things to review

  • How to override Verilog parameters in Verilator?
  • Need to try out GTKWave for Windows and macOS. At the time of writing, win64 binary is only up to 3.3.100, and macOS only up to 3.3.103, and can also be installed via brew install gtkwave? Check out the list of all releases here.
  • VCD file format seems pretty straightforward.
  • FST (Fast Signal Trace) is an alternative to VCD, used by GTKWave. Maybe it has more features? It definitely has compression.
  • CVC is an alternative to Verilator and Icarus?
  • Comments on Accessing Signals in Verilator Models (see also; its subheadings 6.2.x).
  • Learn Makefiles and produce one for use in Verilator, XISE projects, etc. This looks like a great tutorial but I recall a similar site that might have been even better.