Tutorial

In the following, we give a step-by-step tutorial on a typical use case of NLTrace. Note that we type each step into the interactive shell manually, or we copy-paste the commands of entire code blocks (including the comments) into the shell.

Tutorial

The tutorial is structured by a sequence of NLTrace commands that translates an input VCD file into a PDF/SVG/PNG/TEX file. We examine several features of of NLTrace step-by-step, in order to demonstrate the translation flow. The tutorial comes with some sample output in between, to give the user an impression of what is possible with NLTrace. The tutorial examines one concrete example VCD trace of a FIFO buffer implementation step-by-step. The example trace can be found at docs/docs/tutorial/src/ibuffer.vcd. We have to copy it into our working directory.

Preparing for the tutorial

So, let's prepare our tutorial environment: We just create a directory in our home directory, and after copying the tutorial's example VCD trace into it, we change into that directory.

mkdir -p $HOME/nltrace-tutorial
cp docs/docs/tutorial/src/ibuffer.vcd $HOME/nltrace-tutorial/
cd $HOME/nltrace-tutorial

Start NLTrace

Now, let's start NLTrace from the command line:

$ nltrace

This starts an interactive command shell, first issuing some copyright and licensing information, also some information on the registered commands. After that, the command prompt is issued.

NLTrace -- Analyze traces
Copyright (C) 2020--2021 Christian Krieg <christian@drkrieg.at>

All rights reserved. Not (yet) for public use.

THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD
TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN
NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER
IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

[...]

nltrace> 

Now we are ready to issue commands to NLTrace. Comments are introduced by #, and multiple commands per line are separated by ;.

An up-to-date version of the command reference can be found here.

Show a list of available commands

The first thing we do is to get an overview over the existing commands in NLTrace. By default, the built-in help command shows a list of all available commands. Detailed instructions for one command can be obtained by providing the command name as an argument to help.

# Type 'help' to get a list of available commands
help

# Type 'help read_vcd' to get detailed help for 'read_vcd' command
help read_vcd

Read a VCD input file

We read the VCD file to process with read_vcd by providing the path of the VCD file. In this tutorial, we read the example VCD file ibuffer.vcd.

# Read VCD file
read_vcd ibuffer.vcd

List all available VCD files

We now see an entry for the VCD file using the list command.

# Show available VCD files and diagrams
list

The list command generates the following output:

   ibuffer.vcd

Select the VCD file

In order to operate on that VCD file, we have to select it. We accomplish this using the cd command (following the Yosys command naming scheme).

# Select VCD file
cd ibuffer.vcd
list

After selecting the VCD file, we notice an asterisk (*) right before the selected VCD file by calling the list command again.

 * ibuffer.vcd

Add a diagram

The next step is to create a diagram that represents the visualization for a section of the VCD file. This is what the add command does. We only have to provide a name for the diagram; This can be any alphanumeric value. In this example, we create a diagram whose name is 0. When calling the list command again, we notice an entry that as been created for the VCD file. It represents a diagram, indicating a set of parameters.

# Add diagram to VCD file
add 0
list
 * ibuffer.vcd
     [0]: 1.00:0:365:5+0

This list command now shows a new entry for the diagram we just created, with some parameters encoded in the following form:

[id]: scale:begin:end:step+offset

The meaning of these parameters is as follows:

Parameter Description
id The identifier of a diagram
scale A scaling factor to resize the diagram along the time scale
begin Start time of the selected trace
end End time of the selected trace
step Defines the time step for the time-scale's tick marks
offset Specify this integer to add offset to the time scale

We can create as many diagrams as we like to represent our VCD trace.

Select the diagram

To operate on our diagram, we first select it with cd:

# Select diagram
cd 0
list

The list command now shows an asterisk next to our newly created diagram, indicating it as the active diagram. Every subsequent command is now applied to this diagram.

 * ibuffer.vcd
 *   [0]: 1.00:0:365:5+0

Select and list signals of the VCD file into the diagram

Having selected the diagram, we now would like to select all the signals to visualize. This is where the select command comes into action.

# Show selected items (should be empty right now -- i.e., only show `root`)
ls

The select command accepts a Python regular expression as an argument, and selects a signal if its name matches the regular expression. For example, select clk will select both clk and clk_i. If we want to only select clk in this example, we could provide the -e flag to exactly match the signal name. Following this system, select .* selects all signals available in a VCD file.

The ls command lists all selected signal of the active diagram in a tree which reflects design hierarchy. select also provides an option -l to select all signals up to a certain level of design hierarchy. For example, -l 2 instructs NLTrace to select all signals up to hierarchy level 2:

# Select signals (using regex)
select -l2 .*

