{"id":1570,"date":"2021-04-02T16:23:12","date_gmt":"2021-04-02T16:23:12","guid":{"rendered":"http:\/\/retroramblings.net\/?p=1570"},"modified":"2021-04-03T22:22:36","modified_gmt":"2021-04-03T22:22:36","slug":"using-verilator-to-solve-a-puzzle","status":"publish","type":"post","link":"https:\/\/retroramblings.net\/?p=1570","title":{"rendered":"Creating a testbench with Verilator"},"content":{"rendered":"\n<p><strong>Exploring audio DACs in simulation &#8211; Part 2 &#8211; 2021-04-02<\/strong><\/p>\n\n\n\n<p>Having rambled on a little longer than I intended while setting the scene for these experiments, I&#8217;m going to set up a simple testbench with verilator to evaluate the performance of several different DACs.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>Rather than merely simulate a testbench written in verilog and output the signals to a trace file, Verilator takes a slightly different approach: the component under test is compiled into a link library which can directly interact with a testbench written in C++.  For this project this is ideal, since I can use all tools of the C or C++ languages to inject signals into the DACs (even from files if I wish), process the output and log it to a file.  I also have the option of saving a trace file which can be examined in GTKWave, though I&#8217;m not going to do that just yet.<\/p>\n\n\n\n<p>So let&#8217;s start with a very simple 1st-order DAC:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">module sigma_delta_dac_1storder<br> (<br>    input clk,<br>    input reset_n,<br>    input [15:0] d,<br>    output q<br> );<br> reg q_reg;<br> assign q=q_reg;<br> reg [16:0] sigma;<br> always @(posedge clk or negedge reset_n) begin<br>    if(!reset_n) begin<br>       sigma &lt;= 17'h8000;<br>       q_reg &lt;= 1'b0;<br>    end else begin<br>       sigma &lt;= sigma + {1'b0,d} + {sigma[16],16'b0};<br>       q_reg &lt;= sigma[16];<br>    end<br> end<br> endmodule<\/pre>\n\n\n\n<p>(This module, and all the other files needed to repeat these experiments can be found at <a href=\"https:\/\/github.com\/robinsonb5\/DACTests.git\">https:\/\/github.com\/robinsonb5\/DACTests.git<\/a>)<\/p>\n\n\n\n<p>This  module takes an unsigned 16-bit input and emits a bitstream which is sent from the FPGA into a passive low-pass reconstruction filter on the Minimig and MiST boards.<\/p>\n\n\n\n<p>So let&#8217;s create a C++ simulation of this module:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">verilator --trace --top-module sigma_delta_dac_1storder -cc sigma_delta_dac_1storder.v<\/pre>\n\n\n\n<p>There will now be a new directory called obj_dir which contains some c++ source and header files which we can compile along with our testbench &#8211; but better yet it also contains a makefile which creates a static linker library from those files &#8211; so let&#8217;s take advantage of that:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">make -C obj_dir\/ -fVsigma_delta_dac_1storder.mk<\/pre>\n\n\n\n<p>obj_dir now contains a link library, Vsigma_delta_dac_1storder__ALL, which we&#8217;ll link with shortly.<\/p>\n\n\n\n<p>The nice thing about this is that it&#8217;s all very makefile-friendly &#8211; provided the module&#8217;s name within the Verilog file matches its filename, building this library can be done inductively with a makefile rule such as:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">obj_dir\/V%__ALL.a: %.v<br>     verilator --trace --top-module $* -cc $*<em>.v<\/em><br><em>     make -C obj_dir -f V$<\/em>*.mk<\/pre>\n\n\n\n<p>Now we need a testbench &#8211; something that will just create a sine-wave, feed it into the DAC, capture and emit the DAC&#8217;s output&#8230;<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> include &lt;cstdio&gt;<br> include &lt;cmath&gt;<br><br> include DACHEADER<br><br> include \"verilated.h\"<br> include \"verilated_vcd_c.h\"<br><br> undef TRACE<\/pre>\n\n\n\n<p>DACHEADER is externally defined in the Makefile but it&#8217;s a header file which verilator generates, describing the module under test as a class with the various ports defined as members.  Definiting it this way allows me to use the self-same testbench with several different DACS.  I use the TRACE define to determine whether or not I generate a .vcd file for viewing in GTKWave.  They can be both large and slow to generate, so there&#8217;s no point creating one if I&#8217;m not going to use it.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"> static VerilatedVcdC *trace;<br><br> \/\/ Declare the global testbench, of a type externally defined<br> typedef DAC testbench;<br> static testbench *tb;<br><br> \/\/ Simulate at the speed of the MiST Minimig port's SDRAM clock<br> define MHz 113.44<br> static double timestamp = 0;<br><br> void tick() {<br>     tb-&gt;clk = 1;<br>     tb-&gt;eval();<br>     tb-&gt;clk = 0;<br>     tb-&gt;eval();<br>     trace-&gt;dump(timestamp);<br>     timestamp += 500\/MHz;<br> }<br><br> define SAMPLERATE 44100.0<br> define SIGNAL_HZ 200.0<br> define SAMPLES 8192<br> define OVERSAMPLE 2048<br> define OUTFILTERSHIFT 12<br><br> \/\/ Return a sample from a sine wave<br> double sample(double s)<br> {<br>     double period=SAMPLERATE\/SIGNAL_HZ;<br>     return(sin((s*s*M_PI)\/period));<br> }<\/pre>\n\n\n\n<p>This should all be fairly self-explanatory &#8211; in the tick() function we advance the clock, and call eval() after each edge.<\/p>\n\n\n\n<p>An oversampling ratio of 2048 is absurdly high &#8211; but necessary for a 1st-order DAC, because of the idle tones I described last time.  So now we start our actual simulation function:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">void run_test()<br>{<br>  int outfilter=0x8000&lt;&lt;OUTFILTERSHIFT;<br>  int out;<br>  int s;<br>  <code>for(int i=0;i&lt;SAMPLES;++i)<\/code><br>  {<br><code>&nbsp;   int samp=(32767+32767*sample(i));<\/code><br><code>&nbsp;   tb-&gt;d=samp;<\/code><br><code>&nbsp;   for(int j=0;j&lt;OVERSAMPLE;++j)<\/code><br><code>&nbsp;   {<\/code><br><code>&nbsp;     tick();<\/code><br><\/pre>\n\n\n\n<p>So far so good &#8211; we&#8217;ve fed a sample from a sinewave into the DAC, and have just entered an inner loop which we repeat as many times as we&#8217;re oversampling the sinewave.<\/p>\n\n\n\n<p>So how do we handle the output? A high frequency bitstream isn&#8217;t ideal for subsequent analysis, and the physical board passes this bitstream through a low-pass filter to reconstruct the signal before it reaches an amplifier &#8211; so we need to mimic that filter.<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>&nbsp;     s=0xffff*tb-&gt;q;<\/code><br>     <code> \/\/ Single-pole approximation of the reconstruction filter<\/code><br><code>&nbsp;     outfilter+=((s&lt;&lt;OUTFILTERSHIFT)-outfilter)&gt;&gt;OUTFILTERSHIFT;<\/code><br><code>&nbsp;   } \/\/ Output a sample in signed 16-bit little-endian<\/code><br><code>&nbsp;   out=(outfilter&gt;&gt;OUTFILTERSHIFT)-32768;<\/code><br><code>&nbsp;   putchar(out&amp;255);<\/code><br><code>&nbsp;   putchar((out&gt;&gt;8)&amp;255);<\/code><br>  }<br>}<\/pre>\n\n\n\n<p>This is the simplest form of Infinite Impulse Response filter, with the coefficient determined by the shift value.  Here I&#8217;m using a shift of 12, so the coefficient is (1\/4096).  Having completed the inner loop for oversampaling, during which the outputs are accumulated in the filter, we write an output sample to stdout (yes, I&#8217;m still thinking in terms of C, not C++!) in 16-bit signed little-endian format.<\/p>\n\n\n\n<p>Finally, we have a main() function where the basic housekeeping&#8217;s done &#8211; it looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\">int main(int argc, char **argv)<br>{<br> <code>\/\/ Initialize Verilators variables<\/code><br><code>&nbsp;Verilated::commandArgs(argc, argv);<\/code><br><code> Verilated::traceEverOn(true);<\/code><br><code> trace = new VerilatedVcdC;<\/code><br><br><code> \/\/ Create an instance of our module under test<\/code><br><code> tb = new testbench;<\/code><br><br>#ifdef TRACE<br> <code>tb-&gt;trace(trace, 99);<\/code><br><code> trace-&gt;open(\"wave.vcd\");<\/code><br>#endif<br><br> <code>\/\/ Reset the testbench tick();<\/code><br><code>tb-&gt;reset_n = 0;<\/code><br><code>tick();<\/code><br><code>tick();<\/code><br><code>tb-&gt;reset_n = 1;<\/code><br><br><code>\/\/ Run the test<\/code><br><code>run_test();<\/code><br><br>#ifdef TRACE<br> <code>trace-&gt;close();<\/code><br>#endif<br><br> <code>delete tb;<\/code><br><code>return(0);<\/code><br>}<\/pre>\n\n\n\n<p>To compile our testbench we have to make sure two directories are in the includes search path.  One is the obj_dir directory, the other is the directory where verilator&#8217;s own includes are installed.  On my machine this is \/usr\/share\/verilator\/include &#8211; you can find the VERILATOR_ROOT for your installation by typing &#8220;verilator -V&#8221;<\/p>\n\n\n\n<p>There are two C++ files in this verilator include directory which must be compiled alongside the testbench &#8211; these are verilated.cpp and verilated_vcd_c.cpp (the latter providing support for trace files.)<\/p>\n\n\n\n<p>So assuming we&#8217;ve defined a makefile variable DAC to the particular DAC under test &#8211; in this case &#8220;sigma_delta_dac_1storder&#8221;, we compile our testbench with:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>g++ -DDAC=V$(DAC) -DDACHEADER=\\\"obj_dir\/V$(DAC).h\\\" -I obj_dir -I$(VERILATOR_DIR) testbench.cpp <\/code>$(VERILATOR_DIR)\/verilated.cpp $(VERILATOR_DIR)\/verilated_vcd_c.cpp<code> <\/code>obj_dir\/V$(DAC)__ALL.a<code> -o&nbsp;testbench<\/code><\/pre>\n\n\n\n<p>Next time I&#8217;ll look at the results of testing with four different DACs, and how the real world can throw some surprises at us!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Exploring audio DACs in simulation &#8211; Part 2 &#8211; 2021-04-02 Having rambled on a little longer than I intended while setting the scene for these experiments, I&#8217;m going to set up a simple testbench with verilator to evaluate the performance &hellip; <a href=\"https:\/\/retroramblings.net\/?p=1570\">Continue reading <span class=\"meta-nav\">&rarr;<\/span><\/a><\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,8,1],"tags":[],"class_list":["post-1570","post","type-post","status-publish","format-standard","hentry","category-fpga","category-hardware","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1570","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=1570"}],"version-history":[{"count":8,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1570\/revisions"}],"predecessor-version":[{"id":1588,"href":"https:\/\/retroramblings.net\/index.php?rest_route=\/wp\/v2\/posts\/1570\/revisions\/1588"}],"wp:attachment":[{"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=1570"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=1570"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/retroramblings.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=1570"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}