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