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

Introdução ao VUnit


VUnit é uma das estruturas de verificação VHDL de código aberto mais populares disponíveis atualmente. Ele combina um executor de suíte de testes Python com uma biblioteca VHDL dedicada para automatizar seus testbenches.



Para lhe dar este tutorial VUnit gratuito, VHDLwhiz recruta Ahmadmunthar Zaklouta, que está por trás do restante deste artigo, incluindo o projeto de exemplo VUnit simples que você pode baixar e executar em seu computador.

Vamos falar com Ahmad!

Este tutorial tem como objetivo demonstrar o uso do framework VUnit no processo de verificação do seu projeto. Ele o guiará pelo processo de configuração do VUnit, criando o testbench VUnit, usando a biblioteca de verificação VUnit e executando testes VUnit no ModelSim. Ele também demonstra algumas técnicas de verificação.

Visão geral


Este artigo contém várias capturas de tela. Clique nas imagens para aumentá-las!

Use a barra lateral para navegar pelo contorno para este tutorial ou role para baixo e clique no botão de navegação pop-up no canto superior direito se estiver usando um dispositivo móvel.

Requisitos


Este tutorial pressupõe que este software esteja instalado em uma máquina Windows:

Também pressupõe ter conhecimentos básicos de VHDL e habilidades em ModelSim.

Instalando

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


Faça o download do projeto de exemplo


Você pode baixar o projeto de exemplo e o código VHDL usando o formulário abaixo.

Extraia o Zip para C:\vunit_tutorial .

Introdução


VUnit é uma estrutura de teste para HDL que facilita o processo de verificação, fornecendo um fluxo de trabalho orientado a testes “teste cedo e com frequência” e uma caixa de ferramentas para automação e administração de execução de testes. É uma estrutura avançada com recursos ricos e extensos, mas fácil de usar e adaptar. É totalmente de código aberto e pode ser facilmente incorporado em metodologias de teste tradicionais.

O VUnit consiste em dois componentes principais:

A parte VHDL consiste em seis bibliotecas, conforme mostrado no diagrama abaixo. Este tutorial usará as bibliotecas de registro e verificação.


Design em teste


O design usado neste tutorial, chamado motor_start , implementa um procedimento de partida para um motor específico e aciona 3 LEDs que representam o status do motor.

A interface consiste em um registro de entrada motor_start_in com 3 elementos (3 interruptores como entradas) e um registro de saída motor_start_out com 3 elementos (3 LEDs VERMELHO, AMARELO, VERDE como saídas).



Alguns motores precisam ser inicializados no início antes que você possa começar a usá-los. Nosso procedimento de partida do motor tem três etapas:
  1. Carregando configuração
  2. Calibração
  3. Rotação

Sequência de inicialização


Aqui segue as sequências de partida do motor e o significado dos indicadores LED.
  1. Ative o switch_1.
  2. O RED_LED começará a piscar 5 vezes.
  3. Aguarde até que o RED_LED pare de piscar e acenda constantemente.
  1. Agora você pode ativar o switch_2.
  2. Quando o switch_2 está LIGADO, o YELLOW_LED começará a acender constantemente após 5 ciclos de clock, com duração de 10 ciclos de clock. E depois disso, YELLOW_LED e RED_LED serão DESLIGADOS.
  1. Agora, o motor está pronto para uso. Cada vez que o switch_3 é LIGADO, o GREEN_LED começará a acender constantemente até que o switch_3 seja DESLIGADO.
  2. Qualquer violação a esta sequência resultará em todos os 3 LEDs acesos constantemente até que todos os interruptores sejam desligados e a sequência possa ser iniciada novamente.

Desenvolvimento do Testbench


Esta parte do tutorial consiste nas seguintes subseções:
  1. Configurando o script de execução do Python
  2. Configurando o esqueleto VUnit
  3. Configurando o testbench

Configurando o script de execução do Python run.py


Cada projeto VUnit precisa de um script python de nível superior run.py que atua como um ponto de entrada para o projeto e especifica todo o design VHDL, testbench e fontes de biblioteca.

Este arquivo geralmente existe no diretório do projeto. A árvore de diretórios usada neste tutorial é a seguinte:



No run.py arquivo, precisamos fazer três coisas:

1 – Busque o caminho onde este arquivo existe e especifique o caminho para os arquivos de design e testbench.
# ROOT
ROOT = Path(__file__).resolve().parent
# Sources path for DUT
DUT_PATH = ROOT / "design"
# Sources path for TB
TEST_PATH = ROOT / "testbench"

2 – Crie a instância VUnit.
Isso criará uma instância VUnit e a atribuirá a uma variável chamada VU . Então podemos usar VU para criar bibliotecas e várias tarefas.
# create VUnit instance
VU = VUnit.from_argv()
VU.enable_location_preprocessing()

3 – Crie bibliotecas e adicione fontes VHDL a elas.
Eu gosto de separar a parte do design da parte do testbench. Portanto, criaremos duas bibliotecas:design_lib e 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"])

O resto do arquivo é uma configuração para ModelSim usar o wave.do arquivo se ele existir.

Observação: aqui, eu uso o *.vhdl extensão. Você pode precisar modificá-lo se usar *.vhd .

Se você gosta dessa estrutura de trabalho, não precisa alterar esse arquivo. Sempre que você iniciar um novo projeto, basta copiá-lo para o diretório do projeto. No entanto, se você preferir uma estrutura de diretório diferente, precisará modificar os caminhos para ficar em conformidade com sua estrutura de trabalho.

Agora, sempre que usarmos esse arquivo, o VUnit verificará automaticamente os testbenches do VUnit em seu projeto, determinará a ordem de compilação, criará as bibliotecas e compilará as fontes nelas e, opcionalmente, executará o simulador com todos ou casos de teste específicos.

Isso não é incrível? 😀

Configurando o esqueleto VUnit


Para criar um testbench VUnit, precisamos adicionar algum código específico ao nosso arquivo testbench motor_start_tb , conforme explicado nesta subseção.

1 – Adicione as bibliotecas da seguinte forma.

Primeiro, precisamos adicionar a biblioteca VUnit VUNIT_LIB e seu contexto:VUNIT_CONTEXT , para que tenhamos acesso à funcionalidade VUnit da seguinte forma:
LIBRARY VUNIT_LIB;
CONTEXT VUNIT_LIB.VUNIT_CONTEXT;

Segundo, precisamos adicionar as bibliotecas de design e testbench DESIGN_LIB e TB_LIB para que tenhamos acesso ao nosso DUT e pacotes da seguinte forma:
LIBRARY DESIGN_LIB;
USE DESIGN_LIB.MOTOR_PKG.ALL;

LIBRARY TB_LIB;
USE TB_LIB.MOTOR_TB_PKG.ALL;

O DUT tem dois pacotes; um para design:motor_pkg e o outro para elementos testbench motor_tb_pkg . São pacotes triviais que criei porque geralmente é assim que grandes projetos são estruturados. Eu quero mostrar como o VUnit lida com isso.

2 – Adicione a configuração do executor à entidade da seguinte forma:
ENTITY motor_start_tb IS

  GENERIC(runner_cfg : string := runner_cfg_default);

END ENTITY motor_start_tb;

runner_cfg é uma constante genérica que permite que o executor de testes python controle o testbench. Ou seja, podemos executar testes a partir do ambiente python. Este genérico é obrigatório e não muda.

3 – Adicione o esqueleto do testbench VUnit ao nosso testbench da seguinte forma:
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 é o principal processo de controle para o testbench. Sempre começa com o procedimento test_runner_setup e finaliza com o procedimento test_runner_cleanup . A simulação vive entre esses dois procedimentos. test_cases_loop são nossos conjuntos de teste onde todos os nossos casos de teste ocorrem.

Para criar um caso de teste, usamos o run do VUnit função dentro de uma instrução If da seguinte forma:
IF run("test_case_name") THEN
  -- test case code here

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

END IF;

Então podemos executar todos ou casos de teste específicos do ambiente Python simplesmente chamando-os com o nome que especificamos na chamada para run .

Configurando o testbench


Aqui, começamos adicionando os sinais necessários para se comunicar com o DUT, conforme mostrado abaixo:
--------------------------------------------------------------------------
-- 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');

Observação: É uma boa prática inicializar os sinais com um valor inicial.

Em seguida, instancie o DUT assim:
--------------------------------------------------------------------------
-- 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
  ); 

Observação: Agrupei as portas de entrada e as portas de saída em registros. Acho isso benéfico em grandes projetos porque torna as entidades e instanciações menos confusas.

E, finalmente, dirija clk , reset e led_out como mostrado aqui:
--------------------------------------------------------------------------
-- 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);

