Dwa małe operatory, które naprawdę się przydają
Verilog ma dwie postacie {}:
- Konkatenacja,
{a, b, c}, skleja kilka sygnałów w jeden szerszy. - Replikacja,
{N{pattern}}, kopiuje wzorzec N razy.
Będziesz używać obu nieustannie. Za każdym razem, gdy magistralę trzeba podzielić, połączyć, uzupełnić albo rozszerzyć ze znakiem, odpowiedzią jest jeden z tych dwóch operatorów.
Konkatenacja: {a, b, c}
Najprostszy przypadek to ułożenie dwóch sygnałów w jeden:
Czytając od lewej do prawej: a to starsza połowa, a b młodsza. Pierwszy argument wewnątrz {} jest najbardziej znaczący. Szerokość wyniku to suma szerokości argumentów.
Możesz skleić dowolną liczbę argumentów:
wire [31:0] word = {byte3, byte2, byte1, byte0};
A argumenty mogą mieć różne szerokości:
wire [11:0] frame = {start_bit, data_byte, parity, stop_bit};
// 1 bit 8 bits 1 bit 2 bits = 12
Częsta reguła: wszystkie argumenty konkatenacji muszą mieć jawnie podaną szerokość. Literały bez rozmiaru (1 zamiast 1'b1) powodują błędy, bo parser nie wie, ile bitów przydzielić. Zawsze pisz 1'b0, 1'b1, 4'd0, nigdy samo 0 albo 1 wewnątrz {}.
Konkatenacja po lewej stronie
Konkatenacja po lewej stronie przypisania dzieli szeroki sygnał z powrotem na części:
To kanoniczny wzorzec sumatora z przeniesieniem. {carry_out, sum_bits} to jeden 9-bitowy cel po lewej stronie przypisania, a bity są rozdzielane: najstarszy trafia do carry_out, a 8 młodszych do sum_bits.
Konkatenacja po lewej stronie działa w assign i w blokach proceduralnych:
always @(posedge clk) begin
{high_byte, low_byte} <= incoming_word;
end
Replikacja: {N{pattern}}
Operator replikacji kopiuje wzorzec określoną liczbę razy. Ma postać {N{pattern}}: najpierw liczba, potem wzorzec we własnej parze nawiasów:
N musi być stałą, bo kompilator musi znać szerokość wyniku na etapie elaboracji. Wzorcem może być literał, parametr albo dowolne wyrażenie o znanej szerokości.
Nie zapominaj o wewnętrznych nawiasach. {8 1'b1} to błąd składni, a {8{1'b1}} jest poprawne. Zewnętrzne {} to operator, a wewnętrzne {...} obejmuje replikowany wzorzec.
Oba naraz: rozszerzenie ze znakiem i zerami
Oba operatory naturalnie się łączą i pozwalają budować szersze wartości:
{ {8{a_negative[7]}}, a_negative } to standardowy wzorzec rozszerzenia ze znakiem. Czytaj go tak: powtórz bit znaku osiem razy, potem doklej oryginalne 8 bitów. Wynik netto to 16-bitowa reprezentacja tej samej liczby w kodzie uzupełnień do dwóch.
Przy poszerzaniu wartości bez znaku możesz polegać na przypisaniu, bo Verilog automatycznie rozszerza zerami, gdy cel jest szerszy:
wire [15:0] zext_auto = a_unsigned; // works, top 8 bits are 0
Ale rozszerzenie ze znakiem nigdy nie zachodzi niejawnie, chyba że zarówno argument, jak i cel są zadeklarowane jako signed. Jawny idiom z replikacją jest bezpieczniejszy.
Przydatne wzorce
Budowanie maski z N jedynek
parameter N = 5;
wire [31:0] mask = { {32-N{1'b0}}, {N{1'b1}} };
// e.g. with N=5, mask = 32'h0000_001F
Odwracanie wektora
Verilog nie ma wbudowanej funkcji odwracania, ale możesz ją zapisać w miejscu za pomocą konkatenacji:
wire [3:0] forward = 4'b1010;
wire [3:0] reversed = {forward[0], forward[1], forward[2], forward[3]};
// reversed = 4'b0101
Dla szerszych wektorów czytelniejsza jest pętla generate albo function.
Pakowanie flag w bajt statusu
wire [7:0] status = {
error_flag, // [7]
overflow, // [6]
underflow, // [5]
ready, // [4]
busy, // [3]
1'b0, // [2] reserved
1'b0, // [1] reserved
valid // [0]
};
Jednobitowe zarezerwowane pozycje mają jawną szerokość: nigdy samo 0 wewnątrz {}.
Typowe błędy
Literały bez rozmiaru wewnątrz {}. {a, 0} to błąd składni albo zero o szerokości 32 bitów. Zawsze podawaj rozmiar: {a, 1'b0}.
Brak wewnętrznych nawiasów w replikacji. {8 1'b1} się nie parsuje, a {8{1'b1}} tak.
Pomylona kolejność. {a, b} umieszcza a po stronie starszych bitów, a b po stronie młodszych. Jeśli założysz odwrotnie, gdzieś pojawi się odwrócona kolejność bajtów.
Replikacja zero razy. {0{...}} jest niedozwolone w standardowym Verilogu. SystemVerilog na to pozwala (i daje wynik o zerowej szerokości). Zwykły Verilog to odrzuci.
Co dalej
Znasz już wszystkie operatory Veriloga. Następny rozdział zmienia kierunek i zajmuje się strukturą: jak moduły łączą się ze sobą, jakie reguły dotyczą portów i jak wygląda składnia tworzenia instancji podmodułów przy budowaniu większych projektów.
Najczęściej zadawane pytania
Jak połączyć sygnały w Verilogu?
Użyj nawiasów klamrowych: {a, b, c} tworzy jeden szeroki wektor, sklejając argumenty od lewej do prawej. MSB z a staje się MSB wyniku, a LSB z c staje się LSB. Szerokość wyniku to suma szerokości argumentów. Konkatenacja działa zarówno po prawej stronie przypisania (do budowania wartości), jak i po lewej (do dzielenia wartości).
Co oznacza {N{pattern}} w Verilogu?
To operator replikacji: tworzy N sklejonych kopii pattern. {8{1'b1}} to 8-bitowa wartość z samych jedynek (8'hFF). {4{2'b10}} to 8-bitowa wartość 8'b10101010. Replikacja pozwala rozszerzać zerami, rozszerzać ze znakiem albo budować dowolny powtarzalny wzorzec bitów bez wypisywania go ręcznie.
Jak rozszerzyć sygnał ze znakiem w Verilogu?
Użyj replikacji, żeby powtórzyć bit znaku: { {24{a[7]}}, a } rozszerza 8-bitowe a do 32 bitów, powtarzając bit 7 (bit znaku) 24 razy i doklejając oryginał. Dla rozszerzenia zerami bez znaku powtórz 1'b0 albo po prostu pozwól przypisaniu zrobić to niejawnie, gdy cel jest szerszy.
Czy konkatenacji można użyć po lewej stronie przypisania?
Tak. W ten sposób przypisujesz wiele celów z jednego szerokiego źródła. {carry, sum} = a + b; umieszcza wynik dodawania w carry (najstarszy bit) i sum (reszta) w jednej instrukcji. Każdy cel zachowuje swoją szerokość, a parser odpowiednio rozdziela bity prawej strony.