Kurs FPGA Lattice (8). Symulacja w EDA Playground

Kurs FPGA Lattice (8). Symulacja w EDA Playground

Analizator logiczny Reveal, poznany w poprzednim odcinku kursu, pozwala nam badać sygnały wewnątrz FPGA. Jednak wymaga on całkiem sporo zasobów, a jego możliwości są ograniczone dostępnymi zasobami naszego układu. Symulacja, w przeciwieństwie do analizatora, daje nam możliwość badania nieograniczonej liczby sygnałów, a ponadto w ogóle nie potrzebuje żadnego układu FPGA!

Wraz z pakietem Lattice Diamond dostajemy symulator ModelSim. Jest to popularny program i ma ogromne możliwości. Jednak duża liczba dostępnych opcji ukrytych pod różnymi przyciskami i zaszytych w różnych miejscach menu może być przytłaczająca dla początkujących. Dlatego proponuję, by w pierwszej kolejności zapoznać się z dużo prostszym symulatorem EDA Playground, a kiedy nabierzesz biegłości w testowaniu kodu, przesiąść się na ModelSim.

EDA Playground dostępny jest za darmo pod adresem www.edaplayground.com i działa w przeglądarce internetowej. Kiedy pierwszy raz zobaczyłem tę stronę, pomyślałem, że umieszczenie symulatora Veriloga w przeglądarce internetowej to jakaś ułańska fantazja, jednak po kilku próbach ten pomysł bardzo mi przypadł do gustu. Przede wszystkim ten symulator działa bez instalacji na dowolnym komputerze. Kod możemy zapisać w chmurze, więc mamy do niego dostęp wszędzie tam, gdzie jest internet. Możemy go udostępnić znajomym w postaci linku, a oni mogą go natychmiast zasymulować i modyfikować.

Można także kod opublikować dla milionowej społeczności użytkowników EDA Playground oraz można łatwo przeglądać kod innych osób.

Kod, z którym będziemy parcować w tym odcinku kursu, znajdziesz pod adresem https://www.edaplayground.com/x/LC9i. Wystarczy, że otworzysz tę stronę i klikniesz Run, a po chwili zobaczysz wyniki symulacji. Jednak najpierw poznajmy trochę teorii…

Testbench

Kod języka Verilog podzielony jest na moduły, które są odpowiednikiem klas z C++. Kiedy mamy już napisany kod modułu, musimy powołać go do życia, tworząc jedną lub więcej instancji tych modułów. Wyjątkiem jest moduł top, będący odpowiednikiem funkcji main(). W module top tworzymy instancje podrzędnych modułów, a wewnątrz nich kolejne instancje, i tak dalej. W ten sposób tworzy nam się hierarchia projektu od ogółu do szczegółu.

Moduły stosowane w FPGA mogą mieć także instancje w specjalnych modułach, które nazywane są testbenchami. Są to moduły, które nie mają żadnych wejść ani żadnych wyjść. Prosty testbench może zawierać w sobie instancję tylko jednego modułu, który chcemy przetestować, ale może zawierać także całą hierarchię instancji. Możemy zrobić nawet testbench, który zawierał instancję modułu top, stając się tak jakby nadrzędnym modułem względem modułu top.

W tym momencie musimy wprowadzić rozróżnienie pomiędzy modułami syntezowalnymi i niesyntezowalnymi. Moduły syntezowalne to wszystkie te, które dotychczas widzieliśmy w tym kursie. Możliwa jest ich synteza, czyli wygenerowanie jakiegoś zbioru bramek i przerzutników, które można umieścić w strukturze FPGA.

Moduły niesyntezowalne nie mogą być zaimplementowane w FPGA. Mogą działać jedynie w symulatorach. Ich kod należy rozumieć jako zwyczajny język programowania. Instrukcje wykonują się jedna po drugiej, a ponadto możemy stosować pętle i opóźnienia, tak jakbyśmy pisali normalny program.

Listing 1. Kod modułu testowanego

// Plik up_down_counter.v
module UpDownCounter(
input Clock,
input Reset,
input CountUp,
input CountDown,
output reg [7:0] Data
);

