Manufaturação industrial
Internet das coisas industrial | Materiais industriais | Manutenção e reparo de equipamentos | Programação industrial |
home  MfgRobots >> Manufaturação industrial >  >> Industrial programming >> VHDL

Como criar um testbench orientado por Tcl para um módulo de bloqueio de código VHDL


A maioria dos simuladores VHDL usa o Tool Command Language (Tcl) como sua linguagem de script. Ao digitar um comando no console do simulador, você está usando Tcl. Além disso, você pode criar scripts com Tcl que rodam no simulador e interagem com seu código VHDL.

Neste artigo, criaremos um testbench de autoverificação que usou Tcl em vez de VHDL para verificar se um módulo VHDL se comporta corretamente.



Veja também:
Por que você precisa aprender Tcl
Testbench interativo usando Tcl


Você pode baixar o código deste artigo e do projeto ModelSim usando o formulário abaixo.




O DUT:um módulo de bloqueio de código em VHDL


Antes de começarmos no testbench, apresentarei o dispositivo em teste (DUT). Será um módulo de bloqueio de código que desbloqueará um cofre quando inserirmos a sequência numérica correta em um PIN pad.

As pessoas geralmente se referem a um bloqueio de código como um bloqueio de combinação . No entanto, acho esse termo impreciso. Não basta digitar a combinação correta de dígitos para desbloqueá-lo. Você também deve inseri-los na ordem correta. Estritamente falando, um cadeado de combinação é realmente um bloqueio de permuta , mas vamos chamá-lo de bloqueio de código .

A imagem acima mostra um bloqueio de código na forma de um cofre de hotel. Para simplificar, nosso exemplo usará apenas as teclas numéricas e não os botões “CLEAR” e “LOCK”.

Como funciona o módulo de bloqueio de código


Nosso módulo começará na posição bloqueada e, se inserirmos quatro dígitos seguidos que correspondam ao código PIN secreto, ele desbloqueará o cofre. Para rebloqueá-lo, podemos inserir outro número incorreto. Assim, precisamos criar um detector de sequência em VHDL.



A forma de onda acima mostra como o módulo de bloqueio de código vai funcionar. Além do relógio e do reset, existem dois sinais de entrada:input_digit e input_enable . O módulo deve amostrar o dígito de entrada quando a habilitação for '1' em uma borda de clock ascendente.

Existe apenas uma saída deste módulo:o desbloqueio sinal. Imagine que ele controla o mecanismo de travamento de um cofre ou um cofre de algum tipo. O desbloqueio sinal só deve ser '1' somente quando o usuário digitou quatro dígitos consecutivos que correspondem ao PIN correto. Neste artigo, usaremos 1234 como a senha.

A entidade


O código abaixo mostra a entidade do módulo de bloqueio de código. Como o objetivo deste módulo é ser um exemplo simples de DUT para nosso testbench baseado em TCL, estou codificando a senha secreta usando genéricos. As quatro constantes genéricas são decimais codificados binários (BCDs) realizados como números inteiros com um intervalo restrito.
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;

Assim como a senha, o input_digit sinal também é um tipo BCD. As outras entradas e saídas são std_logics.

A região declarativa


Este módulo possui apenas um sinal interno:um registrador de deslocamento que contém os quatro últimos dígitos que o usuário digitou. Mas em vez de usar o intervalo BCD de 0 a 9, deixamos os números irem de -1 a 9. São 11 valores possíveis.
type pins_type is array (0 to 3) of integer range -1 to 9;
signal pins : pins_type;

Temos que usar um valor de redefinição que não seja um dígito que o usuário possa inserir, e é para isso que serve o -1. Se tivéssemos usado o intervalo de 0 a 9 para os pinos array, definir a senha secreta para 0000 teria inicialmente aberto o cofre. Com este esquema, o usuário terá que digitar explicitamente quatro 0s.

A implementação


