Pular para conteúdo

Simulações e Test Benches

Antes de implementar um circuito em FPGA ou ASIC, é fundamental testar seu funcionamento em simulação. Em Verilog, isso é feito usando um test bench, que nada mais é do que um código adicional não sintetizável, escrito para verificar o módulo que você deseja testar.

Considere o seguinte código para um somador de 4 bits:

1
2
3
4
5
module adder(
  input [3:0] a, b,
  output [4:0] sum);
  assign sum = a + b;
endmodule

Este código resulta em um somador simples de 4 bits, que pode ser sintetizado em hardware. Agora, vamos construir um test bench simples para testá-lo. O código do teste serve para simular o comportamento do somador, aplicando diferentes valores de entrada e observando a saída.

  • O design, também chamado de Design Under Test (DUT) ou Unit Under Test (UUT) é o circuito que você escreveu, ele será instanciado dentro do test bench;
  • O test bench gera estímulos (sinais de entrada, como clock, reset, dados) e observa as respostas do DUT;
  • Dessa forma, você pode verificar se o circuito se comporta corretamente antes de gastar tempo e recursos na síntese e na gravação em hardware.

Exemplo simples de test bench para um somador:

module tb_adder;
  reg [3:0] a, b;
  wire [4:0] sum;

  adder uut (.a(a), .b(b), .sum(sum));

  initial begin
    a = 4'd3; b = 4'd2;
    #10 a = 4'd7; b = 4'd8;
    #10 $finish;
  end
endmodule

Aqui o uut (unit under test) recebe valores, e a simulação permite observar se sum tem o resultado esperado.

DigitalJS

Um simulador simples, mas interessante para quem está começando é o DigitalJS. Nele você pode interagir gráficamente com o circuito gerado sem a necessidade de criar um test bench.

Icarus Verilog (iverilog)

O Icarus Verilog é um simulador Verilog de código aberto. Ele ainda não cobre todas as funcionalidades da linguagem, mas é bastante usado por sua simplicidade e facilidade de instalação.

Usando nosso exemplo do somador acima, podemos executar os seguintes comandos para realizar sua simulação e observar sua saída:

 % iverilog *.v && ./a.out
tb_adder.v:10: $finish called at 20 (1s)

Ele é capaz de identificar automaticamente a hierarquia entre a implementação e o test bench, mas apenas indica que a simulação terminou quando encontrou a função $finish. No entanto, ele não nos dá nenhuma informação útil da simulação, então vamos modificar o test bench para incluir uma chamadas às funções $display e $monitor. A primeira imprime uma única vez, então usamos para fazer um cabeçalho. A segunda, monitora os sinais e imprime sempre que um deles é modificado.

module tb_adder;
  reg [3:0] a, b;
  wire [4:0] sum;

  adder uut (.a(a), .b(b), .sum(sum));

  initial begin
    $display("Time\t a\t b\t sum");
    $monitor("%3t\t%d\t%d\t%3d", $time, a, b, sum);
    a = 4'd3; b = 4'd2;
    #10 a = 4'd7; b = 4'd8;
    #10 $finish;
  end
endmodule

A saída agora nos mostra os valores dos sinais ao longo da simulação:

1
2
3
4
5
 % iverilog *.v && ./a.out
Time     a       b       sum
  0      3       2        5
 10      7       8       15
tb_adder.v:12: $finish called at 20 (1s)

Embora a saída na console possa ser útil em alguns casos, podemos gerar também um arquivo do forma de onda (waveform) .VCD para explorar a simulação visualmente. Para isso, vamos incluir a função $dumpvars, que resulta na geração deste arquivo. Ele pode ser aberto usando um leitor como o GTKWave ou extensões do VSCode como WaveTrace ou surfer.

surfer.png

Para conhecer técnicas mais avançadas de simulação e testes, assista a esta sequência de vídeos sobre o assunto.

Verilator

O Verilator é o simulador de Verilog de código aberto mais rápido disponível atualmente. Diferente dos simuladores tradicionais "baseados em eventos" (como o ModelSim ou Icarus Verilog), o Verilator atua como um compilador que converte o hardware em modelos comportamentais de C/C++ altamente otimizados.

No exemplo a seguir, criamos um teste para estimular nosso somador com 10 pares de números aleatórios entre 0 e 15:

#include <stdlib.h>
#include <iostream>
#include "verilated_vcd_c.h"
#include "verilated.h"
#include "Vadder.h"

int main(int argc, char **argv) {
    Vadder *dut = new Vadder;
    Verilated::traceEverOn(true);
    VerilatedVcdC *m_trace = new VerilatedVcdC;
    dut->trace(m_trace, 0);
    m_trace->open("dump.vcd");    
    for (int i = 0; i < 10; i++) {
        dut->a = rand() % 16;
        dut->b = rand() % 16;
        dut->eval();
        m_trace->dump(i);
    }
    m_trace->close();
    delete dut;
    return 0;
}

No exemplo a seguir transformamos o arquivo adder.v em C/C++, o compilamos junto com o teste criado e o executamos para gerar o diagrama de forma de ondas.

