Verilog HDL(Hardware Description Language)是在用途最廣泛的C語言的基礎上發展起來的一種硬件描述語言,具有靈活性高、易學易用等特點。Verilog HDL可以在較短的時間內學習和掌握,目前已經在FPGA開發/IC設計領域占據絕對的領導地位。
簡單的編程案例
為快速入門Verilog語言,先從簡單的編程案例開始。以LED流水燈程序為例來給大家展示Verilog的程序框架,代碼如下所示。
module led(
input sys_clk , //系統時鐘
input sys_rst_n, //系統復位,低電平有效
output reg [3:0] led //4位LED燈
);
//parameter define
parameter WIDTH = 25 ;
parameter COUNT_MAX = 25_000_000; //板載50M時鐘=20ns,0.5s/20ns=25000000,需要25bit
//位寬
//reg define
reg [WIDTH-1:0] counter ;
reg [1:0] led_ctrl_cnt;
//wire define
wire counter_en ;
//***********************************************************************************
//** main code
//***********************************************************************************
//計數到最大值時產生高電平使能信號
assign counter_en = (counter == (COUNT_MAX - 1'b1)) ? 1'b1 : 1'b0;?
//用于產生0.5秒使能信號的計數器
always @(posedge sys_clk)?
begin
if (sys_rst_n == 1'b0)
counter <= 1'b0;
else if (counter_en)
counter <= 1'b0;
else
counter <= counter + 1'b1;
end
//led流水控制計數器
always @(posedge sys_clk)?
begin
if (sys_rst_n == 1'b0)
led_ctrl_cnt <= 2'b0;
else if (counter_en)
led_ctrl_cnt <= led_ctrl_cnt + 2'b1;
end
//通過控制IO口的高低電平實現發光二極管的亮滅
always @(posedge sys_clk)?
begin
if (sys_rst_n == 1'b0)
led <= 4'b0;
else begin
case (led_ctrl_cnt)?
2'd0 : led <= 4'b0001;
2'd1 : led <= 4'b0010;
2'd2 : led <= 4'b0100;
2'd3 : led <= 4'b1000;
default : led <= 4'b1111;
endcase
end
end
endmodule?
需要注意的幾個點:
注釋:兩種方式,一種是以“/*”符號開始,“*/”結束,在兩個符號之間的語句都是注釋語句,因此可擴展到多行。另一種是以//開頭的語句,它表示以//開始到本行結束都屬于注釋語句。
模塊定義:以module開始,endmodule結束;
端口定義:input ?output
數據類型的定義:reg 、wire、parameter
assign語句:條件成立選擇1,否則選擇0
always語句:語句中的posedge代表在時鐘上升沿進行信號觸發。begin/end代表語句的開始和結束。
If-else語句:和C語言是比較類似的。
Case語句:需要一個case關鍵字開始,endcase關鍵字結束,default作為默認分支,和C語言也是類似的。
問號語句:與if-else類似。
Verilog的常用語法
Verilog的數字進制格式?
Verilog數字進制格式包括二進制、八進制、十進制和十六進制,一般常用的為二進制、十進制和十六進制。
二進制表示如下:4’b0101表示4位二進制數字0101;
十進制表示如下:4’d2表示4位十進制數字2(二進制0010);
十六進制表示如下:4’ha表示4位十六進制數字a(二進制1010),十六進制的計數方式為0,1,2,…,9,a,b,c,d,e,f,最大計數為f(f:十進制表示為15)。
當代碼中沒有指定數字的位寬與進制時,默認為32位的十進制,比如100,實際上表示的值為32’d100
Verilog的數據類型?
在Verilog語法中,主要有三大類數據類型,即寄存器類型、線網類型和參數類型。從名稱中,我們可以看出,真正在數字電路中起作用的數據類型應該是寄存器類型和線網類型。
寄存器類型
寄存器類型表示一個抽象的數據存儲單元,它只能在always語句和initial語句中被賦值,并且它的值從一個賦值到另一個賦值過程中被保存下來。如果該過程語句描述的是時序邏輯,即always語句帶有時鐘信號,則該寄存器變量對應為寄存器;如果該過程語句描述的是組合邏輯,即always語句不帶有時鐘信號,則該寄存器變量對應為硬件連線;
寄存器類型的缺省值是x(未知狀態)。
寄存器數據類型有很多種,如reg、integer、real等,其中最常用的就是reg類型,它的使用方法如下:
? ? ? ? ? ? //reg define
? ? ? ? ? ? reg [31:0] delay_cnt;? ?//延時計數器
? ? ? ? ? ? reg key_flag ;? ? ? ? ?//按鍵標志
線網類型
線網表示Verilog結構化元件間的物理連線。它的值由驅動元件的值決定,例如連續賦值或門的輸出。如果沒有驅動元件連接到線網,線網的缺省值為z(高阻態)。線網類型同寄存器類型一樣也是有很多種,如tri和wire等,其中最常用的就是wire類型,它的使用方法如下:
? ? ? ? //wire define
? ? ? ? wire data_en;? ? ? ?//數據使能信號
? ? ? ? wire [7:0] data ;? ?//數據
參數類型
參數類型其實就是一個常量,常被用于定義狀態機的狀態、數據位寬和延遲大小等,由于它可以在編譯時修改參數的值,因此它又常被用于一些參數可調的模塊中,使用戶在實例化模塊時,可以根據需要配置參數。在定義參數時,我們可以一次定義多個參數,參數與參數之間需要用逗號隔開。這里我們需要注意的是參數的定義是局部的,只在當前模塊中有效。它的使用方法如下:
? ? ? ? ? ? //parameter define
? ? ? ? ? ? parameter DATA_WIDTH = 8; //數據位寬為8位
Verilog的運算符
大家看完了Verilog的數據類型,我們再來介紹下Verilog的運算符。Verilog中的運算符按照功能可以分為下述類型:1、算術運算符、 2、關系運算符、3、邏輯運算符、 4、條件運算符、 5、位運算符、 6、移位運算符、 7、拼接運算符。下面我們分別對這些運算符進行介紹。
算術運算符
算術運算符,簡單來說,就是數學運算里面的加減乘除,數字邏輯處理有時候也需要進行數字運算,所以需要算術運算符。常用的算術運算符主要包括加減乘除和模除(模除運算也叫取余運算)如表 所示:
需要注意的是,Verilog實現除法與模除比較浪費組合邏輯資源,尤其是除法。一般2的指數次冪的乘除法使用移位運算來完成運算,詳情可以看移位運算符章節。非2的指數次冪的乘除法一般是調用現成的IP,QUARTUS/ISE等工具軟件會有提供,不過這些工具軟件提供的IP也是由最底層的組合邏輯(與或非門等)搭建而成的。
關系運算符
關系運算符主要是用來做一些條件判斷用的,在進行關系運算符時,如果聲明的關系是假的,則返回值是0,如果聲明的關系是真的,則返回值是1;所有的關系運算符有著相同的優先級別,關系運算符的優先級別低于算術運算符的優先級別如表所示。
邏輯運算符
邏輯運算符是連接多個關系表達式用的,可實現更加復雜的判斷,一般不單獨使用,都需要配合具體語句來實現完整的意思。
條件運算符
條件操作符一般來構建從兩個輸入中選擇一個作為輸出的條件選擇結構,功能等同于always中的if-else語句。
如果a為真,則表達式的值為b,反之為c
位運算符
位運算符是一類最基本的運算符,可以認為它們直接對應數字邏輯中的與、或、非門等邏輯門。位運算符的與、或、非與邏輯運算符邏輯與、邏輯或、邏輯非使用時候容易混淆,邏輯運算符一般用在條件判斷上,位運算符一般用在信號賦值上。
移位運算符
移位運算符包括左移位運算符和右移位運算符,這兩種移位運算符都用0來填補移出的空位。
假設a有8bit數據位寬,那么a<<2,表示a左移2bit,a還是8bit數據位寬,a的最高2bit數據被移位丟棄了,最低2bit數據固定補0。如果a是3(二進制:00000011),那么3左移2bit,3<<2,就是12(二進制:00001100)。一般使用左移位運算代替乘法,右移位運算代替除法,但是這種也只能表示2的指數次冪的乘除法。
拼接運算符
Verilog中有一個特殊的運算符是C語言中沒有的,就是位拼接運算符。用這個運算符可以把兩個或多個信號的某些位拼接起來進行運算操作。
將a與b拼接起來,作為一個新的信號
運算符的優先級
介紹完了這么多運算符,大家可能會想到究竟哪個運算符高,哪個運算符低。為了便于大家查看這些運算符的優先級,我們將它們制作成了表格。
具體還可以參考這個表格,是我在培訓中完善的表格,不過實際應用中,為避免優先級錯誤,可多加括號:
Verilog的常用關鍵字
關鍵字
注意只有小寫的關鍵字才是保留字。例如,標識符always(這是個關鍵詞)與標識符ALWAYS(非關鍵詞)是不同的。?
Verilog的高級知識點
阻塞賦值和非阻塞賦值
在Verilog中有兩種類型的賦值語句:阻塞賦值語句(“=”)和非阻塞賦值語句(“<=”)。正確地使用這兩種賦值語句對于Verilog的設計和仿真非常重要。
Verilog語言中講的阻塞賦值與非阻塞賦值,但從字面意思來看,阻塞就是執行的時候在某個地方卡住了,等這個操作執行完在繼續執行下面的語句,而非阻塞就是不管執行完沒有,我不管執行的結果是什么,反正我繼續下面的事情。而Verilog中的阻塞賦值與非阻塞賦值正好也是這個意思,通過執行一個例子,就可以簡單地明白了:
1、阻塞賦值可以理解為語句的順序執行,因此語句的執行順序很重要;
2、非阻塞賦值可以理解為語句的并行執行,所以語句的執行不考慮順序;
3、在assign的結構中,必須使用的是阻塞賦值。
也就是說:
? ? ? 阻塞:在本語句中“右式計算”和“左式更新”完全完成之后,才開始執行下一條語句;
? ? ? 非阻塞:當前語句的執行不會阻塞下一語句的執行。
阻塞語句的時序:(串行)
非阻塞語句的時序(并行)
阻塞語句的使用
(1)在時序邏輯電路中一般使用非阻塞賦值。
? ? ?非阻塞賦值在塊結束后才完成賦值操作,此賦值方式可以避免在仿真出現冒險和競爭現象。
(2)在組合邏輯電路中一般使用阻塞賦值。
? ? ?使用阻塞方式對一個變量進行賦值時,此變量的值在在賦值語句執行完后就立即改變。
(3)在assign語句中必須使用阻塞賦值語句。
assign和always區別?
assign語句和always語句是Verilog中的兩個基本語句,這兩個都是經常使用的語句。
assign語句使用時不能帶時鐘。always語句可以帶時鐘,也可以不帶時鐘。在always不帶時鐘時,邏輯功能和assign完全一致,都是只產生組合邏輯。比較簡單的組合邏輯推薦使用assign語句,比較復雜的組合邏輯推薦使用always語句。示例如下:
什么是latch?
latch是指鎖存器,是一種對脈沖電平敏感的存儲單元電路。鎖存器和寄存器都是基本存儲單元。
鎖存器是電平觸發的存儲器,是組合邏輯產生的。
寄存器是邊沿觸發的存儲器,是在時序電路中使用,由時鐘觸發產生的。
latch的主要危害的是會產生毛刺(glitch),這種毛刺對下一級電路是很危險的。并且其隱蔽性很強,不易查出。因此,在設計中,應盡量避免latch的使用。
出現latch的原因:代碼里面出現latch的兩個原因是在組合邏輯中,if或者case語句不完整的描述,比如if缺少else分支,case缺少default分支,導致代碼在綜合過程中出現了latch。
解決辦法就是if必須帶else分支,case必須帶default分支。
大家需要注意下,只有不帶時鐘的always語句if或者case語句不完整才會產生latch,帶時鐘的語句if或者case語句不完整描述不會產生latch。下面為缺少else分支的帶時鐘的always語句和不帶時鐘的always語句,通過實際產生的電路圖可以看到第二個是有一個latch的,第一個仍然是普通的帶有時鐘的寄存器。
狀態機
Verilog是硬件描述語言,硬件電路是并行執行的,當需要按照流程或者步驟來完成某個功能時,代碼中通常會使用很多個if嵌套語句來實現,這樣就增加了代碼的復雜度,以及降低了代碼的可讀性,這個時候就可以使用狀態機來編寫代碼。狀態機相當于一個控制器,它將一項功能的完成分解為若干步,每一步對應于二進制的一個狀態,通過預先設計的順序在各狀態之間進行轉換,狀態轉換的過程就是實現邏輯功能的過程。
狀態機,全稱是有限狀態機(Finite State Machine,縮寫為FSM),是一種在有限個狀態之間按一定規律轉換的時序電路,可以認為是組合邏輯和時序邏輯的一種組合。狀態機通過控制各個狀態的跳轉來控制流程,使得整個代碼看上去更加清晰易懂,在控制復雜流程的時候,狀態機優勢明顯,因此基本上都會用到狀態機,如SDRAM控制器等。
根據狀態機的輸出是否與輸入條件相關,可將狀態機分為兩大類,即摩爾(Moore)型狀態機和米勒(Mealy)型狀態機。
? Mealy狀態機:組合邏輯的輸出不僅取決于當前狀態,還取決于輸入狀態。
Moore狀態機:組合邏輯的輸出只取決于當前狀態。
三段式狀態機
根據狀態機的實際寫法,狀態機還可以分為一段式、二段式和三段式狀態機。
一段式:整個狀態機寫到一個always模塊里面,在該模塊中既描述狀態轉移,又描述狀態的輸入和輸出。不推薦采用這種狀態機,因為從代碼風格方面來講,一般都會要求把組合邏輯和時序邏輯分開;從代碼維護和升級來說,組合邏輯和時序邏輯混合在一起不利于代碼維護和修改,也不利于約束。
二段式:用兩個always模塊來描述狀態機,其中一個always模塊采用同步時序描述狀態轉移;另一個模塊采用組合邏輯判斷狀態轉移條件,描述狀態轉移規律以及輸出。不同于一段式狀態機的是,它需要定義兩個狀態,現態和次態,然后通過現態和次態的轉換來實現時序邏輯。
三段式:在兩個always模塊描述方法基礎上,使用三個always模塊,一個always模塊采用同步時序描述狀態轉移,一個always采用組合邏輯判斷狀態轉移條件,描述狀態轉移規律,另一個always模塊描述狀態輸出(可以用組合電路輸出,也可以時序電路輸出)
推薦使用三段式狀態機
實際應用中三段式狀態機使用最多,因為三段式狀態機將組合邏輯和時序分開,有利于綜合器分析優化以及程序的維護;
并且三段式狀態機將狀態轉移與狀態輸出分開,使代碼看上去更加清晰易懂,提高了代碼的可讀性,推薦大家使用三段式狀態機,本文也著重講解三段式。
三段式狀態機的基本格式是:
第一個always語句實現同步狀態跳轉;
第二個always語句采用組合邏輯判斷狀態轉移條件;
第三個always語句描述狀態輸出(可以用組合電路輸出,也可以時序電路輸出)。
在開始編寫狀態機代碼之前,一般先畫出狀態跳轉圖,這樣在編寫代碼時思路會比較清晰,下面以一個7分頻為例(對于分頻等較簡單的功能,可以不使用狀態機,這里只是演示狀態機編寫的方法),狀態跳轉圖如下圖所示。
這里是使用獨熱碼的方式來定義狀態機,每個狀態只有一位為1,當然也可以直接定義成十進制的0,1,2……7。
因為我們定義成獨熱碼的方式,每一個狀態的位寬為7位,接下來還需要定義兩個7位的寄存器,一個用來表示當前狀態,另一個用來表示下一個狀態;
parameter S0 = 7'b0000001; //獨熱碼定義方式
parameter S1 = 7'b0000010;
parameter S2 = 7'b0000100;
parameter S3 = 7'b0001000;
parameter S4 = 7'b0010000;
parameter S5 = 7'b0100000;
parameter S6 = 7'b1000000;
reg [6:0] curr_st ; //當前狀態
reg [6:0] nextst ; //下一個狀態
接下來就可以使用三個always語句來開始編寫狀態機的代碼,第一個always采用同步時序描述狀態轉移;
第二個always采用組合邏輯判斷狀態轉移條件;
第三個always是描述狀態輸出;
? ? ??
評論
查看更多