# Again show selected signals (shows a tree representing design hierarchy)
ls
root
└── ibuffer_tb
    ├── ibuffer_tb.a
    ├── ibuffer_tb.b
    ├── ibuffer_tb.c
    ├── ibuffer_tb.clk
    ├── ibuffer_tb.d
    ├── ibuffer_tb.dat1[0:7]
    ├── ibuffer_tb.dat2[0:7]
    ├── ibuffer_tb.empty
    ├── ibuffer_tb.full
    └── ibuffer_tb.uut

Show the diagram

We now can quickly generate a graphical representation of the waveform using the show command.

# Generate PDF and show waveform
show

We notice that the diagram is somewhat over-scaled in the time scale.

Resize the diagram's time scale

We therefore set the diagram's scale to use the space more efficiently. The set command allows to set the diagram's parameters. The following parameters are supported (just type help set to get a current list of supported parameters):

Parameter Description
begin Integer specifying the start time of the diagram
end Integer specifying the end time of the diagram
scale Float; Scaling factor to resize the diagram's time scale
offset Specify this integer to add offset to the time scale
step Integer; Time step for the time scale's tick marks
plain Boolean; If True, the diagram does not contain a time scale nor a grid

So, we set the diagram's scale to a half, and show it again.

# Scale waveform by a half
set scale .5

# Show again
show

We feel that the diagram is still consuming to much space; so we decrease its scale again by another half to end up with a scale of a quarter.

# Scale waveform by a quarter
set scale .25

# Show again (tick labels are now overlapping)
show

We now think that the diagram is consuming its space very efficiently. However, the tick mark labels now overlap.

Set the time scale's time step

This is why we set the time scale's time step to 25, which means that a tick mark label is generated for any step divisible by 25.

# Set step of the time scale's tick marks
set step 25

# Show again
show

Set diagram's begin and end time

We now would like to highlight the detailed diagram between the times 160 and 280. We can set the diagram's begin and end times:

# Set begin to 160 and show again
set begin 160; show

# Set end to 280 and show again
set end 280; show

Write diagram as TeX

NLTrace renders the selected subset of the VCD trace to a TikZ timing diagram. The write_tex command generates the code that describes the timing diagram, which can be included into your documentation projects.

# Print Latex code for diagram (you can include that into your LaTeX documents)
write_tex
  \begin{tikztimingtable}[xscale=0.25]
        {\tiny\ttfamily ibuffer\_tb.full} & 22.5L7.5L \\
        {\tiny\ttfamily ibuffer\_tb.empty} & 2.5L5.0H10.0L5.0H5.0L2.5H \\
        {\tiny\ttfamily ibuffer\_tb.dat2[0:7][7:0]} & 2.5D{57}15.0D{5A}12.5D{5E} \\
        {\tiny\ttfamily ibuffer\_tb.c} & 2.5H11.25L3.75H5.0L5.0H2.5L \\
        {\tiny\ttfamily ibuffer\_tb.b} & 30.0H \\
        {\tiny\ttfamily ibuffer\_tb.a} & 13.75H16.25L \\
        {\tiny\ttfamily ibuffer\_tb.clk} & 2.5L2.5H2.5L2.5H2.5L2.5H2.5L2.5H2.5L2.5H2.5L2.5H0.0G \\
        {\tiny\ttfamily ibuffer\_tb.d} & 6.25H7.5L16.25H \\
        {\tiny\ttfamily ibuffer\_tb.dat1[0:7][7:0]} & 1.25D{5..}5.0D{5D}5.0D{5E}18.75D{5F} \\
%
        \begin{extracode}
            \begin{background}

            \vertlines[help lines]{}
            \horlines[help lines]{}
            \draw (0,1.5) -- (\twidth*\coldist,1.5);
            \draw (0,-\nrows*\rowdist+\rowdist-0.5) -- (\twidth*\coldist,-\nrows*\rowdist+\rowdist-0.5);
                    \draw (40.0*\coldist-40.0*\coldist,1.5-.1) -- +(0,.2)
                        node [above,inner sep=2pt] {\scalebox{.75}{\tiny160}};
                    \draw (40.0*\coldist-40.0*\coldist,-\nrows*\rowdist+\rowdist-0.4) -- +(0,-.2)
                        node [below,inner sep=2pt] {\scalebox{.75}{\tiny160}};

                    \draw (46.25*\coldist-40.0*\coldist,1.5-.1) -- +(0,.2)
                        node [above,inner sep=2pt] {\scalebox{.75}{\tiny185}};
                    \draw (46.25*\coldist-40.0*\coldist,-\nrows*\rowdist+\rowdist-0.4) -- +(0,-.2)
                        node [below,inner sep=2pt] {\scalebox{.75}{\tiny185}};

                    \draw (52.5*\coldist-40.0*\coldist,1.5-.1) -- +(0,.2)
                        node [above,inner sep=2pt] {\scalebox{.75}{\tiny210}};
                    \draw (52.5*\coldist-40.0*\coldist,-\nrows*\rowdist+\rowdist-0.4) -- +(0,-.2)
                        node [below,inner sep=2pt] {\scalebox{.75}{\tiny210}};

                    \draw (58.75*\coldist-40.0*\coldist,1.5-.1) -- +(0,.2)
                        node [above,inner sep=2pt] {\scalebox{.75}{\tiny235}};
                    \draw (58.75*\coldist-40.0*\coldist,-\nrows*\rowdist+\rowdist-0.4) -- +(0,-.2)
                        node [below,inner sep=2pt] {\scalebox{.75}{\tiny235}};

                    \draw (65.0*\coldist-40.0*\coldist,1.5-.1) -- +(0,.2)
                        node [above,inner sep=2pt] {\scalebox{.75}{\tiny260}};
                    \draw (65.0*\coldist-40.0*\coldist,-\nrows*\rowdist+\rowdist-0.4) -- +(0,-.2)
                        node [below,inner sep=2pt] {\scalebox{.75}{\tiny260}};
          \end{background}
      \end{extracode}
  \end{tikztimingtable}

