Criando um Testbench Dirigido por Tcl para um Módulo Code-Lock VHDL
A maioria dos simuladores VHDL expõe a Tool Command Language (Tcl) como sua interface de script principal. Quando você digita um comando no console do simulador, está usando Tcl, e também pode escrever scripts que acionam e observam seu projeto VHDL.
Este artigo orienta você na construção de um testbench auto-verificável que usa Tcl — em vez de VHDL — para verificar o comportamento de um módulo code-lock simples. O resultado é um harness de teste claro, manutenível e que pode ser executado, modificado e reutilizado sem recompilar o VHDL.
Veja também:
Baixe o código e arquivos de projeto ModelSim abaixo.
O DUT: Um Módulo Code-Lock em VHDL
Antes de mergulhar no testbench, vamos revisar o Device Under Test (DUT). O módulo emula uma trava simples que abre um cofre quando a sequência numérica correta é inserida em um teclado numérico.
Embora muitas pessoas chamem tal dispositivo de "combination lock", o termo correto é "permutation lock" — a ordem dos dígitos importa. Para clareza, chamaremos de code lock ao longo do artigo.

A ilustração acima mostra um cofre de hotel típico. Para simplificar, nosso projeto usa apenas teclas numéricas e omite os botões CLEAR e LOCK.
Como o Módulo Code Lock Funciona
A trava começa em estado travado. Quando um usuário insere quatro dígitos consecutivos que correspondem ao PIN secreto, a trava abre. Inserir uma sequência incorreta retrava o dispositivo. Esse comportamento é implementado como um detector de sequência simples em VHDL.
A forma de onda ilustra o tempo do módulo. Além do clock e reset, o módulo aceita duas entradas: input_digit (um inteiro 0-9) e input_enable (um std_logic). O DUT amostra o dígito na borda de subida do clock quando enable está alto.
A única saída é unlock, um std_logic que vai alto apenas quando os quatro dígitos consecutivos correspondem ao PIN correto. Neste exemplo, a senha é 1234.
A Entidade
A seguinte definição de entidade codifica o PIN secreto via genéricos. Cada genérico é um inteiro no intervalo 0-9, representando um 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;
A Região Declarativa
O módulo usa um único sinal interno — um registrador de deslocamento de 4 bits que armazena os dígitos mais recentes inseridos. Permitimos que o registrador mantenha valores de -1 a 9. O valor especial -1 é usado para o estado de reset, garantindo que um PIN de 0000 não destrave a trava por padrão.
type pins_type is array (0 to 3) of integer range -1 to 9; signal pins : pins_type;
A Implementação
A arquitetura inclui uma atribuição combinacional que aciona unlock alto quando o registrador de deslocamento corresponde aos valores genéricos do PIN. Como pins é clockado, unlock muda apenas na borda de subida do clock.
unlock <= '1' when pins = (pin3, pin2, pin1, pin0) else '0';
O processo a seguir implementa o registrador de deslocamento. No reset, todas as entradas são definidas como -1. Quando input_enable está alto na borda de subida, o dígito atual é armazenado na posição 0, e os dígitos anteriores deslocam para a direita.
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;
O Testbench VHDL
Embora usaremos Tcl para a lógica de verificação, ainda precisamos de um testbench VHDL básico para gerar os sinais de clock e reset. Abaixo está o arquivo de testbench VHDL completo.
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;
O Testbench Tcl
Abaixo está um script Tcl que roda dentro do ModelSim. Se você usa Vivado ou outro simulador, pequenos ajustes serão necessários, pois alguns comandos são específicos do simulador. Consulte o ModelSim Command Reference Manual para detalhes.
Baixe o projeto de exemplo usando o formulário abaixo.
Usando um Namespace
Encapsular o script em um namespace evita conflitos acidentais com variáveis globais. Todo o código Tcl reside dentro do namespace codelocktb.
namespace eval ::codelocktb { # All Tcl code here }
Inicializando a Simulação
Iniciamos a simulação com vsim e carregamos o arquivo de forma de onda se existir.
# Load the simulation vsim work.code_lock_tb # Load the waveform if {[file exists wave.do]} { do wave.do }
Importando Valores VHDL para Tcl
Usando o comando examine do ModelSim, lemos a constante clock_period e os genéricos do PIN. O snippet a seguir extrai o valor numérico e a unidade de tempo.
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]
Também inicializamos uma variável errorCount para rastrear falhas.
variable errorCount 0
Imprimindo com Timestamp
O comando echo exibe mensagens no console do ModelSim em todas as plataformas. O procedimento a seguir prefixa cada mensagem com o tempo de simulação atual.
proc printMsg { msg } { global now variable timeUnits echo $now $timeUnits: $msg }
Executando por um Número Especificado de Ciclos de Clock
Como o DUT é sensível ao clock, avançamos o tempo em múltiplos do período do clock.
proc runClockCycles { count } { variable clockPeriod variable timeUnits set t [expr {$clockPeriod * $count}] run $t $timeUnits }
Verificando Valores de Sinais
O procedimento checkSignal compara um sinal VHDL com um valor esperado, reportando erro se diferirem.
proc checkSignal { signalName expectedVal } { variable errorCount set val [examine $signalName] if {$val != $expectedVal} { printMsg "ERROR: $signalName=$val (expected=$expectedVal)" incr errorCount } }
Simulando Entrada de PIN
Este procedimento aciona as entradas do DUT para simular um usuário inserindo um PIN de 4 dígitos. Ele verifica a saída unlock uma vez que todos os quatro dígitos tenham sido aplicados.
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 } }
Sequência de Teste e Relatório de Resultado
Após um breve período de inicialização, o script libera o reset e executa vários casos de teste: dois casos de canto (0000 e 9999), o PIN correto e a sequência de PIN invertida. Então relata o resultado geral.
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)" }
Executando o Testbench
Execute o script com o seguinte comando dentro do ModelSim:
source code_lock/code_lock_tb.tcl
Uma execução bem-sucedida produz saída similar à seguinte, com timestamps precedendo cada mensagem.
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
Se o DUT contém um defeito (ex: erro de digitação na atribuição unlock), o script reporta a falha e a contagem de erros.
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)
Considerações Finais
Ao longo da minha carreira, escrevi numerosos testbenches baseados em Tcl. Eles oferecem vantagens únicas: teste interativo, capacidade de modificar o harness de teste sem recompilar, e uma camada extra de verificação que protege contra bugs tanto no projeto quanto no testbench.
No entanto, há compensações. Scripts Tcl rodam notavelmente mais devagar que testbenches VHDL puros, e a linguagem pode ser frágil — erros são difíceis de detectar cedo, e a depuração é menos intuitiva que em linguagens de alto nível como Python.
Apesar desses inconvenientes, Tcl continua sendo a linguagem de cola de facto para ferramentas FPGA e simulação. Se você trabalha com Xilinx Vivado, Intel Quartus ou Mentor Graphics Questa, dominar Tcl valerá a pena. Sinta-se à vontade para compartilhar suas experiências nos comentários abaixo.
VHDL
- Criando Listas de Strings em VHDL: Boas Práticas e Exemplo
- Finalizando Graciosamente uma Simulação de Testbench VHDL
- Implementando um Controlador PWM em VHDL: Projeto, Simulação e Demo em FPGA
- Projetando um FIFO Ring Buffer Robusto em VHDL com Block RAM
- Aprimorando a Verificação de Hardware com Testbench Interativo Baseado em Tcl
- Como Construir um Testbench de Auto-Verificação em VHDL – Guia Prático
- Implementando Lista Encadeada Dinâmica em VHDL com Tipos Protegidos e Ponteiros de Acesso
- Usando Funções Impuras em VHDL: Melhorando Legibilidade e Manutenibilidade de FSM
- Dominando Funções VHDL: Guia Prático para Design Eficiente
- Configuração Gratuita de Simulador e Editor VHDL: Guia para Estudantes