Desenvolvimento de casos de teste


Agora vamos voltar ao nosso DUT e começar o trabalho real desenvolvendo alguns casos de teste. Vou apresentar dois cenários:

Cenário do engenheiro de projeto: do ponto de vista do designer, o próprio designer realizando a verificação. Nesse cenário, que geralmente acontece durante a fase de desenvolvimento, o designer pode acessar o código. Este cenário mostrará como o VUnit nos ajuda a “testar cedo e com frequência”.

Cenário do engenheiro de verificação :o desenho (DUT) é tratado como uma caixa preta. Conhecemos apenas a interface externa e os requisitos de teste.

Também veremos essas três técnicas de verificação:

Vamos começar com a primeira técnica e voltar aos dois últimos métodos mais adiante neste artigo.

Driver e verificador no caso de teste


Esta é a abordagem mais direta. O driver e o verificador estão dentro do próprio caso de teste, implementamos as operações de condução e verificação dentro do código do caso de teste.

Vamos supor que desenvolvemos a funcionalidade RED_LED conforme abaixo:
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;

E agora, queremos verificar nosso design antes de continuarmos a desenvolver o restante da funcionalidade.

Para fazer isso, usamos o run do VUnit função dentro do test_suite para criar um caso de teste para verificar a saída de ligar o switch_1, conforme mostrado abaixo:
IF run("switch_1_on_output_check") THEN
  info("------------------------------------------------------------------");
  info("TEST CASE: switches_off_output_check");
  info("------------------------------------------------------------------");
  -- code for your test case here.

Isso criará um caso de teste chamado “switch_1_on_output_check”

Observação: info é um procedimento VUnit da biblioteca de log que imprime na tela de transcrições e no terminal. Vamos usá-lo para exibir o resultado do teste.

Agora, vamos escrever o código para este caso de teste. Usaremos os subprogramas de verificação do VUnit para fazer isso.

Sabemos que o RED_LED piscará 5 vezes depois que o switch_1 for ligado, então criamos um loop VHDL For e realizamos a verificação dentro dele.

O check procedimento realiza uma verificação nos parâmetros específicos que fornecemos. Tem muitas variantes, e aqui, usei várias delas para fins de demonstração.
check(expr => motor_start_out.red_led = '1', 
      msg  => "Expect red_led to be ON '1'");

Aqui, ele testará se RED_LED é '1' neste ponto do tempo de simulação e imprimirá uma mensagem no console:
# 35001 ps - check - PASS - red_led when switch_1 on (motor_start_tb.vhdl:192)

Observação que ele nos diz se é um PASS ou um ERROR, e o timestamp quando esta verificação aconteceu, junto com o nome do arquivo e o número da linha onde esta verificação está.

Outra maneira é usar o check_false procedimento. Aqui, usamos para verificar se YELLOW_LED é '0':
check_false(expr => ??motor_start_out.yellow_led, 
            msg  => result("for yellow_led when switch_1 on"));

Aqui, usamos o result do VUnit função para melhorar a mensagem. A impressão ficará assim:
# 35001 ps - check - PASS - False check passed for yellow_led when switch_1 on 
#                           (motor_start_tb.vhdl:193)

Observação que imprima informações extras sobre o tipo de cheque:“Falso cheque aprovado”.

Outra maneira é usar check_equal . Aqui, usamos para testar se GREEN_LED é '0':
check_equal(got      => motor_start_out.green_led, 
            expected => '0',
            msg      => result("for green_led when switch_1 on"));

Aqui, fornecemos um parâmetro extra '0' para a comparação. A impressão resultante é:
# 35001 ps - check - PASS - Equality check passed for green_led when switch_1 on - 
#                           Got 0. (motor_start_tb.vhdl:194)

Agora, sabemos que após um ciclo de clock, o RED_LED irá desligar, e os outros LEDs ficarão desligados também. Podemos usar check_equal para verificar todos eles simultaneamente, conforme mostrado abaixo:
check_equal(led_out, STD_LOGIC_VECTOR'("000"), 
            result("for led_out when switch_1 on"), warning);

Observação o uso do qualificador STD_LOGIC_VECTOR'("000") , portanto, os valores não são ambíguos para o procedimento. Além disso, especificamos o nível dessa verificação como AVISO, o que significa que, se essa verificação falhar, ela emitirá um aviso em vez de gerar um erro. A saída ficará assim:
#  45001 ps - check - PASS - Equality check passed for led_out when switch_1 on - 
#                            Got 000 (0). (motor_start_tb.vhdl:197)