always @(posedge Clock, negedge Reset)
if(!Reset)
Data <= 0;
else if(CountUp)
Data <= Data + 1’b1;
else if(CountDown)
Data <= Data – 1’b1;

endmodule

Prześledźmy listingi 1 oraz 2. Pierwszy listing zawiera kod modułu, który będziemy chcieli testować, a drugi prezentuje kod modułu testującego, czyli testbencha. W listingu 1 mamy moduł prostego 8-bitowego licznika. Kiedy na wejściu CountUp jest stan wysoki, wtedy licznik zwiększa swoją wartość z każdym zboczem rosnącym sygnału zegarowego. Kiedy CountDown ma stan wysoki, to licznik zmniejsza swoją wartość. Kiedy oba sygnały są w stanie niskim, to licznik nie zmienia się. Natomiast w sytuacji, gdy oba sygnały mają stan wysoki, to licznik liczy w górę, ponieważ warunek if(CountUp) jest sprawdzany wcześniej w drzewku decyzyjnym if-else. Ponadto, licznik zostanie natychmiast wyzerowany, niezależnie od wszystkich innych sygnałów, jeżeli na linii Reset wystąpi stan niski. Listing 2 pokazuje testbench, czyli moduł testujący. Przyjęło się, że moduł syntezowalny oraz jego testbench zapisuje się w osobnych plikach o tej samej nazwie, lecz do nazwy pliku testbencha dodaje się _tb.

Listing 2. Testbench testujący kod z listingu 1

// Plik up_down_counter_tb.v
`timescale 1 ns/1 ps // #1
module UpDownCounter_tb(); // #2

reg Clk = 1’b0; // #3
reg Rst = 1’b1;
reg Up = 1’b0;
reg Dn = 1’b0;
wire [7:0] Out;

// Generator sygnału zegarowego
always begin
#5; // #4
Clk = ~Clk; // #5
end

// Sekwencja testowa
initial begin
$timeformat(-9, 3, “ns”, 10); // #6
$display(“===== START =====”); // #7
$display(“ Time Cnt Up Dn”); // #8
$monitor(“%t %d %d %d”, $realtime, Out, Up, Dn); // #9

#30 Rst = 0; // #10
#55 Rst = 1;
#15 Up = 1;
#90 Up = 0;
#50 Dn = 1;
#70 Dn = 0;

#50;
$display(“===== END =====”); // #11
$stop; // #12
end

// Instancja testowanego modułu
UpDownCounter DUT( // #13
.Clock(Clk),
.Reset(Rst),
.CountUp(Up),
.CountDown(Dn),
.Data(Out)
);

// Wyświetlanie wyników symulacji
initial begin
$dumpfile(“dump.vcd”); // #14
$dumpvars(
2, // #15
Clk,
Rst,
Up,
Dn,
Out
);
end

endmodule

Testbench rozpoczynamy od określenia podstawowej jednostki czasu, która będzie stosowana w instrukcjach opóźniających oraz rozdzielczości. Rozdzielczość to najmniejszy możliwy odstęp czasu, jaki jest możliwy do zarejestrowania w symulacji. Wszystkie odstępy czasowe, mniejsze niż określona rozdzielczość, zostaną zaokrąglone. Ustawienie tych wartości zależy od tego, co zamierzamy zaobserwować w symulacji. Kiedy interesuje nas czas propagacji bramek, to może być konieczne ustawienie rozdzielczości czasu nawet na poziomie femtosekund. Im mniejsza rozdzielczość, tym dokładniejsze i mniejsze odstępy czasowe możemy symulować, ale symulacja potrzebuje więcej pamięci komputera.

W linii #1 za pomocą instrukcji `timescale ustawiliśmy jednostkę czasu na jedną nanosekundę, a rozdzielczość to jedna pikosekunda. Zwróć uwagę, że instrukcja `timescale poprzedzona jest ukośnym apostrofem, a nie apostrofem prostym!

Linia #2 to nazwa modułu. Przyjęło się, aby testbench nazywać tak samo jak moduł, który jest testowany, ale dodaje się do jego nazwy _tb, podobnie jak w przypadku nazwy pliku. Po nazwie modułu testbenacha można umieścić nawiasy okrągłe () bez żadnej zawartości, ponieważ testbench nie ma żadnych wejść ani wyjść. Nawiasy te można także pominąć.

Zaczynając od linii #3, definiujemy zmienne robocze. Wszystkie zmienne typu reg są zmiennymi, które będą podłączone do wejść testowanego modułu, natomiast sygnały wire będą podłączone do jego wyjść. Przypominam, że zmienne reg mają możliwość przechowywania danych, natomiast zmienne wire to tylko przewody, które łączą ze sobą element nadający z elementem odbierającym. Zmienne robocze w testbenchu nazwałem trochę inaczej, niż nazywają się wejścia i wyjścia modułu UpDownCounter. Można je nazwać tak samo – i wiele osób tak robi.

Następnie w kodzie mamy kilka bloków initial i always. Wszystkie te bloki mogą się wykonywać równolegle. Blok initial wykonuje się tylko raz i jego wykonanie rozpoczyna się w chwili rozpoczęcia symulacji. Blok always już znamy i widzieliśmy go łącznie z jakimś warunkiem, jak np. posedge Clock oraz negedge Reset. Jednak warunek nie jest koniecznym elementem bloku always. Jeżeli go nie ma, to blok always będzie się wykonywał bez przerwy w nieskończoność, tak jak pętla nieskończona while(1) z C++.

Pierwszy blok always posłuży nam do wygenerowania sygnału zegarowego. Będzie on działał przez cały czas symulacji, niezależnie od wszystkich innych bloków. W linii #4 widzimy nietypowo wyglądającą instrukcję #5. Oznacza to, że symulator ma poczekać 5 jednostek czasu zdefiniowanych w instrukcji `timescale, czyli w naszym przypadku będzie to 5 ns. Po upływie tego czasu zostanie wykonana instrukcja z linii #5, czyli negacja sygnału zegarowego. Tak przygotowany blok always będzie nam generował sygnał zegarowy, którego stan wysoki będzie trwał 5 ns i stan niski również będzie trwał 5 ns. Zatem okres zegara to 10 ns, a jego częstotliwość jest odwrotnością okresu, czyli 100 MHz.

