Szukaj…


Uwagi

VHDL jest akronimem złożonym dla VHSIC (Very High Speed Integrated Circuit) HDL (Hardware Description Language). Jako język opisu sprzętu służy przede wszystkim do opisywania lub modelowania obwodów. VHDL jest idealnym językiem do opisu obwodów, ponieważ oferuje konstrukcje językowe, które łatwo opisują zarówno współbieżne, jak i sekwencyjne zachowanie, a także model wykonawczy, który usuwa niejednoznaczności wprowadzone podczas modelowania zachowań współbieżnych.

VHDL jest zazwyczaj interpretowany w dwóch różnych kontekstach: do symulacji i syntezy. Interpretowany do syntezy kod jest konwertowany (syntetyzowany) na równoważne modelowane elementy sprzętowe. Tylko podzbiór VHDL jest zazwyczaj dostępny do użycia podczas syntezy, a obsługiwane konstrukcje językowe nie są standaryzowane; jest to funkcja zastosowanego silnika syntezy i docelowego urządzenia sprzętowego. Kiedy VHDL jest interpretowany do symulacji, dostępne są wszystkie konstrukcje językowe do modelowania zachowania sprzętu.

Wersje

Wersja Data wydania
IEEE 1076–1987 1988-03-31
IEEE 1076–1993 1994-06-06
IEEE 1076-2000 2000-01-30
IEEE 1076-2002 2002-05-17
IEEE 1076c-2007 2007-09-05
IEEE 1076-2008 26.01.2009

Instalacja lub konfiguracja

Program VHDL może być symulowany lub syntetyzowany. Symulacja jest najbardziej podobna do wykonywania w innych językach programowania. Synteza przekłada program VHDL na sieć bramek logicznych. Wiele narzędzi do symulacji i syntezy VHDL jest częścią komercyjnych pakietów Electronic Design Automation (EDA). Często obsługują także inne języki opisu sprzętu (HDL), takie jak Verilog, SystemVerilog lub SystemC. Istnieją pewne darmowe i otwarte aplikacje.

Symulacja VHDL

GHDL to prawdopodobnie najbardziej dojrzały darmowy i otwarty symulator VHDL. Występuje w trzech różnych smakach w zależności od zastosowanego backendu: gcc , llvm lub mcode . Poniższe przykłady pokazują, jak używać GHDL (wersja mcode ) i Modelsim, komercyjnego symulatora HDL firmy Mentor Graphics, w systemie operacyjnym GNU / Linux. W przypadku innych narzędzi i innych systemów operacyjnych sytuacja wyglądałaby bardzo podobnie.

Witaj świecie

Utwórz plik hello_world.vhd zawierający:

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;

Jednostka kompilująca VHDL to kompletny program VHDL, który można skompilować samodzielnie. Jednostki są jednostkami kompilacji VHDL, które służą do opisu zewnętrznego interfejsu obwodu cyfrowego, to znaczy jego portów wejściowych i wyjściowych. W naszym przykładzie entity nazywa się hello_world i jest pusta. Obwód, który modelujemy, jest czarną skrzynką, nie ma wejść ani wyjść. Architektury są innym rodzajem jednostek kompilacji. Są one zawsze powiązane z entity i służą do opisu zachowania obwodu cyfrowego. Jedna jednostka może mieć jedną lub więcej architektur opisujących jej zachowanie. W naszym przykładzie encja jest powiązana tylko z jedną architekturą o nazwie arc która zawiera tylko jedną instrukcję VHDL:

  assert false report "Hello world!" severity note;

Instrukcja zostanie wykonana na początku symulacji i wydrukuje Hello world! wiadomość na standardowym wyjściu. Symulacja zakończy się, ponieważ nie będzie już nic więcej do zrobienia. Plik źródłowy VHDL, który napisaliśmy, zawiera dwie jednostki kompilacji. Moglibyśmy je rozdzielić na dwa różne pliki, ale nie moglibyśmy podzielić żadnego z nich na różne pliki: jednostka kompilacji musi być całkowicie zawarta w jednym pliku źródłowym. Zauważ, że tej architektury nie można zsyntetyzować, ponieważ nie opisuje ona funkcji, którą można bezpośrednio przełożyć na bramki logiczne.

Przeanalizuj i uruchom program za pomocą GHDL:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work hello_world.vhd
$ ghdl -r --workdir=gh_work hello_world
hello_world.vhd:6:8:@0ms:(assertion note): Hello world!

Katalog gh_work to miejsce, w którym GHDL przechowuje generowane przez siebie pliki. Tak mówi opcja --workdir=gh_work . Faza analizy sprawdza poprawność składni i tworzy plik tekstowy opisujący jednostki kompilacji znalezione w pliku źródłowym. Faza uruchamiania faktycznie kompiluje, łączy i wykonuje program. Zauważ, że w mcode wersji ghdl żadne pliki binarne są generowane. Program jest rekompilowany za każdym razem, gdy go symulujemy. Wersje gcc lub llvm zachowują się inaczej. Zauważ też, że ghdl -r nie przyjmuje nazwy pliku źródłowego VHDL, podobnie jak ghdl -a , ale nazwę jednostki kompilacyjnej. W naszym przypadku przekazujemy mu nazwę entity . Ponieważ ma tylko jedną architecture , nie trzeba określać, która ma być symulowana.

Z Modelsim:

$ vlib ms_work
$ vmap work ms_work
$ vcom hello_world.vhd
$ vsim -c hello_world -do 'run -all; quit'
...
# ** Note: Hello world!
#    Time: 0 ns  Iteration: 0  Instance: /hello_world
...

