Jeden wire a wiele
Dotąd każdy sygnał, który widzieliśmy, miał szerokość jednego bitu. Prawdziwe projekty prawie nigdy tak nie wyglądają: adresy mają 16 lub 32 bity, magistrale danych 8 lub 64, piksele RGB 24. Verilog daje jeden mechanizm, który zamienia dowolny sygnał w wielobitowy: dodanie zakresu w nawiasach kwadratowych.
wire [7:0] data; // 8-bit wire, bit 7 is MSB, bit 0 is LSB
reg [15:0] address; // 16-bit reg
output reg [31:0] result; // 32-bit module output
Liczby w nawiasach to pozycje bitów najbardziej i najmniej znaczącego bitu. [7:0] oznacza "ten sygnał ma bity ponumerowane od 7 w dół do 0", co daje łącznie 8 bitów. Pierwsza liczba to wyższy indeks. Druga to niższy.
Czasem zobaczysz zamiast tego [0:7]: ta sama liczba bitów, ale odwrotna kolejność przy wycinaniu. Forma [high:low] to zdecydowanie dominująca konwencja w branży. Trzymaj się jej, chyba że masz mocny powód, żeby tego nie robić.
Przykład w praktyce: 8-bitowy sumator
Każdy + dodaje dwa 8-bitowe wektory i daje 9-bitowy wynik. Literały liczbowe takie jak 8'd10 oznaczają "wartość dziesiętna 10 o szerokości 8 bitów". Omówimy je w części Literały liczbowe.
Wycinanie: wybieranie bitów
Mając wektor, możesz wyciągnąć z niego pojedyncze bity albo ciągłe zakresy:
Nie przejmuj się linią force w testbenchu: potrzebujemy po prostu sposobu, żeby wstrzyknąć wartość i pokazać wycinanie. Ciekawe są same wycinki.
Kilka zasad:
- Kierunek wycinka musi zgadzać się z deklaracją. Jeśli zadeklarowano
[7:0], wycinasz przez[high:low]. Odwrócenie kierunku to błąd składni. - Wycinanie poza zakresem daje w symulacji
x(wartość nieznaną). Narzędzie do syntezy może zgłosić ostrzeżenie albo błąd. - Wybór bitu opiera się na indeksie, który napisano w deklaracji:
data[0]to bit o numerze0, który (przy deklaracji[7:0]) jest bitem LSB.
Wycinki o zmiennej bazie: +: i -:
Częsta potrzeba: "daj mi 8 bitów zaczynając od bitu N". Nie możesz napisać wprost data[N+7:N], bo Verilog wymaga, żeby oba końce zakresu były stałymi. Rozwiązuje to taka składnia:
data[base +: width] // width bits starting at `base`, going UP
data[base -: width] // width bits starting at `base`, going DOWN
Szerokość jest stała (za każdym razem bierzemy 8 bitów), ale baza może być wyrażeniem obliczanym w czasie działania. Dokładnie tego potrzebujesz przy pamięciach adresowanych bajtowo, odczepach rejestrów przesuwnych i tak dalej.
Tablice: krok dalej niż wektory
Wektor to pojedynczy sygnał wielobitowy. Tablica to zbiór wektorów indeksowanych niezależnie:
reg [31:0] mem [0:1023];
Ta deklaracja ma dwa zakresy i każdy z nich oznacza coś innego:
[31:0]to wymiar packed: szerokość każdego pojedynczego słowa.[0:1023]to wymiar unpacked: liczba słów.
Zatem mem to 1024 osobne 32-bitowe rejestry. Do jednego z nich odwołujesz się jednym indeksem:
mem[5] = 32'hCAFE_BABE; // write word 5
data = mem[address]; // read the word at `address`
To malutka pamięć przechowująca kwadraty liczb. Prawdziwe projekty używają tego samego wzorca do plików rejestrów, tablic przeglądowych, kolejek FIFO i każdej innej pamięci na chipie, która jest większa niż pojedynczy wektor.
Packed a unpacked: dlaczego to ważne
Podział na wymiary packed i unpacked pojawia się wszędzie. Wiedza, który jest który, oszczędza sporo debugowania:
- Wektor packed to jeden sygnał. Możesz traktować go w całości jak liczbę:
data + 1działa,data == 32'h0działa,data[7:0]działa. - Tablica unpacked to wiele sygnałów. Nie możesz traktować jej w całości jak liczby:
mem + 1to błąd składni. Najpierw musisz wybrać konkretne słowo.
Dozwolonych jest też kilka wymiarów packed:
reg [3:0][7:0] regs; // 4 bytes packed together into a 32-bit signal
regs[0] to bajt (najniższy bajt). regs jako całość ma 32 bity. SystemVerilog intensywnie z tego korzysta.
Kilka wymiarów unpacked tworzy pamięć dwuwymiarową:
reg [31:0] frame [0:479][0:639]; // 480x640 of 32-bit pixels
Do pojedynczego piksela odwołujesz się przez frame[y][x]. Tak wyglądałby bufor obrazu w HDL.
Co dalej
Potrafisz już deklarować sygnały o dowolnej potrzebnej szerokości i nimi operować. Następna strona, Parametry, pokazuje, jak uczynić te szerokości konfigurowalnymi, żeby ten sam moduł działał z 8 bitami w jednej instancji i z 32 w innej. Potem przejdziemy do zasad zapisywania literałów liczbowych (8'b1010_1100, 32'hDEAD_BEEF) oraz wartości x/z, które pojawiają się zawsze, gdy coś nie jest sterowane.
Najczęściej zadawane pytania
Czym jest wektor w Verilogu?
Wektor to sygnał wielobitowy. Deklarujesz go, dodając zakres do wire lub reg: wire [7:0] data to 8-bitowy wire. Liczby w nawiasach kwadratowych to pozycje bitów: w tym przypadku bit 7 jest najbardziej znaczący, a bit 0 najmniej. Możesz wycinać pojedyncze bity (data[3]) albo ciągłe zakresy (data[7:4]).
Co oznacza [7:0] w Verilogu?
[7:0] deklaruje zakres od bitu 7 w dół do bitu 0 włącznie, czyli 8-bitowy sygnał, w którym bit 7 jest najbardziej znaczący. Pierwsza liczba to wyższy indeks, druga to niższy. Możesz też napisać [0:7], żeby indeksować w stylu little-endian, ale forma [high:low] jest zdecydowanie częstszą konwencją w kodzie produkcyjnym.
Jak wyciąć bity w Verilogu?
Użyj indeksowania w nawiasach kwadratowych. data[3] wybiera pojedynczy bit. data[7:4] wybiera cztery najwyższe bity jako 4-bitowy wektor. Wycinek musi mieć ten sam kierunek co deklaracja: jeśli zadeklarowano [7:0], wycinaj przez [high:low]. SystemVerilog dodaje też data[3 +: 4] do wycinków o zmiennej bazie i stałej szerokości.
Czym różni się tablica packed od unpacked w Verilogu?
Tablica packed to jedna ciągła magistrala: reg [31:0] word to jeden 32-bitowy sygnał. Tablica unpacked (czyli 'pamięć') to zbiór niezależnych słów: reg [31:0] mem [0:1023] to 1024 osobne 32-bitowe rejestry. Możesz odczytać lub zapisać całe słowo tablicy unpacked, ale nie możesz operować na całej tablicy jak na jednym sygnale.
Jak zadeklarować pamięć w Verilogu?
reg [31:0] mem [0:1023]; deklaruje pamięć z 1024 pozycjami, każda o szerokości 32 bitów. Pierwsza para nawiasów to szerokość słowa (packed), druga to liczba słów (unpacked). Do pozycji odwołujesz się przez mem[address], a po włączeniu indeksowania z SystemVerilog-2005 możesz odczytać lub zapisać wycinek tej pozycji przez mem[address][7:0].