Este é o código para o caso de teste 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 teste em execução


Agora é hora de executar nosso caso de teste. Podemos executar os testes no terminal ou na GUI do simulador.

Executando um teste no terminal


Para fazer isso, comece abrindo um novo terminal (CMD, PowerShell, Windows Terminal) e navegue até o vunit_tutorial diretório onde o run.py arquivo está localizado.

Para executar o caso de teste, basta digitar:
python .\run.py *switch_1_on_output_check

VUnit irá compilar todos os arquivos VHDL na ordem correta e analisá-los, procurando por testbenches VUnit. E então, o VUnit examinará esses arquivos, procurando por um run função com o nome do caso de teste “switch_1_on_output_check” para executá-lo.

Observação: colocamos o símbolo curinga * antes do caso de teste para corresponder ao nome completo, que é:
tb_lib.motor_start_tb.switch_1_on_output_check

Podemos fazer isso porque a interface de linha de comando VUnit aceita curingas.

A impressão resultante após a simulação é:
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 um teste foi executado e foi APROVADO.

Observação que o VUnit criou um vunit_out pasta no diretório do projeto. Dentro desta pasta, há uma pasta chamada test_output que tem relatórios sobre os testes.

Acima, obtivemos apenas o resultado final do teste sem detalhes sobre cada verificação, mas a ferramenta de linha de comando VUnit fornece várias opções para execução de testes. Para obter mais informações sobre o que está acontecendo durante a simulação, podemos usar a opção detalhada -v :
python .\run.py *switch_1_on_output_check -v

A impressão detalhada ficará assim:



Outros interruptores úteis são:

-l, --list :Lista todos os casos de teste.

--clean :Exclua a pasta de saída primeiro e depois execute o teste.

--compile :esta opção é útil se você deseja compilar sem executar testes para verificar erros, por exemplo.

Executando um teste na GUI do simulador


Muitas vezes é necessária uma inspeção visual da onda. O VUnit fornece uma maneira automatizada de executar testes no simulador usando a opção GUI -g . O VUnit fará toda a compilação e lançará o ModelSim (o simulador que configuramos) com o caso de teste solicitado e adicionará os sinais à janela de onda, dado que um wave.do arquivo está disponível.
python .\run.py *switch_1_on_output_check -g

Agora, o VUnit iniciará o ModelSim para este caso de teste específico, abrirá a janela de forma de onda e adicionará os sinais porque eu já tenho o motor_start_tb_wave.do dentro das ondas pasta.



Observação: Você pode personalizar a forma de onda como quiser, mas deve salvar o arquivo de formato de onda dentro das ondas pasta com esta convenção de nome testbench_file_name_wave.do para que possa ser carregado quando o VUnit iniciar o ModelSim.

Suponha que você altere seu código após descobrir erros e queira executar novamente este caso de teste. Nesse caso, você pode digitar na janela de transcrição do ModelSim:vunit_restart , Isso fará com que o VUnit recompile, reinicie e execute novamente a simulação.

Driver e verificador controlado dentro do caso de teste


Até agora, aprendemos como configurar um testbench VUnit e desenvolver e executar o caso de teste. Nesta seção, desenvolveremos mais casos de teste do ponto de vista do engenheiro de verificação, usando uma abordagem de verificação diferente e uma biblioteca de verificador VUnit.

Ao contrário do caso de teste anterior, esta abordagem tem o driver dentro do caso de teste e o verificador fora, mas o caso de teste ainda o controla.

Vamos supor que temos este requisito de verificação:

Da seção DUT, sabemos que:

Usaremos o check_stable do VUnit procedimento para verificar se:

Usaremos o check_next do VUnit procedimento para verificar se:

check_stable :verifique se os sinais estão estáveis ​​dentro de uma janela que começa com um start_event pulso de sinal e termina com um end_event pulso de sinal.

check_next :verifique se o sinal ='1' após um número de ciclos de clock após um start_event pulso de sinal.

start_event e end_event sinais serão controlados a partir do caso de teste.

Começamos adicionando os sinais necessários para check_stable e check_next procedimentos da seguinte forma:
-- 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';

