前言
當電路比較簡單時,我們可以通過門電路的方式實現相應的功能,當電路規模變大時,如果僅使用門級描述依次完成所有邏輯門的實例化,建模工作就變得非常煩瑣而且容易出錯。這就要求設計者能夠從更高的抽象層次對硬件電路進行描述建模。
數據流級描述便是抽象層次描述的一種。它從數據流動的角度來描述整個電路,所以大多數情況下它依然離不開基本的電路結構圖或邏輯表達式。但是數據流語句的描述重點是數據如何在電路中“流動”,即數據的傳輸和變化情況﹐所以體現在描述語句中,重點是在整個電路從輸入到輸出的過程中,輸入信號經過哪些處理或者運算,最終才能得到最后的輸出信號。而這些數據的處理過程,就是通過等式右側的由操作符和操作數組成的運算表達式獲得的。
點擊查看代碼
//舉例:
module logic_gates(oY,iA,iB,iC);
output oY;
input iA,iB,iC;
assign oY = (iA&iB)|(iA&iC);
assign 語句
連續賦值語句(assign語句)是Verilog HDL數據流建模的基本語句,用於對線網進行賦值,在工程實踐中經常用於組合邏輯電路。
- 等價於門級描述,然而是從更高的抽象角度來對電路進行描述。
連續賦值語句必須以關鍵詞assign開始,其語法如下:
assign [drive_strength] [delay] net_value = expression;
//drive_strength 為可選項,默認值為strong1和strong0
//表示“DRAM驅動強度”。這個參數用來控制內存數據總線 的信號強度,數值越高代表信號強度越高,增加信號強度可以提高超頻的穩定性。
//delay 為可選項,用於指定賦值的延遲
//expression由操作符和操作數組成
舉例:
點擊查看代碼
//連續賦值語句,out、i1、i2也是線網
assign out = i1&i2;
//向量網的賦值語句,addr、addr1、addr2是16位向量
assign addr[15:0] = addr1[15:0]^addr2[15:0];
//拼接操作。賦值操作符左側是標量線網和向量線網的拼接
assign {c_out, sum[ 3:0]}= a[ 3:0] + b[3:0] + c_in;
- (1)連續賦值語句等號左邊的net_value必須是一個標量或向量線網,或者是標量或向量線網的拼接,而不能是向量或向量寄存器。
- (2)連續賦值語句總是處於激活狀態。只要任意一個操作數發生變化,表達式就會被立即重新計算,並且將結果賦給等號左邊的線網。
- (3)等號右邊表達式expression的操作數可以是標量或向量的線網或寄存器,也可以是函數調用。
- (4)賦值延遲用於控制對線網賦予新值的時間,根據仿真時間單位進行說明。賦值延遲類似於門延遲,對於描述實際電路的時序是非常有用的。
- (5)隱式連續賦值:除了首先聲明然后對其進行連續賦值以外,Verilog還提供了另一種對線網賦值的簡便方法,即在線網聲明的同時對其進行賦值。
由於線網只能被聲明一次,因此對線網的隱式聲明賦值只能有一次。
比較:
//普通的連續賦值
wire out;
assign out = in1 & in2 ;
//使用隱式連續賦值實現與上面兩條語句同樣的功能
wire out = in1 & in2 ;
隱式線網聲明:如果一個信號名被用在連續賦值語句的左側,那么Verilog編譯器認為該信號是一個隱式聲明的線網。
如果線網被連接到模塊的端口上,則Verilog編譯器認為隱式聲明線網的寬度等於模塊端口的寬度。
例:
//連續賦值, out為線網類型
wire il, i2;
assign out =il& i2;
//out並未聲明為線網,但Verilog 仿真器會推斷出out是一個隱式聲明的線網
連續賦值語句中的延遲用於控制任一操作數發生變化到語句左值被賦予新值之間的時間間隔。指定賦值延遲的方法有三種:普通賦值延遲、隱式賦值延遲和線網聲明延遲。
普通賦值延遲
即在連續賦值語句中說明延遲值,延遲值位於關鍵詞assign的后面
例:
assign #10 out = inl & in2;//連續賦值語句中的延遲
在上面的例子中,#表示時間延遲,如果inl和 in2中的任意一個發生變化,那么在計算表達式in1 & in2的新值並將新值賦給語句左值之前,會產生10個時間單位的延遲。如果在此10個時間單位期間,即左值獲得新值之前, inl或in2的值再次發生變化,那么在計算表達式的新值時會取inl或in2的當前值。這種性質被稱為慣性延遲。也就是說,脈沖寬度小於賦值延遲的輸入變化不會對輸出產生影響。
隱式連續賦值延遲
使用隱式連續賦值語句來說明對線網的賦值以及賦值延遲。隱式連續賦值等效於聲明一個線網並且對其進行連續賦值。
例:
wire #10 out = in1 & in2;//隱式連續賦值延遲
線網延遲聲明
Verilog 允許在聲明線網的時候指定一個延遲,這樣對該線網的任何賦值都會被推遲指定的時間。
例:
wire #10 out;//線網延遲聲明
assign out = in1 & in2;
與下面的語句等價:
wire out;
assign #10 out = in1 & in2;
操作符
Verilog HDI的操作符有很多種,按其功能大致可以分為邏輯操作符、位操作符、算術操作符、關系操作符,移位操作符,拼接操作符﹑縮減操作符、條件操作符。如果按其處理操作數的個數可以分為單目操作符,雙目操作符和三目操作符。
操作符優先級

