Manufactura industrial
Internet industrial de las cosas | Materiales industriales | Mantenimiento y reparación de equipos | Programación industrial |
home  MfgRobots >> Manufactura industrial >  >> Industrial programming >> VHDL

Primeros pasos con VUnit

VUnit es uno de los marcos de verificación de VHDL de código abierto más populares disponibles en la actualidad. Combina un ejecutor de conjunto de pruebas de Python con una biblioteca VHDL dedicada para automatizar sus bancos de pruebas.

Para brindarle este tutorial gratuito de VUnit, VHDLwhiz recluta a Ahmadmunthar Zaklouta, quien está detrás del resto de este artículo, incluido el proyecto de ejemplo simple de VUnit que puede descargar y ejecutar en su computadora.

¡Démosle la palabra a Ahmad!

Este tutorial tiene como objetivo demostrar el uso del marco VUnit en el proceso de verificación de su diseño. Lo guiará a través del proceso de configuración de VUnit, creación del banco de pruebas de VUnit, uso de la biblioteca de verificación de VUnit y ejecución de pruebas de VUnit en ModelSim. También demuestra algunas técnicas de verificación.

Resumen

Este artículo contiene varias capturas de pantalla. ¡Haz clic en las imágenes para agrandarlas!

Use la barra lateral para navegar por el esquema para este tutorial, o desplácese hacia abajo y haga clic en el botón de navegación emergente en la esquina superior derecha si está utilizando un dispositivo móvil.

Requisitos

Este tutorial asume que este software está instalado en una máquina con Windows:

También supone tener conocimientos básicos de VHDL y habilidades de ModelSim.

Instalando

git clone --recurse-submodules https://github.com/VUnit/vunit.git
 
python setup.py install

Descargar el proyecto de ejemplo

Puede descargar el proyecto de ejemplo y el código VHDL mediante el siguiente formulario.

Extrae el Zip en C:\vunit_tutorial .

Introducción

VUnit es un marco de prueba para HDL que facilita el proceso de verificación al proporcionar un flujo de trabajo basado en pruebas "prueba temprana y frecuente" y una caja de herramientas para la automatización y administración de la ejecución de pruebas. Es un marco avanzado con amplias características ricas, pero fácil de usar y adaptar. Es completamente de código abierto y se puede incorporar fácilmente a las metodologías de prueba tradicionales.

VUnit consta de dos componentes principales:

La parte VHDL consta de seis bibliotecas, como se muestra en el siguiente diagrama. Este tutorial utilizará las bibliotecas de registro y verificación.

Diseño bajo prueba

El diseño utilizado en este tutorial, llamado motor_start , implementa un procedimiento de arranque para un motor en particular y activa 3 LED que representan el estado del motor.

La interfaz consta de un registro de entrada motor_start_in con 3 elementos (3 interruptores como entradas) y un registro de salida motor_start_out con 3 elementos (3 LEDs ROJO, AMARILLO, VERDE como salidas).

Algunos motores necesitan inicialización al principio antes de que pueda comenzar a usarlos. Nuestro procedimiento de arranque del motor consta de tres pasos:

  1. Cargando configuración
  2. Calibración
  3. Rotación

Secuencia de inicio

Aquí siguen las secuencias de arranque del motor y el significado de los indicadores LED.

  1. Activar switch_1.
  2. El LED_RED comenzará a parpadear 5 veces.
  3. Espere hasta que el LED_ROJO deje de parpadear y se ilumine constantemente.
  1. Ahora puede activar switch_2.
  2. Cuando se enciende el interruptor_2, el LED_AMARILLO comenzará a encenderse constantemente después de 5 ciclos de reloj, con una duración de 10 ciclos de reloj. Y después de eso, YELLOW_LED y RED_LED se apagarán.
  1. Ahora, el motor está listo para usar. Cada vez que se enciende el interruptor_3, el LED_VERDE comenzará a iluminarse constantemente hasta que se apague el interruptor_3.
  2. Cualquier violación de esta secuencia dará como resultado que los 3 LED se enciendan constantemente hasta que todos los interruptores se apaguen y la secuencia se pueda comenzar de nuevo.

Desarrollo de banco de pruebas

Esta parte del tutorial consta de las siguientes subsecciones:

  1. Configuración del script de ejecución de Python
  2. Configuración del esqueleto de VUnit
  3. Configuración del banco de pruebas

Configuración del script de ejecución de Python run.py

Cada proyecto de VUnit necesita un script de Python de nivel superior run.py que actúa como punto de entrada al proyecto y especifica todas las fuentes de biblioteca, banco de pruebas y diseño de VHDL.

Este archivo suele existir en el directorio del proyecto. El árbol de directorios utilizado en este tutorial es el siguiente:

En el run.py archivo, tenemos que hacer tres cosas:

1:obtenga la ruta donde existe este archivo y especifique la ruta para los archivos de diseño y banco de pruebas.

# ROOT
ROOT = Path(__file__).resolve().parent
# Sources path for DUT
DUT_PATH = ROOT / "design"
# Sources path for TB
TEST_PATH = ROOT / "testbench"

2:crea la instancia de VUnit.
Esto creará una instancia de VUnit y la asignará a una variable llamada VU . Entonces podemos usar VU para crear bibliotecas y varias tareas.