Em seguida, criamos um novo caso de teste dentro do test_suite usando o run do VUnit funcionar da seguinte forma:
ELSIF run("switch_2_on_output_check") THEN
  info("------------------------------------------------------------------");
  info("TEST CASE: switch_2_on_output_check");
  info("------------------------------------------------------------------");

Criamos um start_event para o estado baixo de YELLOW_LED para uso com o check_stable procedimento da seguinte forma:
WAIT UNTIL reset <= '0';
-- out of reset
enable <= '1';
pulse_high(clk, yellow_low_start_event);
WAIT FOR C_CLK_PERIOD * 3;

O enable sinal ativa o check_stable e check_next procedimentos, e queremos habilitá-los desde o início.

pulse_high é um procedimento trivial de motor_tb_pkg que espera pela próxima borda de clock ascendente e pulsa um sinal por um ciclo de clock. Neste caso, é yellow_ low_start_event .

Agora, ligamos o switch_1 e esperamos até que o RED_LED acenda constantemente e, em seguida, ligamos o 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';

Agora sabemos que após 5 ciclos de clock, YELLOW_LED será '1'. Portanto, criamos um start_event para YELLOW_LED usar com o check_next procedimento:
-- 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

Da mesma forma, criamos um end_event para o estado baixo de YELLOW_LED usar com o check_stable procedimento:
WAIT FOR C_CLK_PERIOD * 3;

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

Agora, YELLOW_LED será alto por 10 ciclos de clock. Portanto, criamos um start_event para o estado alto de YELLOW_LED para o check_stable procedimento:
-- 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';

Aqui eu não usei o pulse_high procedimento porque eu quero que o pulso ocorra agora, e não na próxima borda descendente.

E criamos um end_event para o estado alto de YELLOW_LED para check_stable após 8 ciclos de clock da seguinte forma:
WAIT FOR C_CLK_PERIOD * 8;
-- end event for YELLOW_LED
pulse_high(clk, yellow_high_end_event); -- 10th clk cycle

Com isso, o caso de teste está finalizado. Só precisamos adicionar as chamadas aos procedimentos do verificador após o processo assim:
----------------------------------------------------------------------
-- 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"));

Observação: o allow_restart parâmetro no check_stable procedimento permite que uma nova janela seja iniciada se um novo start_event acontece antes do end_event .

Observação: colocamos 5 em check_next procedimento porque YELLOW_LED estará alto após 5 ciclos de clock de yellow_next_start_event .

Observação: os dois últimos parâmetros em check_next são:

Agora, podemos executar o caso de teste a partir da linha de comando assim:
python .\run.py *switch_2_on_output_check -v

E o resultado será o seguinte:



Podemos executar o caso de teste na GUI do simulador assim:
python .\run.py *switch_2_on_output_check -g

Resultando nesta forma de onda:



Observação:start_event e end_event sinais para check_stable são inclusivos na janela, e os sinais monitorados são referenciados quando start_event='1' .

Neste caso de teste, tiramos as operações de verificação do caso de teste, mas as controlamos a partir do caso de teste. Além disso, observe que não verificamos a funcionalidade RED_LED.

Driver dentro do caso de teste e verificador de autoverificação


Uma desvantagem da abordagem anterior é que ela é menos legível. O caso de teste contém informações que não são interessantes ou relacionadas ao teste nem à funcionalidade, como o start_event e end_event sinais. Queremos ocultar todos esses detalhes, ter apenas o driver no caso de teste e fazer um verificador de autoverificação.

Vamos começar projetando o driver.

Os procedimentos de VHDL são um bom candidato para isso. Se você não sabe como usar um procedimento VHDL, confira este tutorial.

Abaixo está o procedimento 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;

Fornecemos o período de clock para calcular os atrasos e um número inteiro que especifica o atraso desejado para cada switch em períodos de clock.

Agora podemos usar este procedimento dentro do caso de teste da seguinte forma:
switch_driver(motor_start_in,C_CLK_PERIOD,3,12,20);

Isso ligará o switch_1 após 3 ciclos de clock e, em seguida, ligará o switch_2 após 12 ciclos de clock e, finalmente, ligará o switch_3 após 20 ciclos de clock.

Até agora, usamos o verificador padrão do VUnit. O VUnit oferece a possibilidade de ter um verificador personalizado. Isso é útil quando você tem um grande caso de teste com muitas verificações e deseja ter estatísticas sobre a verificação ou não deseja misturar a verificação com o restante do testbench.

