Grundwissen
0.1 Modul (Modul)
Ein Modul in Verilog kann als eine Blackbox mit Eingangs- und Ausgangsports betrachtet werden.Die Blackbox hat Eingangs- und Ausgangsschnittstellen (Signale), und eine bestimmteFunktion wird erreicht, indem bestimmte Operationen an der Eingabe in der Boxdurchgeführt werden. (ähnlich wie Funktionen in C-Sprache)
Abbildung 1 Schematische Darstellung des Moduls
0.1.1 Modulbeschreibung
Die in Abbildung 1 gezeigte Struktur des oberen Moduls (top_module) kann in Verilog wie folgt beschrieben werden:
module top_module(
input a,
input b,
output out
);
.......
endmodule
- Ein Modul beginnt mit module und endet mit endmodule
- top_module ist der Modulname
- input : ist der Eingangsport
- Ausgang: ist der Ausgangsport
- Der gesamte Code muss im Modul module stehen !
In ähnlicher Weise kann die in Abbildung 1 gezeigte Untermodulstruktur (mod_a) in der Verilog-Sprache wie folgt beschrieben werden:
module top_module(
input in1,
input in2,
output out
);
.......
endmodule
Hinweis : Jedes Modul sollte sich in einem separaten Block in einer .v-Datei befinden, und der Modulname ist der Dateiname (kanonischer Code!)
0.1.2 Eingangs- und Ausgangssignale des Moduls
- Ausgabe: Ausgabe
- Eingang: Eingang
Die Eingangs- und Ausgangsports des Moduls können das Signal des Moduls sehen. Wenn der Signaltyp nicht geschrieben ist, ist die Voreinstellung das Drahttypsignal !
// 以下这两个语句本质是一直的
input a;
input wire a;
Neben Drahtsignalen gibt es auch Reg-Signale, siehe Abschnitt 1.4 für Details !
0.1.3 Modul-Instanziierung
Wie in Abbildung 1 gezeigt, sind die beiden Eingangsports des top_module mit den Eingangsports des Submoduls (mod_a) verbunden.Wie verwende ich die Funktion des mod_a-Moduls im top_module-Modul? Dies erfordert eine Instanziierung des Moduls. Top_module kann als Hauptfunktion in der Sprache C betrachtet werden, und das sekundäre Modul mod_a kann als gewöhnliche Funktion betrachtet werden, sodass andere Funktionen in der Hauptfunktion aufgerufen werden können, um die entsprechenden Funktionen zu vervollständigen!
Der Weg zum Instanziieren von mod_a in top_module ist:
Modulinstanzsyntax : Modulname Instanzname (definiert das mit dem Port verbundene Signal);
module top_module(
input a,
input b,
output out
);
mod_a instance2 (.in1(a), .in2(b), .out(out));
endmodule
- Instanziiere in Port-Reihenfolge definiert durch mod_a: mod_a instance1 (a, b, out);
- Instanziieren nach mod_a Portname : mod_a instance2 (.in1(a), .in2(b), .out(out)); (dies wird empfohlen)
0.2 Logikblöcke (immer generieren)
0.2.1 immer Logikblock
Der Always-Block kann kombinatorische Logikblöcke und sequentielle Logikblöcke bilden. Komplexe logische Operationen müssen in diesem Logikblock enthalten sein, wie if, case, for usw.
(1) Kombinationslogikblock
module top_module();
always @(*) begin
....
end
endmodule
- Triggern Sie sofort, wenn sich ein Signal im Always-Logikblock ändert , führen Sie die Anweisung zwischen Beginn und Ende aus
- begin - end wird verwendet, um mehrere Anweisungen zu einem Codeblock zusammenzufassen , und kann weggelassen werden, wenn es nur eine Anweisung gibt
(1) Sequentielle Logikschaltung
module top_module();
always @(posedge clk) begin
....
end
endmodule
- Trigger auf ansteigende Flanke des clk -Signals
- Posedge: steigende Flanke
- Negede: fallende Flanke
0.2.2 Logikblock generieren
generate wird hauptsächlich in Verbindung mit der for-Schleife verwendet.
- Wiederholen Sie die Operation an mehreren Bits in einem Vektor
- Mehrere wiederholte Instanziierungen desselben Moduls (Hauptzweck)
(1) Operationsvektor
module top_module(input [7:0] in, output [7:0] out);
genvar i; // genvar i; 也可以定义在generate内部
generate
for(i=0; i<8; i++) begin: bit
assign out[i]=^in[8-1:i];
end
endgenerate
endmodule
(2) Das Modul wird mehrfach wiederholt instanziiert
module top_module(
input a,
input b,
output out
);
genvar i;
generate
for(i=0; i<8; i++) begin: gen_mod_a // gen_mod_a 为每个begin_end的结构的名称
mod_a instance2 (.in1(a), .in2(b), .out(out));
end
endgenerate
endmodule
- Hinweis: Wenn das Modul mehrmals instanziiert wird, muss der Name jeder begin_end-Struktur geschrieben werden (gen_mod_a).
- Der Simulator identifiziert die generierte Struktur durch gen_mod_a: gen_mod_a[0], gen_mod_a[1]....
0.3 Zuordnungsmethode
Es gibt drei Zuweisungsmethoden in Verilog: fortlaufende Zuweisung, blockierende Zuweisung und nicht-blockierende Zuweisung
0.3.1 Kontinuierliche Zuweisung (Zuweisung)
assign x = y;
- Diese Aussage bedeutet, die beiden Signale von x und y zu verbinden, die reale physikalische Verbindung !
- kann nicht in Always-Blöcken verwendet werden
0.3.2 Sperrzuordnung (=)
// 组合块
always @(*) begin
out1 = a ;
a = b ;
out2 = a ;
end
- Blockierende Zuweisung in einem kombinierten Always-Block
- Ausführungsreihenfolge: Nacheinander gemäß der Reihenfolge im begin_end-Anweisungsblock ausführen Die obigen Ausgabeergebnisse sind: out1 = a, out2 = b
0.3.3 Nicht blockierende Zuweisung (<=)
// 时序块
always @(posedge clk) begin
out1 <= a ;
a <= b ;
out2 <= a ;
end
- Verwenden Sie nicht blockierende Zuweisungen in sequentiellen Always-Blöcken
- Ausführungsreihenfolge: Alle Anweisungen in begin_end werden parallel ausgeführt , die obigen Ausgabeergebnisse sind: out1 = a, out2 = a
Kapitel 1 Grundlegende Grammatik
1.1 Identifikatoren
(1) Zweck: Bezeichner werden verwendet, um Konstanten, Variablen, Signale, Ports, Parameternamen, Modulnamen usw. zu definieren.
(2) Zusammensetzung: beliebige Kombination aus Buchstaben, Zahlen, $ und Unterstrichen
(3) Hinweise:
- Groß-/Kleinschreibung (Verilog und Verilog sind unterschiedlich)
- Das erste Zeichen darf nur ein Buchstabe oder ein Unterstrich sein (123demo ist eine illegale Kennung)
1.2 Logische Werte und logische Operationen
1.2.1 Logischer Wert
Es gibt 4 logische Werte in Verilog: 0, 1, x, z
- 0: niedriger Pegel
- 1: Hohes Niveau
- x: bedeutet, dass der Zustand unbekannt ist
- z: Zeigt den Hochwiderstandszustand an
Hinweis: Bei z und x wird hier nicht zwischen Groß- und Kleinschreibung unterschieden (X, Z können auch verwendet werden)
1.2.2 Logische Operationen
(1) Logische Operatoren: && (und), == (gleich), || (oder), != (ungleich)
- Wie m&&n: beurteilen, ob m und n alle wahr sind (wenn nicht 0 wahr ist ), wenn wahr, 1'b1 ausgeben, andernfalls 1'b0 ausgeben (4'b1010&4'b0101 = 1'b1 )
- Das endgültige Ausgabeergebnis ist nur 1 Bit
(2) Bitweise Operatoren: &, |, ~, ^, ~&, ~^, ~|
- Zum Beispiel m&n: Es soll die bitweise UND-Operation jedes Bits von m und jedes Bits von n ausführen (4'b1010&4'b0101 = 4'b0000 )
- Das Ausgabeergebnis ist dasselbe wie die Anzahl von Bits von m/n
(3) Reduktionsoperatoren: &, |, ~, ^, &, ~^, ~|
- Wenn nur ein Parameter an der Operation beteiligt ist (& ist der unäre Operator), bedeutet dies die Reduktion und Operation, dh die interne Operation des Vektors
&a [3:0] // AND:a[3]&a[2]&a[1]&a [0]相当于(a[3:0]== 4'hf)
|b [3:0] // OR: b[3]|b[2]|b[1]|b [0]相当于(b[3:0]!= 4'h0)
^c [2:0] // XOR:c[2]^c[1]^c[0]
- 即(&4'b0101 = 0&1&0&1 = 1'b0 )
- Das endgültige Ausgabeergebnis ist nur 1 Bit
1.3 Darstellung von Konstanten
Ähnlich wie in der Sprache C gibt es drei Arten von Konstanten: Integer, Real und String.
1.3.1 Ganzzahlkonstanten als Dezimalzahlen darstellen
(1) Positive Zahl: 10 direkt schreiben, um eine Dezimalzahl mit einer Bitbreite von 32 Bit darzustellen (Systemstandard)
(2) Negative Zahlen: -10 muss im Zweierkomplement dargestellt werden, mit einem weiteren Vorzeichenbit ( 1 1010)
(3) In wissenschaftlicher Schreibweise ausdrücken: 12.345e3 bedeutet 12345
1.3.2 Darstellung ganzzahliger Konstanten in Radix
[换算成二进制数后的位宽]'[数制符号][与数制对应的值]
(1) Binär (b): 8'b1000_1100
(2) Hexadezimal (h): 8'h8c
(3) Oktal (o): 8'o214
(4) Dezimal (d): 8'140
Vorsichtsmaßnahmen:
- Bei der Darstellung im Binärformat ist es am besten alle 4 Bit einen Unterstrich zu schreiben, um die Lesbarkeit zu verbessern: z.B. 8'b1000_1100 ist dasselbe wie 8'b10001100
- Wenn x in Radix -Notation angetroffen wird: 4 x hexadezimal, 3 x oktal
- Wenn die Bitbreite größer als die Binärzahl ist, wird die linke Seite automatisch mit 0 aufgefüllt, und wenn die Bitbreite kleiner als die Binärzahl ist, wird 2 von links abgeschnitten !
1.3.3 Strings (mit doppelten Anführungszeichen)
(1) Jedes Zeichen wird durch einen 8-Bit-ASCII-Codewert dargestellt, d. h. es wird 1 Byte Speicherplatz benötigt
(2) Beispiel: Die Zeichenkette „Hallo Welt“ besteht aus 11 ASCII-Symbolen und benötigt 11 Byte Speicherplatz
1.3 Annotationsverfahren
Es gibt zwei Haupttypen von Kommentaren in Verilog: Zeilenkommentare (//) und Blockkommentare (/* .... */) Die Darstellungsmethode ist konsistent mit der Sprache C!
// 行注释
/*
块注释
*/
1.4 Variablen (wire, reg)
Es gibt zwei Haupttypen von Variablen in Verilog: Wire und Reg
1.4.1 Draht
(1) Drahttyp (Draht): Zeigt die physikalische Verbindung zwischen Schaltungen an, und die durch Draht definierten Variablen können auch als Signalports betrachtet werden
(2) Wenn zwei Drahtsignale nacheinander zugewiesen werden, werden sie auf reale physikalische Verbindungen im Logikblock abgebildet. Zu diesem Zeitpunkt werden die Änderungen dieser beiden Signalports synchronisiert !
wire a;
wire b;
assign b = a; // 表示a与b之间生成实际的物理连线
1.4.2 Reg.-Nr
(1) Registertyp (reg): Repräsentiert eine abstrakte Datenspeichereinheit
(2) reg hat die Funktion, den Zustand zu einem bestimmten Zeitpunkt aufrechtzuerhalten
1.4.3 Verwendung und Vorsichtsmaßnahmen
(1) Die in den Always- und Initial- Anweisungen zugewiesenen Variablen (die Variablen links von der Zuweisungsnummer ) sind alle Reg- Variablen
(2) Die in der Zuweisungsanweisung zugewiesene Variable ist eine Drahttypvariable
1.5 Vektoren und Parameter (Konstanten)
1.5.1 Parameter Parameter (Konstante)
(1) Parameter ist eine Art Konstante, die normalerweise innerhalb des Moduls erscheint und häufig verwendet wird, um den Zustand, die Datenbitbreite usw. zu definieren.
parameter STATE = 1'b0;
(2) Funktioniert nur auf der deklarierten Datei und kann flexibel geändert werden !
(3) Lokaler Parameter localparam, wird nur in diesem Modul verwendet
localparam STATE= 1'b1’;
(4) Der Name des Parameters wird im Allgemeinen in Großbuchstaben geschrieben , um andere Variablen zu unterscheiden
1.5.2 Vektor (Vektor)
Vektor (Vektor) ist eine Sammlung einer Gruppe von Signalen , die als Drahtsignal mit einer Bitbreite von mehr als 1 Bit angesehen werden kann.
(1) Definition:
格式:input/output wire/reg [upper:lower] vector_name
//输入输出型
input [7:0] a,b,
output reg [7:0] out
// 模块中间向量
wire [7:0] c, e;
reg [7:0] d;
- [upper:lower] definiert die Bitbreite, z. B. [7:0] bedeutet, dass die Bitbreite 8 Bit beträgt, d. h. obere=7, untere=0
- vector_name kann mehrere Vektoren gleichzeitig schreiben
1.5.3 Vektorchip-Auswahl
- a[3:0] 0~4-Bit-Daten des Vektors a
- b[n] n-te Bitdaten des Vektors b
- c[-1:-2] Die niedrigsten 2 Bits des Vektors c
- c[0:3] Die höchsten 4-Bit- Daten des Vektors c
Multiplexer-Anwendung: Implementieren Sie einen 256-zu-1-Selektor, das sel-Signal wird als Auswahlsignal verwendet, wenn sel = 0, wählen Sie in[3:0], wenn sel = 1, wählen Sie in[7:4] und so weiter An.
module top_module (
input [1023:0] in,
input [7:0] sel,
output [3:0] out
);
assign out = {in[sel*4+3], in[sel*4+2], in[sel*4+1], in[sel*4+0]};
// assign out = in[sel*4 +: 4];
// assign out = in[sel*4+3 -: 4];
endmodule
- Der Eingang des Chipauswahlsignals sel ist eine n-Bit-Binärzahl, die automatisch in eine Dezimalzahl umgewandelt wird, wenn sie an der Operation teilnimmt und als Index dient.
- Das für diese Frage ausgewählte Signalsegment ist: in[sel*4+3: sel*4] , aber dies entspricht nicht den Syntaxregeln für die Chipauswahl von Verilog, daher sollte es wie folgt geschrieben werden:
in[sel*4 +: 4] bedeutet, dass der Index bei sel* beginnt. Das hohe 4-Bit-Signal 4
in[sel*4+3 –: 4] gibt das niedrige 4-Bit-Signal beginnend bei sel*4+3 an - Oder wählen Sie direkt jedes benötigte Bit aus und verwenden Sie dann { }, um es zu einem neuen Vektor zu verketten:
{in[sel*4+3], in[sel*4+2], in[sel*4+1], in[ sel*4+0]}
Referenzartikel: HDLBits: Learning Verilog Online (Thirteen · Problem 60-64) – Knowing (zhihu.com)
1.6 Ternäre Ausdrücke
(1) Wie die Sprache C hat auch Verilog ternäre Ausdrücke :
condition ? if_true : if_false
Wenn die Bedingung wahr ist, wird der Ausdruck zu if_true ausgewertet, andernfalls wird der Ausdruck zu if_false ausgewertet.
(2) Bewerbung
(sel ? b : a) // 一个二选一MUX,通过sel的值选择a或者b
always @(posedge clk) // 一个T触发器
q <= toggle ? ~q : q;
assign out = ena ? q : 1'bz; // 三态缓冲器
(3) Referenzartikel: HDLBits: Learning Verilog Online (8. Problem 35-39) – Zhihu (zhihu.com)
1.7 Verzweigungsanweisungen (if-else, case)
1.7.1 if-else-Anweisung
(1) Die gebräuchlichste Form : (Vorteil: alle möglichen Ausgaben werden geschrieben, es gibt keine unbekannte Pegelausgabe !)
if(<条件表达式 1>)
语句或语句块 1;
else if(<条件表达式 2>)
语句或语句块 2;
………
else
语句或语句块 n;
(2) Es wird nicht empfohlen, if-else-Verschachtelung zu verwenden, da es zu Prioritätsproblemen kommt, die zu logischer Verwirrung führen,
(3) Alle if-else-Anweisungen sollten in Form von (1) geschrieben werden !
(4) Vergleichen Sie sequentiell gemäß dem Bedingungsausdruck, es gibt eine Priorität !
1.7.2 Fallerklärung
(1) Schriftform:
case(<控制表达式>)
<分支语句 1> : 语句块 1;
<分支语句 2> : 语句块 2;
<分支语句 3> : 语句块 3;
………
<分支语句 n> : 语句块 n;
default : 语句块 n+1;
endcase
Sind die Werte von <control expression> und <branch statement n> gleich, wird das entsprechende Statement ausgeführt, ansonsten wird das Statement after default ausgeführt!
(2) Aus der case-Anweisungsstruktur sofort nach Ausführung einer Verzweigungsanweisung springen und die Ausführung der case-Anweisung beenden.
(3) Die Werte von <branch statement n> müssen sich voneinander unterscheiden !
(4) Beenden Sie den case-Anweisungsblock mit encase
(5) Es gibt keine Priorität zwischen den Verzweigungsanweisungen !
(6) Spezifische Anwendung: Anwendungsfall-Anweisung zum Aufbau eines Multiplexers (als Beispiel einen 9-zu-1-Multiplexer nehmen)
module top_module(
input [15:0] a, b, c, d, e, f, g, h, i,
input [3:0] sel,
output [15:0] out );
always @(*) begin
case(sel)
4'h0:begin out = a; end
4'h1:begin out = b; end
4'h2:begin out = c; end
4'h3:begin out = d; end
4'h4:begin out = e; end
4'h5:begin out = f; end
4'h6:begin out = g; end
4'h7:begin out = h; end
4'h8:begin out = i; end
default: out = 16'hffff;
endcase
end
endmodule
1.8 for-Schleife-Anweisung
(1) Schriftform:
integer i;
always @(*) begin
for(i=0; i<n; i++) begin: for_name
<循环语句>
end
end
- Führen Sie <loop statement> n mal aus
- for_name ist der Name jeder Schleife
1.9 Vergleichsoperatoren (>, <, >=, <=)
- Gibt 1 zurück, wenn das Ergebnis der Operation wahr ist
- Gibt 0 zurück, wenn das Ergebnis der Operation falsch ist
- Wenn ein Operandenwert unbestimmt (x) ist, ist der Rückgabewert x
2.0 Verkettungsoperator ({ , })
2.0.1 Spleißen
Verwenden Sie ein Paar geschweifte Klammern und Kommas, um den Verkettungsoperator " { , } " zu bilden, und die durch Kommas getrennten Daten werden nacheinander zu neuen Daten verkettet !
wire [1:0] a;
wire [3:0] b;
wire [5:0] c;
wire [11:0] d = {a, b, c}
2.0.2 Verschiebung durch Spleißen
Links spleißen, um sich nach rechts zu bewegen, und rechts spleißen, um sich nach links zu bewegen!
always @(posedge clk) begin
if(rst_n == 1'b0)
out <= 4'b0;
else
out <= {in, out[3:1]}; // 右移
end
2.0.2 Wiederholte Operationen in Konnektoren
Syntax: {Anzahl der Wiederholungen{Vektor}}
{3{a}} = {a, a, a}
{3'd5, {2{3'd6}}} // 9'b101_110_110.
3 binärer Volladdierer
- a, b sind eingegebene 1-Bit-Daten
- cin ist der Übertragseingang des vorherigen Addierers
- cout ist der Übertragsausgang dieses Addierers
- Summe = a+b
Code:
module add1 (
input a,
input b,
input cin,
output sum,
output cout
);
assign sum = a^b^cin;
assign cout = (a&b) | (a&cin) | (b&cin);
endmodule
4 hexadezimaler Volladdierer
In der obigen Abbildung ist der hexadezimale Volladdierer dargestellt, der im vorherigen Abschnitt aus 16 binären Volladdierern zusammengesetzt werden kann.
Der in Verilog implementierte hexadezimale Volladdierercode lautet:
module add16 (
input [15:0] a,
input [15:0] b,
input cin,
output [15:0] sum,
output cout
);
wire [16:0] Add_cin;
assign Add_cin[0] = cin; // 上图中第一个二进制加法器进位输入为0 assign Add_cin[0] = 1b'0;
// 用 generate 进行模块多次实例化
// generate 应用范围:对矢量(vector)多个位重复操作,模块重复实例化
genvar i;
generate
for(i=0; i<16; i++) begin: gen_add16 // gen_add16 为每个begin_end的结构,仿真器会通过他来标识生成结构,gen_add16[0],gen_add16[1]....
add1 Add16(.a(a[i]), .b(b[i]), .cin(Add_cin[i]), .sum(sum[i]), .cout(Add_cin[i+1]));
end
endgenerate
assign cout = Add_cin[16];
endmodule
5 Parameterübergabe in Modulen
5.1 Definieren eines Moduls, das Parameter übergeben kann
module counter
// 参数传递
#(
parameter COUNT_MAX = 25'd24_999_999,
parameter STATE = 1'b0 // 多个参数用逗号隔开
)
(
input wire sys_clk,
output reg led_out
);
// 代码主体
endmodule
5.2 Instanziierung eines Blocks mit Parametern
// 参数传递
#(
.COUNT_NUM( 25'd24_999_999), // 传入参数
.STATE(1'b0)
)
counter1_init // 实例化模块的名称位置
(
.sys_clk (sys_clk),
.led_out(led_out)
);
Verweise:
[1] Wildfire „FPGA Verilog Development Practical Guide“: [Wildfire] FPGA Verilog Development Practical Guide – Basierend auf dem Altera EP4CE10 Journey Pro Development Board – [Wildfire] FPGA Verilog Development Practical Guide – Basierend auf der Altera EP4CE10 Journey Pro Development Board Documentation (embedfire .com) https://doc.embedfire.com/fpga/altera/ep4ce10_pro/en/latest/index.html
[2] HDLBits Chinese guide: HDLBits Chinese guide-zhihu (zhihu.com)