Industrial manufacturing
Industrial Internet of Things | Industrial materials | Equipment Maintenance and Repair | Industrial programming |
home  MfgRobots >> Industrial manufacturing >  >> Industrial programming >> VHDL

Integrating Quartus Prime IP Libraries into VUnit: A Step‑by‑Step Guide

Have you ever wanted to run a VHDL simulation that includes a Quartus IP core through the VUnit verification framework?

That’s what FPGA engineer Konstantinos Paraskevopoulos had in mind, but he couldn’t find a suitable tutorial for it. Fortunately, he used his talent to figure out how and was kind enough to share it with VHDLwhiz through this guest article.

Let’s give the word to Konstantinos!

It is often desirable to incorporate predefined IPs from the Quartus IP Catalog into your design when simulating your system with VUnit. This tutorial supplies the knowledge needed to generate, incorporate, and link external Quartus IP libraries to the VUnit environment.

New to VUnit? Check out this tutorial: Getting started with VUnit

Overview

This tutorial consists of three main parts :

  1. A brief description of the selected IP
  2. Steps required to generate and link the appropriate libraries
  3. Verification by utilizing VUnit and Modelsim

Requirements

It also assumes having basic VHDL knowledge and ModelSim skills.

Design under test

For our scenario, we utilize the Parallel Adder IP from the Quartus Integer Arithmetic IP list.

Integrating Quartus Prime IP Libraries into VUnit: A Step‑by‑Step Guide

Our design accepts three 16‑bit input vectors and outputs the added result in a 17‑bit vector.

Step 1 : Generate IP

We generate our adder at the IP catalog window by double‑clicking the parallel adder component under Library/Basic functions/Arithmetic.

After we provide a name and customize our component based on our needs, we click the Generate HDL button at the bottom‑right.

Integrating Quartus Prime IP Libraries into VUnit: A Step‑by‑Step Guide

At this point, a window will appear, as depicted in the following figure.

Note: We must set the Create simulation model under the Simulation section to either VHDL or Verilog to generate the simulation files since the default option is none. If we don’t choose one, the given_ip_name.spd file will not be generated, causing the next step to fail.

Integrating Quartus Prime IP Libraries into VUnit: A Step‑by‑Step Guide

The above process generates a file and a folder under our quartus directory:

  1. File: given_ip_name.ip
  2. Folder: given_ip_name

The folder contains .vhd and .v files that need to be added later in our run.py script.

Step 2 : Generate IP simulation files

  1. GUI: Select Tools ➤ Generate Simulator Setup Script for IP and specify output directory in the prompt window,
  2. CMD: By utilizing Qsys commands, we can generate the same files by typing in the terminal the following command:
ip-setup-simulation --quartus-project= <project's_QPF_filepath> \
--output-directory= <my_dir>

Using one of the two methods above, we instruct Quartus to generate a directory for each supported simulator that holds a script to create and compile the IP libraries.

Step 3: Generate and compile IP libraries for Modelsim

The next step is to find the msim_setup.tcl script in the mentor folder created by the previous step and duplicate it with the name setup.tcl. Then, in the setup.tcl file, uncomment the illustrated commands and set the $QSYS_SIMDIR variable.

# # QSYS_SIMDIR is used in the Quartus-generated IP simulation script to
# # construct paths to the files required to simulate the IP in your Quartus
# # project. By default, the IP script assumes that you are launching the
# # simulator from the IP script location. If launching from another
# # location, set QSYS_SIMDIR to the output directory you specified when you
# # generated the IP script, relative to the directory from which you launch
# # the simulator.
# 
 set QSYS_SIMDIR <script generation output directory>
# 
# # Source the generated IP simulation script.
 source $QSYS_SIMDIR/mentor/msim_setup.tcl
# 
# # Set any compilation options you require (this is unusual).
# set USER_DEFINED_COMPILE_OPTIONS <compilation options>
# set USER_DEFINED_VHDL_COMPILE_OPTIONS <compilation options for VHDL>
# set USER_DEFINED_VERILOG_COMPILE_OPTIONS <compilation options for Verilog>
# 
# # Call command to compile the Quartus EDA simulation library.
 dev_com
