VUnit framework - etf-unibl/sava-drina GitHub Wiki

1. Šta je VUnit?

VUnit je open source framework za jedinično testiranje namijenjen za VHDL/SistemVerilog dizajne. Posjeduje funkcionalnost potrebnu za realizaciju kontinuiranog i automatizovanog testiranja VHDL koda. VUnit ne zamjenjuje, već dopunjuje tradicionalne metodologije testiranja tako što podržava pristup tzv. „ranog i čestog" testiranja kroz automatizaciju. Koristi se u proizvodnim okruženjima gdje se hiljade testova pokreću kroz nekoliko sati na moćnim višejezgarnim mašinama, kao i u malim open source projektima gdje je za testiranje manjih paketa potrebno svega nekoliko sekundi.

Osnovna prednost korištenja VUnit framework-a koja je nama od interesa je mogućnost definisanja više testnih slučajeva u okviru jednog testbench fajla od kojih se svaki izvodi u pojedinačnim, nezavisnim, simulacijama.

2. Instaliranje i podešavanje putanja

2.1. Instaliranje

Za uspješno instaliranje VUnit framework-a potrebno je posjedovati instalirane sljedeće softvere:

  • Intel ModelSim

Potrebno je podesiti sistemsku putanju (PATH) do ModelSim-a.

  • Python 3.6 ili više

Link za preuzimanje: Python

Takođe je potrebno podesiti sistemsku putanju do Python-a.

  • Git (opciono)

  • Windows Terminal (opciono)

Preuzimanje VUnit-a moguće je na 2 načina:

  1. Kloniranjem sa odgovarajućeg Git repozitorijuma komandom (iz Git Bash): git clone --recurse-submodules https://github.com/VUnit/vunit.git i smiještanjem na C drive.

  2. Preuzimanjem zip arhive sa linka VUnit (U sekciji Installing link označen sa Download VUnit). Arhiva se zatim može eksthovati na C drive čime se dobija direktorijum vunit-master.

Instaliranje se vrši na sljedeći način:

  1. Otvaranjem terminala i navigiranjem do vunit-master direktorijuma komandom cd ili direktnim otvaranjem terminala u tom direktorijumu

  2. Pokretanjem komande setup.py install čime se izvršava python skripta setup.py

2.2. Podešavanje putanja

Za ispravan rad VUnit framework-a potrebno je dodati 2 sistemske varijable:

  • VUNIT_MODELSIM_PATH : putanja do ModelSim-a na vašem računaru

  • VUNIT_SIMULATOR : ModelSim

Primjer podešavanja sistemskih varijabli prikazan je na sljedećoj slici:

Image

Napomena: Podesiti odgovarajuće putanje prema lokaciji na sopstvenom računaru (U prethodnom primjeru IntelFPGA_lite i vunit-master direktorijum su smješteni na C drive).

3. Struktura VUnit Framework-a

VUnit počiva na 2 osnovne komponente:

  1. Python biblioteka: pruža alate koji pomažu u automatizaciji testiranja, administraciji i konfiguraciji.

  2. VHDL biblioteka: skup biblioteka koji pomaže u uobičajenim zadacima verifikacije.

To je prikazano na dijagramu ispod:

Image

4. Razvoj testbench fajlova korištenjem VUnit Framework-a

Kako bismo uopšte pristupili korištenju osnovnih pogodnosti VUnit alata neophodno je da prethodno imamo definisan VHDL dizajn za koji se piše testbench fajl. Takođe, potrebno je da postoji i kreiran testbench fajl sa svojim osnovnim komponentama (entitet, arhitektura) u VHDL jeziku. Nakon čega se sprovode sljedeći koraci:

4.1. Definisanje python running skripte (run.py)

Python skripta najvišeg nivoa se obično zove run.py. Ova skripta je ulazna tačka za izvršavanje VUnit-a. Skripta definiše lokaciju za svaki VHDL izvorni fajl u projektu, biblioteku kojoj svaka izvorna datoteka pripada, sve eksterne (unaprijed kompajlirane) biblioteke i posebna podešavanja koja mogu biti potrebna za kompajliranje ili simulaciju izvornih datoteka projekta. VUnit Python interfejs se koristi za kreiranje i manipulisanje VUnit projektom unutar run.py datoteke.

