Interfejs modułu
Lista portów modułu to jego interfejs, czyli wszystko, co świat zewnętrzny może zobaczyć i czego może dotknąć. Wewnątrz modułu ciało oblicza wyjścia na podstawie wejść. Porty to przewody, które przekraczają granicę.
Współczesna deklaracja w stylu ANSI podaje kierunek, typ i szerokość w jednym miejscu:
module my_module(
input wire clk,
input wire reset,
input wire [7:0] data_in,
output reg [7:0] data_out,
output wire valid
);
// body
endmodule
To cały kształt. Każdy port ma:
- Kierunek:
input,outputalboinout. - Typ:
wirealboreg(albologicw SystemVerilogu). - Szerokość: zakres taki jak
[7:0]albo jeden bit, gdy zakres jest pominięty. - Nazwę: identyfikator, którego użyjesz w ciele modułu.
Trzy kierunki
input: sterowany z zewnątrz
Wejścia są zawsze typu wire. Źródło sterowania jest poza modułem: to sygnał modułu wyższego poziomu albo reg z testbencha. Wewnątrz tego modułu możesz odczytywać wejścia w wyrażeniach, ale nigdy do nich nie przypisujesz:
module reader(input wire [7:0] data);
initial $display("data = %h", data);
endmodule
Zapis data = ... wewnątrz modułu byłby błędem.
output: sterowany od środka
Wyjścia są sterowane przez coś wewnątrz tego modułu. Mogą być typu wire (sterowane przez assign albo wyjście podmodułu) lub reg (sterowane z always/initial).
module driver(
input wire a,
input wire b,
input wire clk,
output wire y, // wire: sterowane przez assign
output reg q // reg: sterowane przez always
);
assign y = a & b; // OK, bo y to wire
always @(posedge clk)
q <= a; // OK, bo q to reg
endmodule
Próba przypisania do y wewnątrz bloku always albo sterowania q przez assign to błąd kompilacji. Słowo kluczowe musi pasować do źródła sterowania.
inout: dwukierunkowy
Porty inout to przewody, którymi na zmianę może sterować moduł albo to, co jest podłączone z zewnątrz. Pojawiają się na granicach układu: linie SDA w I²C, dwukierunkowe piny GPIO, współdzielone magistrale danych. Wewnątrz modułu kierunkiem steruje się wzorcem trójstanowym:
module bidir_pin(
input wire data_out,
input wire output_enable,
output wire data_in,
inout wire pin
);
assign pin = output_enable ? data_out : 1'bz;
assign data_in = pin;
endmodule
pin to współdzielony przewód. Gdy output_enable jest w stanie wysokim, moduł ustawia na pin wartość data_out. Gdy w niskim, zwalnia pin do stanu wysokiej impedancji (z), pozwalając korzystać z niego zewnętrznemu źródłu. data_in zawsze widzi to, co aktualnie jest na pinie.
inout rzadko występuje w czysto wewnętrznej logice. Budując kontroler pamięci, rdzeń procesora albo potok przetwarzania obrazu, możesz go nigdy nie potrzebować. To funkcja dla pierścienia wejść/wyjść na granicach układu.
Porty jednobitowe i wielobitowe
Zakres szerokości jest opcjonalny. Pomiń go dla portu jednobitowego:
input wire valid, // 1 bit
input wire [7:0] data, // 8 bits
input wire [31:0] addr, // 32 bits
Do określania szerokości możesz używać parametrów, i tak właśnie działają moduły sparametryzowane:
module bus #(
parameter WIDTH = 32
)(
input wire [WIDTH-1:0] in,
output wire [WIDTH-1:0] out
);
assign out = in;
endmodule
Style ANSI i Verilog-1995
Czasem zobaczysz starszy kod, który rozdziela listę portów i deklaracje:
// Stary styl Verilog-1995: NIE rób tak w nowym kodzie
module foo(clk, data_in, data_out);
input clk;
input [7:0] data_in;
output reg [7:0] data_out;
// ...
endmodule
Lista portów zawiera tylko nazwy, a każdy port jest potem deklarowany osobno w ciele. To rozwlekłe, podatne na błędy typu "nazwa jest na liście portów, ale nigdy nie została zadeklarowana" i wymaga dwa razy więcej pisania. Styl ANSI-2001 (używany w całej tej dokumentacji) zastępuje oba elementy:
module foo(
input wire clk,
input wire [7:0] data_in,
output reg [7:0] data_out
);
// ...
endmodule
Używaj stylu ANSI. Narzędzia obsługują go od 2001 roku.
Kompletny przykład
Ten jeden moduł ma:
- Parametr (
WIDTH). - Wejścia zegara, resetu, zezwolenia na ładowanie, danych i zezwolenia na przesuwanie.
- Wyjście
reg(data_out) sterowane wewnątrz blokualways. - Wyjście
wire(msb_out) sterowane przypisaniem ciągłym. - Kompletną deklarację w stylu ANSI, która dla każdego portu podaje kierunek, typ i szerokość.
Tak wygląda prawie każdy syntezowalny moduł, jaki napiszesz.
Co dalej
Następny artykuł, Tworzenie instancji modułów, pokazuje, jak wziąć ten moduł i użyć go w większym projekcie. Napisany właśnie shifter sam w sobie nie jest przydatny. Zaczyna się przydawać, gdy coś tworzy jego instancję i włącza go do systemu.
Najczęściej zadawane pytania
Jakie są trzy kierunki portów w Verilogu?
input, output i inout. input jest sterowany spoza modułu. output jest sterowany z wnętrza modułu. inout jest dwukierunkowy i przydaje się na magistralach trójstanowych, gdzie moduł zarówno steruje tym samym pinem, jak i go odczytuje. Zdecydowana większość portów wewnętrznych to input albo output, a inout pojawia się tylko na pinach układu.
Jaka jest różnica między output wire a output reg?
output wire oznacza, że wyjście jest sterowane przypisaniem ciągłym albo wyjściem podmodułu, czyli wszystkim poza blokiem always. output reg oznacza, że jest sterowane z bloku proceduralnego (initial albo always). Słowo kluczowe decyduje o tym, czy możesz przypisywać do sygnału wewnątrz always, a nie o tym, czy zsyntezowany sprzęt zawiera przerzutnik.
Czym jest styl ANSI dla portów w Verilogu?
Styl ANSI deklaruje kierunek i typ każdego portu bezpośrednio na liście portów: module foo(input wire [7:0] data, output reg [7:0] result);. Starszy styl z Verilog-1995 wypisywał na liście portów tylko nazwy i deklarował je ponownie wewnątrz modułu. W nowym kodzie zawsze używaj stylu ANSI: jest krótszy, mniej podatny na błędy i standardowy we wszystkich współczesnych narzędziach.
Czy moduł w Verilogu może mieć wiele wejść i wyjść?
Tak. Moduł może mieć tyle portów, ile potrzebujesz, w dowolnej kombinacji kierunków. Oddzielaj je przecinkami na liście portów. Kolejność na liście portów decyduje o kolejności połączeń pozycyjnych przy tworzeniu instancji, ale prawie zawsze używa się połączeń po nazwie (.port(signal)), żeby nie polegać na kolejności.