# 
# # Call command to compile the Quartus-generated IP simulation files.
 com
# 

After altering and saving the setup.tcl, we can safely execute the Tcl file using the vsim command.

vsim -c -do "do setup.tcl; quit"

That generates the compiled libraries in the mentor folder.

Step 4: VUnit link

Now that the IP libraries have been generated, we should link them by using the python run.py script.

Check out the figure below to better understand our example’s directory structure. The initial topology consisted of the root folder demo, the tb, vunit, and quartus folders. All subfolders and files under the quartus folder are generated via the Quartus framework after creating a project and completing steps 1 to 3.

Note: Quartus generates more files and folders, but the image below shows those of interest to us.

Integrating Quartus Prime IP Libraries into VUnit: A Step‑by‑Step Guide

Using this distinct view of the topology as a reference, we can specify our ROOT path and the path(s) to the generated libraries, as shown below.

Note that sim_files is the directory we specified in step 2 where the mentor folder has been stored.

from vunit import VUnit
from os.path import join, dirname, abspath
# ROOT
root = join(dirname(__file__), '../')
# Path to generated libraries
path_2_lib = '/quartus/sim_files/mentor/libraries/'
# ROOT

After creating a VUnit instance called vu, we can specify a design library for our VHDL code and link any required external libraries:

# Create VUnit instance by parsing command line arguments
vu = VUnit.from_argv()
# create design's library
my_lib = vu.add_library('my_lib')
# Link external library
vu.add_external_library("parallel_adder", root + path_2_lib + "parallel_adder")

And finally, add our source files. These are located in three subfolders under the given_ip_name directory:

  1. parallel_add_191
  2. synth
  3. sim

The synth and sim dirs contain the same information, namely the top‑level design of our IP. However, the formatting of these files in our case is in VHDL. They could be in Verilog, and this depends on the chosen language at step 1.

In case our top‑level design entails sub‑components, we must also include their source files. They are located under subfolders in the given_ip_name directory, such as the parallel_add_191 component in our case.

my_lib.add_source_files(join(root,'quartus','parallel_adder','sim','parallel_adder.vhd'))
my_lib.add_source_files(join(root,'quartus','parallel_adder','parallel_add_191','sim','parallel_adder_parallel_add_191_oh4guxa.vhd'))
my_lib.add_source_files(join(root,'tb','tb_demo.vhd'))
testbench = my_lib.entity("tb_demo")
vu.main()

Testbench

To begin with, you can check out this link to learn about the basics of VUnit testbench formation.

Back to our testbench, we add the necessary VUnit libraries along with any other library we would like to employ and define our signals.

Note: Process execution in our example is sequential. Thus, control signals (referred to as flags) are used to notify a process whether it shall commence or terminate.

library IEEE;
use IEEE.std_logic_1164.all;
use ieee.numeric_std.all;
library vunit_lib;
context vunit_lib.vunit_context;

entity tb_demo is 
generic ( runner_cfg : string:= runner_cfg_default);
end tb_demo;
architecture sim of tb_demo is
constant clk_period : time := 10 ns;
signal clk : std_logic := '0';
signal rst : std_logic := '0';
-- INPUTS
signal data_a : std_logic_vector(0 to 15):= (others => '0');
signal data_b : std_logic_vector(0 to 15):= (others => '0');
signal data_c : std_logic_vector(0 to 15):= (others => '0');
-- OUTPUTS
signal result : std_logic_vector(0 to 16);
-- CONTROL FLAGS
signal reset_done :boolean := false;
signal sim_done   :boolean := false;
signal start_sim  :boolean := false;

Following up, we instantiate our UUT. Quartus supplies component instantiation examples for VHDL and Verilog under the filename conventions ip_name_inst.vhd and ip_name_inst.v.

begin
-- Unit Under Test
UUT : entity work.parallel_adder
port map (
  data0x => data_a, -- parallel_add_input.data0x
  data1x => data_b, --                   .data1x
  data2x => data_c, --                   .data2x
  result => result  -- parallel_add_output.result
);