操作符分類和使用舉例:

操作數
在操作數中首先要介紹的是數字,數字並不是數據類型中的某一種,但可以使用數字對數據類型進行賦值。
基本格式如下:
<位寬>'<進制><數值>
eg:
2'b10//位寬2,二進制,值為10
2'd10//位寬2,十進制,值為10
Verilog HDL中支持4種進制形式:二進制,八進制、十進制和十六進制,分別用b,o,d ,h來表示(不區分大小寫)。
數值部分指在相應進制下的數值。位寬表示了一個數字包含幾位信息,指明了數字的精確位數,這個位數是以該數字轉化為二進制后所具有的寬度來表示的
eg:
2'b01
4'd11
一般在包含位寬和進制的時候,有以下幾種情況
-
當位寬大於數值寬度時,如果數值部分是確切的數值,缺少的部分采用補零原則;
-
當位寬小於數值寬度時,采用低位對其直接截取的方式,保留位寬中定義的寬度。
-
在數值部分出現不定態x和高阻態z時,x和z也會根據進制的不同被擴展為不同的寬度。
在八進制中一個x相當於三位的二進制數xxx,在十六進制中就變為四位的二進制數xxxx。
- 在數值的首位為x和z時,如果出現了位寬大於數值寬度的情況,則缺少的位分別按x或z補齊。
如果格式中缺少了位寬或進制,則會有其他等效方法
-
如果數字中只包含進制和數值部分,則位寬采用默認寬度﹐主要取決於所使用機器的系統寬度和仿真器所支持的寬度,一般為32位。
-
如果僅有數值部分,則在默認寬度的基礎上默認進制為十進制。
數字也可以表示負數,在數字前直接添加負號即可,此時表示的是當前負數的二進制補碼,負號不可以放在數值部分
-4'd6
數據類型
線網類型
線網(net)表示硬件單元之間的連接。就像在真實的電路中一樣,線網由其連接器件的輸出端連續驅動,包括wire,wand,wor,tri,triand, trior 以及 trireg 等,其中, wire類型的線網聲明最為常用。
wire這個術語和net經常互換使用。如果沒有顯式地說明為向量,則默認線網的位寬為1。
線網的默認值為z(trireg類型的線網例外,其默認值為x),這就是為什么如果仿真失敗后,所看到的線全部為z的原因
線網的值由其驅動源確定,如果沒有驅動源,則線網的值為z。
例如:
wire a; // 聲明a是wire(連線)類型,默認值為z
wire d=1'b0; //連線d在聲明時,d被賦值為邏輯值0
寄存器類型
用來表示存儲元件,它保持原有的數值,直到被改寫。
不要將這里的寄存器與實際電路中由邊沿觸發的觸發器構成的硬件寄存器混淆
在Verilog 中,術語register僅僅意味着一個保存數值的變量。
與線網不同,寄存器不需要驅動源,而且也不像硬件寄存器那樣需要時鍾信號。
在仿真過程中的任意時刻,寄存器的值都可以通過賦值來改變。
寄存器數據類型一般通過使用關鍵字reg來聲明,默認值為x。
例如:
reg reset; //聲明能保持數值的寄存器變量reset;
reset = 1'b1; //把reset 初始化為1,使數字電路復位
#100 reset = 1'b0; //經過100個時間單位后,reset置邏輯0
寄存器也可以聲明為帶符號(Signed)類型的變量,這樣的寄存器就可以用於帶符號的算術運算。
reg signed [31:0] Q; //聲明一個帶符號的寄存器向量
向量
線網和寄存器類型的數據均可以聲明為向量(位寬大於1)。
如果在聲明中沒有指定位寬,則默認為標量(l 位)。
例如:
wire a; //標量線網變量,默認寬度
wire [7:0] bus; //8位長總線
reg s; //標量寄存器,默認寬度
reg [0:40] addr; //向量寄存器,41位寬虛擬地址
向量通過[high: low]或[low: high]進行說明,方括號中左邊的數總是代表向量的最高有效位。
向量addr的最高有效位是它的第0位,bus 最高有效位位第8位
- 向量域選擇:對於上面例子中聲明的向量,可以指定它的某一位或若干個相鄰位。
例如:
bus[7]; //選擇bus的第7位
bus[2:0]; //選擇bus的最低3位
//如果寫成bus[0:2]是非法的,因為高位應該寫在范圍說明的左側。
- 可變的向量域選擇:除了用常量指定向量域以外,Verilog HDL還允許指定可變的向量域選擇,設計者可以通過for循環來動態地選取向量的各個域。
下面是動態域選擇的兩個專用操作符。
[<starting_bit >+ : width] //從起始位開始遞增,位寬為width
[<starting_bit >- : width] //從起始位開始遞減,位寬為width
起始位可以是一個變量,但是位寬必須是一個常量。