Na parte superior da região de arquitetura, adicionei uma instrução simultânea que desbloqueia o cofre quando os pinos sinal corresponde às constantes genéricas. O código abaixo é combinacional, mas como os pinos sinal é cronometrado, o desbloqueio sinal só mudará na borda de subida do relógio.
unlock <= '1' when pins = (pin3, pin2, pin1, pin0) else '0';

O código abaixo mostra o processo que lê a entrada do usuário. Faz um registrador de deslocamento dos pinos sinal mudando todos os valores quando input_enable é '1' em uma borda de clock ascendente. O resultado é que os quatro últimos dígitos que o usuário digitou são armazenados nos pinos variedade.
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;

A bancada de teste VHDL


Antes de tudo, ainda precisamos de um testbench VHDL básico, embora estejamos usando Tcl para a verificação. O código abaixo mostra o arquivo VHDL completo. Eu instanciei o DUT e criei o sinal do relógio, mas isso é tudo. Além de gerar o relógio, este testbench não faz 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;

A bancada de testes Tcl


O código Tcl neste exemplo só funciona com o simulador ModelSim VHDL. Se você quiser usá-lo no Vivado, por exemplo, você precisa fazer algumas alterações nele. Isso porque ele usa alguns comandos específicos desse simulador. É uma desvantagem de usar o Tcl que seu código fica bloqueado para um fornecedor de simulador específico.

Para referência, recomendo o Tcl Developer Xchange, que abrange a linguagem Tcl em geral, e o Manual de referência de comandos do ModelSim, que descreve todos os comandos específicos do ModelSim.

Se você tiver o ModelSim instalado, você pode baixar o projeto de exemplo usando o formulário abaixo.




Usando um namespace


A primeira coisa que recomendo é criar um namespace Tcl. Essa é uma boa ideia porque, caso contrário, você pode substituir involuntariamente as variáveis ​​globais do seu script Tcl. Ao envolver todo o seu código no namespace, você evita essa bagunça em potencial. Vamos colocar todo o código Tcl que escrevermos a partir de agora dentro do codelocktb namespace, como mostrado abaixo.
namespace eval ::codelocktb {

  # Put all the Tcl code in here

}

Dentro do namespace, temos que começar iniciando a simulação, conforme mostrado abaixo. Fazemos isso com o vsim comando, seguido pelo nome da biblioteca e entidade do testbench VHDL. Isso carregará a simulação, mas não a executará. Nenhum tempo de simulação passa até usarmos o run comando mais tarde no script. Eu também gosto de incluir uma instrução If que carregará a forma de onda, se ela existir.
# Load the simulation
vsim work.code_lock_tb

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

Declarando variáveis ​​de namespace


Agora que carregamos a simulação, podemos começar a interagir com o código VHDL. Primeiro, quero ler o clock_period constante e senha genérica no ambiente Tcl.

No código abaixo, estou usando o examine específico do ModelSim comando para ler o sinal VHDL e os valores constantes em Tcl. Então, estou usando os comandos Tcl string e list para extrair o valor de tempo e as unidades de tempo. O pinCode variável torna-se uma lista dos quatro dígitos que lemos das 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]

Observe que estou usando um estilo de codificação diferente no script Tcl do que no código VHDL. Em vez de sublinhados, estou usando tripa de camelo. Isso porque estou seguindo o guia de estilo Tcl. Claro, nada impede você de usar o mesmo estilo nos arquivos Tcl e VHDL, se preferir.

Além disso, se você usou Tcl sem namespaces, provavelmente conhece a palavra-chave set, que é a maneira padrão de definir uma variável em Tcl. Aqui, estou usando a palavra-chave variável mais recente. É como uma variável global vinculada ao namespace atual em vez do escopo global.

Por fim, declaramos uma variável chamada errorCount e inicialize-o com 0, conforme mostrado abaixo. À medida que a simulação avança pelos casos de teste, iremos incrementá-la toda vez que detectarmos um erro. No final, podemos usá-lo para determinar se o módulo foi aprovado ou reprovado.
variable errorCount 0

Imprimindo texto no ModelSim


O comando puts é a maneira padrão de imprimir texto no console em Tcl. Mas esse método funciona de maneira infeliz no ModelSim. A versão do Windows faz o que você esperaria; ele imprime a string no console. Na versão Linux, por outro lado, o texto é gerado no shell de onde você iniciou o ModelSim, e não no console dentro da GUI.

A imagem abaixo mostra o que acontece quando digitamos os puts comando no console ModelSim. Ele aparece na janela do terminal atrás. Pior ainda, se você iniciou o ModelSim usando um atalho na área de trabalho, você nunca verá a saída porque o shell está oculto.



Existem soluções alternativas para alterar o comportamento das puts comando. Você pode, por exemplo, redefini-lo (Sim! Você pode fazer isso em Tcl) e fazê-lo funcionar em ambas as plataformas. Mas uma maneira mais direta de imprimir o texto no console no Linux e no Windows é usar o echo específico do ModelSim comando.

Usaremos o procedimento Tcl personalizado mostrado abaixo para imprimir o texto. E ao fazer isso, também adicionamos a mensagem com o tempo de simulação atual. No ModelSim, você sempre pode obtê-lo usando o $now variável global.
proc printMsg { msg } {
  global now
  variable timeUnits
  echo $now $timeUnits: $msg
}

Simulando para N ciclos de clock


O DUT é um módulo com clock, o que significa que nada acontece entre as bordas ascendentes do clock. Portanto, queremos simular em etapas com base na duração de um ciclo de clock. O procedimento Tcl abaixo usa o clockPeriod e unidades de tempo variáveis ​​que tiramos do código VHDL anteriormente para conseguir isso.
proc runClockCycles { count } {
  variable clockPeriod
  variable timeUnits

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

O procedimento usa um parâmetro:count . Multiplicamos pela duração de um período de clock para obter a duração de N ciclos de clock. Por fim, usamos o ModelSim run comando para simular exatamente por tanto tempo.

Verificando um valor de sinal de Tcl


No ModelSim, podemos ler um sinal VHDL de Tcl usando o examinar comando. O código abaixo mostra o procedimento Tcl que estamos usando para ler um valor de sinal e verificar se está conforme o esperado. Se o sinal não corresponder ao expectedVal parâmetro, imprimimos uma mensagem desagradável e incrementamos o errorCount variável.
proc checkSignal { signalName expectedVal } {
  variable errorCount

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

Testando uma sequência de PIN


A saída do módulo de bloqueio de código depende não apenas das entradas atuais, mas também de seus valores anteriores. Portanto, a verificação das saídas deve ocorrer pelo menos após o envio de quatro dígitos para o DUT. Só então o sinal de desbloqueio deve mudar de '0' para '1' se o PIN estiver correto.

O procedimento Tcl abaixo usa o ModelSim force palavra-chave para alterar os sinais VHDL de Tcl. O -depósito mude para a força palavra-chave significa que ModelSim mudará o valor, mas deixará outro driver VHDL assumir o controle dele mais tarde, embora nenhuma outra entidade esteja controlando as entradas DUT em nosso testbench.
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
  }
}

O tryPin procedimento usa nosso printMsg procedimento para informar sobre o que está fazendo, qual código PIN está digitando e se é a senha correta. Ele também usa o runClockCycles procedimento seja executado por exatamente um período de clock, enquanto manipula as entradas do DUT para simular um usuário digitando um PIN.

Finalmente, ele usa o checkSignal procedimento para verificar se o DUT está se comportando conforme o esperado. Como já expliquei, o checkSignal procedimento imprimirá uma mensagem de erro e incrementará o errorCount variável se o desbloquear sinal não corresponde ao valor esperado.

Casos de teste e status de conclusão


No código Tcl acima, iniciamos a simulação e definimos várias variáveis ​​e procedimentos, mas não simulamos nenhum momento. A simulação ainda está em 0 ns. Nenhum tempo de simulação passou.

No final do nosso namespace personalizado, começamos a chamar os procedimentos Tcl. Conforme mostrado no código abaixo, começamos executando por dez ciclos de clock. Depois disso, liberamos o reset e verificamos se o desbloqueio saída tem o 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)"
}

Poderíamos tentar todos os 10.000 códigos PIN diferentes, mas isso levaria um tempo considerável. A simulação orientada por Tcl é muito mais lenta do que um testbench VHDL puro. O simulador tem que começar e parar muito, e isso leva muito tempo. Portanto, decidi apenas verificar os casos de canto.

Chamamos tryPin quatro vezes, com os códigos PIN:0000, 9999, o PIN correto e os números do PIN correto na ordem inversa. Imagino que seja um erro fácil de se cometer ao criar um código de bloqueio, olhar apenas a combinação, e não a ordenação dos números.

Finalmente, no final do código Tcl, mas ainda dentro do namespace, verificamos o errorCount variável e imprima um “Test:OK” ou um “Test Failure”.

Executando o testbench


E agora vem a parte divertida:rodar o testbench. Eu prefiro usar o comando Tcl source, como mostrado abaixo, mas você também pode usar o do específico do ModelSim comando. Na verdade, os arquivos DO ModelSim são apenas arquivos Tcl com um sufixo diferente.
source code_lock/code_lock_tb.tcl

Na versão final do meu código, não há erros. A lista abaixo mostra a saída de uma simulação bem-sucedida. O script Tcl nos informa sobre o que está fazendo e podemos ver que todas as linhas de mensagem têm um carimbo de data/hora. Esse é o nosso printMsg procedimento no trabalho. Finalmente, o testbench para 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

No entanto, quero mostrar a você como é quando o DUT falha em um teste. Para fazer isso, criei um erro no módulo de bloqueio de código. Substituí a verificação de pin1 com pin2 para que o DUT ignore o pin1 valor. É um erro de digitação fácil de fazer, conforme mostrado no código abaixo.
unlock <= '1' when pins = (pin3, pin2, pin2, pin0) else '0';

Quando agora executamos o testbench, você pode ver na lista abaixo que a falha foi detectada. E, finalmente, o testbench imprime “Test:Failure” junto com o número 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


Eu criei muitos testbenches baseados em Tcl em minha carreira, mas minha opinião sobre eles é um pouco dividida.

Por um lado, você pode fazer algumas coisas legais que não são possíveis apenas com o VHDL. Por exemplo, o testbench interativo. Também é bom que você possa alterar o testbench sem precisar recompilar. E, finalmente, a verificação usando uma linguagem muito diferente pode ser vantajosa. Você teria que cometer o mesmo erro em duas tecnologias diferentes para que ele passasse despercebido, e isso é improvável.

Por outro lado, também existem algumas desvantagens. Os testbenches baseados em Tcl são muito mais lentos do que seus equivalentes em VHDL. Outro problema significativo é o aprisionamento do fornecedor. É impossível criar um testbench Tcl totalmente portátil, enquanto um testbench VHDL pode ser executado em qualquer simulador capaz.

E a razão final pela qual os testbenches Tcl podem não valer a pena é a própria linguagem. Ele não possui ótimos recursos para evitar erros de programação e depurar um problema de Tcl é difícil. Não é uma linguagem intuitiva nem indulgente como Python ou Java.

No entanto, serve como uma linguagem de cola entre o VHDL e o mundo do software. E porque a maioria das ferramentas FPGA, não apenas simuladores, suportam Tcl, eu recomendo aprender.

Esses pensamentos são apenas minhas opiniões. Diga-me o que você pensa na seção de comentários!

VHDL

  1. Como criar uma lista de strings em VHDL
  2. Como parar a simulação em um testbench VHDL
  3. Como criar um controlador PWM em VHDL
  4. Como criar um buffer de anel FIFO em VHDL
  5. Testbench interativo usando Tcl
  6. Como criar um testbench de autoverificação
  7. Como criar uma lista vinculada em VHDL
  8. Como usar uma função impura em VHDL
  9. Como usar uma função em VHDL
  10. Como instalar um simulador e editor VHDL gratuitamente