Menu

Sprzęt a oprogramowanie: czym Verilog różni się od C i Pythona

Dlaczego po językach programowania Verilog wprawia w zakłopotanie: domyślna współbieżność, czas jako pojęcie pierwszej klasy i instrukcje, które nie wykonują się po kolei.

Na tej stronie są działające edytory: edytuj, uruchamiaj i od razu zobacz wynik.

Czas w oprogramowaniu a czas w sprzęcie

W programie czas płynie instrukcja po instrukcji. Procesor kończy linię 1, potem wykonuje linię 2. Jeśli chcesz, żeby dwie rzeczy działy się naraz, sięgasz po wątki, asynchroniczność albo drugą maszynę.

W sprzęcie wszystko dzieje się naraz. Układ nie czeka na swoją kolej. Sumator zawsze dodaje, multiplekser zawsze wybiera, przerzutnik zawsze obserwuje zegar. Nie ma licznika rozkazów. Nie ma "bieżącej linii".

Verilog musi opisać ten świat tekstem. To, jak to robi, jest źródłem każdego nieporozumienia z tego rozdziału.

Dwie warstwy współbieżności

Czytając moduł w Verilogu, patrzysz jednocześnie na dwie różne rzeczy:

  1. Statyczny opis układu. Przewody, rejestry, instancje bramek, instancje podmodułów, przypisania ciągłe. Wszystko to istnieje jednocześnie. Kolejność w pliku nie ma znaczenia.
  2. Bloki proceduralne, initial i always, które wyglądają jak małe programy, przez które przechodzi symulator. Wewnątrz takiego bloku instrukcje faktycznie wykonują się w pewnej kolejności. Ale kilka bloków always może być aktywnych naraz, każdy we własnym małym wątku czasu symulacji.
module example(input wire a, input wire b, output wire y, output wire z);
    assign y = a & b;        // istnieje przez cały czas
    assign z = a | b;        // też istnieje przez cały czas, równolegle

    always @(posedge a) begin
        // osobny, "zawsze włączony" reaktor, który budzi się przy każdym
        // zboczu narastającym `a`
    end
endmodule

Dwie linie assign nie tworzą sekwencji. Opisują dwa fragmenty logiki kombinacyjnej, które narzędzie do syntezy może rozmieścić obok siebie. Blok always to trzecia rzecz, która dzieje się równolegle.

Co oznacza "sygnał"

W oprogramowaniu zmienna przechowuje wartość, dopóki jej nie zmienisz. W Verilogu sygnał ma wartość w sposób ciągły, a w każdej chwili zależy ona od tego, co nim steruje.

wire jest sterowany z zewnątrz (przez assign, port wyjściowy podmodułu, połączenie inout). reg jest sterowany z wnętrza bloku proceduralnego. Oba mają wartość w każdej chwili. Nie ma czegoś takiego jak "niezainicjowany" w rozumieniu C: sygnały mają albo określoną wartość, albo specjalną wartość nieznaną x, albo stan wysokiej impedancji z. Dwa ostatnie przypadki omawiamy w artykule Wartości X i Z.

Zegary zmieniają wszystko

Gdy pojawia się zegar, czas zaczyna mieć znaczenie. Przerzutnik to mały element sprzętu, który zapamiętuje wartość wejścia na zboczu narastającym (albo opadającym) zegara i trzyma ją do następnego zbocza. To zegar pozwala budować liczniki, maszyny stanów, potoki, czyli wszystko, co ma pamięć.

Zwróć uwagę na q <= d zamiast q = d. To przypisanie nieblokujące, podstawowe narzędzie logiki taktowanej. Oznacza "na następnym zboczu zegara zaplanuj, że q przyjmie to, czym d jest teraz". Reguły omawiamy w artykule Przypisania blokujące i nieblokujące. Na razie wystarczy zauważyć, że to przypisanie nie udaje instrukcji z oprogramowania.

RTL: model myślenia oparty na przesłaniach między rejestrami

Większość syntezowalnego kodu w Verilogu pisze się w stylu zwanym Register Transfer Level, czyli RTL. Idea jest prosta:

  • Zdecyduj, jakiego stanu potrzebuje układ (rejestry).
  • Dla każdego rejestru opisz dwie rzeczy: co go resetuje i jaka logika kombinacyjna oblicza jego następną wartość.
  • Połącz wyjścia logiki kombinacyjnej z wejściami rejestrów i masz działający układ.