The first two processes that commence are clk_process and reset_rel. While the latter is suspended after resetting and driving the reset_done flag to true, the clk_process operates throughout the simulation time.

clk_process : process
begin
  clk <= '1';
  wait for clk_period/2;
  clk <= '0';
  wait for clk_period/2;
end process clk_process;

reset_rel : process
begin
  rst <= '1';
  wait for clk_period*2;
  wait until rising_edge(clk);
  rst <= not rst;
  reset_done <= true;
  wait;
end process reset_rel;

Now that the reset is done, we can invoke the test_runner process for executing our tests. Furthermore, the test runner remains active until the sim_done flag is driven to true, which takes place in the last process.

test_runner : process
begin
  test_runner_setup(runner, runner_cfg);
  wait until reset_done and rising_edge(clk);

  iterate : while test_suite loop
    start_sim <= true;
    if run("test_case_1") then
      info ("Start");
      info (running_test_case);
      wait until sim_done;
    end if;
  end loop;
  test_runner_cleanup(runner);
end process test_runner;

Finally, the data_generator process executes several additions by assigning values to the three inputs of our parallel adder by utilizing a for loop.

Note: This process is triggered when the test_runner process instructs so by setting up the start_sim flag. While at the end of this process, it raises the sim_done flag, commanding the test runner to pause the simulation.

data_generator : process
  constant tag2 : log_level_t := new_log_level("INFO", fg => blue, bg => black, style => bright);
  variable a,b,c,d : integer;
begin
  wait until start_sim;
  wait until rising_edge(clk);
  show(display_handler, tag2);
  if running_test_case = "test_case_1" then
    for i in 0 to 10 loop
      data_a <= std_logic_vector(to_unsigned(i+10,data_a'length));
      data_b <= std_logic_vector(to_unsigned(i+20,data_a'length));
      data_c <= std_logic_vector(to_unsigned(i+30,data_a'length));
      wait until rising_edge(clk);
      a := to_integer(unsigned(data_a));
      b := to_integer(unsigned(data_b));
      c := to_integer(unsigned(data_c));
      d := to_integer(unsigned(result));
      log( integer'image(a) &" + & integer'image(b) &" + & integer'image(c) 
          &" = & integer'image(d), tag2);
    end loop;
  end if;
  sim_done <= true;
end process data_generator;

Verification

To run the test case and verify that everything works as expected, we can execute the run.py script from the directory it is located by simply typing in the terminal the following command.

python ./run.py -v

Note: A Customized logger has been used for better illustration in our output that is visible by providing the verbose -v option. In addition, since only one test case is defined, we don’t have to provide an option to specify it.

Integrating Quartus Prime IP Libraries into VUnit: A Step‑by‑Step Guide

Finally, for verifying our results in ModelSim, we could type the following command:

python ./run.py --gui

Integrating Quartus Prime IP Libraries into VUnit: A Step‑by‑Step Guide

(Click the image to make it larger)

Conclusion

To conclude, we learned how to incorporate and test Quartus IPs that reside in the IP catalog within a VUnit environment. While this tutorial used a predefined IP, the same process applies to custom packaged IPs.

Check out this VUnit tutorial if you haven’t already:
Getting started with VUnit

VHDL

  1. VUnit 101: A Practical Guide to Automated VHDL Verification
  2. Creating String Lists in VHDL: Best Practices & Example
  3. How to Build a Self‑Checking Testbench in VHDL – A Practical Guide
  4. Using Impure Functions in VHDL: Enhancing FSM Readability and Maintainability
  5. Mastering VHDL Functions: A Practical Guide to Efficient Design
  6. Using Procedures in VHDL: Simplify Your Design with Reusable Code
  7. Build a Reliable Timer in VHDL: Counting Clock Cycles to Hours
  8. Technology: The Key to Transparent, Ethical Supply Chains
  9. How to Prime a Hydraulic Gear Pump for Optimal Performance
  10. Step-by-Step Guide to Priming a Ford Tractor Hydraulic Pump