Series on Vivado Simulator Scripted Flow (Bash, Makefiles)

New skills to be proud of

If you’ve read Part 1 of this series, you should already be an expert in using the Vivado Simulator from the command line. Now it’s time to get a professional certification in Bash scripting (class attendance is not required). This beginner-friendly step-by-step guide demonstrates how to automate the entire Vivado simulation flow by writing a Bash script.

itsembedded.com Fig. 1: You are not a project flow user anymore

Important Note: This guide is meant to give you a short introduction to Bash scripting with Vivado’s xvlog, xvhdl, xelab and xsim command line tools. I do NOT recommend using a Bash script for automated builds of serious simulation projects - the Makefile-based flow that will be discussed in Part 3 is superior for creating a scripted flow.

As a precursor to Part 3, being familiar with Bash basics is extremely beneficial, as Makefiles can incorporate inline Bash scripts as part of more advanced target recipes. If you’re good with Bash and know your way around the Vivado Simulator command line tools, feel free to skip this guide and go straight to Part 3.

Bash and scripts

What is Bash? Well, the abbreviation stands for Bourne-again shell, and it’s the shell that your Linux terminal uses by default (yes, there are exceptions - if you’re using an alternative shell then you likely don’t need my advice). The Bash shell is what took the commands we issued in Part 1 and called the Vivado tools as requested, along with passing them our desired parameters.

Bash, as well as other shells, allows users to collect multiple commands in a single file, called a script (just like TCL but for programs instead of constraints). If we don’t put our simulation flow steps into a single script, then we need to memorize and type the commands out each time, or keep browsing the terminal history (that’s so unprofessional, what if the client sees you doing this?!).

Honestly, if you know how to use Vivado, you probably know what scripting is, and the only reason I’m writing this is because this text needs some kind of introduction. On to the interesting bits!

Setting up a basic scripted flow

To start off, you have two choices. You can either get the finished project sources and completed script of this guide by cloning my repository:

git clone -b part_2 https://github.com/n-kremeris/vivado-scripted-flow.git

and explore while reading the guide, or, if you wish to continue from Part 1, you can follow all the instructions that follow below.

Open up a new terminal window, and go to work_dir/SIM/. The first thing we do here is get rid of all the junk Vivado generated previously, create an empty script file for our soon-to-be automated flow, and then mark it as executable:

rm -rf *
touch xsim_flow.sh
chmod +x xsim_flow.sh

Open the xsim_flow.sh file in a text editor and paste the following text:

#!/bin/bash
xvlog --sv ../SRC/adder.sv ../SRC/tb.sv
xelab -debug typical -top tb -snapshot adder_tb_snapshot
xsim adder_tb_snapshot -R

The first line tells Bash to use itself as the command interpreter for this script. Setting the interpreter allows you to launch scripts written in different languages (like Python, Perl, Ruby) from the command line by executing the script as if it was a binary executable, meaning that instead of calling Bash explicitly with the script as the parameter: bash scriptname.sh, you can just write ./scriptname.sh.

The remaining three lines are what we previously used to, in matching order, compile the sources, elaborate, and launch the simulation. Save the file and try executing it:

[work_dir/SIM]$ ./xsim_flow.sh
INFO: [VRFC 10-2263] Analyzing SystemVerilog file "<...>/work_dir/SRC/adder.sv" into library work
INFO: [VRFC 10-311] analyzing module adder
<...>
Running: /opt/Xilinx/Vivado/2020.2/bin/unwrapped/lnx64.o/xelab -debug typical -top tb -snapshot adder_tb_snapshot
Multi-threading is on. Using 10 slave threads.
Starting static elaboration
<...>
source xsim.dir/adder_tb_snapshot/xsim_script.tcl
# xsim {adder_tb_snapshot} -autoloadwcfg -runall
Vivado Simulator 2020.2
Time resolution is 1 ps
run -all
TB passed, adder ready to use in production
exit
INFO: [Common 17-206] Exiting xsim at Sat Feb 13 14:46:35 2021...
[work_dir/SIM]$

Great, now you have a simple scripted flow!

Improving the script

Basics

Before proceeding further, there are two things I immediately dislike about our script - firstly, there’s no way to easily launch the waveform view without opening and editing the script or entering another command, and secondly, the text output of all three commands is mashed together into one giant block of text.

The latter is particularly annoying, because if you see a warning message, you wont have a straightforward view of which tool is dissatisfied with your code. So lets fix both of those issues!

