Integración de librerías IP de Quartus Prime en VUnit: guía paso a paso
¿Alguna vez ha querido ejecutar una simulación VHDL que incluya un núcleo IP de Quartus a través del framework de verificación VUnit?
Eso es lo que el ingeniero FPGA Konstantinos Paraskevopoulos tenía en mente, pero no encontró un tutorial adecuado. Afortunadamente, usó su talento para averiguarlo y fue lo suficientemente amable de compartirlo con VHDLwhiz a través de este artículo invitado.
¡Dejemos la palabra a Konstantinos!
A menudo es deseable incorporar IPs predefinidos del Catálogo IP de Quartus en su diseño al simular su sistema con VUnit. Este tutorial proporciona el conocimiento necesario para generar, incorporar y vincular librerías externas IP de Quartus al entorno VUnit.
¿Nuevo en VUnit? Consulte este tutorial: Primeros pasos con VUnit
Visión general
Este tutorial consta de tres partes principales:
- Breve descripción del IP seleccionado
- Pasos requeridos para generar y vincular las librerías apropiadas
- Verificación utilizando VUnit y Modelsim
Requisitos
- Quartus
- Descargar Quartus Prime
- QSYS debe estar en su PATH para el PASO 2 (opción CMD)
- Intel Modelsim
- Consulte este artículo para cómo instalar ModelSim gratis
- ModelSim debe estar en su PATH
- VUnit
- Consulte este artículo para cómo instalar VUnit gratis
- Python 3.6 o superior
- Descargar Python
- Python debe estar en su path
También asume conocimientos básicos de VHDL y ModelSim.
Diseño bajo prueba
Para nuestro escenario, utilizamos el IP Sumador Paralelo de la lista IP de Aritmética Entera de Quartus.

Nuestro diseño acepta tres vectores de entrada de 16 bits y produce el resultado sumado en un vector de 17 bits.
Paso 1: Generar IP
Generamos nuestro sumador en la ventana del catálogo IP haciendo doble clic en el componente sumador paralelo bajo Library/Basic functions/Arithmetic.
Tras proporcionar un nombre y personalizar nuestro componente según necesidades, hacemos clic en el botón Generate HDL en la esquina inferior derecha.

En este punto, aparecerá una ventana como se muestra en la siguiente figura.
Nota: Debemos establecer Create simulation model bajo la sección Simulation a VHDL o Verilog para generar los archivos de simulación ya que la opción por defecto es none. Si no elegimos uno, el archivo given_ip_name.spd no se generará, causando que el siguiente paso falle.

El proceso anterior genera un archivo y una carpeta bajo nuestro directorio quartus:
- Archivo:
given_ip_name.ip - Carpeta:
given_ip_name
La carpeta contiene archivos .vhd y .v que necesitan añadirse luego en nuestro script run.py.
Paso 2: Generar archivos de simulación del IP
- GUI: Seleccionar Tools ➤ Generate Simulator Setup Script for IP y especificar directorio de salida en la ventana de diálogo,
- CMD: Utilizando comandos Qsys, podemos generar los mismos archivos tecleando en terminal el siguiente comando:
ip-setup-simulation --quartus-project= <project's_QPF_filepath> \ --output-directory= <my_dir>
Usando uno de los dos métodos arriba, instruimos a Quartus para generar un directorio para cada simulador soportado que contiene un script para crear y compilar las librerías IP.
Paso 3: Generar y compilar librerías IP para Modelsim
El siguiente paso es encontrar el script msim_setup.tcl en la carpeta mentor creada por el paso anterior y duplicarlo con el nombre setup.tcl. Luego, en el archivo setup.tcl, descomentar los comandos ilustrados y establecer la variable $QSYS_SIMDIR.
# # 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 #
Tras alterar y guardar el setup.tcl, podemos ejecutar el archivo Tcl usando el comando vsim.
vsim -c -do "do setup.tcl; quit"
Eso genera las librerías compiladas en la carpeta mentor.
Paso 4: Vinculación con VUnit
Ahora que las librerías IP han sido generadas, deberíamos vincularlas usando el script python run.py.
Consulte la figura abajo para entender mejor la estructura de directorios de nuestro ejemplo. La topología inicial consistía en la carpeta raíz demo, las carpetas tb, vunit y quartus. Todas las subcarpetas y archivos bajo la carpeta quartus se generan vía el framework Quartus tras crear un proyecto y completar pasos 1 a 3.
Nota: Quartus genera más archivos y carpetas, pero la imagen abajo muestra los de nuestro interés.