Istnieje możliwość, by czas opóźnienia był liczbą ułamkową. W takim przypadku po znaku # podajemy liczbę jako ułamek dziesiętny, pamiętając, że separatorem części ułamkowej jest kropka, a nie przecinek. Nic nie stoi na przeszkodzie, by czas opóźnienia po znaku # był podany w postaci parametru parameter lub zmiennej integer. Wtedy mamy możliwość, by w trakcie symulacji zmienić częstotliwość zegara, jeżeli tylko z jakiegoś powodu mamy taką potrzebę.

Następna część kodu to blok initial, który zawiera program symulacji. Tutaj zmieniamy stany sygnałów sterujących i umieszczamy instrukcje printujące różne komunikaty na konsoli.

W linii #6 umieszczono instrukcję $timeformat, która określa, w jaki sposób na konsoli ma być podawany czas symulacji. Instrukcja $timeformat przyjmuje cztery argumenty. Są to:

  1. Jednostka czasu, wyrażona jako wykładnik zapisu naukowego. Mówiąc po ludzku, wartość -3 oznacza, że chcemy zobaczyć czas podany w milisekundach, -6 to mikrosekundy, -9 to nanosekundy, -12 to pikosekundy, -15 to femtosekundy.
  2. Liczba zer po przecinku. Jeżeli podamy 0, to wynik zostanie podany jako liczba całkowita bez przecinka.
  3. Tekst, jaki ma zostać doklejony do wyświetlanej wartości liczbowej. Najlepiej, by była to jednostka czasu, czyli w naszym przypadku ns.
  4. Liczba znaków, jaka ma zostać przeznaczona na cały zapis, wraz z przecinkiem i jednostką. Cały zwracany tekst jest wyrównywany do prawej.