Pošto je VUnit projekat definisan Python skriptom, puna funkcionalnost Python-a je dostupna za kreiranje dinamičkih pravila za navođenje datoteka koje treba da budu uključene u projekat. Na primer, VHDL datoteke mogu biti rekurzivno uključene iz strukture direktorijuma na osnovu obrasca naziva datoteke. Drugi Python paketi ili moduli mogu biti uvezeni da bi se podesio projekat. Jednom kada su datoteke za projekat uključene, interfejs komandne linije se može koristiti za obavljanje raznih radnji na projektu. Na primjer, navođenje svih otkrivenih testova ili pokretanje pojedinačnih testova koji odgovaraju šablonu džoker znakova.

Pretpostavimo sada da imamo testni VHDL dizajn smješten u fajlu example.vhd i njegov testbench fajl, takođe u VHDL-u example_tb.vhd. Ako su ta 2 fajla smještena u isti direktorijum (npr. example direktorijum) tada u taj isti direktorijum možemo kreirati run.pyskriptu čiji je sadržaj:

from vunit import VUnit

# 1. Kreiranje VUnit instance parsiranjem argumenata komandne linije
vu = VUnit.from_argv()

# 2. Kreiranje biblioteke 'lib'
lib = vu.add_library("lib")

# 3. Dodavanje svih fajlova sa ekstenzijom .vhd u kreiranu biblioteku u trenutnom direktorijumu
lib.add_source_files("*.vhd")

# Pokretanje vunit funkcije
vu.main()
  1. vu = VUnit.from_argv() će kreirati VUnit instancu i dodeliti je promenljivoj pod nazivom vu. Tada možemo koristiti vu za kreiranje biblioteka i raznih zadataka.

  2. lib = vu.add_library("lib") će kreirati biblioteku lib koja se koristi za dodavanje izvornih VHDL fajlova (example.vhd i example_tb.vhd).

  3. lib.add_source_files("*.vhd") vrši dodavanje svih fajlova sa ekstenzijom .vhd (džoker *) koji su smješteni u trenutnom direktorijumu (gdje se nalazi sama run.py skripta). U našem slučaju to su fajlovi example.vhd i example_tb.vhd.

  4. vu.main() vrši pokretanje main funkcije.

U slučaju kompleksnijeg dizajna, kada imamo više VHDL fajlova za sam opis dizajna ili više testbench fajlova i težimo da ih organizujemo u posebne direktorijume/poddirektorijume u okviru direktorijuma samog projekta, tada je neophodno dodati i putanje do odgovarajućih fajlova smještenih u tim poddirektorijumima.

Pa ako pretpostavimo da je za opis dizajna pored fajla example.vhd potreban još jedan .vhd fajl- example1.vhd. Ta dva fajla možemo smjestiti u poddirektorijum design. I ako pretpostavimo i da imamo 2 testbench fajla example_tb.vhd i example1_tb.vhd, i njih možemo organizovati u zaseban poddirektorijum, npr. testbench. Ako su ta 2 poddirektorijuma smještena u direktorijumu projekta- example direktorijum, u kojem se nalazi i run.py skripta, tada je u okviru skripte potrebno obezbijediti putanje do tih fajlova i tada bi run.py skripta bila:

# Uvoz paketa pathlib (iz Path biblioteke Python-a) za definisanje putanje
from pathlib import Path
from vunit import VUnit

# Definisanje putanje ROOT direktorijuma (to je direktorijum example)
ROOT = Path(__file__).resolve().parent

# Definisanje putanje poddirektorijuma design (u odnosu na korijeni direktorijum)
DESIGN_PATH = ROOT / "design"

# Definisanje putanje poddirektorijuma testbench (u odnosu na korijeni direktorijum)
TEST_PATH = ROOT / "testbench"

# Kreiranje VUnit instance
VU = VUnit.from_argv()
VU.enable_location_preprocessing()

# Kreiranje biblioteke design_lib za fajlove iz design poddirektorijuma
design_lib = VU.add_library("design_lib")
# Dodavanje svih izvornih .vhd fajlova iz design poddirektorijuma u design_lib
design_lib.add_source_files([DESIGN_PATH / "*.vhd"])
    