# create VUnit instance
VU = VUnit.from_argv()
VU.enable_location_preprocessing()

3:crear bibliotecas y agregarles fuentes VHDL.
Me gusta separar la parte de diseño de la parte del banco de pruebas. Por lo tanto, crearemos dos bibliotecas:design_lib y tb_lib .

# create design library
design_lib = VU.add_library("design_lib")
# add design source files to design_lib
design_lib.add_source_files([DUT_PATH / "*.vhdl"])

# create testbench library
tb_lib = VU.add_library("tb_lib")
# add testbench source files to tb_lib
tb_lib.add_source_files([TEST_PATH / "*.vhdl"])

El resto del archivo es una configuración para que ModelSim use el wave.do archivo si existe.

Nota: aquí, uso el *.vhdl extensión. Es posible que deba modificarlo si usa *.vhd .

Si le gusta esta estructura de trabajo, entonces no necesita cambiar este archivo en absoluto. Cada vez que comience un nuevo proyecto, simplemente cópielo en su directorio de proyectos. Sin embargo, si prefiere una estructura de directorio diferente, debe modificar las rutas para cumplir con su estructura de trabajo.

Ahora, siempre que usemos este archivo, VUnit buscará automáticamente bancos de prueba de VUnit en su proyecto, determinará el orden de compilación, creará las bibliotecas y compilará fuentes en ellas y, opcionalmente, ejecutará el simulador con todos los casos de prueba o con casos específicos.

¿No es maravilloso? 😀

Configuración del esqueleto de VUnit

Para crear un banco de pruebas de VUnit, necesitamos agregar un código específico a nuestro archivo de banco de pruebas motor_start_tb , como se explica en esta subsección.

1:agregue las bibliotecas de la siguiente manera.

Primero, necesitamos agregar la biblioteca VUnit VUNIT_LIB y su contexto:VUNIT_CONTEXT , para que tengamos acceso a la funcionalidad de VUnit de la siguiente manera:

LIBRARY VUNIT_LIB;
CONTEXT VUNIT_LIB.VUNIT_CONTEXT;

En segundo lugar, debemos agregar las bibliotecas de diseño y banco de pruebas DESIGN_LIB y TB_LIB para que tengamos acceso a nuestro DUT y paquetes de la siguiente manera:

LIBRARY DESIGN_LIB;
USE DESIGN_LIB.MOTOR_PKG.ALL;

LIBRARY TB_LIB;
USE TB_LIB.MOTOR_TB_PKG.ALL;

El DUT tiene dos paquetes; uno para el diseño:motor_pkg y el otro para elementos del banco de pruebas motor_tb_pkg . Son paquetes triviales que creé porque así es como suelen estructurarse los grandes proyectos. Quiero mostrar cómo VUnit se ocupa de eso.

2:agregue la configuración del corredor a la entidad de la siguiente manera:

ENTITY motor_start_tb IS

  GENERIC(runner_cfg : string := runner_cfg_default);

END ENTITY motor_start_tb;

runner_cfg es una constante genérica que permite que el corredor de pruebas de Python controle el banco de pruebas. Es decir, podemos ejecutar pruebas desde el entorno de python. Este genérico es obligatorio y no cambia.

3:agregue el esqueleto del banco de pruebas VUnit a nuestro banco de pruebas de la siguiente manera:

ARCHITECTURE tb OF motor_start_tb IS
  test_runner : PROCESS
  BEGIN
    -- setup VUnit
    test_runner_setup(runner, runner_cfg);

    test_cases_loop : WHILE test_suite LOOP
    
      -- your testbench test cases here
    
    END LOOP test_cases_loop;
    
    test_runner_cleanup(runner); -- end of simulation
  END PROCESS test_runner;
  
END ARCHITECTURE tb;

test_runner es el principal proceso de control del banco de pruebas. Siempre comienza con el procedimiento test_runner_setup y finaliza con el procedimiento test_runner_cleanup . La simulación vive entre estos dos procedimientos. test_cases_loop es en nuestros trajes de prueba donde se llevan a cabo todos nuestros casos de prueba.

Para crear un caso de prueba, usamos run de VUnit funcionan dentro de una instrucción If de la siguiente manera:

IF run("test_case_name") THEN
  -- test case code here

ELSIF run("test_case_name") THEN
  -- test case code here

END IF;

Luego, podemos ejecutar todos los casos de prueba o casos específicos desde el entorno de Python simplemente llamándolos con el nombre que especificamos en la llamada a run .

Configuración del banco de pruebas

Aquí, comenzamos agregando las señales requeridas para comunicarse con el DUT, como se muestra a continuación:

--------------------------------------------------------------------------
-- TYPES, RECORDS, INTERNAL SIGNALS, FSM, CONSTANTS DECLARATION.
--------------------------------------------------------------------------
-- CONSTANTS DECLARATION --
-- simulation constants
CONSTANT C_CLK_PERIOD : time := 10 ns;

-- INTERNAL SIGNALS DECLARATION --
-- DUT interface
SIGNAL clk             : STD_LOGIC := '0';
SIGNAL reset           : STD_LOGIC := '1';
SIGNAL motor_start_in  : MOTOR_START_IN_RECORD_TYPE := 
  (switch_1 => '0', switch_2 => '0', switch_3 => '0');

