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:
- Intel ModelSim
- Consulte este artigo para saber como instalar o ModelSim gratuitamente.
- ModelSim deve estar em seu PATH.
- Python 3.6 ou superior.
- Baixar Python
- O Python deve estar em seu PATH.
- GIT (opcional).
- Baixar GIT
- Terminal Windows (opcional)
- Baixe o terminal do Windows
Também pressupõe ter conhecimentos básicos de VHDL e habilidades em ModelSim.
Instalando
- Obtendo VUnit:
- Se você tiver GIT, poderá cloná-lo do GitHub para sua unidade C:
git clone --recurse-submodules https://github.com/VUnit/vunit.git
- Caso contrário, você pode baixá-lo como um Zip do GitHub e extraí-lo para sua unidade C:
- Faça o download do VUnit.
- Caso contrário, você pode baixá-lo como um Zip do GitHub e extraí-lo para sua unidade C:
- Instalando o VUnit:
- Abra um terminal e navegue até
C:\vunit
e digite o seguinte comando:
- Abra um terminal e navegue até
python setup.py install
- Configurando VUnit:
- Adicione variáveis de ambiente ao seu sistema conforme abaixo.
VUNIT_MODELSIM_PATH
:caminho para ModelSim em sua máquina.VUNIT_SIMULATOR
:ModelSim
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:
- Biblioteca Python: fornece ferramentas que ajudam na automação, administração e configuração de testes.
- Biblioteca VHDL: um conjunto de bibliotecas que ajuda nas tarefas comuns de verificação.
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:
- Carregando configuração
- Calibração
- Rotação
Sequência de inicialização
Aqui segue as sequências de partida do motor e o significado dos indicadores LED.
- RED_LED representa carregar configuração .
- Ative o switch_1.
- O RED_LED começará a piscar 5 vezes.
- Aguarde até que o RED_LED pare de piscar e acenda constantemente.
- YELLOW_LED representa carregando calibração .
- Agora você pode ativar o switch_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.
- GREEN_LED indica que o motor está girando.
- 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.
- 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:
- Configurando o script de execução do Python
- Configurando o esqueleto VUnit
- 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. motor_start
emotor_pkg
será compilado emDESIGN_LIB
.motor_start_tb
emotor_tb_pkg
será compilado emTB_LIB
.
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:
- Driver e verificador no caso de teste.
- Driver e verificador controlado no caso de teste.
- Driver no caso de teste e verificador de autoverificaçã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:
- Verifique a saída depois de ligar a chave_2 enquanto a chave_1 estiver ligada e o RED_LED estiver aceso.
Da seção DUT, sabemos que:
- Quando o switch_2 está LIGADO, YELLOW_LED começará a acender constantemente após 5 ciclos de clock por 10 ciclos de clock e, depois disso, YELLOW_LED e RED_LED serão desligados.
Usaremos o
check_stable
do VUnit procedimento para verificar se:- YELLOW_LED é '0' desde o início até que o switch_2 esteja LIGADO.
- YELLOW_LED é '1' por 10 ciclos de clock.
Usaremos o
check_next
do VUnit procedimento para verificar se:- YELLOW_LED é '1' após 5 ciclos de clock desde a ativação do switch_2.
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:allow_overlapping
:permite um segundostart_event
antes do expr para o primeiro é '1'.allow_missing_start
:permite que expr seja '1' sem umstart_event
.
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.
- Valores naturais (>=0) significa:ative a chave após (
clk_period
*sw_delay
). - O valor -1 significa:desligue o interruptor.
- Todos os outros valores negativos significam:não faça nada.
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:
- Aguarde a ativação do switch_1 e adicione um
start_event
para todos os LEDs baixos:
red_monitor_process : PROCESS BEGIN WAIT UNTIL reset = '0'; pulse_high(clk, led_low_start_event); WAIT UNTIL motor_start_in.switch_1 = '1';
- Usamos o mesmo FOR LOOP do primeiro caso de teste para RED_LED piscando e adicionamos um
start_event
para RED_LED alto:
-- 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;
- Agora esperamos que o switch_2 seja ativado e adicionamos um
end_event
para RED_LED alto:
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;
- Agora adicionamos o
check_stable
procedimentos para RED_LED alto e baixo:
-- 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
- Documentação da VUnit
- VUnitBlog
- Vunit gitter
Baixe o projeto de exemplo usando o formulário abaixo!
VHDL
- Contêineres prontos para código:Introdução às ferramentas de automação de processos na nuvem
- Introdução à impressão 3D em cerâmica
- Familiarizando-se com os corantes básicos!
- Introdução ao TJBot
- Introdução ao RAK 831 Lora Gateway e RPi3
- Primeiros passos com o RAK831 LoRa Gateway e RPi3
- Começar a trabalhar com IoT
- Introdução à IA em seguros:um guia introdutório
- Arduino Tutorial 01:Começando
- Introdução ao My.Cat.com