整數
整數是一種通用的寄存器數據類型
,用於對數量進行操作,使用關鍵字integer進行聲明。
雖然可以使用reg類型的寄存器變量作為通用的變量,但聲明一個整數類型的變量來完成計數等功能顯然更為方便。
整數的默認位寬為主機的字的位數,與具體實現有關,但最小應為32位。
聲明為reg類型的寄存器變量為無符號數,而整數類型的變量則為有符號數。
舉例:
integer counter; //一般用途的變量,用作計數器
counter = 0; //把0存儲到寄存器中
實數
實常量和實數寄存器數據類型使用關鍵字real
來聲明,可以用十進制或科學記數法(例如3e6代表3 000 000)來表示。
實數聲明不能帶有范圍,其默認值為0。
如果將一個實數賦給一個整數,那么實數將會被取整為最接近的整數。

時間寄存器
仿真是按照仿真時間進行的,Verilog使用一個特殊的時間寄存器數據類型來保存仿真時間。
時間變量通過使用關鍵字time
來聲明,其寬度與具體實現有關,最小為64位。
通過調用系統函數$time可以得到當前的仿真時間。
time save_sim_time; //定義時間類型的變量save_sim_time
save_sim_time = $time; //記錄當前的仿真時間
仿真的時間單位為s,和真實時間的表示方法相同,但真實事件和仿真時間的對應由用戶定義完成。
數組
在Verilog 中允許聲明reg,integer,time,real,realtime 及其向量類型的數組,對數組的維數沒有限制,即可以聲明任意維數的數組。
線網數組也可用於連接實例的端口,數組中的每個元素都可以作為一個標量或向量,以同樣的方式來使用,形如<數組名>[<下標>]
。
對於多維數組來講,用戶需要說明其每一維的索引。
reg向量與reg類型的數組在定義上的區別
reg向量的定義方式為
reg [31:0] xl
,reg數組的定義為reg sz[31:0]

不要將數組和線網或寄存器向量混淆起來
向量是一個單獨的元件,它的位寬為n;
數組由多個元件組成,其中的每個元件的位寬為n或1
存儲器
在數字電路仿真中,人們常常需要對寄存器文件,RAM和ROM建模。
在Verilog 中,使用寄存器的一維數組來表示存儲器
。
數組的每個元素稱為一個元素或一個字,由一個數組索引來指定,每個字的位寬為1位或多位。
注意,n個1位寄存器和一個n位寄存器是不同的。
如果需要訪問存儲器中的一個特定的字,則可以通過將字的地址作為數組的下標來完成。
舉例:
reg mem1bit[0:1023]; //1kb(1位)存儲器
reg [7:0] membyte [0:1023];//1kb(8位)存儲器
membyte[511] = 0; //將membyte存儲器第511處所存的內容清零
參數
Verilog 允許使用關鍵字Parameter
在模塊內定義常數。
參數代表常數,不能像變量那樣賦值,但是每個模塊實例的參數值可以在編譯階段被重載。
通過參數重載使得用戶可以對模塊實例進行定制。
除此之外,還可以對參數的類型和范圍進行定義。
通過使用參數,用戶可以更加靈活地對模塊進行說明。
用戶不但可以根據參數來定義模塊,還可以方便地通過參數值重定義來改變模塊的行為:通過模塊實例化或使用defparam語句改變參數值。
在參數定義時需要注意避免使用硬編碼:
Verilog中的局部參數
使用關鍵字localparam
來定義,其作用等同於參數,區別在於它的值不能改變,不能通過參數重載語句(defparam)或通過有序參數列表或命名參數賦值來直接修改
。
例如,狀態機的狀態編碼是不能被修改的,為了避免被意外地更改,應當將其定義為局部參數。
舉例:
localparam state1 = 4'b001,
state2 = 4'b001,
state3 = 4'b001,
state4 = 4'b001;
字符串
保存在reg類型的變量
中,每個字符占用8位(一個字節),因此寄存器變量的寬度應足夠大,以保證容納全部字符。
-
如果寄存器變量的寬度大於字符串的大小(位),則Verilog使用0來填充左邊的空余位;
-
如果寄存器變量的寬度小於字符串的大小(位),則Verilog 截去字符串最左邊的位。
因此,在聲明保存字符串的reg變量時,其位寬應當比字符串的位長稍大
舉例:
reg [8*18:1] string1_1;//聲明變量string_1,其寬度為18個字節
initial
string_1 = "Hello world!";//字符串可以存儲在變量中
有一些特殊字符在顯示字符串時具有特定的意義,例如換行符、制表符和顯示參數的值。
如果需要在字符串中顯示這些特殊的字符,則必須加前綴轉義字符
。