HDLBits:在線學習Verilog(七 · Problem 30-34) 本系列文章將和讀者一起巡禮數字邏輯在線學習網站 HDLBits 的教程與習題,並附上解答和一些作者個人的理解,相信無論是想 7 分鐘精通 Verilog,還是對 Verilog 和數電知識查漏補缺的同學,都能從中有所收穫。 附上傳送門:Always if - HDLBitsProblem 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就方便多了。。。 推薦閱讀: 相關文章 {{#data}} {{title}} {{/data}}