#!/bin/bash
echo "### COMPILING ###"
xvlog --sv ../SRC/adder.sv ../SRC/tb.sv

echo
echo "### ELABORATING ###"
xelab -debug typical -top tb -snapshot adder_tb_snapshot

echo
echo "### RUNNING SIMULATION ###"
xsim adder_tb_snapshot --tclbatch xsim_cfg.tcl

if [ "$1" == "waves" ]; then
    echo "### OPENING WAVES ###"
    xsim --gui adder_tb_snapshot.wdb
fi

The first change adds some echo statements that let us easily know what step we are currently in with just a quick glance. The echo lines without any additional text simply print out empty lines. The output is now much nicer as a result:

[work_dir/SIM]$ ./xsim_flow.sh
### COMPILING SYSTEMVERILOG###
INFO: [VRFC 10-2263] Analyzing SystemVerilog file "<..>work_dir/SRC/adder.sv" into library work
<..>
INFO: [VRFC 10-311] analyzing module tb

### ELABORATING ###
Vivado Simulator 2020.2
<..>
Built simulation snapshot adder_tb_snapshot

### RUNNING SIMULATION ###
****** xsim v2020.2 (64-bit)
<...>
TB passed, adder ready to use in production
exit
INFO: [Common 17-206] Exiting xsim at Sat Feb 13 15:39:32 2021...
[work_dir/SIM]$

The second change is the addition of rudimentary command line parameter parsing. If the word “waves” is passed as the argument to our script, it launches xsim in graphical mode as the last step and opens the waveform database file (.wdb) from the simulated snapshot for you to explore visually. If no arguments are passed, then this step is skipped.

./xsim_flow.sh <- runs the simulation flow in text only mode

./xsim_flow.sh waves <- runs the simulation flow and launches the waveform window

Vivado Xsim Waveform View From Command Line Waveform view launch in action

Sources and parameters

New and updated modules

Lets say we’ve got two new source files that we want to add to our project. Firstly, a subtraction module written in SystemVerilog:

module subtractor_systemverilog (
    input logic  [31:0] x,
    input logic  [31:0] y,
    output logic [31:0] sub
);
    logic [31:0] y_comp2;
    always_comb begin
        y_comp2 = ~y + 32'b1;
        sub = x + y_comp2;
    end
endmodule : subtractor_systemverilog

And secondly, a subtraction module written in VHDL:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity subtractor_vhdl is
   port (
	x   : in  std_logic_vector(31 downto 0);
	y   : in  std_logic_vector(31 downto 0);
	sub : out std_logic_vector(31 downto 0)
   );
end entity subtractor_vhdl;

architecture rtl of subtractor_vhdl is
begin
    process(x, y) begin
	    SUB     <= std_logic_vector( signed(x) - signed(y) );
    end process;
end architecture rtl;

These sources should be placed in work_dir/SRC/subtractor.sv and work_dir/SRC/subtractor.vhdl, respectively.

We also need to modify the existing testbench work_dir/SRC/tb.src to not only instantiate and test the subtractor, but also let us select between instantiating the SystemVerilog or the VHDL versions:

module tb ();
    logic [31:0] a, b, sum; // for the adder
    logic [31:0] x, y, sub; // for the subtractor

    adder DUT_adder(.*); //instantiate the amazing adder

    `ifdef SUBTRACTOR_VHDL
        subtractor_vhdl DUT_subtractor
        (   // wildcard (.*) connection not supported in XSIM for VHDL
            .x(x),
            .y(y),
            .sub(sub)
        );
    `elsif SUBTRACTOR_SV
        subtractor_systemverilog DUT_subtractor(.*);
    `else
        $fatal(1, "Subtractor DUT not selected, please define SUBTRACTOR_VHDL or SUBTRACTOR_SV");
    `endif

    initial begin
        `ifdef SUBTRACTOR_VHDL
            $display("$$$ TESTBENCH: Using VHDL subtractor");
        `elsif SUBTRACTOR_SV
            $display("$$$ TESTBENCH: Using SystemVerilog subtractor");
        `endif

        #1;
    	  a = 1;
    	  b = 2;
        x = 9;
        y = 3;
    	  #1;
    	  assert (sum == 3) else
    	      $fatal(1, "Adder failed");
        assert (sub == 6) else
            $fatal(1, "Subtractor failed");

    	$display("TB passed, adder and subtractor ready to use in production");
    end
endmodule : tb

Adding new SystemVerilog sources and compilation parameters

To include the SystemVerilog subtractor in the compilation, we can modify the xvlog line in our script to include the path to the new file, like so:

xvlog --sv ../SRC/adder.sv ../SRC/tb.sv ../SRC/subtractor.sv

That’s not too bad for 2-3 source files, but what if we have tens or hundreds of source files in our project? That single line can get very long very quickly! Even more so if we want to add some extra parameters or defines later on.

To prevent our single command from growing to unwieldy lengths, we want to store the list of sources in a separate Bash variable named SOURCES_SV.

While we’re at it, lets also put --incr and --relax parameters in a variable named COMP_OPTS_SV. --incr turns on incremental compilation, this means that xvlog which skips re-compiling previously compiled files that have not been edited. --relax tells the compiler to relax strict HDL rules.

Completing the above modifications to the xvlog line results in the following code:

# These are all the SystemVerilog source files
SOURCES_SV=" \
    ../SRC/adder.sv \
    ../SRC/subtractor.sv \
    ../SRC/tb.sv \
"
COMP_OPTS_SV=" \
    --incr \
    --relax
"
echo "### COMPILING SYSTEMVERILOG ###"
xvlog --sv $SOURCES_SV $COMP_OPTS_SV

Note: Lines ending with a backslash “\” tell Bash to ignore the line break and append the next line to where the backslash is located.

Compiling VHDL

To compile the VHDL version of the subtractor, we need to use xvhdl in our flow. Based on the previous section, we can do that by adding the following code to our script:

SOURCES_VHDL=" ../SRC/subtractor.vhdl "
COMP_OPTS_VHDL=" --incr --relax "

echo
echo "### COMPILING VHDL ###"
xvhdl $SOURCES_VHDL $COMP_OPTS_VHDL

Since we only have a single source file, I chose not to use the multiple line notation.

Adding `define’s and return code checking

After completing the modifications listed in the previous sections, our Bash script looks like this:

#!/bin/bash
SOURCES_SV=" \
    ../SRC/adder.sv \
    ../SRC/subtractor.sv \
    ../SRC/tb.sv \
"
COMP_OPTS_SV=" \
    --incr \
    --relax \
"
SOURCES_VHDL=" ../SRC/subtractor.vhdl "
COMP_OPTS_VHDL=" --incr --relax "

echo "### COMPILING SYSTEMVERILOG ###"
xvlog --sv $SOURCES_SV $COMP_OPTS_SV

echo
echo "### COMPILING VHDL ###"
xvhdl $SOURCES_VHDL $COMP_OPTS_VHDL

echo
echo "### ELABORATING ###"
xelab -debug typical -top tb -snapshot adder_tb_snapshot

echo
echo "### RUNNING SIMULATION ###"
xsim adder_tb_snapshot --tclbatch xsim_cfg.tcl

if [ "$1" == "waves" ]; then
    echo "### OPENING WAVES ###"
    xsim --gui adder_tb_snapshot.wdb
fi

Let’s run this and see what happens. Did you notice a problem? If you haven’t removed the stale simulation files in your directory, you should see this:

[work/SIM]$ ./xsim_flow.sh
<...>
### ELABORATING ###
Vivado Simulator 2020.2
<...>
Pass Through NonSizing Optimizer
ERROR: [VRFC 10-8279] $fatal : Subtractor DUT not selected, please define SUBTRACTOR_VHDL or SUBTRACTOR_SV [/home/toby/Documents/blogs/work_dir/SRC/tb.sv:18]
ERROR: [XSIM 43-3322] Static elaboration of top level Verilog design unit(s) in library work failed.

### RUNNING SIMULATION ###
<...>
TB passed, adder ready to use in production
exit
INFO: [Common 17-206] Exiting xsim at Sat Feb 13 17:30:00 2021...
[work/SIM]$