Criaremos um verificador personalizado para RED_LED e definiremos o nível de log com falha como AVISO:
CONSTANT RED_CHECKER : checker_t := new_checker("red_led_checker", WARNING);

E habilitamos as verificações de aprovação de log para RED_CHECKER na seção Setup VUnit:
show(get_logger(RED_CHECKER), display_handler, pass);

Agora vamos passar para o verificador de autoverificação (ou monitor). Vamos projetar um verificador de autoverificação para RED_LED primeiro e usaremos um processo para isso da seguinte forma:
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"));

Vamos testar isso com o seguinte caso de teste:
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 =====");

Rodamos a simulação assim:
python .\run.py *red_led* -v

O resultado será:



Observação que o verificador agora é red_led_checker .

Podemos seguir a mesma abordagem para projetar um verificador de autoverificação para YELLOW_LED, mas deixarei isso como um exercício para o leitor. No entanto, mostrarei as próximas maneiras diferentes de verificar a funcionalidade GREEN_LED usando check , check_stable , check_next e check_equal procedimentos.

Para verificar se GREEN_LED está DESLIGADO desde o início até a primeira vez que o interruptor_3 é LIGADO, basta usar o check_stable procedimento:
-- 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"));

Uma maneira de verificar se o GREEN_LED está LIGADO quando o switch_3 está LIGADO é usando o check_next procedimento. Nós o chamamos com switch_3 como o start_event , atribua 1 ciclo de clock para num_cks , e permitir a sobreposição:
-- 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);

Como permitimos a sobreposição, este procedimento será acionado em cada borda de subida do relógio quando switch_3 for '1'. Espera-se que GREEN_LED seja '1' após um ciclo de clock, e é isso que queremos.

Outra maneira de testar a funcionalidade do GREEN_LED é usar a versão com clock do check procedimento com uma versão atrasada do switch_3 como Enable para este procedimento:
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 é um sinal atrasado de 1 ciclo de clock do switch_3. Assim, ele habilitará este procedimento sempre 1 ciclo de clock após o switch_3 ser ‘1’, e o procedimento verificará se GREEN_LED é ‘1’ quando estiver habilitado.

O switch_3_delayed sinal é uma versão atrasada switch_3 por um ciclo de clock. Assim, ele habilitará este procedimento sempre um ciclo de clock após switch_3 ser '1'. Quando habilitado, o procedimento verifica se GREEN_LED é ‘1’.

Observação: o AND NOT habilitado em switch_3_delayed é apenas mascarar esse sinal quando não estamos usando a abordagem de processo.

Finalmente, podemos usar um processo dedicado com um loop VHDL While para realizar a verificação 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;

Agora desenvolvemos um caso de teste para GREEN_LED da seguinte forma:
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 =====");

Depois de escrever o caso de teste, você pode executá-lo e verificar os diferentes resultados.

Podemos ver que os casos de teste na abordagem de autoverificação são muito mais legíveis, mesmo para engenheiros sem conhecimento em VHDL.

Existem outros casos de teste no arquivo testbench. Podemos iniciá-los usando este comando:
python .\run.py *motor_start_tb* --list

E esta é a saída impressa no console:
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 executar todos os casos de teste digitando:
python .\run.py

E o resultado é o seguinte:
==== 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!

Resumo


A estrutura VUnit fornece recursos avançados para automação de execução de testes e bibliotecas avançadas para desenvolvimento de testbenches. Além disso, permite que os engenheiros de projeto testem seu projeto com antecedência e com frequência.

O VUnit também ajuda os engenheiros de verificação a desenvolver e executar testbenches de maneira mais rápida e fácil. Mais importante ainda, tem uma curva de aprendizado rápida e leve.

Para onde ir a partir daqui


Baixe o projeto de exemplo usando o formulário abaixo!

VHDL

  1. Contêineres prontos para código:Introdução às ferramentas de automação de processos na nuvem
  2. Introdução à impressão 3D em cerâmica
  3. Familiarizando-se com os corantes básicos!
  4. Introdução ao TJBot
  5. Introdução ao RAK 831 Lora Gateway e RPi3
  6. Primeiros passos com o RAK831 LoRa Gateway e RPi3
  7. Começar a trabalhar com IoT
  8. Introdução à IA em seguros:um guia introdutório
  9. Arduino Tutorial 01:Começando
  10. Introdução ao My.Cat.com