vlib , vmap , vcom i vsim to cztery polecenia dostarczane przez Modelsim. vlib tworzy katalog ( ms_work ), w którym będą przechowywane wygenerowane pliki. vmap kojarzy katalog utworzony przez vlib z logiczną nazwą ( work ). vcom kompiluje plik źródłowy VHDL i domyślnie przechowuje wynik w katalogu powiązanym z logiczną nazwą work . Wreszcie vsim symuluje program i generuje ten sam rodzaj danych wyjściowych co GHDL. Zauważ ponownie, że pytanie o vsim nie jest plikiem źródłowym, ale nazwą już skompilowanej jednostki kompilacyjnej. Opcja -c nakazuje symulatorowi działanie w trybie wiersza poleceń zamiast domyślnego trybu graficznego interfejsu użytkownika (GUI). Opcja -do służy do przekazania skryptu TCL do wykonania po załadowaniu projektu. TCL to język skryptowy bardzo często używany w narzędziach EDA. Wartością opcji -do może być nazwa pliku lub, jak w naszym przykładzie, ciąg poleceń TCL. run -all; quit poinstruuj symulator, aby uruchomił symulację, dopóki naturalnie się nie skończy - lub na zawsze, jeśli będzie trwała wiecznie - a następnie wyjdzie.

Licznik synchroniczny

-- File counter.vhd
-- The entity is the interface part. It has a name and a set of input / output
-- ports. Ports have a name, a direction and a type. The bit type has only two
-- values: '0' and '1'. It is one of the standard types.
entity counter is
  port(
    clock: in  bit;    -- We are using the rising edge of CLOCK
    reset: in  bit;    -- Synchronous and active HIGH
    data:  out natural -- The current value of the counter
  );
end entity counter;

-- The architecture describes the internals. It is always associated
-- to an entity.
architecture sync of counter is
  -- The internal signals we use to count. Natural is another standard
  -- type. VHDL is not case sensitive.
  signal current_value: natural;
  signal NEXT_VALUE:    natural;
begin
  -- A process is a concurrent statement. It is an infinite loop.
  process
  begin
    -- The wait statement is a synchronization instruction. We wait
    -- until clock changes and its new value is '1' (a rising edge).
    wait until clock = '1';
    -- Our reset is synchronous: we consider it only at rising edges
    -- of our clock.
    if reset = '1' then
      -- <= is the signal assignment operator.
      current_value <= 0;
    else
      current_value <= next_value;
    end if;
  end process;

  -- Another process. The sensitivity list is another way to express
  -- synchronization constraints. It (approximately) means: wait until
  -- one of the signals in the list changes and then execute the process
  -- body. Sensitivity list and wait statements cannot be used together 
  -- in the same process.
  process(current_value)
  begin
    next_value <= current_value + 1;
  end process;

  -- A concurrent signal assignment, which is just a shorthand for the
  -- (trivial) equivalent process.
  data <= current_value;
end architecture sync;

Witaj świecie

Istnieje wiele sposobów na wydrukowanie klasycznego „Witaj świecie!” wiadomość w VHDL. Najprostszym ze wszystkich jest prawdopodobnie coś takiego:

-- File hello_world.vhd
entity hello_world is
end entity hello_world;

architecture arc of hello_world is
begin
  assert false report "Hello world!" severity note;
end architecture arc;

Środowisko symulacji dla licznika synchronicznego

Środowiska symulacyjne

Środowisko symulacyjne dla projektu VHDL (projekt w trakcie testowania lub test testowy) to kolejny projekt VHDL, który co najmniej:

  • Deklaruje sygnały odpowiadające portom wejściowym i wyjściowym testowanego urządzenia.
  • Tworzy instancję DUT i łączy jego porty z zadeklarowanymi sygnałami.
  • Tworzy instancję procesów sterujących sygnałami podłączonymi do portów wejściowych testowanego urządzenia.

Opcjonalnie środowisko symulacyjne może tworzyć instancje inne niż DUT, takie jak na przykład generatory ruchu na interfejsach, monitory sprawdzające protokoły komunikacyjne, automatyczne weryfikatory wyjść DUT ...

Środowisko symulacji jest analizowane, opracowywane i wykonywane. Większość symulatorów oferuje możliwość wyboru zestawu sygnałów do obserwacji, wykreślenia ich przebiegów graficznych, umieszczenia punktów przerwania w kodzie źródłowym, przejścia do kodu źródłowego ...

Idealnie, środowisko symulacyjne powinno być użyteczne jako solidny test bez regresji, to znaczy, powinno ono automatycznie wykrywać naruszenia specyfikacji DUT, zgłaszać przydatne komunikaty o błędach i gwarantować rozsądne pokrycie funkcji DUT. Gdy takie środowiska symulacyjne są dostępne, można je ponownie uruchomić przy każdej zmianie testowanego urządzenia, aby sprawdzić, czy jest ono funkcjonalnie poprawne, bez potrzeby żmudnych i podatnych na błędy kontroli wizualnych śladów symulacji.

W praktyce projektowanie idealnych, a nawet dobrych środowisk symulacyjnych jest trudne. Często jest to, a nawet trudniejsze, niż zaprojektowanie samego DUT.

W tym przykładzie przedstawiamy środowisko symulacyjne dla przykładu licznika synchronicznego . Pokażemy jak go uruchomić za pomocą ghdl i Modelsim i jak obserwować przebiegi graficzne za pomocą GTKWave z ghdl i wbudowanego w przeglądarkę fali z Modelsim. Następnie omawiamy interesujący aspekt symulacji: jak je zatrzymać?