always @(posedge clk) begin
    if (reset) state <= IDLE;
    else       state <= next_state;
end

always @(*) begin
    case (state)
        IDLE:   next_state = start ? RUNNING : IDLE;
        RUNNING: next_state = done  ? IDLE    : RUNNING;
        default: next_state = IDLE;
    endcase
end

To maszyna o dwóch stanach. Pierwszy blok always jest taktowany, więc to przerzutnik. Drugi jest czysto kombinacyjny, to po prostu równanie. Prawie każda maszyna stanów, licznik i potok, jakie napiszesz, ma taki kształt.

Nawyki, które cię blokują

Jeśli przychodzisz z programowania, oto krótka lista nawyków do porzucenia:

  • "Zmienne aktualizują się, gdy do nich przypisuję." Nie na zboczu zegara: przypisanie nieblokujące planuje aktualizację na koniec kroku czasowego.
  • "Instrukcje wykonują się od góry do dołu." Poza blokami proceduralnymi nie. W bloku taktowanym poniekąd tak, ale przypisania blokujące i nieblokujące zmieniają to, co naprawdę znaczy "po kolei".
  • "Zaalokuję to, gdy będzie potrzebne." Sprzęt niczego nie alokuje. Każdy rejestr i każda bramka muszą istnieć w chwili syntezy. Rozmiar każdego wektora jest stały.
  • "Ta pętla jest szybka, to tylko jedna operacja." Pętla for w syntezowalnym Verilogu jest rozwijana w równoległy sprzęt. Pętla z 64 iteracjami staje się 64 kopiami ciała, a nie instrukcją procesora wykonywaną 64 razy.

Nie uczysz się pisać programu. Uczysz się opisywać układ. Odruch czytania od góry do dołu jest dokładnie tym złym i trzeba trochę czasu, żeby się go oduczyć.

Co dalej

Kolejne artykuły pokazują, jak zainstalować lokalne narzędzia (opcjonalnie, bo edytor w przeglądarce w zupełności wystarcza), a potem jak napisać pierwszy moduł od zera. Do kontrastu między sprzętem a oprogramowaniem będziemy wracać za każdym razem, gdy coś wyda się dziwne, bo większość "dziwnych" rzeczy ma tę samą przyczynę: to nie jest oprogramowanie.

Najczęściej zadawane pytania

Czym w kontekście Veriloga różni się sprzęt od oprogramowania?

Oprogramowanie to ciąg instrukcji wykonywanych przez procesor jedna po drugiej. Sprzęt, czyli to, co opisuje Verilog, to sieć bramek i przewodów, które przenoszą sygnały jednocześnie. Plik w Verilogu opisuje taką sieć. Symulator naśladuje jej równoległe działanie, a narzędzie do syntezy zamienia ją w prawdziwy krzem.

Czy kod w Verilogu wykonuje się od góry do dołu jak w C?

Nie, a traktowanie go w ten sposób to najczęstszy błąd początkujących. Instrukcje najwyższego poziomu (assign, instancje modułów, bloki always) 'istnieją' jednocześnie. Tylko wewnątrz bloków proceduralnych, initial i always, dzieje się coś przypominającego wykonanie sekwencyjne, a nawet tam przypisania nieblokujące burzą to złudzenie.

Co oznacza 'współbieżny' w Verilogu?

Oznacza, że wiele instrukcji opisuje części układu, które działają w tym samym czasie. Dwie instrukcje assign w tym samym module to nie 'linia 1, potem linia 2', tylko dwa fragmenty sprzętu działające równolegle i stale reagujące na swoje wejścia.

Czym jest projektowanie RTL?

RTL to skrót od Register Transfer Level (poziom przesłań międzyrejestrowych). To styl pisania w Verilogu, w którym opisujesz układ jako rejestry (przerzutniki) i logikę kombinacyjną obliczającą ich następne wartości. Większość syntezowalnego kodu w Verilogu to RTL. Poziom wyżej jest poziom behawioralny, a poziom niżej poziom bramek.

Ilustracja języków programowania w Coddy

Ucz się programowania z Coddy

ZACZNIJ