Usando esta vista distinta de la topología como referencia, podemos especificar nuestra ruta ROOT y la(s) ruta(s) a las librerías generadas, como se muestra abajo.
Note que sim_files es el directorio que especificamos en el paso 2 donde se ha almacenado la carpeta mentor.
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
Tras crear una instancia de VUnit llamada vu, podemos especificar una librería de diseño para nuestro código VHDL y vincular cualquier librería externa requerida:
# 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")
Y finalmente, añadir nuestros archivos fuente. Estos están ubicados en tres subcarpetas bajo el directorio given_ip_name:
parallel_add_191synthsim
Los directorios synth y sim contienen la misma información, es decir, el diseño de nivel superior de nuestro IP. Sin embargo, el formato de estos archivos en nuestro caso es VHDL. Podrían ser Verilog, y esto depende del lenguaje elegido en el paso 1.
En caso de que nuestro diseño de nivel superior implique sub‑componentes, también debemos incluir sus archivos fuente. Están ubicados bajo subcarpetas en el directorio given_ip_name, como el componente parallel_add_191 en nuestro caso.
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
Para empezar, puede consultar este enlace para aprender sobre los fundamentos de formación de testbench VUnit.
Volviendo a nuestro testbench, añadimos las librerías VUnit necesarias junto con cualquier otra librería que quisiéramos emplear y definimos nuestras señales.
Nota: La ejecución de procesos en nuestro ejemplo es secuencial. Así, señales de control (referidas como flags) se usan para notificar a un proceso si debe comenzar o terminar.
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;
Seguidamente, instanciamos nuestra UUT. Quartus suministra ejemplos de instanciación de componente para VHDL y Verilog bajo las convenciones de nombre de archivo ip_name_inst.vhd y 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 );
Los dos primeros procesos que comienzan son clk_process y reset_rel. Mientras el segundo se suspende tras resetear y poner la flag reset_done a true, el clk_process opera durante todo el tiempo de simulación.
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;
Ahora que el reset está hecho, podemos invocar el proceso test_runner para ejecutar nuestros tests. Además, el test runner permanece activo hasta que la flag sim_done se pone a true, lo que ocurre en el último proceso.
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;
Finalmente, el proceso data_generator ejecuta varias adiciones asignando valores a las tres entradas de nuestro sumador paralelo utilizando un bucle for.
Nota: Este proceso se dispara cuando el proceso test_runner lo instruye poniendo la flag start_sim. Al final de este proceso, eleva la flag sim_done, ordenando al test runner pausar la simulación.
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;
Verificación
Para ejecutar el caso de test y verificar que todo funciona como se espera, podemos ejecutar el script run.py desde el directorio donde está ubicado simplemente tecleando en terminal el siguiente comando.
python ./run.py -v
Nota: Se ha usado un logger personalizado para mejor ilustración en nuestra salida visible al proporcionar la opción verbose -v. Además, como solo se define un caso de test, no tenemos que proporcionar opción para especificarlo.

Finalmente, para verificar nuestros resultados en ModelSim, podríamos teclear el siguiente comando:
python ./run.py --gui

(Haga clic en la imagen para ampliarla)
Conclusión
Para concluir, aprendimos cómo incorporar y testar IPs de Quartus que residen en el catálogo IP dentro de un entorno VUnit. Aunque este tutorial usó un IP predefinido, el mismo proceso aplica a IPs empaquetados personalizados.
Consulte este tutorial de VUnit si aún no lo ha hecho:
Primeros pasos con VUnit
VHDL
- Máquina para Cercas de Malla Ciclónica: Funcionamiento y Ventajas
- Tecnología: La clave para cadenas de suministro transparentes y éticas
- Cómo Cebar una Bomba de Engranajes Hidráulica para un Rendimiento Óptimo
- Guía Paso a Paso para Cebar la Bomba Hidráulica de un Tractor Ford
- SigFox explicado: Tecnología, impacto en el mercado y comparación con Link Labs
- VUnit 101: Guía práctica para verificación automatizada de VHDL
- Creación de listas de cadenas en VHDL: Buenas prácticas y ejemplo
- Finalizar elegantemente una simulación de testbench VHDL
- Implementación de un controlador PWM en VHDL: Diseño, simulación y demo en FPGA
- Generación de números aleatorios en VHDL: De uniforme a OSVVM
- Cómo construir un testbench de auto-comprobación en VHDL: Guía práctica