SIGNAL motor_start_out : MOTOR_START_OUT_RECORD_TYPE;

-- simulation signals
SIGNAL led_out : STD_LOGIC_VECTOR(2 DOWNTO 0) := (OTHERS => '0');

Nota: Es una buena práctica inicializar las señales con un valor inicial.

A continuación, cree una instancia del DUT de esta manera:

--------------------------------------------------------------------------
-- DUT INSTANTIATION.
--------------------------------------------------------------------------
motor_start_tb_inst : ENTITY DESIGN_LIB.motor_start(rtl)
  PORT MAP( 
    clk             => clk, 
    reset           => reset,
    motor_start_in  => motor_start_in,
    motor_start_out => motor_start_out
  ); 

Nota: Agrupé los puertos de entrada y los puertos de salida en registros. Encuentro esto beneficioso en proyectos grandes porque hace que las entidades y las instancias estén menos abarrotadas.

Y finalmente, conduce clk , reset y led_out como se muestra aquí:

--------------------------------------------------------------------------
-- SIGNAL DEFINITION OF UNUSED OUTPUT PORTS AND FIXED SIGNALS.
--------------------------------------------------------------------------
led_out(0) <= motor_start_out.red_led;
led_out(1) <= motor_start_out.yellow_led; 
led_out(2) <= motor_start_out.green_led; 

--------------------------------------------------------------------------
-- CLOCK AND RESET.
--------------------------------------------------------------------------
clk   <= NOT clk after C_CLK_PERIOD / 2;
reset <= '0' after 5 * (C_CLK_PERIOD / 2);

Desarrollo de casos de prueba

Ahora volvamos a nuestro DUT y comencemos el trabajo real desarrollando algunos casos de prueba. Voy a presentar dos escenarios:

Escenario de ingeniero de diseño: desde el punto de vista del proyectista, realizando la verificación el propio proyectista. En este escenario, que suele ocurrir durante la fase de desarrollo, el diseñador puede acceder al código. Este escenario mostrará cómo VUnit nos ayuda a "probar pronto y con frecuencia".

Escenario del ingeniero de verificación :el diseño (DUT) se trata como una caja negra. Solo conocemos la interfaz externa y los requisitos de prueba.

También veremos estas tres técnicas de verificación:

Comencemos con la primera técnica y volvamos a los dos últimos métodos más adelante en este artículo.

Controlador y verificador dentro del caso de prueba

Este es el enfoque más directo. El controlador y el verificador están dentro del propio caso de prueba, implementamos las operaciones de conducción y verificación dentro del código del caso de prueba.

Supongamos que desarrollamos la funcionalidad RED_LED de la siguiente manera:

WHEN SWITCH_1_ON =>
  IF (motor_start_in.switch_1 = '0' OR
      motor_start_in.switch_2 = '1' OR
      motor_start_in.switch_3 = '1') THEN
    state = WAIT_FOR_SWITCHES_OFF;
  ELSIF (counter = 0) THEN
    led_s.red_led <= '1';
    state         <= WAIT_FOR_SWITCH_2;
  ELSE
    led_s.red_led <= NOT led_s.red_led;
  END IF;

Y ahora, queremos verificar nuestro diseño antes de proceder a desarrollar el resto de la funcionalidad.

Para hacer eso, usamos run de VUnit función dentro del test_suite para crear un caso de prueba para verificar el resultado de encender switch_1, como se muestra a continuación:

IF run("switch_1_on_output_check") THEN
  info("------------------------------------------------------------------");
  info("TEST CASE: switches_off_output_check");
  info("------------------------------------------------------------------");
  -- code for your test case here.

Esto creará un caso de prueba llamado "switch_1_on_output_check"

Nota: info es un procedimiento VUnit de la biblioteca de registro que se imprime en la pantalla de transcripciones y en la terminal. Lo usaremos para mostrar el resultado de la prueba.

Ahora, escribiremos el código para este caso de prueba. Usaremos los subprogramas de verificación de VUnit para hacer eso.

Sabemos que RED_LED parpadeará 5 veces después de que se encienda switch_1, por lo que creamos un bucle VHDL For y realizamos la verificación dentro de él.

El check procedimiento realiza una comprobación de los parámetros específicos que proporcionamos. Tiene muchas variantes, y aquí usé varias de ellas con fines de demostración.

check(expr => motor_start_out.red_led = '1', 
      msg  => "Expect red_led to be ON '1'");

Aquí, probará si RED_LED es '1' en este punto del tiempo de simulación e imprimirá un mensaje en la consola:

# 35001 ps - check - PASS - red_led when switch_1 on (motor_start_tb.vhdl:192)

Nota que nos dice si es un PASA o un ERROR, y la marca de tiempo cuando se realizó esta verificación, junto con el nombre del archivo y el número de línea donde se encuentra esta verificación.

Otra forma es usar el check_false procedimiento. Aquí, lo usamos para comprobar que YELLOW_LED es '0':

check_false(expr => ??motor_start_out.yellow_led, 
            msg  => result("for yellow_led when switch_1 on"));

Aquí, usamos result de VUnit Función para mejorar el mensaje. La impresión se verá así:

# 35001 ps - check - PASS - False check passed for yellow_led when switch_1 on 
#                           (motor_start_tb.vhdl:193)

Nota que imprime información adicional sobre el tipo de cheque:"Falso cheque aprobado".

