FPGA: Testen von VHDL mit vunit

Will man seine in VHDL erstellten Komponenten testen bietet sich eine Simulation mit anschließender Überprüfung der entsprechenden Signale an. Dies manuell durchzuführen ist zeitaufwändig und fehleranfällig.

Eine Lösung bietet VUnit in Kombination mit GHDL an. GHDL ist ein quell offener Simulator für VHDL Programme. Leider ist GHDL nicht in den Debian Paketquellen enthalten, da die IEEE Bibliotheken von Debian als unfrei eingestuft werden. Also müssen wir dieses Paket selbst bauen und installieren. Dazu bietet sich eine VM / ein Docker Container oder ähnliches an, da einige Pakete zum Erstellen des Pakets notwendig sind. Folgende Kommandos erstellen aus den Quellen von github ein installierbares Debian-Paket:

apt update
apt install git gnat build-essential zlib1g-dev checkinstall
git clone https://github.com/ghdl/ghdl.git
git checkout v0.35
cd ghdl
./configure --prefix=/usr/local
make
checkinstall -y --install=no --requires=libgnat-6 \
  --maintainer=ghdl@lusiardi.de --nodoc --pkglicense=GPLv2 \
  --pkgversion=0.35
dpkg -i ghdl_0.35-1_amd64.deb

Vorteil: dieses Paket kann über die gewohnte Paketverwaltung installiert und auch wieder deinstalliert werden. Natürlich sollte man immer das aktuellste Release oder auch den Master mit git auschecken.

Anschließend installieren wir VUnit mit

pip install -U vunit_hdl

Nun können mit einem kleinen Python-Wrapper in VHDL geschriebene Unit-Tests ausgeführt werden:

#!/usr/bin/env python2

from os.path import join, dirname
from vunit import VUnit

root = dirname(__file__)

ui = VUnit.from_argv()
lib = ui.add_library("lib")
lib.add_source_files(join(root, "*.vhd"))
ui.main()

Wie sieht aber nun ein solcher Unit-Test aus? Nehmen wir als Beispiel einen 4-bit Parity Generator:

LIBRARY ieee;
USE ieee.std_logic_1164.all;

ENTITY four_bit_parity IS
    PORT (
        data: IN std_logic_vector(3 DOWNTO 0);
        parity: OUT std_logic
    );
END ENTITY;

ARCHITECTURE behavior_four_bit_parity OF four_bit_parity IS
BEGIN
    parity <= data(0) xor data(1) xor data(2) xor data(3);
END behavior_four_bit_parity;

Ein entsprechender Unit-Test könnte nun so aussehen:

library IEEE;
use IEEE.std_logic_1164.all;

library vunit_lib;
context vunit_lib.vunit_context;

entity four_bit_parity_tb is
    generic (runner_cfg : string);
end four_bit_parity_tb;

architecture sim of four_bit_parity_tb is
    SIGNAL d_in: STD_LOGIC_VECTOR(3 DOWNTO 0);
    SIGNAL p: STD_LOGIC;
    constant clk_period : time := 10 ps;
begin
    main : process
    begin
        test_runner_setup(runner, runner_cfg);
        while test_suite loop
            if run("0000") then
                d_in <= "0000";
                wait for 10 ps;
                check_equal(p, '0');
            end if;
            if run("0001") then
                d_in <= "0001";
                wait for 10 ps;
                check_equal(p, '1');
            end if;
        end loop;
        test_runner_cleanup(runner);
    end process;

    uut: entity work.four_bit_parity port map (d_in, p);
END sim;

Nun kann man mit ./run.py die Tests durchführen und erhält (hoffentlich) folgendes Ergebnis:

$ ./run.py 
Re-compile not needed

Starting lib.four_bit_parity_tb.0000
Output file: vunit_out/test_output/lib.four_bit_parity_tb.0000_86984b0155942bf3d3645c1aa56926538590d91f/output.txt
pass (P=1 S=0 F=0 T=2) lib.four_bit_parity_tb.0000 (0.6 seconds)

Starting lib.four_bit_parity_tb.0001
Output file: vunit_out/test_output/lib.four_bit_parity_tb.0001_fdfc81920e3c3468dfe6138ee1107bbf4bea3958/output.txt
pass (P=2 S=0 F=0 T=2) lib.four_bit_parity_tb.0001 (0.6 seconds)

==== Summary =======================================
pass lib.four_bit_parity_tb.0000 (0.6 seconds)
pass lib.four_bit_parity_tb.0001 (0.6 seconds)
====================================================
pass 2 of 2
====================================================
Total time was 1.2 seconds
Elapsed time was 1.2 seconds
====================================================
All passed!

Frohes Testen! Weitere Informationen zu VUnit und den vorhandenen Funktionen findet man in der VUnit Dokumentation. Etwas ist allerdings zu beachten, dass eventuell nicht alle VHDL Konstrukte, die durch GHDL in Tests akzeptiert werden schlussendlich auch durch z.B. Quartus II synthetisierbar sind. Hier sind also eventuell Nacharbeiten am eigentlichen VHDL-Code notwendig.