1
2
3
4
5
6
7
8
 % verilator -Wall --trace -cc adder.v --exe --build tb_adder.cpp && obj_dir/Vadder 
make: Entering directory '/srv/QuestIO42/tutoriais/code/verilog/adder/obj_dir'
g++  -I.  -MMD -I/usr/local/share/verilator/include -I/usr/local/share/verilator/include/vltstd -DVERILATOR=1 -DVM_COVERAGE=0 -DVM_SC=0 -DVM_TIMING=0 -DVM_TRACE=1 -DVM_TRACE_FST=0 -DVM_TRACE_VCD=1 -DVM_TRACE_SAIF=0 -faligned-new -fcf-protection=none -Wno-bool-operation -Wno-int-in-bool-context -Wno-shadow -Wno-sign-compare -Wno-subobject-linkage -Wno-tautological-compare -Wno-uninitialized -Wno-unused-but-set-parameter -Wno-unused-but-set-variable -Wno-unused-parameter -Wno-unused-variable      -Os  -c -o tb_adder.o ../tb_adder.cpp
g++    tb_adder.o verilated.o verilated_vcd_c.o verilated_threads.o Vadder__ALL.a    -pthread -lpthread -latomic   -o Vadder
make: Leaving directory '/srv/QuestIO42/tutoriais/code/verilog/adder/obj_dir'
- V e r i l a t i o n   R e p o r t: Verilator 5.043 devel rev v5.042-27-geafad9742
- Verilator: Built from 0.000 MB sources in 0 modules, into 0.000 MB in 0 C++ files needing 0.000 MB
- Verilator: Walltime 0.699 s (elab=0.000, cvt=0.000, bld=0.698); cpu 0.001 s on 1 threads; alloced 19.203 MB

Como boa parte do código Verilog escrito nos test benchs não é sintetizável, não faz diferença e pode até ser mais útil escrevê-lo em uma linguagem de programação, dependendo dos objetivos dos testes. Abaixo a simulação com as entradas aleatórias geradas via software.

surfer_rand.png

Cocotb

cocotb (COroutine-based COsimulation TestBench) é uma ferramenta de código aberto usada para verificar designs de hardware (RTL) utilizando Python em vez das tradicionais linguagens de verificação como Verilog ou VHDL. Diferente das abordagens tradicionais onde o testbench roda inteiramente dentro do simulador, o cocotb conecta o interpretador Python ao simulador através de interfaces padrão como VPI (Verilog Procedural Interface), VHPI ou FLI. Isso permite que você escreva a lógica do teste em Python enquanto o simulador processa o código HDL.

O exemplo a seguir foi adaptado do projeto original para testar o nosso já conhecido somador. Primeiro descrevemos um golden model em Python para o nosso somador:

1
2
3
def adder_model(a: int, b: int) -> int:
    """model of adder"""
    return a + b

Depois escrevemos os testes propriamente ditos. O primeiro testa o somador com os valores 6 e 10 fixos:

@cocotb.test()
async def adder_basic_test(dut):
    """Test for 6 + 10"""
    A = 6
    B = 10
    dut.a.value = A
    dut.b.value = B
    await Timer(2, unit="ns")
    assert dut.sum.value == adder_model(A, B), (
        f"Adder result is incorrect: {dut.sum.value} != {adder_model(A, B)}"
    )

O segundo teste injeta 10 pares de números aleatórios:

@cocotb.test()
async def adder_randomised_test(dut):
    """Test for adding 2 random numbers multiple times"""
    for _ in range(10):
        A = random.randint(0, 15)
        B = random.randint(0, 15)
        dut.a.value = A
        dut.b.value = B
        await Timer(2, unit="ns")
        assert dut.sum.value == adder_model(A, B), (
            f"Randomised test failed with: {dut.a.value} + {dut.b.value} = {dut.sum.value} != {adder_model(A, B)}"
        )

A partir de um Makefile, a ferramenta invoca o simulador e roda todos os testes comparando automaticamente os resultados:

 % make
rm -f results.xml
"make" -f Makefile results.xml
make[1]: Entering directory '/srv/QuestIO42/tutoriais/code/python/adder/tests'
/usr/local/bin/iverilog -o sim_build/sim.vvp -s adder -g2012 -f sim_build/cmds.f -s cocotb_iverilog_dump  /srv/QuestIO42/tutoriais/code/python/adder/tests/../../../verilog/adder/adder.v sim_build/cocotb_iverilog_dump.v
rm -f results.xml
COCOTB_TEST_MODULES=test_adder COCOTB_TESTCASE= COCOTB_TEST_FILTER= COCOTB_TOPLEVEL=adder TOPLEVEL_LANG=verilog \
         /usr/local/bin/vvp -M /home/menotti/.local/lib/python3.12/site-packages/cocotb/libs -m libcocotbvpi_icarus   sim_build/sim.vvp -fst  
     -.--ns INFO     gpi                                ..mbed/gpi_embed.cpp:93   in _embed_init_python              Using Python 3.12.4 interpreter at /usr/bin/python3
     -.--ns INFO     gpi                                ../gpi/GpiCommon.cpp:79   in gpi_print_registered_impl       VPI registered
     0.00ns INFO     cocotb                             Running on Icarus Verilog version 13.0 (devel)
     0.00ns INFO     cocotb                             Seeding Python random module with 1776432540
     0.00ns INFO     cocotb                             Initialized cocotb v2.0.1 from /home/menotti/.local/lib/python3.12/site-packages/cocotb
     0.00ns INFO     cocotb                             Running tests
     0.00ns INFO     cocotb.regression                  running test_adder.adder_basic_test (1/2)
                                                            Test for 6 + 10
FST info: dumpfile sim_build/adder.fst opened for output.
     2.00ns INFO     cocotb.regression                  test_adder.adder_basic_test passed
     2.00ns INFO     cocotb.regression                  running test_adder.adder_randomised_test (2/2)
                                                            Test for adding 2 random numbers multiple times
    22.00ns INFO     cocotb.regression                  test_adder.adder_randomised_test passed
    22.00ns INFO     cocotb.regression                  ******************************************************************************************
                                                        ** TEST                              STATUS  SIM TIME (ns)  REAL TIME (s)  RATIO (ns/s) **
                                                        ******************************************************************************************
                                                        ** test_adder.adder_basic_test        PASS           2.00           0.00       2660.52  **
                                                        ** test_adder.adder_randomised_test   PASS          20.00           0.00      50472.97  **
                                                        ******************************************************************************************
                                                        ** TESTS=2 PASS=2 FAIL=0 SKIP=0                     22.00           0.00      10002.05  **
                                                        ******************************************************************************************

WSL

A maioria das ferramentas open source para desenvolvimento de hardware rodam no Linux, mas é possível executá-las usando o Windows Subsystem for Linux (WSL).

A documentação completa está disponível aqui, mas alguns passos requerem permissão de administrador.

Extensão WSL no VS Code

Procure e instale a extensão WSL para o VS Code.

Abrir um terminal WSL

No meu iniciar, digite WSL ou pressione ⊞+R para executar o mesmo comando.

Testando a ligação entre VS Code e WSL

Para verificar se as instalações estão corretas, abra um terminal WSL e digite code . na pasta desejada, o VS Code deve abrir. O sistema de arquivos do Windows é montado em /mnt/c/ e lá você pode encontrar sua área de usuário (observe o caminho na saída abaixo).

No VS Code, clique em Terminal e depois em New Terminal ou digite Ctrl+Shift+'. No terminal digite uname para verificar se se trata de um terminal Linux.

root@CP-DK-214591:/mnt/c/Users/705990/Downloads/ld# uname
Linux

Pacotes úteis para as disciplinas de hardware

No terminal Linux, use o comando a seguir para instalar pacotes relevantes para a disciplina:

apt update
apt install gh iverilog gtkwave make gcc-riscv64-linux-gnu gcc-riscv64-unknown-elf qemu-user-static

Acessando os repositórios das disciplinas

Os repositórios da disciplina são privados e ficam dentro da organização DC-UFSCar no GitHub. Cada estudante só tem acesso aos seus repositórios individuais e os de seu grupo.

Siga os seguinte passos para se autenticar no GitHub e poder acessar os repositórios:

1
2
3
4
root@CP-DK-214591:~# gh auth login
? What account do you want to log into?  [Use arrows to move, type to filter]
> GitHub.com
  GitHub Enterprise Server
1
2
3
4
5
root@CP-DK-214591:~# gh auth login
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations on this host?  [Use arrows to move, type to filter]
  HTTPS
> SSH
1
2
3
4
5
6
root@CP-DK-214591:~# gh auth login
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations on this host? SSH
? Upload your SSH public key to your GitHub account?  [Use arrows to move, type to filter]
  /root/.ssh/id_ed25519.pub
> Skip
1
2
3
4
5
6
7
root@CP-DK-214591:~# gh auth login
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations on this host? SSH
? Upload your SSH public key to your GitHub account? Skip
? How would you like to authenticate GitHub CLI?  [Use arrows to move, type to filter]
> Login with a web browser
  Paste an authentication token

Copie o código gerado nesta etapa:

1
2
3
4
5
6
7
8
root@CP-DK-214591:~# gh auth login
? What account do you want to log into? GitHub.com
? What is your preferred protocol for Git operations on this host? SSH
? Upload your SSH public key to your GitHub account? Skip
? How would you like to authenticate GitHub CLI? Login with a web browser

! First copy your one-time code: XXXX-XXXX
Press Enter to open github.com in your browser...

Se o navegador não abrir sozinho, acesse a URL indicada para se logar: https://github.com/login/device, cole o código nela e clique em Continue, depois clique em Authorize github.

Pronto! Agora você já terá acesso ao repositório via linha de comando e pode usar os comandos clone, commit, push, etc. normalmente. Use sempre a URL SSH git@github.com:DC-UFSCar/SEU_REPO_AQUI.git.