We have two issues here. Firstly, we haven’t defined neither SUBTRACTOR_VHDL nor SUBTRACTOR_SV anywhere in our HDL, nor have we passed a `define parameter to xvlog, and therefore the elaboration step fails.

The second, and more alarming issue, is that despite the failed elaboration, the simulation step completes successfully!

To fix the missing define issue, we will pass the required define to xvlog as a parameter: -d OUR_NEW_DEFINE. Lets use the VHDL subtractor version for now. As we may want to use more than 1 define in the future, we store the defines in a variable named DEFINES_SV, and append it to the xvlog line:

DEFINES_SV=" -d SUBTRACTOR_VHDL "
xvlog --sv $SOURCES_SV $COMP_OPTS_SV $DEFINES_SV

As for the second issue, synthesis is started regardless of the failed elaboration due to the files of the previously elaborated snapshot still existing in our work directory. To fix this, we can check the exit code of the xelab command.

The exit code is a value that’s returned by an executable to the shell after exiting, and typically, a value of 0 means that the command executed successfully, while other values signify various errors. The return code of the last command is stored in the special variable $?, let me demonstrate this with the touch command:

[work_dir/SIM]$ touch
touch: missing file operand
Try 'touch --help' for more information.
[work_dir/SIM]$ echo $?
1

[work_dir/SIM]$ touch filename_operand
[work_dir/SIM]$ echo $?
0

As you can see, when the touch command is called without a filename argument, it prints an error message. It also sets the exit code to 1 upon completion, which we can read with echo $?.

Conversely, when the touch command is given a filename as an argument, no error message is printed, and the exit code is set to 0.

Therefore, to solve our simulation flow problem, we should check if the elaboration step has completed successfully, and if not, we should stop the script without running the simulation step. The same also applies to the SV and VHDL compilation steps. Here is what the return code checking looks like for the SystemVerilog compilation step:

xvlog --sv $COMP_OPTS_SV $DEFINES_SV $SOURCES_SV
if [ $? -ne 0 ]; then
    echo "### SYSTEMVERILOG COMPILATION FAILED ###"
    exit 10
fi

As you can see, we check the $? special variable right after executing xvlog, and if the value of $? is not 0, we print a failure message and exit the script. We give the exit command an arbitrary custom exit value of 10. If we had another script that were to call xsim_flow.sh, we could check the exit value of the script to see if it has finished successfully just like we checked the exit status of xvlog.

The full resulting script should now look like this:

#!/bin/bash
GUI=-R
if [ "$1" == "waves" ]; then
        GUI=--gui
fi
SOURCES_SV=" \
    ../SRC/adder.sv \
    ../SRC/subtractor.sv \
    ../SRC/tb.sv \
"
COMP_OPTS_SV=" \
    --incr \
    --relax
"
DEFINES_SV=" -d SUBTRACTOR_VHDL "
SOURCES_VHDL=" ../SRC/subtractor.vhdl "
COMP_OPTS_VHDL=" --incr --relax "

echo
echo "### COMPILING SYSTEMVERILOG ###"
xvlog --sv $COMP_OPTS_SV $DEFINES_SV $SOURCES_SV
if [ $? -ne 0 ]; then
    echo "### SYSTEMVERILOG COMPILATION FAILED ###"
    exit 10
fi

echo
echo "### COMPILING VHDL ###"
xvhdl $COMP_OPTS_VHDL $SOURCES_VHDL
if [ $? -ne 0 ]; then
    echo "### VHDL COMPILATION FAILED ###"
    exit 11
fi

echo
echo "### ELABORATING ###"
xelab -debug all -top tb -snapshot adder_tb_snapshot
if [ $? -ne 0 ]; then
    echo "### ELABORATION FAILED ###"
    exit 12
fi

echo
echo "### RUNNING SIMULATION ###"
xsim adder_tb_snapshot --tclbatch xsim_cfg.tcl

if [ "$1" == "waves" ]; then
    echo
    echo "### OPENING WAVES ###"
    xsim --gui adder_tb_snapshot.wdb
fi

exit 0

If we run it now, it should complete successfully, and it should simulate both the adder and the VHDL version of the subtractor:

[worl_dir/SIM]$ ./xsim_flow.sh
### COMPILING SYSTEMVERILOG ###
INFO: [VRFC 10-2263] Analyzing SystemVerilog file "<..>/work_dir/SRC/adder.sv" into library work
INFO: [VRFC 10-311] analyzing module adder
<..>

### COMPILING VHDL ###
INFO: [VRFC 10-163] Analyzing VHDL file "/home/toby/Documents/blogs/work_dir/SRC/subtractor.vhdl" into library work
INFO: [VRFC 10-3107] analyzing entity 'subtractor_vhdl'

### ELABORATING ###
Vivado Simulator 2020.2
Copyright 1986-1999, 2001-2020 Xilinx, Inc. All Rights Reserved.
Running: /opt/Xilinx/Vivado/2020.2/bin/unwrapped/lnx64.o/xelab -debug all -top tb -snapshot adder_tb_snapshot
Multi-threading is on. Using 10 slave threads.
Starting static elaboration
<..>

### RUNNING SIMULATION ###
****** xsim v2020.2 (64-bit)
<..>
run -all
$$$ TESTBENCH: Using VHDL subtractor
TB passed, adder and subtractor ready to use in production
exit
INFO: [Common 17-206] Exiting xsim at Sat Feb 13 21:35:20 2021...

You can see from the output that the entire flow now completes successfully. Most importantly, the simulation output shows that we are definitely using the VHDL version of the subtractor, meaning the latest version of the code is now built correctly.

And if we add add some broken syntax into one of our modules, for example, the adder:

 5 module adder (
 6     input logic [31:0] a,
 7     input logic [31:0] b,
 8     output logic [31:0] sum,
 9     this is not correct syntax $%!@#$%~!
10 );

then we see that compilation fails when re-running the flow script, and the process is aborted:

[work_dir/SIM]$ ./xsim_flow.sh
### COMPILING SYSTEMVERILOG ###
INFO: [VRFC 10-2263] Analyzing SystemVerilog file "<..>/work_dir/SRC/adder.sv" into library work
INFO: [VRFC 10-311] analyzing module adder
ERROR: [VRFC 10-4982] syntax error near 'not' [<..>/work_dir/SRC/adder.sv:9]
ERROR: [VRFC 10-2790] SystemVerilog keyword not used in incorrect context [<..>/work_dir/SRC/adder.sv:9]
### SYSTEMVERILOG COMPILATION FAILED ##
[work_dir/SIM]$

as a bonus, we can also see that the special variable “$?” has been set to our custom value:

[work_dir/SIM]$ echo $?
10

and if you managed to get this far, congratulations, you now have a fully scripted Vivado simulation flow!

Limitations

  • Norbert, if I want to change the subtractor selection define, I have to go and edit the script! What if I want to switch the defines without editing our xsim_flow file?

Well, we could add a more advanced argument parsing and allow switchi…

  • And, you know what, what if I just want to run the simulation step without rebuilding anything?

It’s possible to add extra arguments when calling the script and parse them accordi…

  • Worst of all, if I just want to run the compile step to check my syntax, I must wait for the entire flow to complete or kill it after the compilation step!

This isn’t really a question, but you can try… No, no, let’s stop here while we can.

Conclusion

From the brief Q&A in the previous section, you can see that our script is far from perfect. However, it is a fully working script that lets us run the entire simulation flow with one command - ./xsim_flow.sh, which is just as easy as clicking the start button in the Vivado GUI, but without any graphical overhead, thousands of menu windows, and with very fast customization.

The listed limitations can be fixed with additional modifications to the current Bash script, however, it is not worth the effort! In Part 3, we’re going to move the current functionality of our Bash script into a Makefile, which is used by a tool called… make. Yeah, big exciting reveal, I know.

A Makefile will allow us to create separate “targets” for compilation, elaboration, simulation, cleanup, or others, and even combine those into new, bigger targets.

For example:

  • We can have a “do-everything” target that consists of smaller compile, elaborate and simulate targets. Running this target would execute the smaller subtargets in order, and if one of the steps fail, the following subtarget would not be launched. In essence, this would act as a direct equivalent of the script we created in this guide.

  • We can, however, still run the “simulate” target with the old snapshot if we want to, even if we modified the sources and broke our code.

  • We can also only run the “compile” step for a quick syntax check.

  • We can additionally only run the “elaborate” step after compilation, without launching simulation, for example, to check if our module connections are done correctly.

I hope you’re excited to continue, so I will see you Part 3!

FAQ

  • When I try to run xvlog/xvhdl/xelab/xsim, I get an error saying bash: xvlog/xvhdl/xelab/xsim: command not found

Did you forget to source the Vivado configuration script at /opt/Xilinx/Vivado/2020.2/settings.sh? You have to do this every time you open a new terminal if you plan to use Vivado command line tools.

  • There is no such file at /opt/Xilinx/Vivado/2020.2/settings.sh!

Then you have a different version of Vivado or have it installed somewhere else than me. If you can’t find it, do this:

find / -iname "Vivado" -print 2>/dev/null

This should find you the path of the Vivado installation regardless of where it is in the system. It may take a while.

[work_dir/SIM]$ find / -iname "Vivado" -print 2>/dev/null
/opt/Xilinx/Vivado
/opt/Xilinx/Vivado/2020.2/bin/vivado
/opt/Xilinx/Vivado/2020.2/bin/unwrapped/lnx64.o/vivado
<...>



If you have any questions or observations regarding this guide

Feel free to add me on my LinkedIn, i’d be happy to connect!

Or send me an email: