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

Cómo crear un banco de pruebas controlado por Tcl para un módulo de bloqueo de código VHDL

La mayoría de los simuladores VHDL utilizan el lenguaje de comandos de herramientas (Tcl) como lenguaje de secuencias de comandos. Cuando escribes un comando en la consola del simulador, estás usando Tcl. Además, puede crear scripts con Tcl que se ejecutan en el simulador e interactúan con su código VHDL.

En este artículo, crearemos un banco de pruebas de autocomprobación que utilizó Tcl en lugar de VHDL para verificar que un módulo VHDL se comporta correctamente.

Consulte también:
Por qué necesita aprender Tcl
Banco de pruebas interactivo con Tcl

Puedes descargar el código de este artículo y el proyecto ModelSim utilizando el siguiente formulario.

El DUT:un módulo de bloqueo de código en VHDL

Antes de comenzar con el banco de pruebas, presentaré el dispositivo bajo prueba (DUT). Será un módulo de bloqueo de código que desbloqueará una bóveda cuando ingresemos la secuencia de números correcta en un teclado PIN.

La gente a menudo se refiere a un candado de código como un candado de combinación . Sin embargo, encuentro que este término es inexacto. No es suficiente ingresar la combinación correcta de dígitos para desbloquearlo. También debe ingresarlos en el orden correcto. Estrictamente hablando, un candado de combinación es realmente un candado de permutación , pero llamémoslo un bloqueo de código .

La imagen de arriba muestra un código de bloqueo de este tipo en forma de caja fuerte de hotel. Para simplificar, nuestro ejemplo solo usará las teclas numéricas y no los botones "BORRAR" y "BLOQUEAR".

Cómo funciona el módulo de bloqueo de código

Nuestro módulo comenzará en la posición de bloqueo, y si ingresamos cuatro dígitos seguidos que coincidan con el código PIN secreto, desbloqueará la caja fuerte. Para volver a bloquearlo, podemos ingresar otro número incorrecto. Por lo tanto, necesitamos crear un detector de secuencias en VHDL.

La forma de onda anterior muestra cómo funcionará el módulo de bloqueo de código. Además del reloj y el reinicio, hay dos señales de entrada:input_digit y input_enable . El módulo muestreará el dígito de entrada cuando la habilitación sea '1' en un flanco ascendente del reloj.

Solo hay una salida de este módulo:el desbloqueo señal. Imagine que controla el mecanismo de bloqueo de una caja fuerte o una bóveda de algún tipo. El desbloqueo la señal solo será '1' solo cuando el usuario haya ingresado cuatro dígitos consecutivos que coincidan con el PIN correcto. En este artículo, usaremos 1234 como código de acceso.

La entidad

El siguiente código muestra la entidad del módulo de bloqueo de código. Debido a que el propósito de este módulo es ser un DUT de ejemplo simple para nuestro banco de pruebas basado en TCL, estoy codificando el código de acceso secreto usando genéricos. Las cuatro constantes genéricas son decimales codificados en binario (BCD) realizados como números enteros con un rango restringido.

entity code_lock is
  generic (pin0, pin1, pin2, pin3 : integer range 0 to 9);
  port (
    clk : in std_logic;
    rst : in std_logic;
    input_digit : in integer range 0 to 9;
    input_enable : in std_logic;
    unlock : out std_logic
  );
end code_lock;

Al igual que el código de acceso, el input_digit la señal también es de tipo BCD. Las otras entradas y salidas son std_logics.

La región declarativa

Este módulo tiene una sola señal interna:un registro de desplazamiento que contiene los cuatro últimos dígitos que el usuario ha tecleado. Pero en lugar de usar el rango BCD de 0 a 9, dejamos que los números vayan de -1 a 9. Son 11 valores posibles.

type pins_type is array (0 to 3) of integer range -1 to 9;
signal pins : pins_type;

Tenemos que usar un valor de reinicio que no es un dígito que el usuario pueda ingresar, y para eso está el -1. Si hubiéramos usado el rango de 0 a 9 para los pines array, establecer el código de acceso secreto en 0000 habría abierto inicialmente la bóveda. Con este esquema, el usuario deberá ingresar explícitamente cuatro 0.

La implementación

En la parte superior de la región de arquitectura, agregué una declaración simultánea que desbloquea la bóveda cuando pins la señal coincide con las constantes genéricas. El siguiente código es combinacional, pero dado que los pines la señal es cronometrada, el desbloqueo la señal solo cambiará en el flanco ascendente del reloj.

unlock <= '1' when pins = (pin3, pin2, pin1, pin0) else '0';

El siguiente código muestra el proceso que lee la entrada del usuario. Hace un registro de desplazamiento a partir de los pines señal cambiando todos los valores cuando input_enable es '1' en un flanco ascendente del reloj. El resultado es que los cuatro últimos dígitos que el usuario ha ingresado se almacenan en los pines matriz.

PINS_PROC : process(clk)
begin
  if rising_edge(clk) then
    if rst = '1' then
      pins <= (others => -1);

    else

      if input_enable  = '1' then
        pins(0) <= input_digit;
        pins(1 to 3) <= pins(0 to 2);
      end if;

    end if;
  end if;
end process;

El banco de pruebas VHDL

En primer lugar, todavía necesitamos un banco de pruebas VHDL básico, aunque estamos usando Tcl para la verificación. El siguiente código muestra el archivo VHDL completo. He instanciado el DUT y creado la señal del reloj, pero eso es todo. Aparte de generar el reloj, este banco de pruebas no hace nada.

library ieee;
use ieee.std_logic_1164.all;

entity code_lock_tb is
end code_lock_tb;

architecture sim of code_lock_tb is

  constant clk_hz : integer := 100e6;
  constant clock_period : time := 1 sec / clk_hz;

  signal clk : std_logic := '1';
  signal rst : std_logic := '1';
  signal input_digit : integer range 0 to 9;
  signal input_enable : std_logic := '0';
  signal unlock : std_logic;

begin

  clk <= not clk after clock_period;

  DUT : entity work.code_lock(rtl)
    generic map (1,2,3,4)
    port map (
      clk => clk,
      rst => rst,
      input_digit => input_digit,
      input_enable => input_enable,
      unlock => unlock
    );

end architecture;

El banco de pruebas de Tcl

El código Tcl en este ejemplo solo funciona con el simulador ModelSim VHDL. Si desea usarlo en Vivado, por ejemplo, debe realizar algunos cambios. Eso es porque usa algunos comandos que son específicos de este simulador. Una desventaja de usar Tcl es que su código se bloquea para un proveedor de simulador en particular.

Como referencia, recomiendo Tcl Developer Xchange, que cubre el lenguaje Tcl en general, y ModelSim Command Reference Manual, que describe todos los comandos específicos de ModelSim.

Si tiene instalado ModelSim, puede descargar el proyecto de ejemplo mediante el siguiente formulario.

Usando un espacio de nombres

Lo primero que recomiendo es crear un espacio de nombres Tcl. Esa es una buena idea porque, de lo contrario, puede sobrescribir involuntariamente las variables globales de su script Tcl. Al envolver todo su código en el espacio de nombres, evita ese posible desorden. Pondremos todo el código Tcl que escribamos a partir de ahora dentro del codelocktb espacio de nombres, como se muestra a continuación.

namespace eval ::codelocktb {

  # Put all the Tcl code in here

}

Dentro del espacio de nombres, debemos comenzar iniciando la simulación, como se muestra a continuación. Lo hacemos con el vsim comando, seguido del nombre de la biblioteca y la entidad del banco de pruebas VHDL. Eso cargará la simulación, pero no la ejecutará. No pasa el tiempo de simulación hasta que usamos el ejecutar comando más adelante en el script. También me gusta incluir una instrucción If que cargará la forma de onda, si existe.

# Load the simulation
vsim work.code_lock_tb

# Load the waveform
if {[file exists wave.do]} {
  do wave.do
}

Declaración de variables de espacio de nombres

Ahora que hemos cargado la simulación, podemos comenzar a interactuar con el código VHDL. Primero, quiero leer el clock_period constante y contraseña genérica en el entorno Tcl.