# Kreiranje biblioteke za tb_lib za fajlove iz testbench poddirektorijuma
tb_lib = VU.add_library("tb_lib")
# Dodavanje svih izvornih .vhd fajlova iz testbench poddirektorijuma u tb_lib
tb_lib.add_source_files([TEST_PATH / "*.vhd"])

VU.main()

U odnosu na prethodni kod ovjde imamo 2 dodatne linije kojima se definišu putanje do poddirektorijuma design i testbench. Pored toga, vrši se kreiranje 2 biblioteke design_lib (u koju se smiještaju svi izvorni fajlovi iz design poddirektorijuma) i tb_lib (u koju se smiještaju svi izvorni fajlovi iz testbench poddirektorijuma).

4.2. Podešavanje testbench fajla

Da bismo postojeće testbench fajlove učinili kompatibilnim sa VUnit-om neophodno je uvesti nekoliko dodatnih stavki u testbench fajl. Recimo da imamo testbench fajl example_tb. Potrebna podešavanja tog fajla u svrhu postizanja kompatibilnosti sa VUnit-om data su u okviru sljedećeg koda:

library vunit_lib;
context vunit_lib.vunit_context;

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

architecture tb of example_tb is
begin
  main : process
  begin
    test_runner_setup(runner, runner_cfg);
    report "Hello world!";
    test_runner_cleanup(runner); -- Simulation ends here
  end process;
end architecture;

Prvo, treba da dodamo VUnit biblioteku vunit_lib i njen kontekst: vunit_context, tako da imamo pristup funkcionalnosti VUnit.

library vunit_lib;
context vunit_lib.vunit_context;

Zatim se dodaje runner configuration u entitet testbench fajla (koji je inače prazan) komandom generic (runner_cfg : string);. runner_cfg je generička konstanta koja omogućava Python test runner-u da kontroliše testbench. To jest, možemo pokrenuti testove iz Python okruženja. Ova generička konstanta je obavezna i ne mijenja se.

main je glavni kontrolni proces za testbench. Uvijek počinje sa procedurom test_runner_setup i završava se sa procedurom test_runner_cleanup. Preciznije govoreći, mi umotavamo svoj postojeći kod u glavni kontrolni proces sa pozivima test_runner_setup i test_runner_cleanup. Simulacija živi između ove dvije procedure, tj. test_runner_cleanup terminira simulaciju i zato se treba ukloniti kod iz našeg postojećeg testbench fajla koji vrši terminiranje (kao što je npr. poziv assert za simulaciju). VUnit testbench mora biti prekinut pozivom test_runner_cleanup procedure. Ove procedure su dio VUnit run biblioteke. Više informacija o ovoj biblioteci može se pronaći u njenom korisničkom uputstvu.

Prethodni primjer je jednostavan. Samo se vrši report poruke "Hello world!", ali nas zanima kako napraviti više testnih slučajeva u okviru jednog testbench fajla od kojih se svaki izvodi u pojedinačnim, nezavisnim, simulacijama. Stavljanje više testova u testbench fajl je dobar način za dijeljenje zajedničkog okruženja za testiranje.

Kako kreirati jedan ili više testnih slučajeva?

Testni slučajevi se definišu u okviru while-loop konstrukcije:

  main : process
  begin
    test_runner_setup(runner, runner_cfg);

    while test_suite loop

    -- OVDE SE DEFINIŠU SVI POTREBNI TESTNI SLUČAJEVI

      end if;
    end loop;

    test_runner_cleanup(runner);

A za kreiranje testnih slučajeva koristi se run funkcija VUnit-a i if-else konstrukcija:

      if run("test_case_name_1") then

      -- KOD ZA PRVI TESTNI SLUČAJ

      elsif run("test_case_name_2") then

      -- KOD ZA DRUGI TESTNI SLUČAJ

      ...

      end if;

Tada možemo pokrenuti sve ili određene testne slučajeve iz Python okruženja tako što ćemo ih jednostavno pozvati imenom koje smo definisali u pozivu funkcije run : run test_case_name_1 ili run test_case_name_2.

