top of page

MicroZed Chronicles: Vivado Simulator Interface – Using C Test Benches on HDL

Last week we examined Xilinx simulation and how we could create test benches for behavioral and post-layout simulation along with creating the switching activity file that is used to provide more accurate power estimate.

The test bench we created last week was self-checking because it checked the output value against expected values supplied from a text file. This works well for simple examples like the one presented, however, for more complex examples we may want to use a different structure.

For complex algorithms, it is often a good idea to first create a model which defines the algorithm’s behavior. This algorithm could be developed in a high-level language such as C or Python. It is important for some projects to get the customer to sign off and agree to the models before further RTL development.

We can use these models within the test bench to verify the HDL implementation on a cycle-by-cycle basis. This ensures the implemented algorithm works exactly as the model intends it.

One of the best ways we can use C or even Python models to verify our HDL behavior is to use the Xilinx Simulator Interface (XSI). This enables us to interface with the Vivado simulator using C++ which allows us to create the test bench in C that can leverage the capabilities provided by a higher-level language. We can also add Python bindings to the C, enabling it to be accessed from an even higher level. Graeme Smecher has a great example of this on his GitHub.

Using the XSI interface is straight forward. Indeed a Vivado installation provides all of the software, libraries, and even examples we need to get up and going.

All of the necessary information is contained within UG900 if we want to set up a XSI simulation using C.

Getting started is straightforward following these steps:

1) Set the location if the Simulation Kernel shared Variable in the Windows path. This is available under your Vivado installation <install path>\ Vivado\2020.1\lib\win64.o directory.

2) We also need to set up the MinGW libraries in the Windows path. This is also included under the Vivado installation

<install path>\Vivado\2020.1\tps\mingw\6.2.0\win64.o\nt\x86_64-w64-mingw32\lib

When the C files are compiled, a project shared library will be created under the directory we are working in and named after the project. For example <simulation dir>/xsim.dir/<project name>.

The best way to understand how to do this is to run one of the Xilinx examples which comes with Vivado. There are two examples available under <install path>\Vivado\2020.1\examples\xsim\vhdl\xsi

Copy the adder directory to an area outside of the Vivado project directory to a working directory. This leaves the examples preserved, as we will be making a few changes.

With the adder example copied, open up the provided BAT file and add in the install location of the Vivado installation.

You might also want to add the command pause at the end of the file. This stops the BAT file from closing once completed.

Initial BAT File
Modified BAT File

Running the BAT should produce the output below where the C test bench file is applied. The results can be seen in the output.

Output from the XSI Example

But, what is really going on here if we wish to start from scratch and create our own XSI test bench?

Let’s look at the files in the example directory:

  • Adder.prj – This defines the list of VHDL files in the project and their associated libraries

  • Addr.vhd – The actual Unit Under Test

  • Run.bat – Window BAT file to execute simulation

  • Set_env.csh & run.csh – Linux scripts to execute simulation

  • Testbench.cpp – The test bench which stimulates and checks the UUT

  • Xsi_loader.ccp/h – API for working with the XSI to enable loading, running, and driving the simulation

  • Xsi_shared_lib.h – Header file for the shared libraries

If we want to start from scratch, a good starting point is one of the example directories. All we need to do then is update the example for our test bench. This will include updating the project file to include the files we want to simulate. It is of course a good idea to change the name to one which accurately reflects the project being simulated.

We will need to update the batch or Linux scripts to consider the new name for the project. However, the main file we will want to change is the test bench file. This file makes several calls to the shared library to set up and execute the simulation.

I thought it would be a good idea to try simulating the example code I created last week so I modified the files to support this, including the test bench.

The test bench file is pretty simple to follow. The biggest confusion could be how the test bench represents the nine values of std logic. It does this using a char array.

const char* std_logic_literal[]={"U", "X", "0", "1", "Z", "W", "L", "H", "-"};

Therefore, to set a logic one, we use the value 3 and a logic zero has the value 2. The best way to work with these is to declare constants to make the code more readable.

If we have input or output vectors on the entity, we again declare a C array of the same width as the number of bits.

We are then able to set elements of the array to a std logic value and they will be reflected in the std_logic_vector input on the UUT.

Setting up the test bench uses the following flow:

  1. Load to simulation project shared library

  2. Load in the design library and simulation engine

  3. Get the entity port ID’s

  4. Stimulate the design and capture the outputs

Running this through using last week’s example to check the first value out from the average function we created gives the following output in the terminal window, which is correct.

To stimulate this design, I used the following commands:

  • Xsi_Instance.get_port_number – To get the entity port number for a specific port.

  • Xsi_Instance.num_ports – Get the port information, name, width, direction, and size.

  • Xsi_Instance.put_value – Apply a value std_logic value to a selected port.

  • – Run the simulator for a specific period of time.

  • Xsi_Instance.display_port_values – Display the port values.

By using these and other calls to the XSI shared library as defined with UG900, we can create test benches which use C++ to stimulate the unit under test and allows for easy comparison with algorithm models.

The example project is available here on my GitHub if you want to examine it further!



bottom of page