En el siguiente código, estoy usando el examinar específico de ModelSim Comando para leer señal VHDL y valores constantes en Tcl. Luego, estoy usando los comandos de cadena y lista de Tcl para extraer el valor de tiempo y las unidades de tiempo. El código PIN variable se convierte en una lista de los cuatro dígitos que leemos de las constantes genéricas.

# Read the clock period constant from the VHDL TB
variable clockPeriod [examine clock_period]

# Strip the braces: "{10 ns}" => "10 ns"
variable clockPeriod [string trim $clockPeriod "{}"]

# Split the number and the time unit
variable timeUnits [lindex $clockPeriod 1]
variable clockPeriod [lindex $clockPeriod 0]

# Read the correct PIN from the VHDL generics
variable pinCode [examine dut.pin0 dut.pin1 dut.pin2 dut.pin3]

Tenga en cuenta que estoy usando un estilo de codificación diferente en el script Tcl que en el código VHDL. En lugar de guiones bajos, estoy usando camel casing. Eso es porque estoy siguiendo la guía de estilo Tcl. Por supuesto, nada te impide usar el mismo estilo en los archivos Tcl y VHDL si eso es lo que prefieres.

Además, si ha usado Tcl sin espacios de nombres, probablemente conozca la palabra clave set, que es la forma estándar de definir una variable en Tcl. Aquí, estoy usando la palabra clave variable más nueva en su lugar. Es como una variable global que está vinculada al espacio de nombres actual en lugar del ámbito global.

Finalmente, declaramos una variable llamada errorCount e inicialícelo a 0, como se muestra a continuación. A medida que la simulación avance a través de los casos de prueba, la incrementaremos cada vez que detectemos un error. Al final, podemos usarlo para determinar si el módulo pasó o falló.

variable errorCount 0

Imprimir texto en ModelSim

El comando puts es la forma estándar de imprimir texto en la consola en Tcl. Pero este método funciona de manera desafortunada en ModelSim. La versión de Windows hace lo que cabría esperar; imprime la cadena en la consola. En la versión de Linux, por otro lado, el texto se genera en el shell desde donde inició ModelSim, y no en la consola dentro de la GUI.

La siguiente imagen muestra lo que sucede cuando escribimos puts Comando en la consola de ModelSim. Aparece en la ventana de terminal detrás. Peor aún, si inició ModelSim usando un acceso directo en el escritorio, nunca verá el resultado porque el shell está oculto.

Hay soluciones para cambiar el comportamiento de los puts dominio. Puede, por ejemplo, redefinirlo (¡Sí! Puede hacerlo en Tcl) y hacerlo funcionar en ambas plataformas. Pero una forma más sencilla de imprimir el texto en la consola tanto en Linux como en Windows es usar el echo específico de ModelSim. comando.

Usaremos el procedimiento Tcl personalizado que se muestra a continuación para imprimir texto. Y mientras lo hacemos, también anteponemos el mensaje con el tiempo de simulación actual. En ModelSim, siempre puede obtenerlo usando $now variable global.

proc printMsg { msg } {
  global now
  variable timeUnits
  echo $now $timeUnits: $msg
}

Simulación para N ciclos de reloj

El DUT es un módulo sincronizado, lo que significa que no sucede nada entre los flancos ascendentes del reloj. Por lo tanto, queremos simular en pasos basados ​​en la duración de un ciclo de reloj. El siguiente procedimiento Tcl utiliza el clockPeriod y unidades de tiempo variables que extrajimos del código VHDL anteriormente para lograrlo.

proc runClockCycles { count } {
  variable clockPeriod
  variable timeUnits

  set t [expr {$clockPeriod * $count}]
  run $t $timeUnits
}

El procedimiento toma un parámetro:count . Lo multiplicamos por la duración de un período de reloj para obtener la duración de N ciclos de reloj. Finalmente, usamos ModelSim run comando para simular precisamente ese tiempo.

Comprobando un valor de señal de Tcl

En ModelSim, podemos leer una señal VHDL de Tcl usando el examinar dominio. El siguiente código muestra el procedimiento Tcl que estamos usando para leer un valor de señal y verificar que sea el esperado. Si la señal no coincide con el expectedVal parámetro, imprimimos un mensaje desagradable e incrementamos el errorCount variables.

proc checkSignal { signalName expectedVal } {
  variable errorCount

  set val [examine $signalName]
  if {$val != $expectedVal} {
    printMsg "ERROR: $signalName=$val (expected=$expectedVal)"
    incr errorCount
  }
}

Probar una secuencia de PIN

La salida del módulo de bloqueo de código depende no solo de las entradas actuales sino también de sus valores anteriores. Por lo tanto, la verificación de las salidas debe realizarse al menos después de enviar cuatro dígitos al dispositivo bajo prueba. Solo entonces la señal de desbloqueo debería cambiar de '0' a '1' si el PIN es correcto.

El siguiente procedimiento Tcl utiliza ModelSim force palabra clave para cambiar las señales VHDL de Tcl. El -depósito cambiar a la fuerza La palabra clave significa que ModelSim cambiará el valor, pero permitirá que otro controlador VHDL tome el control más adelante, aunque ninguna otra entidad controle las entradas del DUT en nuestro banco de pruebas.

proc tryPin { digits } {
  variable pinCode

  set pinStatus "incorrect"
  if { $digits == $pinCode } {
    set pinStatus "correct"
  }

  printMsg "Entering $pinStatus PIN code: $digits"

  foreach i $digits {
    force input_digit $i -deposit
    force input_enable 1 -deposit
    runClockCycles 1
    force input_enable 0 -deposit
    runClockCycles 1
  }

  if { $pinStatus == "correct" } {
    checkSignal unlock 1
  } else {
    checkSignal unlock 0
  }
}

El tryPin procedimiento utiliza nuestro printMsg procedimiento para informar sobre lo que está haciendo, qué código PIN está ingresando y si es el código de acceso correcto. También utiliza runClockCycles procedimiento para ejecutarse exactamente durante un período de reloj, mientras se manipulan las entradas del dispositivo bajo prueba para simular que un usuario ingresa un PIN.

Finalmente, utiliza el checkSignal procedimiento para verificar que el dispositivo bajo prueba se está comportando como se esperaba. Como ya expliqué, la checkSignal El procedimiento imprimirá un mensaje de error e incrementará el errorCount variable si el desbloquear la señal no coincide con el valor esperado.

Casos de prueba y estado final

En el código Tcl anterior, comenzamos la simulación y definimos un montón de variables y procedimientos, pero no simulamos ningún momento. La simulación todavía está en 0 ns. No ha pasado el tiempo de simulación.

Hacia el final de nuestro espacio de nombres personalizado, comenzamos a llamar a los procedimientos Tcl. Como se muestra en el código a continuación, comenzamos ejecutando diez ciclos de reloj. Después de eso, liberamos el reinicio y verificamos que el desbloqueo la salida tiene el valor esperado de '0'.

runClockCycles 10

# Release reset
force rst '0' -deposit
runClockCycles 1

# Check reset value
printMsg "Checking reset value"
checkSignal unlock 0

# Try a few corner cases
tryPin {0 0 0 0}
tryPin {9 9 9 9}
tryPin $pinCode
tryPin [lreverse $pinCode]

if { $errorCount == 0 } {
  printMsg "Test: OK"
} else {
  printMsg "Test: Failure ($errorCount errors)"
}

Podríamos probar todos los 10000 códigos PIN diferentes, pero eso llevaría una cantidad considerable de tiempo. La simulación impulsada por Tcl es mucho más lenta que un banco de pruebas VHDL puro. El simulador tiene que iniciarse y detenerse muchas veces, y eso lleva mucho tiempo. Por lo tanto, he decidido solo revisar casos de esquina.

Llamamos a tryPin cuatro veces, con los códigos PIN:0000, 9999, el PIN correcto y los números del PIN correcto en orden inverso. Me imagino que este es un error fácil de cometer al crear un código de bloqueo, mirar solo la combinación y no el orden de los números.

Finalmente, al final del código Tcl, pero aún dentro del espacio de nombres, verificamos el errorCount e imprima una "Prueba:OK" o un "Error de prueba".

Ejecutar el banco de pruebas

Y ahora viene la parte divertida:ejecutar el banco de pruebas. Prefiero usar el comando fuente Tcl, como se muestra a continuación, pero también puede usar el do específico de ModelSim dominio. De hecho, los archivos ModelSim DO son en realidad solo archivos Tcl con un sufijo diferente.