Otra forma más es usar check_equal . Aquí, lo usamos para probar que GREEN_LED es '0':

check_equal(got      => motor_start_out.green_led, 
            expected => '0',
            msg      => result("for green_led when switch_1 on"));

Aquí, proporcionamos un parámetro adicional '0' para la comparación. La impresión resultante es:

# 35001 ps - check - PASS - Equality check passed for green_led when switch_1 on - 
#                           Got 0. (motor_start_tb.vhdl:194)

Ahora, sabemos que después de un ciclo de reloj, RED_LED se apagará y los otros LED también permanecerán apagados. Podemos usar check_equal para comprobarlos todos simultáneamente, como se muestra a continuación:

check_equal(led_out, STD_LOGIC_VECTOR'("000"), 
            result("for led_out when switch_1 on"), warning);

Nota el uso del calificador STD_LOGIC_VECTOR'("000") , por lo que los valores no son ambiguos para el procedimiento. Además, especificamos que el nivel de esta verificación sea ADVERTENCIA, lo que significa que si falla, emitirá una advertencia en lugar de generar un error. La salida se verá así:

#  45001 ps - check - PASS - Equality check passed for led_out when switch_1 on - 
#                            Got 000 (0). (motor_start_tb.vhdl:197)

Este es el código para el caso de prueba completo:

WAIT UNTIL reset <= '0';
WAIT UNTIL falling_edge(clk);
motor_start_in.switch_1 <= '1';  -- turn on switch_1
FOR i IN 0 TO 4 LOOP
  WAIT UNTIL rising_edge(clk);
  WAIT FOR 1 ps;
  check(expr => motor_start_out.red_led = '1', 
        msg  => "Expect red_led to be ON '1'");

 check_false(expr => ??motor_start_out.yellow_led, 
             msg  => result("for yellow_led when switch_1 on"));

 check_equal(got      => motor_start_out.green_led, 
             expected => '0',
             msg      => result("for green_led when switch_1 on"));   

  WAIT UNTIL rising_edge(clk);
  WAIT FOR 1 ps;
  check_equal(led_out, STD_LOGIC_VECTOR'("000"), 
              result("for led_out when switch_1 on"), warning);
END LOOP;
info("===== TEST CASE FINISHED =====");

Caso de prueba en ejecución

Ahora es el momento de ejecutar nuestro caso de prueba. Podemos ejecutar las pruebas en la terminal o en la GUI del simulador.

Ejecutando una prueba en la terminal

Para hacer eso, comience abriendo una nueva terminal (CMD, PowerShell, Terminal de Windows) y navegue hasta vunit_tutorial directorio donde se encuentra el run.py se encuentra el archivo.

Para ejecutar el caso de prueba, simplemente escriba:

python .\run.py *switch_1_on_output_check

VUnit compilará todos los archivos VHDL en el orden correcto y los analizará, buscando bancos de prueba de VUnit. Y luego, VUnit buscará dentro de esos archivos, buscando un run función con el nombre de caso de prueba "switch_1_on_output_check" para ejecutarlo.

Nota: colocamos el símbolo comodín * antes del caso de prueba para que coincida con su nombre completo, que es:

tb_lib.motor_start_tb.switch_1_on_output_check

Podemos hacerlo porque la interfaz de línea de comandos de VUnit acepta comodines.

La impresión resultante después de la simulación es:

Starting tb_lib.motor_start_tb.switch_1_on_output_check
Output file: C:\vunit_tutorial\vunit_out\test_output\tb_lib.motor_start_tb.
switch_1_on_output_check_6df3cd7bf77a9a304e02d3e25d028a92fc541cf5\output.txt
pass (P=1 S=0 F=0 T=1) tb_lib.motor_start_tb.switch_1_on_output_check (1.1 seconds)

==== Summary ==========================================================
pass tb_lib.motor_start_tb.switch_1_on_output_check (1.1 seconds)
=======================================================================
pass 1 of 1
=======================================================================
Total time was 1.1 seconds
Elapsed time was 1.1 seconds
=======================================================================
All passed!

Podemos ver que se ejecutó una prueba y fue APROBADA.

Nota que VUnit ha creado un vunit_out carpeta en el directorio del proyecto. Dentro de esta carpeta, hay una carpeta llamada test_output que tiene informes sobre las pruebas.

Arriba, solo obtuvimos el resultado final de la prueba sin detalles sobre cada verificación, pero la herramienta de línea de comandos VUnit proporciona varios interruptores para ejecutar pruebas. Para obtener más información sobre lo que sucede durante la simulación, podemos usar el modificador detallado -v :

python .\run.py *switch_1_on_output_check -v

La impresión detallada se verá así:

Otros interruptores útiles son:

-l, --list :enumera todos los casos de prueba.

--clean :elimine primero la carpeta de salida y luego ejecute la prueba.

--compile :este modificador es útil si desea compilar sin ejecutar pruebas para comprobar si hay errores, por ejemplo.

Ejecutar una prueba en la GUI del simulador

A menudo se requiere una inspección visual de la ola. VUnit proporciona una forma automatizada de ejecutar pruebas en el simulador usando el interruptor GUI -g . VUnit hará toda la compilación e iniciará ModelSim (el simulador que configuramos) con el caso de prueba solicitado y agregará las señales a la ventana de onda, dado que un wave.do el archivo está disponible.

