Verilog to język opisu sprzętu (HDL – Hardware Description Language), który służy do modelowania, projektowania i symulacji układów cyfrowych. Obok VHDL jest to najpopularniejszy standard HDL wspierany przez praktycznie wszystkie współczesne systemy EDA/CAD przeznaczone do projektowania systemów cyfrowych.
Popularność Veriloga wynika z prostej, czytelnej składni przypominającej język C oraz z hierarchicznej struktury projektów, która ułatwia opisywanie nawet złożonych systemów.
Zastosowania Veriloga
Najczęstsze zastosowania języka w praktyce projektowej obejmują:
- symulację – opis zachowania układów logicznych w celu ich weryfikacji przed implementacją;
- syntezę logiczną – przekształcanie kodu HDL w rzeczywisty sprzęt (netlisty, konfiguracje FPGA);
- projektowanie RTL – tworzenie architektury na poziomie transferu rejestrowego;
- modelowanie na FPGA – implementację i testy systemów wbudowanych na platformach programowalnych.
Nie wszystkie konstrukcje Veriloga są syntezowalne – część służy wyłącznie do symulacji i weryfikacji, co pozwala szybciej wykrywać błędy projektowe.
Poziomy abstrakcji w Verilogu
Verilog pozwala opisywać układy na kilku poziomach szczegółowości:
- Poziom tranzystorów – najniższy poziom abstrakcji;
- Poziom bramek logicznych – opis za pomocą bramek AND, OR, NOT itp.;
- Poziom równań logicznych – reprezentacja funkcji boolowskich;
- Poziom transferu rejestrowego (RTL) – operacje między rejestrami;
- Poziom zachowań (behawioralny) – opis algorytmiczny, niezależny od implementacji;
- Poziom strukturalny (systemowy) – łączenie komponentów wyższego poziomu.
Każdy z tych poziomów znajduje zastosowanie na innym etapie projektu; wybór zależy od celu i wymaganego stopnia szczegółowości.
Podstawowe konstrukcje Veriloga
Moduły – fundamentalna jednostka kodu
Moduł jest podstawową konstrukcją języka Verilog. Reprezentuje blok logiki z ustalonym zbiorem wejść i wyjść; rozpoczyna się słowem kluczowym module i kończy endmodule.
Struktura modułu obejmuje:
- nazwę modułu,
- interfejs (porty wejścia/wyjścia),
- deklaracje sygnałów wewnętrznych,
- kod opisujący funkcjonalność.
Moduły mogą być parametryzowane (np. zmienna szerokość danych), co ułatwia tworzenie wielokrotnego użytku, elastycznych komponentów.
Sygnały i rejestry
Rejestry (reg) – przechowują wartość do czasu jej zmiany w bloku proceduralnym; przypominają zmienne w językach programowania, ale odzwierciedlają zachowanie sprzętu.
Przewody (wire) – reprezentują połączenia między komponentami, odwzorowując ciągłe powiązania logiczne w układzie.
Deklaracje mogą obejmować wektory bitowe; przykładowo 1‑bitowy sygnał zadeklarujesz tak: wire a;
Wartości logiczne
W Verilogu każdy bit może przyjmować jedną z czterech wartości:
- 0 – logiczne zero bądź fałsz;
- 1 – logiczna jedynka albo prawda;
- X – wartość nieokreślona (nieistotna) – może być zarówno 0, jak i 1;
- Z – stan wysokiej impedancji (stan trzeci, stan pływający).
Czterowartościowa logika jest kluczowa w symulacjach, ponieważ pozwala odróżnić nieznane stany od wartości zdefiniowanych.
Procesy i bloki proceduralne
Proces w Verilogu to blok kodu uruchamiany na zmianę wskazanych sygnałów; definiuje się go słowem kluczowym always i można w nim stosować instrukcje warunkowe oraz pętle.
Przykład procesu reagującego na zmiany sygnałów:
always @(a, b, c) begin
d = 0;
if (a) d = 1;
if (b) d = 2;
if (c) d = 3;
end
Kluczową cechą Veriloga jest równoległość wykonywania – procesy i instancje modułów działają równolegle, co wiernie odwzorowuje naturę sprzętu cyfrowego.
Praktyczne przykłady
Multiplekser
Multiplekser wybiera jedno z wielu wejść na podstawie sygnału selekcji. Przykładowa implementacja:
module mux1(sel, din1, din2, din3, din4, dout);
input [1:0] sel; // selektor
input din1; // wejścia
input din2;
input din3;
input din4;
output reg dout;
always @(sel or din1 or din2 or din3 or din4)
if (sel == 2'd0) dout = din1;
else if (sel == 2'd1) dout = din2;
else if (sel == 2'd2) dout = din3;
else dout = din4;
endmodule
Licznik
Licznik inkrementuje lub dekrementuje swoją wartość na każdym zboczu zegara. Przykładowy kod:
always @(posedge clk or negedge reset)
if (!reset) counter <= {length{1'b0}}; // reset asynchroniczny
else if (load) counter <= data; // wpisanie równoległe
else if (dir) counter <= counter + 1'b1; // zliczanie w górę
else counter <= counter - 1'b1; // zliczanie w dół
Przerzutnik
Przerzutnik jest podstawowym elementem pamięci w logice sekwencyjnej. Przykładowa definicja modułu parametryzowanego:
module flip_flop #(parameter dlength = 8) // parametr
(d, clk, reset, e, q);
input [dlength-1:0] d; // wejście D
input clk; // zegar
input reset; // asynchroniczny reset
input e; // enable
output reg [dlength-1:0] q; // wyjście
always @(posedge clk or posedge reset)
// kod logiki przerzutnika
endmodule
Cykl projektowania systemów cyfrowych
Typowy przebieg projektu z użyciem Veriloga obejmuje etapy:
- Specyfikacja – określenie wymagań funkcjonalnych;
- Schemat blokowy – wizualizacja architektury systemu;
- Projekt niskiego poziomu – szczegółowy opis poszczególnych modułów;
- Kodowanie RTL – implementacja na poziomie transferu rejestrowego;
- Weryfikacja – testowanie poprawności działania poprzez symulację;
- Synteza – konwersja kodu na netlist logiczny, a następnie na układ fizyczny.
Verilog a FPGA
Verilog jest szczególnie popularny w projektach na platformach FPGA (programowalne macierze bramek). FPGA można rekonfigurować po produkcji, dzięki czemu jeden układ realizuje wiele funkcji. Verilog pozwala efektywnie opisywać złożone systemy, które następnie implementuje się w tych strukturach.