source code_lock/code_lock_tb.tcl

En la versión final de mi código, no hay errores. La siguiente lista muestra el resultado de una simulación exitosa. El script Tcl nos informa sobre lo que está haciendo y podemos ver que todas las líneas del mensaje tienen una marca de tiempo. Ese es nuestro printMsg procedimiento en el trabajo. Finalmente, el banco de pruebas se detiene e imprime "Test:OK".

VSIM> source code_lock/code_lock_tb.tcl
# vsim 
...
# 110 ns: Checking reset value
# 110 ns: Entering incorrect PIN code: 0 0 0 0
# 190 ns: Entering incorrect PIN code: 9 9 9 9
# 270 ns: Entering correct PIN code: 1 2 3 4
# 350 ns: Entering incorrect PIN code: 4 3 2 1
# 430 ns: Test: OK

Sin embargo, quiero mostrarle cómo se ve cuando el DUT no pasa una prueba. Para hacer eso, he creado un error en el módulo de bloqueo de código. He reemplazado la comprobación de pin1 con pin2 para que el DUT ignore el pin1 valor. Es un error tipográfico fácil de hacer, como se muestra en el código a continuación.

unlock <= '1' when pins = (pin3, pin2, pin2, pin0) else '0';

Cuando ahora ejecutamos el banco de pruebas, puede ver en la lista a continuación que se detectó la falla. Y finalmente, el banco de pruebas imprime "Prueba:falla" junto con la cantidad de errores.

VSIM> source code_lock/code_lock_tb.tcl
# vsim 
...
# 110 ns: Checking reset value
# 110 ns: Entering incorrect PIN code: 0 0 0 0
# 190 ns: Entering incorrect PIN code: 9 9 9 9
# 270 ns: Entering correct PIN code: 1 2 3 4
# 350 ns: ERROR: unlock=0 (expected=1)
# 350 ns: Entering incorrect PIN code: 4 3 2 1
# 430 ns: Test: Failure (1 errors)

Reflexiones finales

He creado muchos bancos de pruebas basados ​​en Tcl en mi carrera, pero mi opinión sobre ellos está algo dividida.

Por un lado, puede hacer algunas cosas geniales que no son posibles solo con VHDL. Por ejemplo, el banco de pruebas interactivo. También es bueno que pueda cambiar el banco de pruebas sin tener que volver a compilar. Y finalmente, la verificación usando un lenguaje muy diferente puede ser ventajoso. Tendría que cometer el mismo error en dos tecnologías diferentes para que no se detecte, y eso es poco probable.

Por otro lado, también hay algunas desventajas. Los bancos de pruebas basados ​​en Tcl son magnitudes más lentas que sus contrapartes VHDL. Otro problema importante es el bloqueo de proveedores. Es imposible crear un banco de pruebas Tcl completamente portátil, mientras que un banco de pruebas VHDL puede ejecutarse en cualquier simulador capaz.

Y la razón final por la que los bancos de prueba Tcl pueden no valer la pena es el lenguaje en sí. No tiene excelentes funciones para prevenir errores de programación, y es difícil depurar un problema de Tcl. No es un lenguaje intuitivo ni indulgente como Python o Java.

Sin embargo, tiene un propósito como lenguaje de unión entre VHDL y el mundo del software. Y debido a que la mayoría de las herramientas FPGA, no solo los simuladores, son compatibles con Tcl, recomiendo aprenderlo.

Estos pensamientos son solo mis opiniones. ¡Dime lo que piensas en la sección de comentarios!


VHDL

  1. Cómo crear una lista de cadenas en VHDL
  2. Cómo detener la simulación en un banco de pruebas VHDL
  3. Cómo crear un controlador PWM en VHDL
  4. Cómo crear un FIFO de búfer de anillo en VHDL
  5. Banco de pruebas interactivo usando Tcl
  6. Cómo crear un banco de pruebas de autocomprobación
  7. Cómo crear una lista enlazada en VHDL
  8. Cómo usar una función impura en VHDL
  9. Cómo usar una función en VHDL
  10. Cómo crear una máquina de estados finitos en VHDL
  11. Cómo instalar un simulador y editor VHDL gratis