Export diagram to other graphical output formats

Likewise, the diagram can be written to a variety of output formats:

# Wrap the diagram into a LaTeX standalone document (and save it to a file)
write_tex --standalone
write_tex --standalone ibuffer.tex

# Save result in a PDF file
write_pdf ibuffer.pdf

# Also create PNG and SVG versions of the waveform
write_png ibuffer.png
write_svg ibuffer.svg

Write the selected trace as VCD

We also want to store the selected subset of the original VCD trace into its own VCD file. NLTrace therefore provides a VCD backend to do the job, therefore acting as a VCD filter. If no filename is given, write_vcd prints to stdout.

# Print a VCD trace for the selected subset of the input VCD file
write_vcd
$date 2020-07-22 15:43:08.634338 $end
$timescale 1 s $end
$scope module ibuffer_tb $end
$var wire 1 ! full $end
$var wire 1 " empty $end
$var wire 8 # dat2[0:7] $end
$var wire 1 $ c $end
$var wire 1 % b $end
$var reg 1 & a $end
$var reg 1 ' clk $end
$var reg 1 ( d $end
$var reg 8 ) dat1[0:7] $end
$upscope $end
$enddefinitions $end
#0
$dumpvars
0!
0"
b1010111 #
1$
1%
1&
0'
1(
b1011100 )
$end
#5
b1011101 )
#10
1"
b1011010 #
0$
1'
#20
0'
#25
0(
b1011110 )
#30
0"
1'
#40
0'
#45
b1011111 )
#50
1'
#55
1$
0&
1(
#60
0'
#70
1"
b1011110 #
0$
1'
#80
0'
#90
0"
1$
1'
#100
0'
#110
1"
0$
1'
#120
0'

But it's the regular use case to provide a filename in order to store the new VCD trace into a file (in this example, ibuffer-small.vcd).

# ... and save it to a newly created VCD file
write_vcd ibuffer-small.vcd

Load, select, and show the newly-created VCD file

We now can already apply those steps we learned from this tutorial so far by reading and processing the newly-created VCD trace. When list-ing, we notice that there is another VCD trace loaded, but not yet selected.


# Read the newly created VCD file
read_vcd ibuffer-small.vcd
list
 * ibuffer.vcd
 *   [0]: 0.25:160:280:20+0
   ibuffer-small.vcd

We select the new VCD trace...

# Select newly created VCD file (and check it's selected)
cd ibuffer-small.vcd; list
   ibuffer.vcd
     [0]: 0.25:160:280:20+0
 * ibuffer-small.vcd

... create a diagram ....

# Add a diagram, select it, again list to see the diagram selected
add detail
cd detail
list
   ibuffer.vcd
     [0]: 0.25:0:280:20+0
 * ibuffer-small.vcd
 *   [detail]: 1.00:0:120:5+0

... and select all signals available in that new VCD file.

# Select all signals (are much less than original input VCD) and then show the
# diagram. Note that scale and step were not preserved
select .*
ls; show
root
└── ibuffer_tb
    ├── ibuffer_tb.a
    ├── ibuffer_tb.b
    ├── ibuffer_tb.c
    ├── ibuffer_tb.clk
    ├── ibuffer_tb.d
    ├── ibuffer_tb.dat1[0:7]
    ├── ibuffer_tb.dat2[0:7]
    ├── ibuffer_tb.empty
    └── ibuffer_tb.full

Dump the selected trace

NLTrace provides the dump command to print the internal represenation of the selected VCD trace:

# Select a subset of the signals matching the regex
select dat*
ls
root
└── ibuffer_tb
    ├── ibuffer_tb.dat1[0:7]
    └── ibuffer_tb.dat2[0:7]
# ... and dump internal representation of VCD
dump
"#" : {
    "references": [
        "ibuffer_tb.dat2[0:7]"
    ],
    "size": "8",
    "var_type": "wire",
    "tv": [
        [
            0,
            "1010111"
        ],
        [
            10,
            "1011010"
        ],
        [
            70,
            "1011110"
        ]
    ]
}
")" : {
    "references": [
        "ibuffer_tb.dat1[0:7]"
    ],
    "size": "8",
    "var_type": "reg",
    "tv": [
        [
            0,
            "1011100"
        ],
        [
            5,
            "1011101"
        ],
        [
            25,
            "1011110"
        ],
        [
            45,
            "1011111"
        ]
    ]
}

Adding offset to the time scale

We now realize that we extracted a detail from the original VCD, but the time scale starts at 0. This is where we add offset to the time scale to remain consistent with the original VCD's time scale.

set offset 160
show
list

   ibuffer.vcd
     [0]: 0.25:160:280:25+0
 * ibuffer-small.vcd
 *   [detail]: 0.50:0:120:20+160

We again scale the diagram and set a bigger time step:

set scale .5
set step 20
show

It's so easy to add more signals to the diagram. Let's add the clock signal:

select -a ibuffer_tb.clk
show

Hide axes and grid

The actual time scale may be irrelevant for small diagrams -- or traces of clock-driven, synchronous sequential circuits. It would not add value to the diagram. In this case, NLTrace provides a diagram parameter 'plain', which instructs the backaend to hide axes and grid when set to "True". Default vaule is "False".

# Remove axes and grid
set plain True
show

Use the command history

NLTrace provides a history command to list the last issued commands. This is very practical in order to write scripts that automate tasks to generate diagrams from VCD traces. The history command accepts a filename to store its output, and a --limit option to limit the number of output lines. For example, we can easily save the commands entered during this tutorial to a script file; After that, we can edit the script file, and execute the script by NLTrace. This way, it is easy to quickly try out steps and options to draw a diagram, and yet to reproduce them with the script command.

# Print the command history along with line numbers and check at which line this tutorial starts (e.g., 79)
history -i

# Save the history of this tutorial in a file
history --limit 100 tutorial.nl

Exit NLTrace

We simply type exit to quit NLTrace.

exit

Running an NLTrace script

Now, we edit our file tutorial.nl in order to remove unwanted commands (like the history commands above), but also preceding history not relating to this tutorial. Then, we can either instruct NLTrace via command-line parameters to execute the script, or we use the script command from within the interactive command shell.

# Type 'help' to get a list of available commands
help
# Type 'help read_vcd' to get detailed help for 'read_vcd' command
help read_vcd
# Read VCD file
read_vcd ibuffer.vcd
# Show available VCD files and diagrams
list
# Select VCD file
cd ibuffer.vcd
list
# Add diagram to VCD file
add 0
list
# Select diagram
cd 0
list
# Show selected items (should be empty right now -- i.e., only show `root`)
ls
# Select signals (using regex)
select -l2 .*
# Again show selected signals (should show a tree representing design hierarchy)
ls
# Generate PDF and show waveform
show
# Scale waveform by a half
set scale .5
# Show again
show
# Scale waveform by a quarter
set scale .25
# Show again (tick labels are now overlapping)
show
# Set step of the time scale's tick marks
set step 25
# Show again
show
# Set begin to 160 and show again
set begin 160; show
# Set end to 280 and show again
set end 280; show
# Print Latex code for diagram (you can include that into your LaTeX documents)
write_tex
# Wrap the diagram into a LaTeX standalone document (and save it to a file)
write_tex --standalone
write_tex --standalone ibuffer.tex
# Save result in a PDF file
write_pdf ibuffer.pdf
# Also create PNG and SVG versions of the waveform
write_png ibuffer.png
write_svg ibuffer.svg
# Print a VCD trace for the selected subset of the input VCD file
write_vcd
# ... and safe it to a newly created VCD file
write_vcd ibuffer-small.vcd
# Read the newly created VCD file
read_vcd ibuffer-small.vcd
# List available VCD files
list
# Select newly created VCD file (and check it's selected)
cd ibuffer-small.vcd; list
add detail
cd detail
list
# Select all signals (are much less than original input VCD) and then show the
# diagram. Note that scale and step were not preserved
select .*
ls; show
# Dump internal representation of VCD
# Select a subset of the signals matching the regex and dump again
select dat*
ls; dump
# Set offset and adjust scale/time step
set offset 160
show
list
set scale .5
set step 20
show
# Add clock signal
select -a ibuffer_tb.clk
show
# Remove axes and grid
set plain True
show

We execute the script via command-line parameter:

nltrace -s tutorial.nl

Or, with the script command from within the interactive command shell:

script tutorial.nl

Previous Post