Pierwsze środowisko symulacyjne dla licznika synchronicznego

Licznik synchroniczny ma dwa porty wejściowe i jeden port wyjściowy. Bardzo prostym środowiskiem symulacyjnym może być:

-- File counter_sim.vhd
-- Entities of simulation environments are frequently black boxes without
-- ports.
entity counter_sim is
end entity counter_sim;

architecture sim of counter_sim is

  -- One signal per port of the DUT. Signals can have the same name as
  -- the corresponding port but they do not need to.
  signal clk:  bit;
  signal rst:  bit;
  signal data: natural;

begin

  -- Instantiation of the DUT
  u0: entity work.counter(sync)
  port map(
    clock => clk,
    reset => rst,
    data  => data
  );

  -- A clock generating process with a 2ns clock period. The process
  -- being an infinite loop, the clock will never stop toggling.
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
  end process;

  -- The process that handles the reset: active from beginning of
  -- simulation until the 5th rising edge of the clock.
  process
  begin
    rst  <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst  <= '0';
    wait; -- Eternal wait. Stops the process forever.
  end process;

end architecture sim;

Symulacja z GHDL

Kompilujmy i symulujmy to za pomocą GHDL:

$ mkdir gh_work
$ ghdl -a --workdir=gh_work counter_sim.vhd
counter_sim.vhd:27:24: unit "counter" not found in 'library "work"'
counter_sim.vhd:50:35: no declaration for "rising_edge"

Następnie komunikaty o błędach mówią nam dwie ważne rzeczy:

  • Analizator GHDL odkrył, że nasz projekt tworzy instancję o nazwie counter ale ta jednostka nie została znaleziona w work biblioteki. Jest tak, ponieważ nie skompilowaliśmy counter przed counter_sim . Podczas kompilacji VHDL wzorów, które podmioty instancji, poziom dolny zawsze musi być skompilowany przed najwyższych szczeblach (hierarchiczne projekty mogą być również zestawiane odgórnie, ale tylko wtedy, gdy instancję component , a nie podmioty).
  • Funkcja rising_edge używana w naszym projekcie nie jest zdefiniowana. Wynika to z faktu, że funkcja ta została wprowadzona w VHDL 2008 i nie powiedzieliśmy GHDL, aby używała tej wersji języka (domyślnie używa VHDL 1993 z tolerancją składni VHDL 1987).

Naprawmy dwa błędy i uruchom symulację:

$ ghdl -a --workdir=gh_work --std=08 counter.vhd counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
^C

Zauważ, że do analizy i symulacji potrzebna jest opcja --std=08 . Zauważ też, że uruchomiliśmy symulację na encji counter_sim , architekturze sim , a nie na pliku źródłowym.

Ponieważ nasze środowisko symulacji ma niekończący się proces (proces generujący zegar), symulacja nie zatrzymuje się i musimy przerwać ją ręcznie. Zamiast tego możemy określić czas zatrzymania za pomocą opcji --stop-time :

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns
ghdl:info: simulation stopped by --stop-time

Ponieważ symulacja nie mówi nam wiele o zachowaniu naszego testera maszynowego. Zrzućmy zmiany wartości sygnałów w pliku:

$ ghdl -r --workdir=gh_work --std=08 counter_sim sim --stop-time=60ns --vcd=counter_sim.vcd
Vcd.Avhpi_Error!
ghdl:info: simulation stopped by --stop-time

(zignoruj komunikat o błędzie, jest to coś, co należy naprawić w GHDL i to nie ma konsekwencji). Plik counter_sim.vcd został utworzony. Zawiera w formacie VCD (ASCII) wszystkie zmiany sygnałów podczas symulacji. GTKWave może nam pokazać odpowiednie przebiegi graficzne:

$ gtkwave counter_sim.vcd

gdzie możemy zobaczyć, że licznik działa zgodnie z oczekiwaniami.

Kształt fali GTKWave

Symulacja przy pomocy Modelsim

Zasada jest dokładnie taka sama w Modelsim:

$ vlib ms_work
...
$ vmap work ms_work
...
$ vcom -nologo -quiet -2008 counter.vhd counter_sim.vhd
$ vsim -voptargs="+acc" 'counter_sim(sim)' -do 'add wave /*; run 60ns'

wprowadź opis zdjęcia tutaj

Zauważ, że -voptargs="+acc" przekazana do vsim : zapobiega symulacji optymalizacji sygnału data i pozwala nam zobaczyć go na przebiegach.

Pełne wdzięku symulacje

W przypadku obu symulatorów musieliśmy przerwać niekończącą się symulację lub określić czas zatrzymania za pomocą dedykowanej opcji. To nie jest zbyt wygodne. W wielu przypadkach czas zakończenia symulacji jest trudny do przewidzenia. Znacznie lepiej byłoby zatrzymać symulację z wnętrza kodu VHDL środowiska symulacji, gdy zostanie spełniony określony warunek, na przykład gdy bieżąca wartość licznika osiągnie 20. Można to osiągnąć, twierdząc w proces, który obsługuje reset:

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    loop
      wait until rising_edge(clk);
      assert data /= 20 report "End of simulation" severity failure;
    end loop;
  end process;

Dopóki data różnią się od 20, symulacja trwa. Gdy data osiągną 20, symulacja ulega awarii z komunikatem o błędzie:

$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:90:24:@51ns:(assertion failure): End of simulation
ghdl:error: assertion failed
  from: process work.counter_sim(sim2).P1 at counter_sim.vhd:90
ghdl:error: simulation failed

