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:
-
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. -
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:
-
Otvaranjem terminala i navigiranjem do vunit-master direktorijuma komandom
cd
ili direktnim otvaranjem terminala u tom direktorijumu -
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:
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:
-
Python biblioteka: pruža alate koji pomažu u automatizaciji testiranja, administraciji i konfiguraciji.
-
VHDL biblioteka: skup biblioteka koji pomaže u uobičajenim zadacima verifikacije.
To je prikazano na dijagramu ispod:
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:
run.py
)
4.1. Definisanje python running skripte (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.py
skriptu č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()
-
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. -
lib = vu.add_library("lib")
će kreirati biblioteku lib koja se koristi za dodavanje izvornih VHDL fajlova (example.vhd
iexample_tb.vhd
). -
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 samarun.py
skripta). U našem slučaju to su fajloviexample.vhd
iexample_tb.vhd
. -
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
.
Zatim je prikazana inicijalizacija dizajna koji se testira.
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.
U procesu stimuli_generator
(kod prikazan na slici ispod) se dodjeljuju vrijednosti za gore pomenute varijable u zavisnosti od konkretnih slučajeva.
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.
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:
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.