always塊(組合)
由于數字電路是由用導線連接的邏輯門組成的,因此任何電路都可以表示為模塊和賦值語句的某種組合。然而,有時這并不是描述電路的最方便的方式。過程(其中總是塊是一個示例)提供了一種用于描述電路的替代語法。
對于綜合硬件,有兩種類型的 always 塊是相關的:
組合:always @( * ) 時序邏輯:always @(posedge clk)
組合always塊等效于assign語句,因此總有一種方法可以雙向表達組合電路。選擇使用哪個,主要是哪個語法更方便的問題。程序塊內部代碼的語法與外部代碼不同。程序塊具有更豐富的語句集(例如,if-then、case),不能包含連續賦值*,但也引入了許多新的非直觀的錯誤方式。 (*過程連續賦值確實存在,但與連續賦值有些不同,并且不可合成。)
例如,assign 和combinational always 塊描述了相同的電路。兩者都創建了相同的組合邏輯塊。每當任何輸入(右側)更改值時,兩者都會重新計算輸出。 assign out1 = a & b | c ^ d; always @() out2 = a & b | c ^ d;
對于組合 always 塊,始終使用()的敏感度列表。明確列出信號容易出錯(如果您錯過了),并且在硬件綜合時會被忽略。如果您明確指定敏感度列表并遺漏了一個信號,合成的硬件仍然會像指定了(*)一樣運行,但模擬不會也不匹配硬件的行為。(在 SystemVerilog 中,使用always_comb。)
關于 wire 與 reg 的說明:assign 語句的左側必須是net類型(例如,wire),而過程賦值(在 always 塊中)的左側必須是變量類型(例如,reg)。這些類型(wire 與 reg)與合成的硬件無關,只是 Verilog 用作硬件模擬語言時遺留下來的語法。
練習
使用assign 語句和組合always 塊構建AND 門。
模塊聲明
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
output wire out_assign,
output reg out_alwaysblock
);
答案
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
output wire out_assign,
output reg out_alwaysblock
);
assign out_assign = a & b;
always @(*)begin
out_alwaysblock = a & b;
end
endmodule
always塊(時序)
對于硬件綜合,有兩種相關的always塊:
組合:always @( * ) 時序邏輯:always @(posedge clk) 時鐘always塊創建組合邏輯塊,就像組合總是塊一樣,但也在組合邏輯塊的輸出處創建一組觸發器(或“寄存器”)。邏輯塊的輸出不是立即可見,而是僅在下一個 (posedge clk) 之后立即可見。
阻塞與非阻塞分配
Verilog 中有三種類型的賦值:
連續賦值:(assign x = y;)。只能在不在過程內部時使用(“始終阻塞”)。 程序阻塞賦值:( x = y; )。只能在程序內部使用。 程序非阻塞賦值:( x <= y; )。只能在程序內部使用。 在一個組合always塊,使用阻塞分配。在時序always塊中,使用非阻塞分配。充分理解為什么對硬件設計不是特別有用,需要很好地理解 Verilog 模擬器如何跟蹤事件。不遵循此規則會導致極難發現仿真和綜合硬件之間的不確定性和不同的錯誤。
練習
以三種方式構建 XOR 門,使用賦值語句、組合的 always 塊和時序的 always 塊。請注意,時鐘始終塊產生與其他兩個不同的電路:有一個觸發器,因此輸出被延遲。
模塊聲明
// synthesis verilog_input_version verilog_2001
module top_module(
input clk,
input a,
input b,
output wire out_assign,
output reg out_always_comb,
output reg out_always_ff );
答案
// synthesis verilog_input_version verilog_2001
module top_module(
input clk,
input a,
input b,
output wire out_assign,
output reg out_always_comb,
output reg out_always_ff );
assign out_assign = a^b;
always @(*)begin
out_always_comb = a ^ b;
end
always @(posedge clk)begin
out_always_ff <= a ^ b;
end
endmodule
if語句
個如果語句通常會產生一個2至1多路復用器,選擇如果該條件為真一個輸入端,而另一個輸入,如果條件為假。
always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end
這等效于使用帶有條件運算符的連續賦值: assign out = (condition) ? x : y; 然而,程序化的if語句提供了一種新的出錯方式。只有當out總是被分配一個值時,電路才是組合的。
練習
構建一個在a和b之間進行選擇的 2 對 1 多路復用器。選擇b如果兩個 sel_b1和sel_b2是真實的。否則,選擇一個。做同樣的事情兩次,一次使用assign語句,一次使用過程if 語句。
Module Declaration
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
input sel_b1,
input sel_b2,
output wire out_assign,
output reg out_always );
答案
// synthesis verilog_input_version verilog_2001
module top_module(
input a,
input b,
input sel_b1,
input sel_b2,
output wire out_assign,
output reg out_always );
assign out_assign = (sel_b1 == 1 && sel_b2 == 1)? b : a ;
always @(*)begin
if(sel_b1==1 && sel_b2 == 1)
out_always = b;
else
out_always = a;
end
endmodule
if語句出現鎖存器
錯誤的常見來源:如何避免出現鎖存器設計電路時,你必須首先想到在電路方面:
- 我想要這個邏輯門
- 我想要一個具有這些輸入并產生這些輸出的邏輯組合塊
- 我想要一個邏輯組合塊,然后是一組觸發器 你不能做的是先寫代碼,然后希望它生成一個合適的電路。 If (cpu_overheated) then shut_off_computer = 1; If (~arrived) then keep_driving = ~gas_tank_empty; 語法正確的代碼不一定會產生合理的電路(組合邏輯 + 觸發器)。通常的原因是:“在您指定的情況之外的情況下會發生什么?”。 Verilog 的答案是:保持輸出不變。
這種“保持輸出不變”的行為意味著需要記住當前狀態,從而產生一個鎖存器。組合邏輯(例如,邏輯門)無法記住任何狀態。注意警告(10240):......推斷閂鎖(es)“消息。除非閂鎖是故意的,否則它幾乎總是表明存在錯誤。組合電路必須在所有條件下為所有輸出分配一個值。這通常意味著您總是需要else子句或分配給輸出的默認值。
練習
以下代碼包含創建閂鎖的錯誤行為。修復錯誤,以便您僅在計算機確實過熱時才關閉計算機,并在到達目的地或需要加油時停止駕駛。
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end
Module Declaration
// synthesis verilog_input_version verilog_2001
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving );
答案
// synthesis verilog_input_version verilog_2001
module top_module (
input cpu_overheated,
output reg shut_off_computer,
input arrived,
input gas_tank_empty,
output reg keep_driving ); //
always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
else
shut_off_computer = 0;
end
always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
else
keep_driving = 0;
end
endmodule
case語句
Verilog 中的 Case 語句幾乎等同于一系列 if-elseif-else,它將一個表達式與其他表達式的列表進行比較。它的語法和功能與C 中的switch語句不同。
always @(*) begin // This is a combinational circuit
case (in)
1'b1: begin
out = 1'b1; // begin-end if >1 statement
end
1'b0: out = 1'b0;
default: out = 1'bx;
endcase
end
- case 語句以case開頭,每個“case item”以冒號結尾。沒有“switch”。
- 每個 case 項只能執行一個語句。這使得 C 中使用的“break”變得不必要。但這意味著如果您需要多個語句,則必須使用begin ... end。
- 允許重復(和部分重疊)的case項。使用第一個匹配的。C 不允許重復的 case 項。
練習
如果有大量case項,Case 語句比 if 語句更方便。因此,在本練習中,創建一個 6 對 1 多路復用器。當sel在0到5之間時,選擇對應的數據輸入。否則,輸出 0。數據輸入和輸出均為 4 位寬。
Module Declaration
// synthesis verilog_input_version verilog_2001
module top_module (
input [2:0] sel,
input [3:0] data0,
input [3:0] data1,
input [3:0] data2,
input [3:0] data3,
input [3:0] data4,
input [3:0] data5,
output reg [3:0] out );
答案
// synthesis verilog_input_version verilog_2001
module top_module (
input [2:0] sel,
input [3:0] data0,
input [3:0] data1,
input [3:0] data2,
input [3:0] data3,
input [3:0] data4,
input [3:0] data5,
output reg [3:0] out );//
always@(*) begin // This is a combinational circuit
case(sel)
0:out = data0;
1:out = data1;
2:out = data2;
3:out = data3;
4:out = data4;
5:out = data5;
default:out = 4'b0000;
endcase
end
endmodule
case語句2
甲優先級編碼器是一個組合電路,給定一個輸入位向量時,輸出第一的位置1中的向量位。例如,給定輸入8'b100 1 0000的 8 位優先級編碼器將輸出3'd4,因為 bit[4] 是第一個高位。
練習
構建一個 4 位優先級編碼器。對于這個問題,如果沒有一個輸入位為高(即輸入為零),則輸出零。請注意,一個 4 位數字有 16 種可能的組合。
Module Declaration
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos );
答案
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos );
always@(*) begin // This is a combinational circuit
case(in)
0:pos = 0;
1:pos = 0;
2:pos = 1;
3:pos = 0;
4:pos = 2;
5:pos = 0;
6:pos = 1;
7:pos = 0;
8:pos = 3;
9:pos = 0;
10:pos = 1;
11:pos = 0;
12:pos = 2;
13:pos = 0;
14:pos = 1;
15:pos = 0;
default:pos = 4'b0000;
endcase
end
endmodule
casez語句
從上一個練習,case 語句中有 256 個 case。如果 case 語句中的 case 項支持 don't-care 位,我們可以減少這種情況(減少到 9 個 case)。這就是z 的情況:它在比較中將具有z值的位視為不關心的。 例如,這將實現上一個練習中的 4 輸入優先級編碼器:
always @(*) begin
casez (in[3:0])
4'bzzz1: out = 0; // in[3:1] can be anything
4'bzz1z: out = 1;
4'bz1zz: out = 2;
4'b1zzz: out = 3;
default: out = 0;
endcase
end
case 語句的行為就像是按順序檢查每個項目(實際上,它的作用更像是生成一個巨大的真值表然后制作門)。請注意某些輸入(例如4'b1111)如何匹配多個 case 項。選擇第一個匹配項(因此4'b1111匹配第一項out = 0,但不匹配后面的任何項)。
還有一個類似的casex將x和z 都視為無關緊要。我認為在casez上使用它沒有多大意義。 ==?== 是z的同義詞。所以2'bz0和2'b?0是一樣的。
練習
為 8 位輸入構建一個優先編碼器。給定一個 8 位向量,輸出應報告向量中的第一個位1。如果輸入向量沒有高位,則報告零。例如,輸入8'b100 1 0000應該輸出3'd4,因為 bit[4] 是第一個高位。
Module Declaration
// synthesisverilog_input_versionverilog_2001
moduletop_module (
input[7:0]in,
outputreg[2:0]pos );
答案
// synthesisverilog_input_versionverilog_2001
moduletop_module (
input[7:0]in,
outputreg[2:0]pos );
always @(*) begin
casez (in)
8'bzzzz_zzz1: pos = 0;
8'bzzzz_zz1z: pos = 1;
8'bzzzz_z1zz: pos = 2;
8'bzzzz_1zzz: pos = 3;
8'bzzz1_zzzz: pos = 4;
8'bzz1z_zzzz: pos = 5;
8'bz1zz_zzzz: pos = 6;
8'b1zzz_zzzz: pos = 7;
default: pos = 0;
endcase
end
endmodule
避免鎖存器
假設您正在構建一個電路來處理來自游戲的 PS/2 鍵盤的掃描碼。給定接收到的最后兩個字節的掃描碼,您需要指出是否按下了鍵盤上的箭頭鍵之一。這涉及一個相當簡單的映射,它可以實現為具有四個 case 的 case 語句(或 if-elseif)。
您的電路有一個 16 位輸入和四個輸出。構建這個電路來識別這四個掃描碼并斷言正確的輸出。 為避免創建鎖存器,必須在所有可能的條件下為所有輸出分配一個值。僅僅有一個默認情況是不夠的。您必須在所有四種情況和默認情況下為所有四個輸出分配一個值。這可能涉及許多不必要的打字。解決此問題的一種簡單方法是在 case 語句之前為輸出分配一個“默認值” :
always @(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case (scancode)
... // Setto 1 asnecessary.
endcase
end
這種代碼風格確保在所有可能的情況下為輸出分配一個值(0),除非 case 語句覆蓋分配。這也意味著default: case 項變得不必要了。邏輯合成器生成一個組合電路,其行為與代碼描述的相同。硬件不會按順序“執行”代碼行。
Module Declaration
// synthesisverilog_input_versionverilog_2001
moduletop_module (
input[15:0]scancode,
outputregleft,
outputregdown,
outputregright,
outputregup );
練習答案
// synthesisverilog_input_versionverilog_2001
moduletop_module (
input[15:0]scancode,
outputregleft,
outputregdown,
outputregright,
outputregup );
always @(*) begin
up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
case (scancode)
16'he06b: left = 1'b1;
16'he072: down = 1'b1;
16'he074:right = 1'b1;
16'he075: up = 1'b1;
endcase
end
endmodule