U nastavku slijedi jednosavan primjer sa višestrukim testnim slučajevima koji samo rade report odgovarajuće poruke. U opštem slučaju to će biti VHDL kod koji je potreban za odgovarajuću simulaciju definisanu tim tstnim slučajem.

library vunit_lib;
context vunit_lib.vunit_context;

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

architecture tb of example_tb is
begin
  main : process
  begin
    test_runner_setup(runner, runner_cfg);

    while test_suite loop

      if run("test_pass") then
        report "This will pass";

      elsif run("test_fail") then
        assert false report "It fails";

      end if;
    end loop;

    test_runner_cleanup(runner);
  end process;
end architecture;

Kako izgleda struktura jednog testbench fajla?

Na primjeru kombinacione mreže koja implementira sabiranje i oduzimanje dva 16-bitna broja, kao i inkrementovanje i dekrementovanje biće objašnjeno šta je sve potrebno da sadrži jedan testbench fajl.

Prvi korak je definisanje signala koji su potrebni za dizajn koji se testira i same komponente sub1.

Image

Zatim je prikazana inicijalizacija dizajna koji se testira.

Image

Kao što je iznad pomenuto testni slučajevi se definišu unutar while-loop konstrukcije. Da bismo verifikovali dizajn koristimo VUnit funkciju run unutar test-suite da bismo kreirali testne slučajeve za verifikaciju izlaza. Na slici ispod vidimo da imamo četiri testna slučaja za ovaj dizajn. Nakon poziva funkcije run za određen testni slučaj, provjerava se ispunjenost uslova. Check i check_false procedure vrše provjeru vrijednosti specifičnih parametara, u konkretnom slučaju vrijednost parametra ctrl_in i dodjeljene vrijednosti parametru expr i ispisuje konkretnu poruku.

Image

U procesu stimuli_generator (kod prikazan na slici ispod) se dodjeljuju vrijednosti za gore pomenute varijable u zavisnosti od konkretnih slučajeva.

Image

5. Pokretanje testa iz terminala

Za pokretanje testa iz terminala najprije je neophodno otvoriti novi terminal (CMD, PowerShell ili Windows terminal) i pozicionirati se u odgovarajući direktorijum projekta gdje se nalazi skripta run.py. Da bismo pokrenuli testni slučaj jednostavno samo otkucamo:

python .\run.py *test_addition

Ovom komandom VUnit će kompajlirati sve VHDL datoteke u ispravnom redoslijedu i analizirati ih, tražeći VUnit testbench fajlove. A zatim će pogledati unutar tih datoteka, tražeći run funkciju sa imenom testnog slučaja test_addition da bi je izvršio.

Na slici ispod je prikazan primjer pokretanja testnog slučaja test_addition iz gore opisanog primjera kombinacione mreže.

Image

6. Pokretanje testa u GUI simulatoru

VUnit obezbjeđuje automatizovan način za pokretanje testova i u simulatoru. Na ovaj način dobijamo vizuelni prikaz signala u ModelSim simulatoru koji smo konfigurisali. Za pokretanje testa u grafičkom korisničkom interfejsu potrebno je otvoriti terminal i pozicionirati se u odgovarajući direktorijum projekta gdje se nalazi skripta run.py. Za pokretanje testnog slučaja potrebno je otkucati:

python .\run.py *test_addition -g

Napomena: Kako bi vizuelni prikaz signala u ModelSim simulatoru bio moguć neophodno je da se prije toga definiše fajl sa ekstenzijom .do koji po konvenciji treba da ima naziv naziv_tb_fajla_wave.do i da se u run.py skripti obezbijedi putanja do tog fajla. U okviru .do fajla se dodaju signali koji će biti vizuelno prikazani i navode se druga potrebna podešavanja same simulacije.

Izgled sub1_tb_wave.do simulacionog fajla za navedeni primjer kombinacione mreže dat je na slici ispod:

Image

Ukoliko se desi da vizuelnom inspekcijom otkrijemo eventualne greške u okviru testnog slučaja zbog čega mijenjamo kod, ponovno kompajliranje, restartovanje i pokrtanje simulaciju odgovarajućeg testnog slučaja postiže se unosom komande vunit_restart u Transcript prozor ModelSim-a.