Creación de un testbench dirigido por Tcl para un módulo VHDL de cerradura por código
La mayoría de simuladores VHDL exponen el Tool Command Language (Tcl) como su interfaz de scripting principal. Cuando escribe un comando en la consola del simulador, está usando Tcl, y también puede escribir scripts que controlen y observen su diseño VHDL.
Este artículo le guía en la construcción de un testbench auto-comprobable que usa Tcl —en lugar de VHDL— para verificar el comportamiento de un módulo simple de cerradura por código. El resultado es un arnés de prueba claro, mantenible, que puede ejecutarse, modificarse y reutilizarse sin recompilar el VHDL.
Ver también:
Descargue el código y archivos de proyecto ModelSim a continuación.
El DUT: Un módulo de cerradura por código en VHDL
Antes de sumergirnos en el testbench, revisemos el Device Under Test (DUT). El módulo emula una cerradura simple que abre una caja fuerte cuando se introduce la secuencia numérica correcta en un teclado PIN.
Aunque muchos llaman a este dispositivo "combination lock", el término correcto es "permutation lock" —el orden de los dígitos importa. Por claridad, lo llamaremos code lock en todo momento.

La ilustración anterior muestra una caja fuerte típica de hotel. Para simplificar, nuestro diseño usa solo teclas numéricas y omite los botones CLEAR y LOCK.
Cómo funciona el módulo de cerradura por código
La cerradura comienza en estado bloqueado. Cuando un usuario introduce cuatro dígitos consecutivos que coinciden con el PIN secreto, la cerradura se abre. Introducir una secuencia incorrecta vuelve a bloquear el dispositivo. Este comportamiento se implementa como un detector de secuencia simple en VHDL.
La forma de onda ilustra el tiempo del módulo. Además de reloj y reset, el módulo acepta dos entradas: input_digit (entero 0–9) y input_enable (std_logic). El DUT muestrea el dígito en flanco ascendente de reloj cuando enable está alto.
La única salida es unlock, un std_logic que pasa a alto solo cuando los cuatro dígitos consecutivos coinciden con el PIN correcto. En este ejemplo, la contraseña es 1234.
La Entidad
La siguiente definición de entidad codifica el PIN secreto mediante genéricos. Cada genérico es un entero en el rango 0–9, representando un dígito Binary Coded Decimal (BCD).
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;
La Región Declarativa
El módulo usa una única señal interna: un registro de desplazamiento de 4 bits que almacena los dígitos más recientes introducidos. Permitimos que el registro contenga valores de –1 a 9. El valor especial –1 se usa para el estado de reset, asegurando que un PIN de 0000 no desbloquee la cerradura por defecto.
type pins_type is array (0 to 3) of integer range -1 to 9; signal pins : pins_type;
La Implementación
La arquitectura incluye una asignación combinacional que pone unlock a alto cuando el registro de desplazamiento coincide con los valores genéricos del PIN. Como pins tiene reloj, unlock cambia solo en flanco ascendente de reloj.
unlock <= '1' when pins = (pin3, pin2, pin1, pin0) else '0';
El siguiente proceso implementa el registro de desplazamiento. En reset, todas las entradas se ponen a –1. Cuando input_enable está alto en un flanco ascendente, el dígito actual se almacena en posición 0, y los dígitos previos se desplazan a la derecha.
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 Testbench VHDL
Aunque usaremos Tcl para la lógica de verificación, todavía necesitamos un testbench VHDL básico para generar las señales de reloj y reset. A continuación está el archivo completo del testbench VHDL.
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 Testbench Tcl
A continuación hay un script Tcl que se ejecuta dentro de ModelSim. Si usa Vivado u otro simulador, se requerirán ajustes menores, ya que algunos comandos son específicos del simulador. Consulte el ModelSim Command Reference Manual para detalles.
Descargue el proyecto de ejemplo usando el formulario a continuación.
Uso de un espacio de nombres
Encapsular el script en un espacio de nombres evita choques accidentales con variables globales. Todo el código Tcl reside dentro del espacio de nombres codelocktb.
namespace eval ::codelocktb { # Todo el código Tcl aquí }
Inicialización de la simulación
Iniciamos la simulación con vsim y cargamos el archivo de forma de onda si existe.
# Cargar la simulación vsim work.code_lock_tb # Cargar la forma de onda if {[file exists wave.do]} { do wave.do }
Importar valores VHDL a Tcl
Usando el comando examine de ModelSim, leemos la constante clock_period y los genéricos PIN. El siguiente fragmento extrae el valor numérico y la unidad de tiempo.
variable clockPeriod [examine clock_period] variable clockPeriod [string trim $clockPeriod "{}"] variable timeUnits [lindex $clockPeriod 1] variable clockPeriod [lindex $clockPeriod 0] variable pinCode [examine dut.pin0 dut.pin1 dut.pin2 dut.pin3]
También inicializamos una variable errorCount para rastrear fallos.
variable errorCount 0
Impresión con marca de tiempo
El comando echo muestra mensajes en la consola de ModelSim en todas las plataformas. El siguiente procedimiento prefija cada mensaje con el tiempo de simulación actual.
proc printMsg { msg } { global now variable timeUnits echo $now $timeUnits: $msg }
Ejecutar por un número específico de ciclos de reloj
Como el DUT es sensible al reloj, avanzamos el tiempo en múltiplos del período de reloj.
proc runClockCycles { count } { variable clockPeriod variable timeUnits set t [expr {$clockPeriod * $count}] run $t $timeUnits }
Comprobación de valores de señales
El procedimiento checkSignal compara una señal VHDL contra un valor esperado, reportando error si difieren.
proc checkSignal { signalName expectedVal } { variable errorCount set val [examine $signalName] if {$val != $expectedVal} { printMsg "ERROR: $signalName=$val (expected=$expectedVal)" incr errorCount } }
Simulación de entrada de PIN
Este procedimiento controla las entradas del DUT para simular a un usuario introduciendo un PIN de 4 dígitos. Comprueba la salida unlock una vez aplicados los cuatro dígitos.
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 } }
Secuencia de prueba e informe de resultados
Tras un breve período de inicialización, el script libera el reset y ejecuta varios casos de prueba: dos casos esquina (0000 y 9999), el PIN correcto, y la secuencia inversa del PIN. Luego informa el resultado global.
runClockCycles 10 force rst '0' -deposit runClockCycles 1 printMsg "Checking reset value" checkSignal unlock 0 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)" }
Ejecución del testbench
Ejecute el script con el siguiente comando dentro de ModelSim:
source code_lock/code_lock_tb.tcl
Una ejecución exitosa produce una salida similar a la siguiente, con marcas de tiempo precediendo cada mensaje.
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
Si el DUT contiene un defecto (p.ej., un error tipográfico en la asignación de unlock), el script reporta el fallo y el conteo 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
A lo largo de mi carrera, he escrito numerosos testbenches basados en Tcl. Ofrecen ventajas únicas: pruebas interactivas, capacidad de modificar el arnés de prueba sin recompilar, y una capa extra de verificación que protege contra errores tanto en el diseño como en el testbench.
Sin embargo, hay compensaciones. Los scripts Tcl se ejecutan notablemente más lento que testbenches VHDL puros, y el lenguaje puede ser frágil: los errores son difíciles de detectar temprano, y la depuración es menos intuitiva que en lenguajes de alto nivel como Python.
A pesar de estos inconvenientes, Tcl sigue siendo el lenguaje de pegamento de facto para herramientas FPGA y simulación. Si trabaja con Xilinx Vivado, Intel Quartus, o Mentor Graphics Questa, dominar Tcl le dará dividendos. Siéntase libre de compartir sus experiencias en los comentarios a continuación.
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
- Diseño de un búfer circular FIFO robusto en VHDL usando RAM de bloques
- Mejora la verificación de hardware con un testbench interactivo basado en Tcl
- Cómo construir un testbench de auto-comprobación en VHDL: Guía práctica
- Implementación de lista enlazada dinámica en VHDL con tipos protegidos y punteros de acceso
- Uso de funciones impuras en VHDL: Mejorando legibilidad y mantenibilidad de FSM
- Dominando funciones VHDL: Guía práctica para diseño eficiente
- Diseño de máquina de estados finitos en VHDL: Ejemplo práctico de semáforo
- Configuración gratis de simulador y editor VHDL: Guía para estudiantes