Zauważ, że ponownie skompilowaliśmy tylko środowisko symulacyjne: jest to jedyny projekt, który się zmienił i jest to najwyższy poziom. Gdybyśmy zmodyfikowali tylko counter.vhd , musielibyśmy ponownie skompilować zarówno: counter.vhd ponieważ się zmienił, jak i counter_sim.vhd ponieważ zależy od counter.vhd .

Awaria symulacji z komunikatem o błędzie nie jest zbyt elegancka. Problemem może być nawet automatyczne analizowanie komunikatów symulacji w celu ustalenia, czy automatyczny test nie-regresyjny przeszedł pomyślnie. Lepszym i znacznie bardziej eleganckim rozwiązaniem jest zatrzymanie wszystkich procesów po osiągnięciu określonego warunku. Można to zrobić na przykład przez dodanie boolean sygnału końca symulacji ( eof ). Domyślnie jest inicjowany na false na początku symulacji. Jeden z naszych procesów sprawi, że stanie się to true gdy nadejdzie czas zakończenia symulacji. Wszystkie pozostałe procesy będą monitorować ten sygnał i zatrzymają się na wieczne wait kiedy się true :

  signal eos:  boolean;
...
  process
  begin
    clk <= '0';
    wait for 1 ns;
    clk <= '1';
    wait for 1 ns;
    if eos then
      report "End of simulation";
      wait;
    end if;
  end process;

  process
  begin
    rst <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    eos <= true;
    wait;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
counter_sim.vhd:120:24:@50ns:(report note): End of simulation

Wreszcie, w VHDL 2008 wprowadzono jeszcze lepsze rozwiązanie ze standardowym pakietem env oraz deklarowanymi przez niego procedurami stop i finish :

use std.env.all;
...
  process
  begin
    rst    <= '1';
    for i in 1 to 5 loop
      wait until rising_edge(clk);
    end loop;
    rst    <= '0';
    for i in 1 to 20 loop
      wait until rising_edge(clk);
    end loop;
    finish;
  end process;
$ ghdl -a --workdir=gh_work --std=08 counter_sim.vhd
$ ghdl -r --workdir=gh_work --std=08 counter_sim sim
simulation finished @49ns

Sygnały a zmienne, krótki przegląd semantyki symulacji VHDL

Ten przykład dotyczy jednego z najbardziej podstawowych aspektów języka VHDL: semantyki symulacji. Jest przeznaczony dla początkujących w VHDL i przedstawia uproszczony widok, w którym pominięto wiele szczegółów (odroczone procesy, interfejs proceduralny VHDL, wspólne zmienne ...) Czytelnicy zainteresowani prawdziwą kompletną semantyką powinni zapoznać się z podręcznikiem językowym (LRM).

Sygnały i zmienne

Większość klasycznych imperatywnych języków programowania używa zmiennych. Są to kontenery wartości. Operator przypisania służy do przechowywania wartości w zmiennej:

a = 15;

wartość przechowywaną obecnie w zmiennej można odczytać i wykorzystać w innych instrukcjach:

if(a == 15) { print "Fifteen" }

VHDL używa również zmiennych i mają one dokładnie taką samą rolę jak w większości imperatywnych języków. Ale VHDL oferuje również inny rodzaj kontenera wartości: sygnał. Sygnały również przechowują wartości, można je także przypisywać i odczytywać. Typ wartości, które można przechowywać w sygnałach, jest (prawie) taki sam jak w zmiennych.

Po co więc mieć dwa rodzaje kontenerów wartości? Odpowiedź na to pytanie jest niezbędna i stanowi istotę języka. Zrozumienie różnicy między zmiennymi a sygnałami jest pierwszą rzeczą do zrobienia przed próbą zaprogramowania czegokolwiek w VHDL.

Zilustrujmy tę różnicę na konkretnym przykładzie: zamianie.

Uwaga: wszystkie następujące fragmenty kodu są częścią procesów. Zobaczymy później, jakie są procesy.

    tmp := a;
    a   := b;
    b   := tmp;

zamienia zmienne a i b . Po wykonaniu tych 3 instrukcji nowa zawartość a jest starą zawartością b i odwrotnie. Podobnie jak w większości języków programowania, potrzebna jest trzecia zmienna tymczasowa ( tmp ). Gdyby zamiast zmiennych chcieliśmy zamienić sygnały, napisalibyśmy:

    r <= s;
    s <= r;

lub:

    s <= r;
    r <= s;

z tym samym rezultatem i bez potrzeby trzeciego tymczasowego sygnału!

Uwaga: operator przypisania sygnału VHDL <= różni się od operatora przypisania zmiennej := .

Spójrzmy na drugi przykład, w którym zakładamy, że podprogram print drukuje dziesiętną reprezentację jego parametru. Jeśli a jest zmienną całkowitą, a jej bieżąca wartość wynosi 15, wykonanie:

    a := 2 * a;
    a := a - 5;
    a := a / 5;
    print(a);

wydrukuje:

5

Jeśli mamy wykonać ten krok po kroku w debuggera widzimy wartości a zmieniającym się od początkowego 15 do 30, 25 i wreszcie 5.

Ale jeśli s jest sygnałem liczby całkowitej, a jego bieżąca wartość wynosi 15, wykonanie:

    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);

wydrukuje:

15
3

Jeśli wykonamy ten krok po kroku w debuggerze, nie zobaczymy żadnej zmiany wartości s dopóki nie pojawi się instrukcja wait . Co więcej, ostateczna wartość s nie będzie wynosić 15, 30, 25 lub 5, ale 3!

To pozornie dziwne zachowanie wynika z zasadniczo równoległej natury sprzętu cyfrowego, co zobaczymy w kolejnych sekcjach.