W liniach #7 i #8 mamy dwie instrukcje $display, które podobnie jak funkcja printf() w C++, służą do wyświetlania tekstu na konsoli. Wyświetlanie napisów START i END w liniach #7 i #11 jest pomocne, aby odszukać początek i koniec symulacji w logu, który oprócz naszych komunikatów może mieć jeszcze całą masę logów symulatora – listing 3. Chcemy, aby interesujące nas zmienne były wyświetlone w formie tabelarycznej. Instrukcja z linii #8 wyświetla nagłówek tabeli.

Listing 3. Fragment logu z symulacji

# Loading work.UpDownCounter_tb(fast)
# Loading work.UpDownCounter(fast)
#
# vsim -voptargs=+acc=npr
# run -all
# ===== START =====
# Time Cnt Up Dn
# 0.000ns x 0 0
# 30.000ns 0 0 0
# 100.000ns 0 1 0
# 105.000ns 1 1 0
# 115.000ns 2 1 0
# 125.000ns 3 1 0
# 135.000ns 4 1 0
# 145.000ns 5 1 0
# 155.000ns 6 1 0
# 165.000ns 7 1 0
# 175.000ns 8 1 0
# 185.000ns 9 1 0
# 190.000ns 9 0 0
# 240.000ns 9 0 1
# 245.000ns 8 0 1
# 255.000ns 7 0 1
# 265.000ns 6 0 1
# 275.000ns 5 0 1
# 285.000ns 4 0 1
# 295.000ns 3 0 1
# 305.000ns 2 0 1
# 310.000ns 2 0 0
# ===== END =====
# ** Note: $stop : testbench.sv(34)
# Time: 360 ns Iteration: 0 Instance: /UpDownCounter_tb
# Break at testbench.sv line 34
# exit
# End time: 16:27:25 on Jan 21,2023, Elapsed time: 0:00:01
# Errors: 0, Warnings: 0
Finding VCD file...
./dump.vcd
[2023-01-21 16:27:25 EST] Opening EPWave...
Done

Instrukcja $monitor z linii #9 służy również do wyświetlania komunikatów na konsoli, podobnie jak $display, jednak ma istotną różnicę. Instrukcja ta monitoruje stan zmiennych, które zostały podane w argumentach i wyświetla tekst za każdym razem, kiedy którakolwiek zmienna zostanie zmodyfikowana.
Instrukcje $display oraz $monitor mogą wyświetlać różne zmienne. Należy jest podać w ciągu tekstowym za pomocą znaku % oraz specyfikatora formatu. Ciąg tekstowy jest pierwszym argumentem tych funkcji, a kolejne argumenty muszą odpowiadać kolejnym znakom % z tekstu. Działa to podobnie jak printf() w C++. Możliwe są następujące opcje:

  • %d – wyświetlanie liczby dziesiętnej,
  • %b – wyświetlenie liczby binarnej,
  • %h – wyświetlenie liczby szesnastkowej małymi literami (0123456789abcdef),
  • %H – wyświetlenie liczby szesnastkowej dużymi literami (0123456789ABCDEF),
  • %t – wyświetlenie czasu,
  • %f – wyświetlenie liczby zmiennoprzecinkowej w formacie dziesiętnym,
  • %e – wyświetlenie liczby zmiennoprzecinkowej w formacie naukowym,
  • %s – wyświetlenie zmiennej tekstowej,
  • %m – wyświetlenie nazwy modułu, z którego printowany jest komunikat.

Dane liczbowe wyświetlane są w bloku tekstu o stałej szerokości i są wyrównane do prawej. Przykładowo, liczba 1 zapisana w rejestrze 8-bitowym, wyświetlona w formacie binarnym, zostanie zapisana jako 00000001. Gdybyśmy chcieli tę zmienną wyświetlić w formacie szesnastkowym, to zobaczymy 01.

W formacie dziesiętnym zostanie wyświetlona cyfra 1, ale będzie poprzedzona dwiema spacjami, ponieważ maksymalną wartością zmiennej 8-bitowej jest 255, co zajmuje trzy cyfry. Taki sposób wyrównywania tekstu pozwala na ładne prezentowanie wyników w postaci tabelarycznej, jak widać na listingu 3.

