本系列文章將和讀者一起巡禮數字邏輯在線學習網站 HDLBits 的教程與習題,並附上解答和一些作者個人的理解,相信無論是想 7 分鐘精通 Verilog,還是對 Verilog 和數電知識查漏補缺的同學,都能從中有所收穫。

附上傳送門:Always if - HDLBits

Problem 30: If statement(Always if)

if語句通常對應一個二選一多路復用器,如果條件為真,則選擇其中一個輸入作為輸出;反之如果條件為假,則選擇另一個輸入所謂輸出。if語句必須在過程塊內使用。

下面給出了一個基本的if語句和其綜合出來的電路。

always @(*) begin
if (condition) begin
out = x;
end
else begin
out = y;
end
end

這與下面使用條件運算符連續賦值的語句是等價的:

assign out = (condition) ? x : y;

但是,過程if語句使用不當可能會引入新的錯誤,只有out在所有的條件下都被賦值才會生成正確的組合電路,具體的錯誤下一個訓練才會講到,

牛刀小試

構建一個可以在a和b之間選擇的二選一多路復用器。如果sel_b1和sel_b2都為真,輸出b,其他情況輸出a。請使用兩種方法作答,一次使用assign賦值,一次使用if語句。

解答與分析

// 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? sel_b2? b:a :a;

always@(*)
if(sel_b1&sel_b2)
out_always = b;
else
out_always = a;

endmodule

Problem 31: If statement latches(Always if2)

常見的錯誤來源:如何避免引入鎖存器

在設計電路時,必須首先具體考慮電路:

1、我想實現一個邏輯門;

2、我想實現一個具有輸入併產生輸出的組合邏輯塊;

3、我想實現一組組合邏輯,緊接著一組觸發器。

不要上來就寫代碼,這樣往往與你想像的電路相差很遠。

if (cpu_overheated) then shut_off_computer = 1;
if (~arrived) then keep_driving = ~gas_tank_empty;

除了你指定的情況以外,會發生些什麼,答案是什麼也不會發生,輸出保持不變。而這往往就導致了電路的錯誤,所以說語法正確的代碼不一定能產生合理的電路(組合邏輯+觸發器)。

輸出保持不變,這就意味著電路需要記住當前狀態,從而產生鎖存器。組合邏輯(比如邏輯門)不能記住任何狀態。

Warning (10240): ... inferring latch(es)

上述這類警告通常情況下代表錯誤,除非鎖存器是故意生成的。組合電路輸出必須在所有輸入的情況下都有值。這意味著必須需要else子句或著輸出默認值。

牛刀小試

示例:以下代碼包含生成鎖存器的錯誤,請勿模仿!!!修復錯誤,只有當它真的過熱時才關閉計算機,真的到達目的地或者需要加油時,才停止駕駛。

always @(*) begin
if (cpu_overheated)
shut_off_computer = 1;
end

always @(*) begin
if (~arrived)
keep_driving = ~gas_tank_empty;
end

解答與分析

// 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 = ~arrived;
end

endmodule

Problem 32: Casestatement(Always case)

Verilog中的case語句幾乎等同於if-else if-else序列,它將一個表達式與其他表達式列表進行比較。它的語法和功能與C語言中的switch語句稍有不同:

always @(*) begin // This is a combinational circuit
case (in)
1b1: begin
out = 1b1; // begin-end if >1 statement
end
1b0: out = 1b0;
default: out = 1bx;
endcase
end

1、case語句以case開頭,每個case項以冒號結束。而switch語句沒有。

2、每個case項只執行一個語句。 這樣就不需要C語言中break來跳出switch。但這也意味著如果您需要多個語句,則必須使用begin ... end。

3、case項允許重複和部分重疊,執行程序匹配到的第一個,而C語言不允許重複的case項目。

牛刀小試

如果存在大量的case項,則case語句比if語句更方便。 因此,在本練習中,創建一個6選1的多路復用器。當sel介於0和5之間時,選擇相應的數據輸入。 其他情況輸出0。數據輸入和輸出均為4位寬。

注意:不要生成鎖存器(詳見:Problem 31: If statement latches(Always if2))。

解答與分析

// 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)
3b000: out = data0;
3b001: out = data1;
3b010: out = data2;
3b011: out = data3;
3b100: out = data4;
3b101: out = data5;
default: out = 4b0000;
endcase
end

endmodule

注意這裡一定要用default聲明一下不在case項裏的輸出,否則會生成不必要的寄存器,影響電路的功能。

Problem 33: Priority encoder(Always case2)

優先編碼器是組合電路,當給定輸入時,輸出輸入向量中的右邊第一個1的位置。例如,輸入8b10010000的,則優先編碼器將輸出3d4,因為位[4]是從右數第一個1。

牛刀小試

構建一個4位優先編碼器。如果沒有輸入均為零,則輸出零。請注意,4位數字有16種輸入發的可能。

小提示:使用十六進位(4hb)或十進位(4d11)與二進位(4b1011)相比可以節省打字量。

解答與分析

這是二進位編碼的寫法,看起來相對直觀一點。

// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos);

always@(*)
case(in)
4b0000: pos = 2b00;
4b0001: pos = 2b00;
4b0010: pos = 2b01;
4b0011: pos = 2b00;
4b0100: pos = 2b10;
4b0101: pos = 2b00;
4b0110: pos = 2b01;
4b0111: pos = 2b00;
4b1000: pos = 2b11;
4b1001: pos = 2b00;
4b1010: pos = 2b01;
4b1011: pos = 2b00;
4b1100: pos = 2b10;
4b1101: pos = 2b00;
4b1110: pos = 2b01;
4b1111: pos = 2b00;
default: pos = 2b00;
endcase

endmodule

作者提供了16進位的編碼的寫法,他覺得這樣打字打的少,2333333。

module top_module (
input [3:0] in,
output reg [1:0] pos
);

always @(*) begin // Combinational always block
case (in)
4h0: pos = 2h0; // I like hexadecimal because it saves typing.
4h1: pos = 2h0;
4h2: pos = 2h1;
4h3: pos = 2h0;
4h4: pos = 2h2;
4h5: pos = 2h0;
4h6: pos = 2h1;
4h7: pos = 2h0;
4h8: pos = 2h3;
4h9: pos = 2h0;
4ha: pos = 2h1;
4hb: pos = 2h0;
4hc: pos = 2h2;
4hd: pos = 2h0;
4he: pos = 2h1;
4hf: pos = 2h0;
default: pos = 2b0; // Default case is not strictly necessary because all 16 combinations are covered.
endcase
end

endmodule

當然,有更簡單的寫法,那就看下一題吧。

不過還是先談談優先解碼器,可能在學數字電路的時候優先解碼器是左邊第一個為0的數字,在本題中是右面第一個。兩者都可以叫做優先解碼器,出現的第一個數字把後面的數字屏蔽掉了,第一個數字具有較高的優先順序,所以叫做優先解碼器。

與優先解碼器相對應的簡單解碼器,簡單解碼器的輸入要求只能有一個1。出現多個1的時候視不同情況有不同的處理方式。

Problem 34: Priority encoder with casez(Always casez)

牛刀小試

構建一個8輸入的優先編碼器。給定一個8位向量,輸出輸入向量中左數第一個1的位置。如果輸入均為0,則輸出零。例如,輸入8b10010000應該輸出3d4,因為位[4]是第一個出現1的位置。

如果還按上一個練習(Problem 33: Priority encoder(Always case2))寫case語句的話,case語句中將有256個case項。如果case語句中的case項與某些輸入無關,就可以減少列出的case項(在本題中減少到9個)。這就是casez的用途:它在比較中將具有值z的位視為無關項(即輸入01都會匹配到)。

例如:下面的代碼就是上一個聯繫中的4輸入優先編碼器:

always @(*) begin
casez (in[3:0])
4bzzz1: out = 0; // in[3:1]輸入什麼都可以
4bzz1z: out = 1;
4bz1zz: out = 2;
4b1zzz: out = 3;
default: out = 0;
endcase
end

case項是按順序檢查的(實際上,它更像是生成一個巨大的真值表然後生成超大的門)。注意有輸入(例如,4b1111)匹配多個case項。選擇第一個匹配(因此4b1111匹配第一個case項,out = 0)。

還有一個類似的casex,將輸入的x和z都視為無關。不認為casex比casez有什麼特別的好處。(作者個人感覺沒必要用casex,z和x狀態的問題涉及電路的基本知識)

符號"?" 是z的同義詞,所以2bz0與2b?0相同。

解答與分析

// synthesis verilog_input_version verilog_2001
module top_module (
input [7:0] in,
output reg [2:0] pos);

always @(*)
casez (in)
8bzzzzzzz1: pos = 0;
8bzzzzzz1z: pos = 1;
8bzzzzz1zz: pos = 2;
8bzzzz1zzz: pos = 3;
8bzzz1zzzz: pos = 4;
8bzz1zzzzz: pos = 5;
8bz1zzzzzz: pos = 6;
8b1zzzzzzz: pos = 7;
default: pos = 0;
endcase

endmodule

嗯,用casez就方便多了。。。


推薦閱讀:
相關文章