Równoległość

VHDL jest sprzętowym językiem opisu (HDL), z natury jest równoległy. Program VHDL to zbiór programów sekwencyjnych, które działają równolegle. Te programy sekwencyjne nazywane są procesami:

P1: process
begin
  instruction1;
  instruction2;
  ...
  instructionN;
end process P1;

P2: process
begin
  ...
end process P2;

Procesy, podobnie jak sprzęt, który modelują, nigdy się nie kończą: są nieskończonymi pętlami. Po wykonaniu ostatniej instrukcji wykonywanie jest kontynuowane od pierwszej.

Podobnie jak w przypadku każdego języka programowania, który obsługuje taką czy inną formę równoległości, harmonogram decyduje o tym, który proces wykonać (i kiedy) podczas symulacji VHDL. Ponadto język oferuje specyficzne konstrukcje do komunikacji i synchronizacji między procesami.

Planowanie

Program planujący utrzymuje listę wszystkich procesów i dla każdego z nich rejestruje bieżący stan, który może być running , run-able lub suspended . Istnieje najwyżej jeden proces w stanie running : ten, który jest aktualnie wykonywany. Tak długo, jak aktualnie uruchomiony proces nie wykonuje instrukcji wait , kontynuuje działanie i uniemożliwia wykonanie jakiegokolwiek innego procesu. Program planujący VHDL nie ma charakteru zapobiegawczego: każdy proces ma obowiązek zawiesić się i zezwolić na uruchomienie innych procesów. Jest to jeden z problemów, które często napotykają początkujący VHDL: proces swobodnego działania.

  P3: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    r <= a;
  end process P3;

Uwaga: zmienna a jest deklarowana lokalnie, podczas gdy sygnały s i r są deklarowane gdzie indziej, na wyższym poziomie. Zmienne VHDL są lokalne dla procesu, który je deklaruje i nie mogą być widoczne dla innych procesów. Inny proces mógłby również zadeklarować zmienną o nazwie a , nie byłaby to ta sama zmienna, co zmienna procesu P3 .

Gdy tylko harmonogram wznowi proces P3 , symulacja utknie, aktualny czas symulacji nie będzie już postępował, a jedynym sposobem na zatrzymanie tego jest zabicie lub przerwanie symulacji. Powodem jest to, że P3 nie wait oświadczenie i tym samym pozostanie w running stan wiecznie, zapętlenie nad jego 3 instrukcji. Żaden inny proces nigdy nie będzie miał szansy na uruchomienie, nawet jeśli można go run-able .

Nawet procesy zawierające instrukcję wait mogą powodować ten sam problem:

  P4: process
    variable a: integer;
  begin
    a := s;
    a := 2 * a;
    if a = 16 then
      wait on s;
    end if;
    r <= a;
  end process P4;

Uwaga: operatorem równości VHDL jest = .

Jeśli proces P4 zostanie wznowiony, a wartość sygnału s wynosi 3, będzie on działał wiecznie, ponieważ warunek a = 16 nigdy nie będzie prawdziwy.

Załóżmy, że nasz program VHDL nie zawiera takich patologicznych procesów. Gdy uruchomiony proces wykonuje instrukcję wait , jest ona natychmiast zawieszana, a program planujący ustawia ją w stan suspended . Instrukcja wait również spełnia warunek ponownego run-able procesu. Przykład:

    wait on s;

środki zawiesza mi aż wartość sygnału s zmian. Ten warunek jest rejestrowany przez program planujący. Następnie program planujący wybiera inny proces spośród możliwych do run-able , ustawia go w stanie running i wykonuje. To samo powtarza się, aż wszystkie run-able procesy zostaną wykonane i zawieszone.

Ważna uwaga: gdy można run-able kilka procesów, standard VHDL nie określa, w jaki sposób program planujący powinien wybrać, który ma zostać uruchomiony. Konsekwencją jest to, że w zależności od symulatora, wersji symulatora, systemu operacyjnego lub czegokolwiek innego dwie symulacje tego samego modelu VHDL mogą w pewnym momencie dokonać różnych wyborów i wybrać inny proces do wykonania. Gdyby wybór ten miał wpływ na wyniki symulacji, moglibyśmy powiedzieć, że VHDL jest niedeterministyczny. Ponieważ niedeterminizm jest zwykle niepożądany, odpowiedzialnością programistów byłoby unikanie sytuacji niedeterministycznych. Na szczęście VHDL zajmuje się tym i to właśnie tam sygnały wchodzą do obrazu.

Sygnały i komunikacja międzyprocesowa

VHDL pozwala uniknąć niedeterminizmu, wykorzystując dwie specyficzne cechy:

  1. Procesy mogą wymieniać informacje tylko za pomocą sygnałów
  signal r, s: integer;  -- Common to all processes
...
  P5: process
    variable a: integer; -- Different from variable a of process P6
  begin
    a := s + 1;
    r <= a;
    a := r + 1;
    wait on s;
  end process P5;

  P6: process
    variable a: integer; -- Different from variable a of process P5
  begin
    a := r + 1;
    s <= a;
    wait on r;
  end process P6;

Uwaga: komentarze VHDL rozciągają się od -- do końca linii.

  1. Wartość sygnału VHDL nie zmienia się podczas wykonywania procesów

Za każdym razem, gdy przypisywany jest sygnał, przypisana wartość jest rejestrowana przez program planujący, ale bieżąca wartość sygnału pozostaje niezmieniona. Jest to kolejna ważna różnica w przypadku zmiennych, które przyjmują nową wartość natychmiast po przypisaniu.

