Verilog描述方法與層次
Verilog語言有多種描述方法,這些方法也可以在多個層次上來描述硬件。
描述方式
在上一篇當中已經引入過數據流描述、行為描述、結構化描述這三種描述的方式的概念,本篇將繼續深入說明這三種描述方式。
數據流描述
1.數據流 :組合邏輯電路的信號傳輸其實就類似於數據的流動,數據從中經過但是不會存儲,一旦輸入改變,輸出隨之在一定的延時(Tpd)之后發生改變。
連續賦值語句
連續賦值語句具有以下的特點:
1.連續驅動:輸入的改變將導致該語句重新計算
2.只有線網型變量可以用assign賦值:仿真器中不會儲存assign賦值的變量,寄存器有存儲要求顯然不能用assign賦值,只有線網型可以。另外,assign語句允許多驅動即一個變量可以多個輸入(實際上就是實現線與和線或),建議復習一下數字電路中線與和線或的實現。
3.適用於組合邏輯建模:若要模擬延遲可以通過 # 來加延遲
4.並行性:assign語句同其他語句塊是並發的。
以一個異或門為例:
module FullAdder(input x,input y,input c_in,output c_out,output sum);
assign sum = x^y^c_in;
assign c_out = (x&y) || (x&c_in) || (y&c_in)
//以上兩條語句都是並發執行且連續賦值的。
endmodule
延時
任何一個元器件都必然存在延時,而在數據流描述方式中,我們也可以選擇對其進行建模。
延時具體也可以分為:上升延時即輸出變為1時的延時,下降延時即輸出變為0的延時,關閉延時即輸出變為高阻態的延時,以及輸出變為X即中間態的延時(通常取前面三種中最小的)。
關於延時的建模也有多種方法,以下是一個例子:
assign #1 out = in1^in2;//`timescale 1ns/1ns輸入到輸出須1ns
assign #(1,2) out1 = in1^in2;//上升延時1ns,下降延時2ns,變x和關閉取最小為1ns
assign #(1,2,3) out2 = in1^in2;//上升1ns,下降2ns,關閉3ns,變X1ns
assign #(4:5:6,3:4:5)out3 = in1^in2;
//(min:typ:max)描述的時延時最小典型最大三種情況,同樣第一個是上升,第二個是下降
注意:連續賦值中的延時是慣性延時,即出現小於延時的信號(毛刺信號)會被過慮掉。用戶只能在邏輯綜合中對時序加以控制,沒辦法直接操控器件本身的特性。
多驅動線網
多驅動的意思實際上就是可以將幾條線連到一起,實際上這樣的情況主要集中在:線與、線或以及三態總線等實際應用當中。
wor wire_or;
wand wire_and;
tri wire_tri;
assign wire_or = in1 & in2;
assign wire_or = in3 & in4;//以上兩個右邊結果的或
assign wire_and = in1 | in2;
assign wire_and = in3 | in4;//以上兩個右邊結果的與
assign wire_tri = (Write)? mem1 : 1'bz;
assign wire_tri = (read)? mem1 : 1'bz;//高阻態確保了能切斷電氣連接不會混亂
千萬注意:一般的wire類型是絕對不允許多驅動的,數字電路中一旦這樣接就會產生各種各樣的競爭冒險,造成嚴重的輸出混亂。
行為描述
行為描述使用語言描述電路的行為。行為描述的語句只有:always和initial。
行為描述的語句格式
1.initial與always語句塊
主要把握兩者的區別:initial只執行一次之后就會永久掛起,而always一旦符合敏感表的條件,就會反復執行。
2.過程塊中的語句種類
主要有四類:阻塞語句與非阻塞語句,連續過程賦值(是可以做連續過程賦值的)和高級編程語句。
always@(posedge Clock or negedge Rst_n)//always語句塊
begin
if(!Rst_n)//高級編程語句
begin
Reg_A <= 0;//非阻塞語句
Reg_b <= 0;
end
else
begin
Reg_A = Input_A;//阻塞語句
Reg_b = Input_B;
end
end
3.時序控制(Timing Control)
時序控制主要通過三種方式進行控制:事件語句“@ ”,延時語句“#”,等待語句
`timescale 1ns/1ns
module Dff(input D,input Clk,input Rst,output Q);
always@(posedge Clk or negedge Rst)//事件發生即變量發生變化時執行
begin
if(Rst)
Q <= 0;
else
Q <= D;
end
endmodule
initial
begin
#5 clk = 0;//表示5ns后clk賦值0
end
wait()語句許多綜合工具仍不支持,也主要用在仿真時,所以不贅述了。
過程賦值語句
過程賦值語句即在initial塊和always塊中賦值的語句,主要分為:阻塞語句與非阻塞語句。
1.阻塞語句
常規形式 寄存器變量 = 表達式
所謂阻塞其實有兩層含義:其一,計算表達式與賦值是統一的原子操作,不可再分,其中不能插入其他操作。其二,在begin...end代碼塊中必須把這個表達式賦值完才能做后續的操作,完成之前將完全阻塞后續操作,是順序執行的。而阻塞往往也是用在組合邏輯電路當中的。
wire A;
wire B;
wire C;
reg temp;
reg D;
always@(A or B or C )
begin
temp = A&B;
D = temp | C;//這樣寫可以使得邏輯非常清晰
end
有意思的是,實際綜合的過程中 D 與 temp 是不會被作為寄存器綜合的,綜合時仍然作為組合電路去處理。
2.非阻塞賦值
常規形式 寄存器變量 <= 表達式
對應於阻塞賦值的情況:非阻塞意味着左右兩邊的操作不是作為一個原子操作進行的,執行時首先計算右邊表達式,但並不馬上賦值。同時在begin...end語句中出現的非阻塞賦值語句會默認優先級較低,計算后會在靠后的時間才賦值。這帶給我們以良好的並發特性,在時序邏輯電路中非常實用。
always@(posedge Clk)
begin
Q1 <= D;
Q2 <= Q1;
Q3 <= Q2;//這很好地描述了一個三級流水寄存器電路
end
3.過程連續賦值
在過程語句中是允許用assign對寄存器賦值的。但是需要注意這一賦值是強制性的,有需要時必須放開否則無法繼續對該寄存器進行操作了。常用在測試中。這即稱為連續過程賦值。
關鍵字:assign 與 deassign;force 與 release;
module(input D,input Clk,input Clr,output Q);
always@(Clr)
begin
if(!Clr)//實現一個異步清零的操作
assign Q = 0;
else
deassign;
end
always@(posedge Clk)
Q <= D;
endmodule
語句組
語句組包含兩類:順序語句組begin...end...;並行語句組fork...join;
1.順序語句組
其中的語句是一條一條的順序執行的。當然,正如前文所述,對於非阻塞性的語句,是不會阻塞后續執行的。
下面以一個典型的帶延時的語句來說明這一點:
`timescale 1ns/1ns
initial
begin
#2 Data = 0;
#4 Data = 1;
#8 Data = 2;
end
//實際上如果觀察波形圖會發現:2ns時Data為0,6ns時Data為1,14ns時Data為2
//三個語句是順序的
2,並行語句組
用同樣的例子來闡述這一情況
`timescale 1ns/1ns
initial
fork
#2 Data = 0;
#4 Data = 1;
#8 Data = 2;
join
//實際上如果觀察波形圖會發現:2ns時Data為0,4ns時Data為1,8ns時Data為2
//三個語句是並發的
3.語句組的標識符
通過語句組的標識符可以實現類似C中局部變量的存在,即只在代碼塊內有效的變量。
高級編程語句
Verilog的特點就是設計層次較高,不僅在晶體管級和門級,主要在RTL級和行為級描述,以及編寫測試激勵。
為了提高抽象能力,Verilog從C中借鑒了一些語句,同時也創造了一些語句,統稱為高級編程語句。
注意:高級編程語句只能出現在initial和always過程塊中,也可以互相嵌套。
主要有三類:if...else語句,case語句,循環語句
1.if...else語句
正如C語言中那樣,if...else表示的是條件判斷。其中也允許繼續嵌套語句組(begin..end或fork..join)。但是在硬件當中,則會體現一些新的特點。
首先,if...elif...else天然就具有優先級。
always@(a or b or c or sela or selb)
begin
if(sel_a)
out = a;
else if(sel_b)
out = b;
else
out = c;
end
//實際上實現了一個兩級的多路選擇器
//而選擇sela的優先級最高,如果sela是關鍵路徑則顯然有利於電路實現
注意:應該避免使用if語句產生鎖存器。盡量應該避免出現 a = a的情況導致鎖存,因而有時需要遍歷全部的條件,或者設置不關心值 x。
always@(sel or a or b or c)
begin
if(sel == 2'b00)
q = a;
else if(sel == 2'b01)
q = b;
else if(sel == 2'b10)
q = c;
//這里沒有說明sel = 2‘b11時的情況,因而系統默認此時q值不變從而出現鎖存器
//鎖存器(latch)是應該避免的器件,因為容易產生競爭冒險
//應該加上以下語句:
//else q = 1'bx 此時不關心q取什么。
end
2.case語句
case語句的語法仍然同C語言中沒有大的區別。但是我們要注意同為條件判斷語句在硬件實現中它與if...else的區別與聯系。
區別:case中所有判斷是並發的,不存在優先級的概念。
聯系:case中也容易產生鎖存器,因而也需要格外注意default的設置。
always@(sel or a or b or c)
begin
case(sel)
2'b00 : q = a;
2'b01 : q = b;
2'b10 : q = c;
default : q = 1'bx;//如果不寫就會出現鎖存。
endcase
end
case還有兩個派生的語句casex(表示對x不關心)與casez(表示對高阻態不關心)
casez(encoder)
4'b1zzz : out1 = 3;
4'bz1zz : out1 = 2;
4'bzz1z : out1 = 1;
4'bzzz1 : out1 = 0;
endcase
casex(encoder1)
4'b1xxx : out2 = 3;
4'bx1xx : out2 = 2;
4'bxx1x : out2 = 1;
4'bxxx1 : out2 = 0;
endcase
實際上,在單熱線(one-hot)中為了節省譯碼時間經常這樣編碼加快運行速度。
3.循環語句
循環語句用於重復的操作。
在描述硬件的過程中,不是非常推薦寫循環語句,因為在綜合時,軟件實際上時對循環從頭到尾全部執行一遍。可能看起來接近軟件程序的寫法比較簡潔,實際上對於硬件描述不是很好。
通常循環語句都是用於后期對於電路的驗證激勵。
循環語句主要有四種:forever, repeat, for(), while()
//===================單獨的代碼塊
initial
begin
clk = 0;
count = 0;
while(count<101)
begin
$display("count = %d",count);
count = count + 1;
end
forever # clk = ~clk;//forever常常用於生成時鍾
end
//===================單獨的代碼塊
integer i;
always@(posedge clk)
begin
for(i = 0; i<100;i = i+1)//也是常常用在激勵中做輸入輸出
q = i;
end
//===================單獨的代碼塊
if(in == 1)
repeat(8)//表示重復以下操作八次
a = a + 1;
結構化描述
結構化描述實際上就是在描述中使用已有的或者已經寫好的功能模塊。包括:門源語,用戶自定義原語,其他模塊。
寫好結構化描述對於代碼的維護和擴展有很大的幫助。也容易理清各個模塊之間的關系。
module half_add(
input a, input b, output c_out, output sum)
xor u_xor(sum,a,b);//assign sum = a^b;
and u_and(c_out,a,b);//assign c_out = a&b;
//使用了門原語
endmodule
實例化模塊的方法
1.首先要再次強調輸入輸出的問題:
input 輸入在模塊內是默認一個線網類型
output 輸出在模塊內默認為一個寄存器或者線網類型
inout 在模塊內默認是線網類型,雙向信號應該是tri
2.實例的方式
名稱對應:module(.x(a),.y(b),.z(c) ...)通過名稱做對應;
位置對應:module(a,b,c ...)外部的信號與對應端口信號在括號的位置一一對應,類似C語言的函數調用;
參數化模塊
1.參數定義
參數在硬件描述中有着重要的位置,例如上一篇提到過的,使用參數而不是編譯指令去對狀態機的狀態進行描述等等。
實際上,當實例化模塊是,用戶是可以修改模塊的參數來實現不同的特性。因此,用戶可以定制一個模塊具有缺省的參數值從而來改變參數做成不同的模塊。
2.參數的定制
有兩種方法:主流的Altera和Xlinx各使用了一種。這里介紹簡單介紹一下:
通過defparam關鍵字對參數重新定義
在實例化模塊中代入。類似於模塊的位置對應。
系統層次
上一篇提到過,Verilog語言共有五個層次,自上到下:系統級,行為級,RTL級,門級,晶體管級。
我們設計中主要使用的都是RTL級。RTL級也是主流綜合器最常用的級別,行為級雖然描述簡單,但是實際綜合中會出現很多錯誤。
而對於驗證中則常常使用行為級。
嚴格的說,描述層次之間沒有嚴格的界限,根據工作特點和關注要素不同要合理選擇。
主要內容和示例來源:《輕松稱為設計高手 Verilog HDL實用精解》;北京航空航天大學出版社;
推薦練習網站:https://hdlbits.01xz.net/wiki