python .\run.py *switch_1_on_output_check -g

Ahora, VUnit lanzará ModelSim para este caso de prueba específico, abrirá la ventana de forma de onda y agregará las señales porque ya tengo el motor_start_tb_wave.do dentro de las olas carpeta.

Nota: Puede personalizar la forma de onda como desee, pero debe guardar el archivo de formato de forma de onda dentro de las ondas carpeta con esta convención de nombres testbench_file_name_wave.do para que pueda cargarse cuando VUnit inicie ModelSim.

Suponga que cambia su código después de descubrir errores y desea volver a ejecutar este caso de prueba. En ese caso, puede escribir en la ventana de transcripción de ModelSim:vunit_restart , Eso hará que VUnit vuelva a compilar, reiniciar y volver a ejecutar la simulación.

Conductor y verificador controlado dentro del caso de prueba

Hasta ahora, hemos aprendido cómo configurar un banco de pruebas VUnit y desarrollar y ejecutar el caso de prueba. En esta sección, desarrollaremos más casos de prueba desde el punto de vista del ingeniero de verificación, utilizando un enfoque de verificación diferente y la biblioteca de comprobación de VUnit.

A diferencia del caso de prueba anterior, este enfoque tiene al controlador dentro del caso de prueba y al verificador afuera, pero el caso de prueba aún lo controla.

Supongamos que tenemos este requisito de verificación:

De la sección DUT, sabemos que:

Usaremos el check_stable de VUnit procedimiento para verificar que:

Usaremos el check_next de VUnit procedimiento para verificar que:

verificar_estable :verifique que las señales sean estables dentro de una ventana que comience con un start_event pulso de señal y termina con un end_event pulso de señal.

verificar_siguiente :verifique que la señal ='1' después de un número de ciclos de reloj después de un start_event pulso de señal.

start_event y end_event las señales se controlarán desde el caso de prueba.

Comenzamos agregando las señales requeridas para check_stable y check_next procedimientos de la siguiente manera:

-- VUnit signals
SIGNAL enable                  : STD_LOGIC := '0';
-- for yellow_led
SIGNAL yellow_next_start_event : STD_LOGIC := '0';
SIGNAL yellow_low_start_event  : STD_LOGIC := '0';
SIGNAL yellow_low_end_event    : STD_LOGIC := '0';
SIGNAL yellow_high_start_event : STD_LOGIC := '0';
SIGNAL yellow_high_end_event   : STD_LOGIC := '0';

Luego creamos un nuevo caso de prueba dentro del test_suite usando el run de VUnit funcionan de la siguiente manera:

ELSIF run("switch_2_on_output_check") THEN
  info("------------------------------------------------------------------");
  info("TEST CASE: switch_2_on_output_check");
  info("------------------------------------------------------------------");

Creamos un start_event para el estado bajo de YELLOW_LED para usar con el check_stable procedimiento de la siguiente manera:

WAIT UNTIL reset <= '0';
-- out of reset
enable <= '1';
pulse_high(clk, yellow_low_start_event);
WAIT FOR C_CLK_PERIOD * 3;

El enable la señal activa el check_stable y check_next procedimientos, y queremos habilitarlos desde el principio.

pulse_high es un procedimiento trivial de motor_tb_pkg que espera el próximo flanco ascendente del reloj y emite una señal durante un ciclo de reloj. En este caso, es yellow_ low_start_event .

Ahora, encendemos switch_1 y esperamos hasta que RED_LED se encienda constantemente, y luego encendemos switch_2:

-- turn ON switch_1
motor_start_in.switch_1 <= '1';
-- wait until RED_LED finished
WAIT FOR C_CLK_PERIOD * 12;
-- turn ON switch_2
motor_start_in.switch_2 <= '1';

Ahora sabemos que después de 5 ciclos de reloj, YELLOW_LED será '1'. Por lo tanto, creamos un start_event para YELLOW_LED para usar con el check_next procedimiento:

-- after 5 clk cycles YELLOW_LED will light
-- next_start_event for YELLOW_LED high
pulse_high(clk, yellow_next_start_event); -- 1st clk cycle

Del mismo modo, creamos un end_event para el estado bajo de YELLOW_LED para usar con el check_stable procedimiento:

WAIT FOR C_CLK_PERIOD * 3;

-- end event for YELLOW_LED low
pulse_high(clk, yellow_low_end_event);  -- 5th clk cycle

Ahora, YELLOW_LED estará alto durante 10 ciclos de reloj. Por lo tanto, creamos un start_event para el estado alto de YELLOW_LED para el check_stable procedimiento:

-- YELLOW_LED is high for 10 clk cycles
-- start event for YELLOW_LED high
yellow_high_start_event <= '1';
WAIT UNTIL rising_edge(clk); -- 1st clk cycle
yellow_high_start_event <= '0';

Aquí no usé el pulse_high procedimiento porque quiero que el pulso ocurra ahora, y no en el próximo flanco descendente.

Y creamos un end_event para el estado alto de YELLOW_LED para check_stable después de 8 ciclos de reloj de la siguiente manera:

WAIT FOR C_CLK_PERIOD * 8;
-- end event for YELLOW_LED
pulse_high(clk, yellow_high_end_event); -- 10th clk cycle

