硬件描述語言Verilog設計經驗總結

一、硬件描述語言Verilog
粗略地看Verilog與C語言有許多相似之處。分號用於結束每個語句,註釋符也是相同的(/* ... */和// 都是熟悉的),運算符“==”也用來測試相等性。Verilog的if..then..else語法與C語言的也非常相似,只是Verilog用關鍵字 begin和end代替了C的大括號。事實上,關鍵字begin和end對於單語句塊來說是可有可無的,就與C中的大括號用法一樣。Verilog和C都對大小寫敏感。  

當然,硬件和軟件的一個重要區別是它們的“運行”方式。硬件設計中用到的許多單元都是並行工作的。一旦設備電源開啓,硬件的每個單元就會一直處於運行狀態。雖然根據具體的控制邏輯和數據輸入,設備的一些單元可能不會改變它們的輸出信號,但它們還是一直在“運行”中。相反,在同一時刻整個軟件設計中只有一小部分(即使是多軟件任務也只有一個任務)在執行。如果只有一個處理器,同一時間點只能有一條指令在執行。軟件的其它部分可以被認爲處於休眠狀態,這與硬件有很大的不同。變量可能以一個有效值而存在,但大多數時間裏它們都不在使用狀態。 
軟硬件的不同行爲會直接導致硬件和軟件代碼編程方式的不同。軟件是串行執行的,每一行代碼的執行都要等到前一行代碼執行完畢後才能進行(中斷的非線性或操作系統的命令除外)。 

二、模塊(module)
一個Verilog模塊的開頭是關鍵字module,緊跟其後的是模塊名稱和端口列表,端口列表列出了該模塊用到的所有輸入輸出名稱。接下來是端口聲明部分。注意:所有的輸入輸出既出現在模塊第一行的端口列表中,也會出現在端口聲明(declaration)部分中。 

以下三個模塊分別以∶ 結構式(structural)、數據流式(data-flow)及行爲式(behavioral)來描述一個二輸入與門。
//structural
module AND2 (in1,in2,out);
input in1;
input in2;
output out;
wire in1,in2,out;
and u1 (out,in1,in2);
endmodule   

//data flow
module AND2 (in1,in2,out);
input in1;
input in2;
output out;
wire in1,in2,out;
assign out=in1&in2;
endmodule

//behavioral
module AND2 (in1,in2,out); 
input in1; 
input in2; 
output out; 
wire in1,in2; 
reg out; 
always@(in1 or in2); 
out=in1 & in2;
endmodule

結構式的描述∶在這層次中模塊是由邏輯閘(Gate Level)連接而成,在這層次的設計工作就好像以前用描繪邏輯閘來設計線路一樣。例1中的AND是Verilog的基本元件 (primitive),是Verilog語言預先定義好的函式。
數據流式的描述∶ 它是一種模擬組合函式的方法。當任何輸入有所改變時,輸出會被重新計算而跟著改變。數據流式只能用來實踐組合函式。在這個層次中,要說明數據如何在暫存器中儲存與傳送,如何處理數據。例2數據流式使用關鍵字assign進行描述。
行爲式的描述∶它是一種使用高階語言來描述硬件的方式。因Verilog提供很普遍及功能強大的描述方式。在這個層次的設計工作就好像寫C語言一樣,使用行爲式所描述的模塊其描述與真實的電路可能毫無關連。甚至某些行爲式模塊可能硬件實踐。這種特性是Verilog的優點也是缺點。 例3中的行爲式描述有一個使用關鍵字always進行描述。

三、Verilog中端口的描述
1、端口的位寬最好定義在I/O說明中,不要放在數據類型定義中;
Example1:
module test(addr,read,write,datain,dataout)
input[7:0]  datain;
input[15:0] addr;
input       read,write;
output[7:0] dataout;  //要這樣定義端口的位寬!
wire addr,read,write,datain;
reg  dataout;

Example2:
module test(addr,read,write,datain,dataout)
input  datain,addr,read,write;
output dataout;
wire[15:0] addr;
wire[7:0]  datain;
wire       read,write;
reg[7:0]   dataout;   // 不要這樣定義端口的位寬!!

2、端口的I/O與數據類型的關係:
module內部     module外部
input              wire          wire或reg
output           wire或reg         wire
inout              wire            wire


3、assign語句的左端變量必須是wire;直接用"="給變量賦值時左端變量必須是reg!
Example:
assign a=b; //a必須被定義爲wire!!
********
begin
a=b; //a必須被定義爲reg!
end

在Verilog中有二種類型的內部信號用得比較多,它們是reg和wire。它們具有不同的功能。wire是線網形變量,它不能存儲值,必須受到驅動器或者連續賦值語句的驅動。reg是數據存儲單元的抽象,通過賦值語句可以改變寄存器存儲的值,其作用與改變觸發器存儲的值相當。所有端口都有一個名稱相同且聲明爲wire的信號。因此連線line被聲明爲wire不是必要的。reg會保持上次的賦值,因此不需要每次都進行驅動。wire型信號用於異步邏輯,有時也用來連接信號。因爲 reg可以保持上次的值,因此輸入不能被聲明爲reg類型。在Verilog模塊中可以在任何時候異步地將輸入改變爲任何事件。reg和wire的主要區別是,reg類型的信號只能在過程塊(後面會談到)中賦值,而wire類型的信號只能在過程塊外賦值。這兩種信號類型都可以出現在過程塊內部和外部的賦值運算符右邊。 使用關鍵字reg並不一定意味着編譯器會創建一個寄存器,理解這一點是非常重要的。

四、敏感變量的描述完備性
Verilog中,用always塊設計組合邏輯電路時,在賦值表達式右端參與賦值的所有信號都必須在 always @(敏感電平列表)中列出,always中if語句的判斷表達式必須在敏感電平列表中列出。如果在賦值表達式右端引用了敏感電平列表中沒有列出的信號,在綜合時將會爲沒有列出的信號隱含地產生一個透明鎖存器。這是因爲該信號的變化不會立刻引起所賦值的變化,而必須等到敏感電平列表中的某一個信號變化時,它的作用才表現出來,即相當於存在一個透明鎖存器,把該信號的變化暫存起來,待敏感電平列表中的某一個信號變化時再起作用,純組合邏輯電路不可能作到這一點。綜合器會發出警告。
Example1:
input a,b,c;
reg e,d;
always @(a or b or c)
begin
e=d&a&b; /*d沒有在敏感電平列表中,d變化時e不會立刻變化,直到a,b,c中某一個變化*/
d=e|c;
end

Example2:
input a,b,c;
reg e,d;
always @(a or b or c or d)
begin
e=d&a&b; /*d在敏感電平列表中,d變化時e立刻變化*/
d=e |c;
end
Verilog中用於上升沿和下降沿的關鍵字分別是posedge和negedge。這二個關鍵字經常被用於敏感列表。

五、條件的描述完備性
如果if語句和case語句的條件描述不完備,也會造成不必要的鎖存器。
Example1:
if (a==1'b1) 
q=1'b1;//如果a==1'b0,q=? q將保持原值不變, 生成鎖存器!

Example2:
if (a==1'b1) 
q=1'b1;
else         
q=1'b0;//q有明確的值, 不會生成鎖存器!

Example3:
reg[1:0] a,q;
....
case (a)
2'b00 : q=2'b00;
2'b01 : q=2'b11;//如果a==2'b10或a==2'b11,q=? q將保持原值不變, 鎖存器!
endcase

Example4:
reg[1:0] a,q;
....
case (a)
2'b00 : q=2'b00;
2'b01 : q=2'b11;
default: q=2'b00;//q有明確的值. 不會生成鎖存器!

endcase

六、描述的規範性
以觸發器爲例說明描述的規範性
1、無置位/清零的時序邏輯
always @( posedge CLK)
begin
Q<=D;
end
2、有異步置位/清零的時序邏輯
異步置位/清零是與時鐘無關的,當異步置位/清零信號到來時,觸發器的輸出立即被置爲1或0,不需要等到時鐘沿到來才置位/清零。所以,必須要把置位/清零信號   列入always塊的事件控制表達式。
always @( posedge CLK or negedge RESET)
begin
if (!RESET)
Q=0;
else
Q<=D;
end
3、有同步置位/清零的時序邏輯
同步置位/清零是指只有在時鐘的有效跳變時刻置位/清零,才能使觸發器的輸出分別轉換爲1或0。所以,不要把置位/清零信號列入always塊的事件控制表達式。但是必須在always塊中首先檢查置位/清零信號的電平。
always @( posedge CLK )
begin
if (!RESET)
Q=0;
else
Q<=D;
end

七、非阻塞賦值和阻塞賦值
always塊中的賦值運算符與以關鍵字assign開頭的連續賦值語句中用到的運算符不一樣。"<="運算符用於非阻塞性(nonblocking)賦值,而"="運算符用於阻塞性(blocking)賦值。在一組阻塞性賦值語句中,在下一個阻塞性賦值語句執行前需要計算並賦值第一個賦值語句。這一過程就象C語言中語句的順序執行。而非阻塞語句在執行時,所有賦值語句的右邊被同時計算和賦值,在always塊結束後才完成賦值操作。連續賦值語句必須使用阻塞賦值語句(否則編譯器會報錯)。 
爲了減少代碼出錯的概率,建議在順序邏輯(例如希望以寄存器方式實現的邏輯)always塊中的所有賦值語句使用非阻塞性賦值語句。大多數always塊應該使用非阻塞性賦值語句。如果always塊都是組合邏輯,那麼就需要使用阻塞性賦值語句。  

現列舉八條“非阻塞賦值”和“阻塞賦值”指導方針,謹遵這些方針可以幫助Verilog設計者減少所遇到的90-100%的Verilog競爭:
1: 當爲時序邏輯建模,使用“非阻塞賦值”。
2: 當爲鎖存器(latch)建模,使用“非阻塞賦值”。
3: 當用always塊爲組合邏輯建模,使用“阻塞賦值”
4: 當在同一個always塊裏面既爲組合邏輯又爲時序邏輯建模,使用“非阻塞賦值”。
5: 不要在同一個always塊裏面混合使用“阻塞賦值”和“非阻塞賦值”。
6: 不要在兩個或兩個以上always塊裏面對同一個變量進行賦值。
7: 使用$strobe以顯示已被“非阻塞賦值”的值。
8: 不要使用#0延遲的賦值。 

八、其他一些設計規範和原則
1:不使用初始化語句,用復位脈衝初始化信號和變量。
2:不使用延時語句;
3:不使用循環次數不確定的語句,如:forever,while等;
4:儘量採用同步方式設計電路;
5:儘量採用行爲語句完成設計;
6:always過程塊描述組合邏輯,應在敏感信號表中列出所有的輸入信號;
7:所有的內部寄存器都應該可以被複位;避免使用內部生成的異步置位/清零信號,內部生成的置位/清零信號會引起測試問題。使某些輸出信號被置位或清零,無法正常測試。
8:用戶自定義原件(UDP元件)是不能被綜合的。
9: if...else if ... else 語句是有優先級的,一般說來第一個if的優先級最高,最後一個else的優先級最低。 而case語句是"平行"的結構,所有的case的條件和執行都沒有“優先級”。而建立優先級結構會消耗大量的組合邏輯,所以如果能夠使用case語句的地方,儘量使用case替換if...else結構。
10: 狀態機的一般設計原則,Biary, gray-code 編碼使用最少的觸發器,較多的組合邏輯。而one-hot編碼反之。所以CPLD多使用GRAY-CODE, 而FPGA多使用ONE-HOT編碼。另一方面,小型設計使用GRAY-CODE和BINARY編碼更有效,而大型狀態機使用ONE-HOT更有效。
11:fpga設計中不要使用門時鐘,內部生成的時鐘稱爲門生時鐘(gated clock)。時鐘信號必須連接到全局時鐘管腳上。
12:不要使用內部三態信號,否則增加功耗。
13:避免使用負延觸發的雙穩態多諧振盪器(flip flop)。
14:不要在代碼中使用buffer 類型的端口讀取輸出數據;要使用out 類型,再增加另外變量或信號,以獲取輸出值。這是因爲buffer 類型的端口不能連接到其他類型的端口上,因此buffer 類型就會在整個設計的端口中傳播下去。
15:對變量要先讀後寫;如果先寫後讀,就會產生長的組合邏輯和鎖存器(或寄存器)。這是因爲變量值是立即獲取的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章