本系列文章將和讀者一起巡禮數字邏輯在線學習網站 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
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
注意這裡一定要用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
作者提供了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
當然,有更簡單的寫法,那就看下一題吧。
不過還是先談談優先解碼器,可能在學數字電路的時候優先解碼器是左邊第一個為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
嗯,用casez就方便多了。。。