Con eso, el caso de prueba está terminado. Solo necesitamos agregar las llamadas a los procedimientos del verificador después del proceso como este:

----------------------------------------------------------------------
-- Related YELLOW_LED check
----------------------------------------------------------------------
-- check that YELLOW_LED is low from start until switch_2 is ON
check_stable(clock       => clk, 
             en          => enable, 
             start_event => yellow_low_start_event, 
             end_event   => yellow_low_end_event, 
             expr        => motor_start_out.yellow_led, 
             msg         => result("YELLOW_LED Low before switch_2"),
             active_clock_edge => rising_edge,
             allow_restart     => false);

-- check that YELLOW_LED is high after switch_2 is ON
check_next(clock       => clk,
           en          => enable, 
           start_event => yellow_next_start_event, 
           expr        => motor_start_out.yellow_led, 
           msg         => result("for yellow_led is high after 5 clk"),
           num_cks     => 5, 
           allow_overlapping   => false, 
           allow_missing_start => true);

-- check that YELLOW_LED is high after for 10 clk cycle
check_stable(clk, enable, yellow_high_start_event, yellow_high_end_event,
             motor_start_out.yellow_led, 
             result("for YELLOW_LED High after switch_2"));

Nota: el allow_restart parámetro en el check_stable procedimiento permite que se inicie una nueva ventana si un nuevo start_event sucede antes del end_event .

Nota: ponemos 5 en check_next procedimiento porque YELLOW_LED estará alto después de 5 ciclos de reloj de yellow_next_start_event .

Nota: los dos últimos parámetros en check_next son:

Ahora, podemos ejecutar el caso de prueba desde la línea de comandos de esta manera:

python .\run.py *switch_2_on_output_check -v

Y el resultado será el siguiente:

Podemos ejecutar el caso de prueba en la GUI del simulador de esta manera:

python .\run.py *switch_2_on_output_check -g

Dando como resultado esta forma de onda:

Nota:start_event y end_event señales para check_stable son inclusivos en la ventana, y se hace referencia a las señales monitoreadas cuando start_event='1' .

En este caso de prueba, sacamos las operaciones de verificación del caso de prueba pero las controlamos desde el caso de prueba. Además, tenga en cuenta que no verificamos la funcionalidad RED_LED.

Conductor dentro del caso de prueba y verificador de autocomprobación

Un inconveniente del enfoque anterior es que es menos legible. El caso de prueba contiene información que no es interesante ni está relacionada con la prueba ni con la funcionalidad, como el start_event y end_event señales Queremos ocultar todos estos detalles, tener solo el controlador en el caso de prueba y hacer un verificador de autocomprobación.

Comencemos por diseñar el controlador.

Los procedimientos de VHDL son un buen candidato para eso. Si no sabe cómo usar un procedimiento VHDL, consulte este tutorial.

A continuación se muestra el procedimiento switch_driver de motor_tb_pkg .

PROCEDURE switch_driver(
  SIGNAL switches     : OUT MOTOR_START_IN_RECORD_TYPE;
  CONSTANT clk_period : IN TIME;
  CONSTANT sw1_delay  : IN INTEGER;
  CONSTANT sw2_delay  : IN INTEGER;
  CONSTANT sw3_delay  : IN INTEGER) IS
BEGIN
  IF (sw1_delay = 0) THEN
    WAIT FOR clk_period * sw1_delay;
    switches.switch_1 <= '1';
  ELSIF (sw1_delay = -1) THEN
    switches.switch_1 <= '0';
  END IF;
  IF (sw2_delay = 0) THEN
    WAIT FOR clk_period * sw2_delay;
    switches.switch_2 <= '1';
  ELSIF (sw2_delay = -1) THEN
    switches.switch_2 <= '0';
  END IF;
  IF (sw3_delay = 0) THEN
    WAIT FOR clk_period * sw3_delay;
    switches.switch_3 <= '1';
  ELSIF (sw3_delay = -1) THEN
    switches.switch_3 <= '0';
  END IF;
END PROCEDURE switch_driver;

Proporcionamos el período de reloj para calcular los retrasos y un número entero que especifica el retraso deseado para cada cambio en períodos de reloj.

Ahora podemos usar este procedimiento dentro del caso de prueba de la siguiente manera:

switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20);

Esto encenderá switch_1 después de 3 ciclos de reloj y luego encenderá switch_2 después de 12 ciclos de reloj y, finalmente, encenderá switch_3 después de 20 ciclos de reloj.

Hasta ahora, hemos estado usando el verificador predeterminado de VUnit. VUnit brinda la posibilidad de tener un verificador personalizado. Esto es útil cuando tiene un gran caso de prueba con muchas comprobaciones y quiere tener estadísticas sobre la comprobación, o no quiere que la comprobación se mezcle con el resto del banco de pruebas.

Crearemos un verificador personalizado para RED_LED y estableceremos el nivel de registro fallido en ADVERTENCIA:

CONSTANT RED_CHECKER : checker_t := new_checker("red_led_checker", WARNING);

Y habilitamos el registro de aprobación de controles para RED_CHECKER en la sección Configuración de VUnit:

show(get_logger(RED_CHECKER), display_handler, pass);

Ahora pasemos al verificador (o monitor) de autoverificación. Primero diseñaremos un verificador de autocomprobación para RED_LED y usaremos un proceso para esto de la siguiente manera:

red_monitor_process : PROCESS
BEGIN
  WAIT UNTIL reset = '0';
  pulse_high(clk, led_low_start_event);
  WAIT UNTIL motor_start_in.switch_1 = '1';
-- RED_LED is blinking
FOR i IN 0 TO 4 LOOP
  IF (motor_start_in.switch_1 = '1' AND
      motor_start_in.switch_2 = '0' AND
      motor_start_in.switch_3 = '0') THEN

    WAIT UNTIL rising_edge(clk);
    WAIT FOR 1 ps;
    check(RED_CHECKER, motor_start_out.red_led = '1', 
          result("for red_led blink high"));

    WAIT UNTIL rising_edge(clk);
    WAIT FOR 1 ps;
    check(RED_CHECKER, motor_start_out.red_led = '0',
          result("for red_led blink low"));

    -- RED_LED is constantly lighting start event
    IF (i = 4) THEN -- finish blinking
      WAIT UNTIL rising_edge(clk);
      WAIT FOR 1 ps;
      check(RED_CHECKER, motor_start_out.red_led = '1',
            result("for red_led blink low"));
      pulse_high(clk, red_high_start_event);
    END IF;
  ELSE
  -- perform check for error (All led high until all switches are off)
  END IF;
END LOOP;
  IF (motor_start_in.switch_2 /= '1') THEN
    WAIT UNTIL motor_start_in.switch_2 = '1';
  END IF;
  WAIT UNTIL rising_edge(clk);
  WAIT FOR C_CLK_PERIOD * 14;
  -- RED_LED is constantly lighting end event
  pulse_high(clk, red_high_end_event);
END PROCESS red_monitor_process;
-- check that RED_LED is low from start until switch_1 is ON
-- Note the use of motor_start_in.switch_1 as end_event
check_stable(RED_CHECKER, clk, enable, led_low_start_event, 
             motor_start_in.switch_1, motor_start_out.red_led,
             result("RED_LED low before switch_1"));

-- check that RED_LED is low after switch_2 is ON
check_stable(RED_CHECKER, clk, enable, red_high_start_event, 
             red_high_end_event, motor_start_out.red_led,
             result("RED_LED high after switch_1"));

Probemos esto con el siguiente caso de prueba:

ELSIF run("red_led_output_self-check") THEN
  info("---------------------------------------------------------------");
  info("TEST CASE: red_led_output_self-check");
  info("---------------------------------------------------------------");
  WAIT UNTIL reset = '0';
  -- turn switch_1 on after 3 clk cycles and switch_2 after 13 clk cycles
  switch_driver(motor_start_in,C_CLK_PERIOD,3,13,-1);

  WAIT FOR C_CLK_PERIOD * 3; -- dummy wait
  info("===== TEST CASE FINISHED =====");

Ejecutamos la simulación así:

python .\run.py *red_led* -v

El resultado será:

Nota que el verificador ahora es red_led_checker .

Podemos seguir el mismo enfoque para diseñar un comprobador de autoverificación para YELLOW_LED, pero dejaré esto como un ejercicio para el lector. Sin embargo, mostraré las siguientes formas diferentes de verificar la funcionalidad GREEN_LED usando check , check_stable , check_next y check_equal procedimientos.

Para verificar que el GREEN_LED está APAGADO desde el principio hasta la primera vez que se enciende switch_3, simplemente usamos el check_stable procedimiento:

-- check that GREEN_LED is low from start until switch_3 is ON.
check_stable(clk, enable, led_low_start_event, 
             motor_start_in.switch_3, motor_start_out.green_led,
             result("GREEN_LED low before switch_3"));

Una forma de verificar que GREEN_LED está ENCENDIDO cuando switch_3 está ENCENDIDO es usando el check_next procedimiento. Lo llamamos con switch_3 como start_event , asigna 1 ciclo de reloj a num_cks y permitir la superposición:

-- check that GREEN_LED is high using check_next
check_next(clock       => clk, 
           en          => green_next_en,
           start_event => motor_start_in.switch_3,
           expr        => motor_start_out.green_led,
           msg         => result("for green_led high using check_next"),
           num_cks     => 1,
           allow_overlapping   => true,
           allow_missing_start => false);

Debido a que permitimos la superposición, este procedimiento se activará en cada flanco ascendente del reloj cuando switch_3 sea '1'. Esperará que GREEN_LED sea '1' después de un ciclo de reloj, y esto es lo que queremos.

Otra forma de probar la funcionalidad GREEN_LED es usar la versión sincronizada del check procedimiento con una versión retrasada de switch_3 como Habilitar para este procedimiento:

switch_3_delayed <= motor_start_in.switch_3'delayed(C_CLK_PERIOD + 1 ps)
                    AND NOT enable;
check(clock => clk,
      en    => switch_3_delayed,
      expr  => motor_start_out.green_led,
      msg   => result("for green_led high using delayed"));

switch_3_delayed es una señal retrasada de 1 ciclo de reloj de switch_3. Por lo tanto, habilitará este procedimiento siempre 1 ciclo de reloj después de que switch_3 esté en '1', y el procedimiento verifique que GREEN_LED esté en '1' cuando esté habilitado.

El switch_3_delayed la señal es una versión retrasada switch_3 por un ciclo de reloj. Por lo tanto, habilitará este procedimiento siempre un ciclo de reloj después de que switch_3 sea '1'. Cuando está habilitado, el procedimiento verifica que GREEN_LED sea '1'.

Nota: el Y NO habilitado en switch_3_delayed es solo para enmascarar esta señal cuando no estamos usando el enfoque de proceso.

Finalmente, podemos usar un proceso dedicado con un ciclo while de VHDL para realizar la verificación de GREEN_LED:

green_monitor_process : PROCESS
BEGIN
  WAIT UNTIL enable = '1' AND 
             motor_start_in.switch_1 = '1' AND
             motor_start_in.switch_2 = '1' AND
             motor_start_in.switch_3 = '1';
  WHILE motor_start_in.switch_3 = '1' LOOP
    WAIT UNTIL rising_edge(clk);
    WAIT FOR 1 ps;
    check_equal(led_out, STD_LOGIC_VECTOR'("100"), 
                result("for led_out when switch_3 on"));
  END LOOP;
END PROCESS green_monitor_process;

Ahora desarrollamos un caso de prueba para GREEN_LED de la siguiente manera:

ELSIF run("switch_3_on_output_check") THEN
  info("-------------------------------------------------------------");
  info("TEST CASE: switch_3_on_output_check");
  info("-------------------------------------------------------------");
  info("check using a clocked check PROCEDURES");
  WAIT UNTIL reset = '0';
  switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20);
  WAIT FOR C_CLK_PERIOD * 5;
  switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,-1);
  WAIT FOR C_CLK_PERIOD * 3; -- dummy wait
  info("-------------------------------------------------------------");
  
  info("check using check_next PROCEDURES");
  green_next_en <= '1';
  switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,10);
  WAIT FOR C_CLK_PERIOD * 5;
  switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,-1);
  WAIT FOR C_CLK_PERIOD * 3; -- dummy wait
  green_next_en <= '0';
  info("-------------------------------------------------------------");
  
  info("check using check_equal process");
  enable <= '1';
  switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,10);
  WAIT FOR C_CLK_PERIOD * 5;
  switch_driver(motor_start_in,C_CLK_PERIOD,-2,-2,-1);
  WAIT FOR C_CLK_PERIOD * 10; -- dummy wait
  info("===== TEST CASE FINISHED =====");

Después de escribir el caso de prueba, puede ejecutarlo y verificar los diferentes resultados.

Podemos ver que los casos de prueba en el enfoque de autocomprobación son mucho más legibles, incluso para ingenieros sin conocimiento en VHDL.

Hay otros casos de prueba en el archivo del banco de pruebas. Podemos iniciarlos usando este comando:

python .\run.py *motor_start_tb* --list

Y este es el resultado impreso en la consola:

tb_lib.motor_start_tb.switches_off_output_check
tb_lib.motor_start_tb.switch_1_on_output_check
tb_lib.motor_start_tb.switch_2_on_output_check
tb_lib.motor_start_tb.red_led_output_self-check
tb_lib.motor_start_tb.switch_3_on_output_check
tb_lib.motor_start_tb.switch_2_error_output_check
Listed 6 tests

Podemos ejecutar todos los casos de prueba escribiendo:

python .\run.py

Y el resultado es el siguiente:

==== Summary =============================================================
pass tb_lib.motor_start_tb.switch_1_on_output_check    (0.4 seconds)
pass tb_lib.motor_start_tb.switch_2_on_output_check    (0.4 seconds)
pass tb_lib.motor_start_tb.red_led_output_self-check   (0.4 seconds)
pass tb_lib.motor_start_tb.switch_3_on_output_check    (0.4 seconds)
fail tb_lib.motor_start_tb.switches_off_output_check   (0.9 seconds)
fail tb_lib.motor_start_tb.switch_2_error_output_check (0.4 seconds)
==========================================================================
pass 4 of 6
fail 2 of 6
==========================================================================
Total time was 2.9 seconds
Elapsed time was 2.9 seconds
==========================================================================
Some failed!

Resumen

El marco VUnit proporciona funciones avanzadas para la automatización de la ejecución de pruebas y ricas bibliotecas para el desarrollo de bancos de pruebas. Además, permite a los ingenieros de diseño probar su diseño con anticipación y con frecuencia.

VUnit también ayuda a los ingenieros de verificación a desarrollar y ejecutar bancos de pruebas de forma más rápida y sencilla. Lo que es más importante, tiene una curva de aprendizaje rápida y ligera.

Adónde ir desde aquí

¡Descargue el proyecto de ejemplo utilizando el formulario a continuación!


VHDL

  1. Contenedores listos para el código:Introducción a las herramientas de automatización de procesos en la nube
  2. Introducción a la impresión 3D de cerámica
  3. ¡Familiarizándose con los tintes básicos!
  4. Introducción a TJBot
  5. Introducción a RAK 831 Lora Gateway y RPi3
  6. Introducción a RAK831 LoRa Gateway y RPi3
  7. Ponerse manos a la obra con IoT
  8. Introducción a la IA en seguros:una guía introductoria
  9. Tutorial de Arduino 01:Primeros pasos
  10. Cómo comenzar con My.Cat.com
  11. Node-RED y los primeros pasos con Docker