Istnieje możliwość zmiany liczby poprzedzających spacji lub zer. Oto kilka przykładów:

  • %5d – wyświetlenie liczby w formacie dziesiętnym, która ma zajmować 5 znaków, a jeżeli liczba jest zbyt mała, to mają być wyświetlone przed nią spacje,
  • %05d – wyświetlenie liczby w formacie dziesiętnym, która ma zajmować 5 znaków, a jeżeli liczba jest zbyt mała, to mają być wyświetlone przed nią zera,
  • %5H – wyświetlenie liczby w formacie szesnastkowym, która ma zajmować 5 znaków, a jeżeli liczba jest zbyt mała, to mają być wyświetlone przed nią zera,
  • %5b – wyświetlenie liczby w formacie binarnym, która ma zajmować 5 znaków, a jeżeli liczba jest zbyt mała, to mają być wyświetlone przed nią zera.

W ten sposób w linii #9 definiujemy ciąg znaków, do którego mają być wstawiane zmienne liczbowe, podane w kolejnych argumentach instrukcji $monitor. Jako pierwszą zmienną podałem $realtime, który zwraca aktualny czas symulacji. Kolejne argumenty to zmienne zdefiniowane w linii #3.

Przechodzimy wreszcie do sekwencji testowej, gdzie zmieniają się sygnały sterujące. W linii #10 i kolejnych zmieniamy stany sygnałów Rst, Up oraz Dn. Zmiana tych sygnałów wpływa na pracę testowanego modułu. Przez pewien czas licznik będzie liczył w górę, potem się zatrzyma, a następnie będzie liczył w dół. Zwróć uwagę na to, że w symulacji stosujemy przypisanie blokujące (=).

W linii #11 wyświetlamy komunikat o zakończeniu sekwencji testowej. W linii #12 poleceniem $stop informujemy symulator, aby zakończył symulację i wyświetlił wyniki. Następnie musimy utworzyć instancję testowanego modułu w podobny sposób, jak to robiliśmy w poprzednich odcinkach kursu. W linii #13 tworzymy instancję modułu typu UpDownCounter i nazywamy ją DUT, czyli device under test. Tak przyjęło się nazywać testowane moduły. Można spotkać się także z nazwami UUT, czyli unit under test lub MUT, czyli module under test.

W kolejnym bloku initial mamy dwie instrukcje, które są wymagane przez EDA Playground. Nie są potrzebne, kiedy używamy ModelSim. W linii #14 podajemy nazwę pliku, w którym przechowywane są wyniki symulacji. Ten plik nie będzie nas interesował – jest on zapisywany na serwerze EDA Playground, a po zakończeniu symulacji jest usuwany.

W linii #15 mamy instrukcję $dumpvars, określającą, jakie zmienne chcemy oglądać na wykresie czasowym, który zostanie wygenerowany po zakończeniu symulacji. Kluczowe znaczenie ma pierwszy argument, który przyjmować może trzy wartości:

  • 0 – w drugim argumencie należy podać nazwę instancji, z której zostaną wyświetlone wszystkie zmienne, łącznie ze zmiennymi we wszystkich podmodułach, które w tej instancji zostały utworzone. W przypadku bardziej skomplikowanych testbenchy ta opcja może wygenerować bardzo dużo informacji na wykresie, przez co stanie się nieczytelny.
  • 1 – w argumentach należy podać nazwy instancji, z których zostaną wyświetlone wszystkie zmienne, ale zmiennych w ich podmodułach.
  • 2 – w argumentach należy podać nazwy zmiennych, które nas interesują. Jeżeli zmienna jest wewnątrz jakiejś instancji, należy podać także nazwę instancji z kropką, na przykład DUT.Data.

EDA Playground