Spójrzmy na wykonanie procesu P5 powyżej i załóżmy, że a=5 , s=1 i r=0 gdy zostanie wznowione przez program planujący. Po wykonaniu instrukcji a := s + 1; , wartość zmiennej a zmienia się i staje się 2 (1 + 1). Podczas wykonywania następnej instrukcji r <= a; jest to nowa wartość a (2) przypisana do r . Ale r jest sygnałem, aktualna wartość r wynosi nadal 0. Zatem, wykonując a := r + 1; , zmienna a przyjmuje (natychmiast) wartość 1 (0 + 1), a nie 3 (2 + 1), jak by to mówiła intuicja.

Kiedy sygnał r naprawdę przyjmie nową wartość? Kiedy program planujący wykona wszystkie działające procesy i wszystkie zostaną zawieszone. Jest to również określane jako: po jednym cyklu delta . Dopiero wtedy program planujący przyjrzy się wszystkim wartościom przypisanym do sygnałów i faktycznie zaktualizuje wartości sygnałów. Symulacja VHDL jest naprzemienną fazą wykonania i fazą aktualizacji sygnału. Podczas faz wykonania wartość sygnałów jest zamrożona. Symbolicznie mówimy, że między fazą wykonania a następną fazą aktualizacji sygnału upłynęła delta czasu. To nie jest czas rzeczywisty. Cykl delta nie ma fizycznego czasu trwania.

Dzięki temu mechanizmowi opóźnionej aktualizacji sygnału VHDL jest deterministyczny. Procesy mogą komunikować się tylko z sygnałami, a sygnały nie zmieniają się podczas wykonywania procesów. Kolejność wykonywania procesów nie ma zatem znaczenia: ich środowisko zewnętrzne (sygnały) nie zmienia się podczas wykonywania. Pokażmy to na poprzednim przykładzie z procesami P5 i P6 , gdzie stan początkowy to P5.a=5 , P6.a=10 , s=17 , r=0 i gdzie planista decyduje się na uruchomienie najpierw P5 a następnie P6 . Poniższa tabela pokazuje wartość dwóch zmiennych, bieżącą i następną wartość sygnałów po wykonaniu każdej instrukcji każdego procesu:

proces / instrukcja P5.a P6.a s.current s.next r.current r.next
Stan początkowy 5 10 17 0
P5 / a := s + 1 18 10 17 0
P5 / r <= a 18 10 17 0 18
P5 / a := r + 1 1 10 17 0 18
P5 / wait on s 1 10 17 0 18
P6 / a := r + 1 1 1 17 0 18
P6 / s <= a 1 1 17 1 0 18
P6 / wait on r 1 1 17 1 0 18
Po aktualizacji sygnału 1 1 1 18

Przy tych samych warunkach początkowych, jeśli program planujący zdecyduje się najpierw uruchomić P6 a następnie P5 :

proces / instrukcja P5.a P6.a s.current s.next r.current r.next
Stan początkowy 5 10 17 0
P6 / a := r + 1 5 1 17 0
P6 / s <= a 5 1 17 1 0
P6 / wait on r 5 1 17 1 0
P5 / a := s + 1 18 1 17 1 0
P5 / r <= a 18 1 17 1 0 18
P5 / a := r + 1 1 1 17 1 0 18
P5 / wait on s 1 1 17 1 0 18
Po aktualizacji sygnału 1 1 1 18

Jak widać, po wykonaniu naszych dwóch procesów wynik jest taki sam bez względu na kolejność wykonywania.

Ta sprzeczna z intuicją semantyka przypisywania sygnałów jest przyczyną drugiego rodzaju problemów, które często napotykają początkujący VHDL: przypisanie, które najwyraźniej nie działa, ponieważ jest opóźnione o jeden cykl delta. Podczas prowadzenia procesu P5 krok po kroku, w debugera, gdy r przypisano 18 i przypisano a r + 1 , można oczekiwać, że wartość to 19 jednak debugera uporczywie mówi, że a r=0 i a=1 ...

Uwaga: ten sam sygnał może być przypisany kilka razy podczas tej samej fazy wykonania. W tym przypadku to ostatnie przypisanie decyduje o następnej wartości sygnału. Pozostałe zadania nie mają żadnego efektu, tak jakby nigdy nie zostały wykonane.

Czas sprawdzić nasze zrozumienie: wróć do naszego pierwszego przykładu zamiany i spróbuj zrozumieć, dlaczego:

  process
  begin
    ---
    s <= r;
    r <= s;
    ---
  end process;

faktycznie zamienia sygnały r i s bez potrzeby trzeciego sygnału czasowego i dlaczego:

  process
  begin
    ---
    r <= s;
    s <= r;
    ---
  end process;

byłoby ściśle równoważne. Spróbuj także zrozumieć, dlaczego, jeśli s jest sygnałem całkowitym, a jego bieżąca wartość wynosi 15, a my wykonujemy:

  process
  begin
    ---
    s <= 2 * s;
    s <= s - 5;
    s <= s / 5;
    print(s);
    wait on s;
    print(s);
    ---
  end process;

dwa pierwsze przypisania sygnału s nie działają, dlaczego s jest ostatecznie przypisane 3 i dlaczego dwie drukowane wartości to 15 i 3.

Czas fizyczny

Aby modelować sprzęt, bardzo przydatna jest możliwość modelowania czasu fizycznego wymaganego przez niektóre operacje. Oto przykład tego, jak można to zrobić w VHDL. Przykład modeluje licznik synchroniczny i jest to pełny, samodzielny kod VHDL, który można skompilować i zasymulować:

-- File counter.vhd
entity counter is
end entity counter;

architecture arc of counter is
  signal clk: bit; -- Type bit has two values: '0' and '1'
  signal c, nc: natural; -- Natural (non-negative) integers
begin
  P1: process
  begin
    clk <= '0';
    wait for 10 ns; -- Ten nano-seconds delay
    clk <= '1';
    wait for 10 ns; -- Ten nano-seconds delay
  end process P1;

  P2: process
  begin
    if clk = '1' and clk'event then
      c <= nc;
    end if;
    wait on clk;
  end process P2;

  P3: process
  begin
    nc <= c + 1 after 5 ns; -- Five nano-seconds delay
    wait on c;
  end process P3;
end architecture arc;

W procesie P1 instrukcja wait nie jest używana do czekania, aż wartość sygnału zmieni się, jak widzieliśmy do tej pory, ale do oczekiwania przez określony czas. Ten proces modeluje generator zegara. Sygnał clk jest zegarem naszego systemu, jest okresowy z okresem 20 ns (50 MHz) i ma cykl pracy.

Proces P2 modeluje rejestr, który, jeśli pojawiło się zbocze narastające clk , przypisuje wartość swojego wejścia nc do wyjścia c a następnie czeka na następną zmianę wartości clk .

Proces P3 modeluje inkrementator, który przypisuje wartość swojego wejścia c , zwiększoną o jeden, do jego wyjścia nc ... z fizycznym opóźnieniem 5 ns. Następnie czeka, aż zmieni się wartość jego wejścia c . To jest także nowe. Do tej pory zawsze przypisywaliśmy sygnały za pomocą:

  s <= value;

z powodów wyjaśnionych w poprzednich sekcjach możemy pośrednio przełożyć się na:

  s <= value; -- after delta

Ten mały cyfrowy system sprzętowy może być reprezentowany przez następujący rysunek:

Licznik synchroniczny

Wraz z wprowadzeniem czasu fizycznego i wiedząc, że mamy również czas symboliczny mierzony w delcie , mamy teraz dwuwymiarowy czas, który oznaczymy T+D gdzie T jest czasem fizycznym mierzonym w nanosekundach, a D liczbą delt (bez fizycznego czasu trwania).

Pełny obraz

Jest jeden ważny aspekt symulacji VHDL, o którym jeszcze nie rozmawialiśmy: po fazie wykonania wszystkie procesy są w stanie suspended . Nieformalnie stwierdziliśmy, że program planujący następnie aktualizuje wartości przypisanych sygnałów. Ale czy w naszym przykładzie licznika synchronicznego ma on jednocześnie aktualizować sygnały clk , c i nc ? Co z fizycznymi opóźnieniami? A co stanie się dalej ze wszystkimi procesami w stanie suspended a żaden w run-able ?

Kompletny (ale uproszczony) algorytm symulacji jest następujący:

  1. Inicjalizacja
    • Ustaw aktualny czas Tc na 0 + 0 (0 ns, 0 cykl delta)
    • Zainicjuj wszystkie sygnały.
    • Wykonuj każdy proces, aż zawiesi się na instrukcji wait .
      • Zapisz wartości i opóźnienia przypisań sygnałów.
      • Zapisz warunki wznowienia procesu (opóźnienie lub zmiana sygnału).
    • Oblicz następnym razem Tn jako najwcześniejszy z:
      • Czas wznowienia procesów zawieszony przez wait for <delay> .
      • Następnym razem, kiedy wartość sygnału powinna się zmienić.
  1. Cykl symulacji
    • Tc=Tn .
    • Zaktualizuj sygnały, które muszą być.
    • Ustaw w run-able wykonalnym wszystkie procesy, które czekały na zmianę wartości jednego z zaktualizowanych sygnałów.
    • Ustaw w run-able wykonalnym wszystkie procesy, które zostały zawieszone przez instrukcję wait for <delay> i dla których czas wznowienia wynosi Tc .
    • Wykonuj wszystkie działające procesy do momentu ich zawieszenia.
      • Zapisz wartości i opóźnienia przypisań sygnałów.
      • Zapisz warunki wznowienia procesu (opóźnienie lub zmiana sygnału).
    • Oblicz następnym razem Tn jako najwcześniejszy z:
      • Czas wznowienia procesów zawieszony przez wait for <delay> .
      • Następnym razem, kiedy wartość sygnału powinna się zmienić.
    • Jeśli Tn jest nieskończonością, zatrzymaj symulację. W przeciwnym razie rozpocznij nowy cykl symulacji.

Symulacja ręczna

Podsumowując, przeprowadźmy teraz ręcznie uproszczony algorytm symulacji na liczniku synchronicznym przedstawionym powyżej. Dowolnie decydujemy, że gdy kilka procesów będzie uruchomionych, kolejność będzie P3 > P2 > P1 . Poniższe tabele przedstawiają ewolucję stanu systemu podczas inicjalizacji i pierwszych cykli symulacji. Każdy sygnał ma własną kolumnę, w której wskazana jest bieżąca wartość. Po wykonaniu przypisania sygnału, wartość zaplanowana jest dołączana do bieżącej wartości, np. a/b@T+D jeśli bieżącą wartością jest a a następną wartością będzie b w czasie T+D (czas fizyczny plus cykle delta) . 3 ostatnie kolumny wskazują warunek wznowienia zawieszonych procesów (nazwa sygnałów, które muszą się zmienić lub czas, w którym proces zostanie wznowiony).

Faza inicjalizacji:

Operacje Tc Tn clk c nc P1 P2 P3
Ustaw aktualny czas 0 + 0
Zainicjuj wszystkie sygnały 0 + 0 „0” 0 0
P3/nc<=c+1 after 5 ns 0 + 0 „0” 0 0/1 przy 5 + 0
P3/wait on c 0 + 0 „0” 0 0/1 przy 5 + 0 c
P2/if clk='1'... 0 + 0 „0” 0 0/1 przy 5 + 0 c
P2/end if 0 + 0 „0” 0 0/1 przy 5 + 0 c
P2/wait on clk 0 + 0 „0” 0 0/1 przy 5 + 0 clk c
P1/clk<='0' 0 + 0 „0” / „0” @ 0 + 1 0 0/1 przy 5 + 0 clk c
P1/wait for 10 ns 0 + 0 „0” / „0” @ 0 + 1 0 0/1 przy 5 + 0 10 + 0 clk c
Oblicz następnym razem 0 + 0 0 + 1 „0” / „0” @ 0 + 1 0 0/1 przy 5 + 0 10 + 0 clk c

Cykl symulacji nr 1

Operacje Tc Tn clk c nc P1 P2 P3
Ustaw aktualny czas 0 + 1 „0” / „0” @ 0 + 1 0 0/1 przy 5 + 0 10 + 0 clk c
Aktualizuj sygnały 0 + 1 „0” 0 0/1 przy 5 + 0 10 + 0 clk c
Oblicz następnym razem 0 + 1 5 + 0 „0” 0 0/1 przy 5 + 0 10 + 0 clk c

Uwaga: podczas pierwszego cyklu symulacji nie ma fazy wykonania, ponieważ żaden z naszych 3 procesów nie spełnia warunku wznowienia. P2 czeka na zmianę wartości clk i nastąpiła transakcja na clk , ale ponieważ stare i nowe wartości są takie same, nie jest to zmiana wartości.

Cykl symulacji nr 2

Operacje Tc Tn clk c nc P1 P2 P3
Ustaw aktualny czas 5 + 0 „0” 0 0/1 przy 5 + 0 10 + 0 clk c
Aktualizuj sygnały 5 + 0 „0” 0 1 10 + 0 clk c
Oblicz następnym razem 5 + 0 10 + 0 „0” 0 1 10 + 0 clk c

Uwaga: ponownie nie ma fazy wykonania. nc zmieniło się, ale na nc nie czeka proces.

Cykl symulacji nr 3

Operacje Tc Tn clk c nc P1 P2 P3
Ustaw aktualny czas 10 + 0 „0” 0 1 10 + 0 clk c
Aktualizuj sygnały 10 + 0 „0” 0 1 10 + 0 clk c
P1/clk<='1' 10 + 0 „0” / „1” przy 10 + 1 0 1 clk c
P1/wait for 10 ns 10 + 0 „0” / „1” przy 10 + 1 0 1 20 + 0 clk c
Oblicz następnym razem 10 + 0 10 + 1 „0” / „1” przy 10 + 1 0 1 20 + 0 clk c

Cykl symulacji # 4

Operacje Tc Tn clk c nc P1 P2 P3
Ustaw aktualny czas 10 + 1 „0” / „1” przy 10 + 1 0 1 20 + 0 clk c
Aktualizuj sygnały 10 + 1 „1” 0 1 20 + 0 clk c
P2/if clk='1'... 10 + 1 „1” 0 1 20 + 0 c
P2/c<=nc 10 + 1 „1” 0/1 przy 10 + 2 1 20 + 0 c
P2/end if 10 + 1 „1” 0/1 przy 10 + 2 1 20 + 0 c
P2/wait on clk 10 + 1 „1” 0/1 przy 10 + 2 1 20 + 0 clk c
Oblicz następnym razem 10 + 1 10 + 2 „1” 0/1 przy 10 + 2 1 20 + 0 clk c

Cykl symulacji nr 5

Operacje Tc Tn clk c nc P1 P2 P3
Ustaw aktualny czas 10 + 2 „1” 0/1 przy 10 + 2 1 20 + 0 clk c
Aktualizuj sygnały 10 + 2 „1” 1 1 20 + 0 clk c
P3/nc<=c+1 after 5 ns 10 + 2 „1” 1 1/2 @ 15 + 0 20 + 0 clk
P3/wait on c 10 + 2 „1” 1 1/2 @ 15 + 0 20 + 0 clk c
Oblicz następnym razem 10 + 2 15 + 0 „1” 1 1/2 @ 15 + 0 20 + 0 clk c

Uwaga: można by pomyśleć, że aktualizacja nc będzie zaplanowana na 15+2 , podczas gdy my zaplanujemy na 15+0 . Po dodaniu niezerowego opóźnienia fizycznego (tutaj 5 ns ) do bieżącego czasu ( 10+2 ), cykle delta znikają. Rzeczywiście, cykle delta są użyteczne tylko do rozróżnienia różnych czasów symulacji T+0 , T+1 ... z tym samym czasem fizycznym T Gdy tylko zmienia się czas fizyczny, cykle delta można zresetować.

Cykl symulacji # 6

Operacje Tc Tn clk c nc P1 P2 P3
Ustaw aktualny czas 15 + 0 „1” 1 1/2 @ 15 + 0 20 + 0 clk c
Aktualizuj sygnały 15 + 0 „1” 1 2) 20 + 0 clk c
Oblicz następnym razem 15 + 0 20 + 0 „1” 1 2) 20 + 0 clk c

Uwaga: ponownie nie ma fazy wykonania. nc zmieniło się, ale na nc nie czeka proces.

Symulacja ... przełącz się na angielski, aby kontynuować czytanie

Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow