HDLBits答案(4)_如何避免生成锁存器?
HDLBits_Verilog Language_Procedures
HDLBits鏈接
組合邏輯的always塊
[David說]:
考慮到硬件的可綜合性,有兩種always塊是有意義的:
- 組合:always @(*)
 - 時序:always @(posedge clk)
 
組合邏輯的always塊和assign賦值是等價的,使用哪一種完全看哪一種更方便。always塊內可有更豐富的狀態(tài),如if-then,case等,但不能有連續(xù)賦值語句assign。
如果在always塊內指定了特定的信號,但沒有的話,和always(*)綜合結果相同,此時功能仿真結果和硬件結果就有所差異。
assign賦值語句的左邊一般為wire類型,always塊中左邊的變量一般為reg類型。這些類型與硬件綜合無關,僅是verilog語法的要求。
題目描述:
使用賦值語句和組合always塊兩種方式構建與門。
Solution:
module top_module(input a, input b,output wire out_assign,output reg out_alwaysblock );assign out_assign = a & b;always @(*) beginout_alwaysblock = a & b; end endmodule時序邏輯的always塊
[David說]:時序always塊可像組合always塊那樣生成電路,同時也會生成一系列的觸發(fā)器,寄存器等,因為輸出要等到下個時鐘延才能輸出。
阻塞賦值 vs 非阻塞賦值
verilog中有三種賦值方式:
- 連續(xù)賦值(assign x=y;),只能在always塊外使用。
 - 阻塞賦值(x=y;),只能在always塊內使用。
 - 非阻塞賦值(x<=y;)只能在always塊內使用。
 
在組合邏輯的always塊中(always @(*))使用阻塞賦值語句;
在時序邏輯的always塊中(always @(posedge clk))使用非阻塞賦值語句。
題目描述:
使用賦值語句、組合always塊和時序always塊三種方式構建一個異或門。
Solution:
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 @(*) beginout_always_comb = a ^ b;endalways @(posedge clk) beginout_always_ff <= a ^ b;end endmoduleIF選擇器
[David說]:
一個if語句會產(chǎn)生一個2選1的數(shù)據(jù)選擇器,需注意的是,并不是if選擇的那路數(shù)據(jù)才被實現(xiàn)成電路模式,而是if和else兩路都被實現(xiàn)為電路形式然后用選擇器選擇輸出。
if的兩種方式:
always @(*) beginif (condition) beginout = x;endelse beginout = y;end end assign out = (condition) ? x : y;題目描述:
2-1選擇器
| 0 | 0 | a | 
| 0 | 1 | a | 
| 1 | 0 | a | 
| 1 | 1 | b | 
Solution:
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;always @(*) beginif(sel_b2&sel_b1) beginout_always = b;endelse beginout_always = a;endend endmoduleIF中鎖存器問題
[David說]:
如何避免在使用if語句時生成鎖存器?
tip:鎖存器與觸發(fā)器的區(qū)別?
- 鎖存器是一種對脈沖電平(也就是0或者1)敏感的存儲單元電路,而觸發(fā)器是一種對脈沖邊沿(即上升沿或者下降沿)敏感的存儲電路。
 
當我們在設計電路時,不能直接先寫成代碼然后期望它直接生成為合適的電路,如下典型錯誤所示:
- If (cpu_overheated) then shut_off_computer = 1;
 - If (~arrived) then keep_driving = ~gas_tank_empty;
 
語法上正確的代碼并不意味著設計成的電路也是合理的。我們來思考這么一個問題,如上圖的錯誤示例,如果if條件不滿足,輸出如何變化呢?Verilog給出的解決方法是:保持輸出不變。因為組合邏輯電路不能記錄當前的狀態(tài),所以就會綜合出鎖存器。
所以當我們使用if語句或者case語句時,我們必須考慮到所有情況并給對應情況的輸出進行賦值,就意味著我們要為else或者default中的輸出賦值。
題目描述:找BUG,解決下面的代碼中包含的創(chuàng)建鎖存器的不正確行為。
always @(*) beginif (cpu_overheated)shut_off_computer = 1; endalways @(*) beginif (~arrived)keep_driving = ~gas_tank_empty; endSolution:
module top_module (input cpu_overheated,output reg shut_off_computer,input arrived,input gas_tank_empty,output reg keep_driving ); //always @(*) beginif (cpu_overheated) beginshut_off_computer = 1;endelse beginshut_off_computer = 0;endendalways @(*) beginif (~arrived) beginkeep_driving = ~gas_tank_empty;endelse beginkeep_driving = 0;endendendmoduleCase語句
[David說]:
在Verilog中,case語句與if-elseif-else相近,與c語言中的switch差別較大,示例如下:
always @(*) begin // This is a combinational circuitcase (in)1'b1: begin out = 1'b1; // begin-end if statement >1end1'b0: out = 1'b0;default: out = 1'bx;endcase end- case語句以case開始,每個case的選項以分號結束。
 - 每個case的選項中只能執(zhí)行一個statement,所以就無需break語句。但如果我們想在一個case選項中執(zhí)行多個statement,就需要使用begin...end
 - case中可以有重復的case item,首次匹配的將會被執(zhí)行。
 
題目描述:6-1數(shù)據(jù)選擇器
Be careful of inferring latches!(需添加Default)
Solution:
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 circuitcase(sel)3'b000:out=data0;3'b001:out=data1;3'b010:out=data2;3'b011:out=data3;3'b100:out=data4;3'b101:out=data5;default:out=3'b0;endcaseendendmodule優(yōu)先編碼器
題目描述:優(yōu)先編碼器是一種組合電路,當給定輸入位向量時,輸出該向量中第一個1的位置。 例如,給定輸入8’b10010000的8位優(yōu)先級編碼器將輸出3’d4,因為bit [4]是高的第一位。
構建一個4位優(yōu)先級編碼器。 對于此問題,如果所有輸入位都不為高(即輸入為零),則輸出零。 請注意,一個4位數(shù)字具有16種可能的組合。
Solution:
1、根據(jù)慣性思維使用case語法,列出每一種情況,然后列出其對應的輸出。
case(input)4'b0001: output = 1;.......4'b1111:output = 1;default: output = 0;endcase2、如果按上述思路來寫,那么更多位的優(yōu)先編碼器如何實現(xiàn)呢?其實有更簡單的方法,目前既不用casex也不用casez,這兩位要在后面出場。來看另一種思路:
module top_module (input [3:0] in,output reg [1:0] pos );always @(*) begincase(1)in[0]:pos = 0;in[1]:pos = 1;in[2]:pos = 2;in[3]:pos = 3;default:pos = 0;endcaseend endmodule根據(jù)上面所說的case的性質,case中可以有重復的case item,但首次匹配的才會被執(zhí)行。再看上面的case語句,即從低到高位去比較in中是否有數(shù)據(jù)位為1,下面即使有重復為1的也只會執(zhí)行首次匹配的操作。如此,N位優(yōu)先編碼器只需要N個case分支即可,大大簡化代碼量。
casez實現(xiàn)優(yōu)先編碼器
題目描述:
為8位輸入構建優(yōu)先級編碼器。當給定輸入位向量時,輸出該向量中第一個1的位置。如果輸入向量沒有高位,則報告為零。 例如,給定輸入8’b10010000的8位優(yōu)先級編碼器將輸出3’d4,因為bit [4]是高的第一位。
[David說]:由上一個練習我們知道,case語句中將有256種case item,使用casez以后,我們可以減少需比較的case item,這就是casez的目的,在比較中,將值z的位視作無關位。
所以上題的另一種解法為:
always @(*) begincasez (in[3:0])4'bzzz1: out = 0; // in[3:1] can be anything4'bzz1z: out = 1;4'bz1zz: out = 2;4'b1zzz: out = 3;default: out = 0;endcase endcase語句的行為就好像是按順序檢查每個項一樣(實際上,它所做的事情更像是生成一個巨大的真值表,然后進行門操作)。注意某些輸入(例如4’b1111)是如何匹配多個case項的。選擇第一個匹配項(因此4’b1111匹配第一個項目,out = 0,但不匹配后面的任何項目)。
- 還有類似的casex,它將x和z均無視掉,但用它來代替casez的意義不大。
 - 符號?是z的同義詞,所以2‘bz0=2’b?0
 
Solution:
module top_module (input [7:0] in,output reg [2:0] pos );always @(*) begincasez(in)8'bzzzzzzz1:pos=0;8'bzzzzzz1z:pos=1;8'bzzzzz1zz:pos=2;8'bzzzz1zzz:pos=3;8'bzzz1zzzz:pos=4;8'bzz1zzzzz:pos=5;8'bz1zzzzzz:pos=6;8'b1zzzzzzz:pos=7;default:pos=0;endcaseend endmodule該題也可以按Priority encoder中的解法2來寫,復雜度甚至更低。
去除鎖存器
題目描述:
假設構建一個電路來處理游戲的PS/2鍵盤上的掃描代碼。對于收到的最后兩個字節(jié)的掃描碼,我們需要指示是否按下了鍵盤上的一個方向鍵。這涉及到一個相當簡單的映射,它可以實現(xiàn)為一個case語句(或if-elseif),包含四個case。
電路有一個16位輸入和四個輸出。建立能識別這四種掃描碼并正確輸出的電路。
| 16’he06b | left arrow | 
| 16’he072 | down arrow | 
| 16’he074 | right arrow | 
| 16’he075 | up arrow | 
| Anything else | none | 
[David說]:為避免生成鎖存器,所有的輸入情況必須要被考慮到。但僅有一個簡單的default是不夠的,我們必須在case item和default中為4個輸出進行賦值,這會導致很多不必要的代碼編寫。
一種簡單的方式就是對輸出先進行賦初值的操作,這種類型的代碼確保在所有可能的情況下輸出都被賦值,除非case語句覆蓋了賦值。這也意味著不再需要缺省的default項。如下面示例代碼:
always @(*) beginup = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;case (scancode)... // Set to 1 as necessary.endcase endSolution:
module top_module (input [15:0] scancode,output reg left,output reg down,output reg right,output reg up ); always @(*) beginleft=0;down=0;right=0;up=0;case(scancode)16'he06b:left=1;16'he072:down=1;16'he074:right=1;16'he075:up=1;endcaseend endmodule總結:
-  
學習了組合和時序兩種always塊,并知曉了兩種類型的always塊中變量的類型即賦值方式。何時使用wire與reg,何時用阻塞和非阻塞賦值。
 -  
學習了如何在使用if和case語句時避免生成鎖存器:
將所有狀態(tài)均考慮到并為其輸出賦值,考慮不全可用else/default。
在case之前為所有輸出賦初值,這樣可以不用使用default,除非滿足case item進行覆蓋賦值,否則仍保持初值。
 -  
學習了case語句的妙用,以及使用casez忽略無關位,更簡便的實現(xiàn)優(yōu)先編碼器。
 
總結
以上是生活随笔為你收集整理的HDLBits答案(4)_如何避免生成锁存器?的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: 使用tableView崩溃
 - 下一篇: HDLBits答案(5)_Generat