Wchodzimy na stronę www.edaplayground.com. Możemy od razu pisać kod, ale warto wcześniej założyć sobie konto, aby mieć możliwość zapisania naszych projektów w chmurze. Po prawej stronie widzimy podgląd pliku design.sv – jest to plik, w którym należy umieścić kod modułu testowanego. Po lewej stronie jest testbench.sv, gdzie umieszczamy moduł testujący. Rozszerzenie *.sv oznacza, że są to pliki w języku SystemVerilog. Jest to rozszerzenie Veriloga, które daje różne ciekawe możliwości. Wprowadzono między innymi operatory inkrementacji ++ oraz dekrementacji --, znane z C. SystemVerilog jest kompatybilny wstecz z Verilogiem.

Rysunek 1. Ekran EDA Playground po wykonaniu symulacji

W menu po lewej stronie musimy wybrać symulator, jaki chcemy zastosować. Do wyboru mamy kilka komercyjnych symulatorów oraz kilka darmowych. Każdy z nich powinien dać nam taki sam wynik symulacji, ale różnią się one komunikatami printowanymi w konsoli. Na początek można wybrać dowolny, na przykład Mentor Questa. Poniżej zaznacz opcję Open EPWave after run. Bez zaznaczenia tej opcji nie zobaczymy wykresu z przebiegami badanych sygnałów. W ustawieniach profilu możemy wybrać, czy chcemy, by wykres otwierał się w osobnym oknie przeglądarki czy ma być w tym samym, ale wtedy zasłania edytor kodu. Klikamy Run lub wciskamy kombinację klawiszy CTRL-ENTER i po chwili powinniśmy zobaczyć logi w konsoli, tak jak na rysunku 1 oraz przebiegi badanych sygnałów, jak na rysunku 2.

Rysunek 2. Przebiegi czasowe badanych sygnałów

Domyślnie w EDA Playground stosowana jest dość mała czcionka. Jeżeli uważasz, że powinna być większa, możesz powiększyć czcionkę, wciskając przycisk CTRL i kręcąc kółkiem myszki. Zwróć uwagę, że na początku rejestr Out ma wartość XX. Co to znaczy? Otóż po uruchomieniu symulacji wszystkie zmienne reg mają wartość nieznaną. Ma to symbolizować przypadkowy stan pamięci, która nie została prawidłowo zainicjalizowana. Rejestr Out przyjmuje wartość zerową dopiero wtedy, kiedy sygnał Reset przyjmie stan logiczny 0.

Istnieje jeszcze kilka ciekawych konstrukcji, które warto znać, a nie miałem pomysłu, jak je zastosować w listingu 1 i 2. Zobacz listing 4. Przykład z linii #1 pokazuje, jak zatrzymać wykonywanie instrukcji z bloku always lub initial. W takiej sytuacji inne bloki funkcjonują dalej. Instrukcja wait(warunek) zatrzymuje wykonywanie kodu tak długo, aż warunek podany w nawiasach zostanie spełniony. W linii #2 pokazano trochę inny sposób, przydatny do wykrywania zboczy sygnałów. Instrukcję @(warunek) już znamy z dotychczas analizowanych układów sekwencyjnych, które oczekiwały na zbocze rosnące sygnału zegarowego posedge Clock lub zbocze opadające sygnału resetującego negedge Reset. Dokładnie tę samą instrukcję możemy zastosować wewnątrz sekwencji testowej w testbenchu.

Listing 4. Inne instrukcje przydatne do symulacji

integer i;
reg [3:0] x;

// Sekwencja testowa
initial begin
(...)

// Czekaj, aż licznik osiągnie wartość 8’h20
wait(Out == 8’h20); // #1
$display("Out osiągnęło wartość 8’h20 w chwili %t", $realtime);

// Czekaj na zbocze rosnące na Out[7]
@(posedge Out[7]); // #2
$display("Zbocze rosnące na Out[7] wykryto w chwili %t", $realtime);

// Czekaj przez 10 taktów zegara niezależnie od jego częstotliwości
repeat(10) // #3
@(posedge Clk);
$display("10 taktów zegara później: %t", $realtime);

// W pętli for wylosuj 10 liczb z zakresu od 1 do 10
for(i = 0; i < 10; i = i + 1) begin // #4
x = $urandom_range(1, 10) // #5
$display("Liczba losowa: %d", x);
end

(...)
end

W testbenchach mamy możliwość zastosowania pętli. Najprostszą z nich jest pętla repeat(), która wykonuje się określoną liczbę razy. W przykładzie z linii #3 zademonstrowano, w jaki sposób za pomocą pętli repeat() zawiesić wykonywanie programu na 10 cykli zegarowych, niezależnie od tego, jaka jest częstotliwość zegara. W Verilogu mamy do dyspozycji także pętlę for(), znaną z C. Specjalnie napisałem C, a nie C++, ponieważ iterator pętli musi być utworzony przed pętlą, a nie w jej pierwszym wyrażeniu w nawiasach za słowem for. Iterator możemy wykorzystywać wewnątrz pętli. Przykład tej pętli przedstawiono w linii #4.

Podczas testów przydaje się także funkcja generująca liczby losowe. Służy do tego #urandom_range(min, max), która zwraca liczbę losową bez znaku z przedziału podanego w argumentach. Testbench może także automatycznie weryfikować, czy dane zwracane przez testowany moduł są prawidłowe, czy nie. Taka funkcjonalność jest przydatna w dużych projektach, kiedy chcemy automatycznie testować dużo różnych modułów, ale bez ręcznego oglądania logów i wykresów czasowych. Specjalne instrukcje sterujące mówią symulatorowi, że powinien poinformować użytkownika o jakimś ostrzeżeniu, błędzie lub błędzie krytycznym. Błąd krytyczny różni się od zwykłego błędu tym, że natychmiast zatrzymuje całą symulację. Zobacz przykłady wymienione na listingu 5.

Listing 5. Przykłady instrukcji zgłaszających ostrzeżenia i błędy
$info(0, “To jest informacja”);
$warning(0, “To jest ostrzeżenie”);
$error(0, “To jest błąd”);
$fatal(0, “To jest błąd krytyczny”);

Zachęcam do eksperymentów z symulatorem EDA Playground, aby nabrać biegłości przed przesiadką na ModelSim. Symulacja zdecydowanie oszczędza czas i pozwala śledzić wszystkie zmienne w każdej chwili. Pisanie testbenchy jest bardzo pomocne, nawet jeżeli wydaje nam się, że moduł jest prosty i powinien działać poprawnie.

Jest pewna sprawa, której nie poruszyliśmy w tym odcinku kursu, a ma ona kluczowe znaczenie w praktyce. Może nawet sprawić, że nasz kod w FPGA nie będzie działał, pomimo że w symulatorze działa wyśmienicie. Może tak się stać dlatego, że w dotychczasowych kodach symulacji nie uwzględniliśmy czasu propagacji bramek i przerzutników. Wszystkie instrukcje, umieszczone pomiędzy instrukcjami opóźnień #, wykonywały się w zerowym czasie. Aby symulacja jak najlepiej odzwierciedlała rzeczywistość, musimy poznać takie pojęcia jak setup time, hold time, slack i skew oraz zagłębić się w tematykę statycznej analizy czasowej. O tym będzie w kolejnych odcinkach kursu.

Dominik Bieczyński
leonow32@gmail.com

Artykuł ukazał się w
Elektronika Praktyczna
czerwiec 2023
Elektronika Praktyczna Plus lipiec - grudzień 2012

Elektronika Praktyczna Plus

Monograficzne wydania specjalne

Elektronik grudzień 2024

Elektronik

Magazyn elektroniki profesjonalnej

Raspberry Pi 2015

Raspberry Pi

Wykorzystaj wszystkie możliwości wyjątkowego minikomputera

Świat Radio styczeń - luty 2025

Świat Radio

Magazyn krótkofalowców i amatorów CB

Automatyka, Podzespoły, Aplikacje listopad - grudzień 2024

Automatyka, Podzespoły, Aplikacje

Technika i rynek systemów automatyki

Elektronika Praktyczna grudzień 2024

Elektronika Praktyczna

Międzynarodowy magazyn elektroników konstruktorów

Elektronika dla Wszystkich styczeń 2025

Elektronika dla Wszystkich

Interesująca elektronika dla pasjonatów