【正点原子FPGA连载】第七章 Verilog HDL语 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0
1)實(shí)驗(yàn)平臺(tái):正點(diǎn)原子領(lǐng)航者ZYNQ開發(fā)板
2)平臺(tái)購買地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套實(shí)驗(yàn)源碼+手冊(cè)+視頻下載地址:http://www.openedv.com/thread-301505-1-1.html
4)對(duì)正點(diǎn)原子FPGA感興趣的同學(xué)可以加群討論:994244016
5)關(guān)注正點(diǎn)原子公眾號(hào),獲取最新資料更新
第七章 Verilog HDL語法
Verilog HDL(Hardware Description Language)是在用途最廣泛的C語言的基礎(chǔ)上發(fā)展起來的一種硬件描述語言,具有靈活性高、易學(xué)易用等特點(diǎn)。Verilog HDL可以在較短的時(shí)間內(nèi)學(xué)習(xí)和掌握,目前已經(jīng)在FPGA開發(fā)/IC設(shè)計(jì)領(lǐng)域占據(jù)絕對(duì)的領(lǐng)導(dǎo)地位。
本章包括以下幾個(gè)部分:
1.1 Verilog概述
1.2 Verilog基礎(chǔ)知識(shí)
1.3 Verilog程序框架
1.4 Verilog高級(jí)知識(shí)點(diǎn)
1.5 Verilog編程規(guī)范
1.1 Verilog概述
本節(jié)主要描述了Verilog HDL(以下簡稱Verilog)簡介、Verilog和VHDL以及和C語言的區(qū)別。
1.1.1 Verilog簡介
Verilog是一種硬件描述語言,以文本形式來描述數(shù)字系統(tǒng)硬件的結(jié)構(gòu)和行為的語言,用它可以表示邏輯電路圖、邏輯表達(dá)式,還可以表示數(shù)字邏輯系統(tǒng)所完成的邏輯功能。
數(shù)字電路設(shè)計(jì)者利用這種語言,可以從頂層到底層逐層描述自己的設(shè)計(jì)思想,用一系列分層次的模塊來表示極其復(fù)雜的數(shù)字系統(tǒng)。然后利用電子設(shè)計(jì)自動(dòng)化(EDA)工具,逐層進(jìn)行仿真驗(yàn)證,再把其中需要變?yōu)閷?shí)際電路的模塊組合,經(jīng)過自動(dòng)綜合工具轉(zhuǎn)換到門級(jí)電路網(wǎng)表。接下來,再用專用集成電路ASIC或FPGA自動(dòng)布局布線工具,把網(wǎng)表轉(zhuǎn)換為要實(shí)現(xiàn)的具體電路結(jié)構(gòu)。
Verilog語言最初是于1983年由Gateway Design Automation公司為其模擬器產(chǎn)品開發(fā)的硬件建模語言。由于他們的模擬、仿真器產(chǎn)品的廣泛使用,Verilog HDL作為一種便于使用且實(shí)用的語言逐漸為眾多設(shè)計(jì)者所接受。在一次努力增加語言普及性的活動(dòng)中,Verilog HDL語言于1990年被推向公眾領(lǐng)域。Verilog語言于1995年成為IEEE標(biāo)準(zhǔn),稱為IEEE Std1364-1995,也就是通常所說的Verilog-95。
設(shè)計(jì)人員在使用Verilog-95的過程中發(fā)現(xiàn)了一些可改進(jìn)之處。為了解決用戶在使用此版本Verilog過程中反映的問題,Verilog進(jìn)行了修正和擴(kuò)展,這個(gè)擴(kuò)展后的版本后來成為了電氣電子工程師學(xué)會(huì)Std1364-2001標(biāo)準(zhǔn),即通常所說的Verilog-2001。Verilog-2001是對(duì)Verilog-95的一個(gè)重大改進(jìn)版本,它具備一些新的實(shí)用功能,例如敏感列表、多維數(shù)組、生成語句塊、命名端口連接等。目前,Verilog-2001是Verilog的最主流版本,被大多數(shù)商業(yè)電子設(shè)計(jì)自動(dòng)化軟件支持。
1.1.2 為什么需要Verilog
在FPGA設(shè)計(jì)里面,我們有多種設(shè)計(jì)方式,如原理圖設(shè)計(jì)方式、編寫描述語言(代碼)等方式。一開始很多工程師對(duì)原理圖設(shè)計(jì)方式很鐘愛,這種輸入方式能夠很直觀的看到電路結(jié)構(gòu)并快速理解,但是隨著電路設(shè)計(jì)規(guī)模的不斷增加,邏輯電路設(shè)計(jì)也越來越復(fù)雜,這種設(shè)計(jì)方式已經(jīng)越來越不滿足實(shí)際的項(xiàng)目需求了。這個(gè)時(shí)候Verilog語言就取而代之了,目前Verilog已經(jīng)在FPGA開發(fā)/IC設(shè)計(jì)領(lǐng)域占據(jù)絕對(duì)的領(lǐng)導(dǎo)地位。
1.1.3 Verilog和VHDL區(qū)別
這兩種語言都是用于數(shù)字電路系統(tǒng)設(shè)計(jì)的硬件描述語言,而且都已經(jīng)是IEEE的標(biāo)準(zhǔn)。 VHDL 1987年成為標(biāo)準(zhǔn),而Verilog是1995年才成為標(biāo)準(zhǔn)的。這是因?yàn)閂HDL是美國軍方組織開發(fā)的,而Verilog是由一個(gè)公司的私有財(cái)產(chǎn)轉(zhuǎn)化而來。為什么Verilog能成為IEEE標(biāo)準(zhǔn)呢?它一定有其獨(dú)特的優(yōu)越性才行,所以說Verilog有更強(qiáng)的生命力。
這兩者有其共同的特點(diǎn):
但是兩者也各有特點(diǎn)。Verilog推出已經(jīng)有20年了,擁有廣泛的設(shè)計(jì)群體,成熟的資源,且Verilog容易掌握,只要有C語言的編程基礎(chǔ),通過比較短的時(shí)間,經(jīng)過一些實(shí)際的操作,可以在1個(gè)月左右掌握這種語言。而VHDL設(shè)計(jì)相對(duì)要難一點(diǎn),這個(gè)是因?yàn)閂HDL不是很直觀,一般認(rèn)為至少要半年以上的專業(yè)培訓(xùn)才能掌握。
近10年來,EDA界一直在對(duì)數(shù)字邏輯設(shè)計(jì)中究竟用哪一種硬件描述語言爭論不休,目前在美國,高層次數(shù)字系統(tǒng)設(shè)計(jì)領(lǐng)域中,應(yīng)用Verilog和VHDL的比率是80%和20%;日本與中國臺(tái)灣和美國差不多;而在歐洲VHDL發(fā)展的比較好;在中國很多集成電路設(shè)計(jì)公司都采用Verilog。我們推薦大家學(xué)習(xí)Verilog,本教程全部的例程都是使用Verilog開發(fā)的。
1.1.4 Verilog和C的區(qū)別
Verilog是硬件描述語言,在編譯下載到FPGA之后,會(huì)生成電路,所以Verilog全部是并行處理與運(yùn)行的;C語言是軟件語言,編譯下載到單片機(jī)/CPU之后,還是軟件指令,而不會(huì)根據(jù)你的代碼生成相應(yīng)的硬件電路,而單片機(jī)/CPU處理軟件指令需要取址、譯碼、執(zhí)行,是串行執(zhí)行的。
Verilog和C的區(qū)別也是FPGA和單片機(jī)/CPU的區(qū)別,由于FPGA全部并行處理,所以處理速度非常快,這個(gè)是FPGA的最大優(yōu)勢(shì),這一點(diǎn)是單片機(jī)/CPU替代不了的。
1.2 Verilog基礎(chǔ)知識(shí)
本節(jié)主要講解了Verilog的基礎(chǔ)知識(shí),包括5個(gè)小節(jié),下面我們分別給大家介紹這5個(gè)小節(jié)的內(nèi)容。
1.2.1 Verilog的邏輯值
我們先看下邏輯電路中有四種值,即四種狀態(tài):
邏輯 0:表示低電平,也就是對(duì)應(yīng)我們電路的GND;
邏輯 1:表示高電平,也就是對(duì)應(yīng)我們電路的VCC;
邏輯 X:表示未知,有可能是高電平,也有可能是低電平;
邏輯 Z:表示高阻態(tài),外部沒有激勵(lì)信號(hào)是一個(gè)懸空狀態(tài)。
如下圖所示:
圖 7.2.1.1 Verilog邏輯值
1.2.2 Verilog的標(biāo)識(shí)符
定義
標(biāo)識(shí)符(identifier)用于定義模塊名、端口名和信號(hào)名等。Verilog的標(biāo)識(shí)符可以是任意一組字母、數(shù)字、和(下劃線)符號(hào)的組合,但標(biāo)識(shí)符的第一個(gè)字符必須是字母或者下劃線。另外,標(biāo)識(shí)符是區(qū)分大小寫的。以下是標(biāo)識(shí)符的幾個(gè)例子:CountCOUNT//與Count不同。R5668FIVE和_(下劃線)符號(hào)的組合,但標(biāo)識(shí)符的第一個(gè)字符必須是字母或者下劃線。另外,標(biāo)識(shí)符是區(qū)分大小寫的。以下是標(biāo)識(shí)符的幾個(gè)例子: Count COUNT //與Count不同。 R56_68 FIVE和(?下劃線)符號(hào)的組合,但標(biāo)識(shí)符的第一個(gè)字符必須是字母或者下劃線。另外,標(biāo)識(shí)符是區(qū)分大小寫的。以下是標(biāo)識(shí)符的幾個(gè)例子:CountCOUNT//與Count不同。R566?8FIVE
雖然標(biāo)識(shí)符寫法很多,但是要簡潔、清晰、易懂,推薦寫法如下:
count
fifo_wr
不建議大小寫混合使用,普通內(nèi)部信號(hào)建議全部小寫,參數(shù)定義建議大寫,另外信號(hào)命名最好體現(xiàn)信號(hào)的含義。
規(guī)范建議
以下是一些書寫規(guī)范的要求:
1、用有意義的有效的名字如sum、cpu_addr等。
2、用下劃線區(qū)分詞語組合,如cpu_addr。
3、采用一些前綴或后綴,比如:時(shí)鐘采用clk前綴:clk_50m,clk_cpu;低電平采用_n后綴:enable_n;
4、統(tǒng)一縮寫,如全局復(fù)位信號(hào)rst。
5、同一信號(hào)在不同層次保持一致性,如同一時(shí)鐘信號(hào)必須在各模塊保持一致。
6、自定義的標(biāo)識(shí)符不能與保留字(關(guān)鍵詞)同名。
7、參數(shù)統(tǒng)一采用大寫,如定義參數(shù)使用SIZE。
1.2.3 Verilog的數(shù)字進(jìn)制格式
Verilog數(shù)字進(jìn)制格式包括二進(jìn)制、八進(jìn)制、十進(jìn)制和十六進(jìn)制,一般常用的為二進(jìn)制、十進(jìn)制和十六進(jìn)制。
二進(jìn)制表示如下:4’b0101表示4位二進(jìn)制數(shù)字0101;
十進(jìn)制表示如下:4’d2表示4位十進(jìn)制數(shù)字2(二進(jìn)制0010);
十六進(jìn)制表示如下:4’ha表示4位十六進(jìn)制數(shù)字a(二進(jìn)制1010),十六進(jìn)制的計(jì)數(shù)方式為0,1,2…9,a,b,c,d,e,f,最大計(jì)數(shù)為f(f:十進(jìn)制表示為15)。
當(dāng)代碼中沒有指定數(shù)字的位寬與進(jìn)制時(shí),默認(rèn)為32位的十進(jìn)制,比如100,實(shí)際上表示的值為32’d100。
1.2.4 Verilog的數(shù)據(jù)類型
在Verilog語法中,主要有三大類數(shù)據(jù)類型,即寄存器類型、線網(wǎng)類型和參數(shù)類型。從名稱中,我們可以看出,真正在數(shù)字電路中起作用的數(shù)據(jù)類型應(yīng)該是寄存器類型和線網(wǎng)類型。
寄存器類型表示一個(gè)抽象的數(shù)據(jù)存儲(chǔ)單元,它只能在always語句和initial語句中被賦值,并且它的值從一個(gè)賦值到另一個(gè)賦值過程中被保存下來。如果該過程語句描述的是時(shí)序邏輯,即always語句帶有時(shí)鐘信號(hào),則該寄存器變量對(duì)應(yīng)為寄存器;如果該過程語句描述的是組合邏輯,即always語句不帶有時(shí)鐘信號(hào),則該寄存器變量對(duì)應(yīng)為硬件連線;寄存器類型的缺省值是x(未知狀態(tài))。
寄存器數(shù)據(jù)類型有很多種,如reg、integer、real等,其中最常用的就是reg類型,它的使用方法如下:
線網(wǎng)表示Verilog結(jié)構(gòu)化元件間的物理連線。它的值由驅(qū)動(dòng)元件的值決定,例如連續(xù)賦值或門的輸出。如果沒有驅(qū)動(dòng)元件連接到線網(wǎng),線網(wǎng)的缺省值為z(高阻態(tài))。線網(wǎng)類型同寄存器類型一樣也是有很多種,如tri和wire等,其中最常用的就是wire類型,它的使用方法如下:
//wire define
wire data_en; //數(shù)據(jù)使能信號(hào)
wire [7:0] data ; //數(shù)據(jù)
我們?cè)賮砜聪聟?shù)類型,參數(shù)其實(shí)就是一個(gè)常量,常被用于定義狀態(tài)機(jī)的狀態(tài)、數(shù)據(jù)位寬和延遲大小等,由于它可以在編譯時(shí)修改參數(shù)的值,因此它又常被用于一些參數(shù)可調(diào)的模塊中,使用戶在實(shí)例化模塊時(shí),可以根據(jù)需要配置參數(shù)。在定義參數(shù)時(shí),我們可以一次定義多個(gè)參數(shù),參數(shù)與參數(shù)之間需要用逗號(hào)隔開。這里我們需要注意的是參數(shù)的定義是局部的,只在當(dāng)前模塊中有效。它的使用方法如下:
//parameter define
parameter DATA_WIDTH = 8; //數(shù)據(jù)位寬為8位
1.2.5 Verilog的運(yùn)算符
大家看完了Verilog的數(shù)據(jù)類型,我們?cè)賮斫榻B下Verilog的運(yùn)算符。Verilog中的運(yùn)算符按照功能可以分為下述類型:1、算術(shù)運(yùn)算符、 2、關(guān)系運(yùn)算符、3、邏輯運(yùn)算符、 4、條件運(yùn)算符、 5、位運(yùn)算符、 6、移位運(yùn)算符、 7、拼接運(yùn)算符。下面我們分別對(duì)這些運(yùn)算符進(jìn)行介紹。
算術(shù)運(yùn)算符,簡單來說,就是數(shù)學(xué)運(yùn)算里面的加減乘除,數(shù)字邏輯處理有時(shí)候也需要進(jìn)行數(shù)字運(yùn)算,所以需要算術(shù)運(yùn)算符。常用的算術(shù)運(yùn)算符主要包括加減乘除和模除(模除運(yùn)算也叫取余運(yùn)算)如下表所示:
表 7.2.1 算術(shù)運(yùn)算符
符號(hào) 使用方法 說明
- a + b a 加上 b
- a - b a 減去 b
- a * b a 乘以 b
/ a / b a 除以 b
% a % b a 模除 b
大家要注意下,Verilog實(shí)現(xiàn)乘除比較浪費(fèi)組合邏輯資源,尤其是除法。一般2的指數(shù)次冪的乘除法使用移位運(yùn)算來完成運(yùn)算,詳情可以看移位運(yùn)算符章節(jié)。非2的指數(shù)次冪的乘除法一般是調(diào)用現(xiàn)成的IP,QUARTUS/ISE等工具軟件會(huì)有提供,不過這些工具軟件提供的IP也是由最底層的組合邏輯(與或非門等)搭建而成的。
關(guān)系運(yùn)算符主要是用來做一些條件判斷用的,在進(jìn)行關(guān)系運(yùn)算符時(shí),如果聲明的關(guān)系是假的,則返回值是0,如果聲明的關(guān)系是真的,則返回值是1;所有的關(guān)系運(yùn)算符有著相同的優(yōu)先級(jí)別,關(guān)系運(yùn)算符的優(yōu)先級(jí)別低于算術(shù)運(yùn)算符的優(yōu)先級(jí)別如下表所示。
表 7.2.2 關(guān)系運(yùn)算符
符號(hào) 使用方法 說明
a > b a 大于 b
< a < b a 小于 b
= a >= b a 大于等于 b
<= a <= b a 小于等于 b
== a == b a 等于 b
!= a != b a 不等于 b
邏輯運(yùn)算符是連接多個(gè)關(guān)系表達(dá)式用的,可實(shí)現(xiàn)更加復(fù)雜的判斷,一般不單獨(dú)使用,都需要配合具體語句來實(shí)現(xiàn)完整的意思,如下表所示。
表 7.2.3 邏輯運(yùn)算符
符號(hào) 使用方法 說明
! !a a的非,如果a為0,那么a的非是1。
&& a && b a 與上 b,如果a和b都為1,a&&b結(jié)果才為1,表示真。
|| a || b a 或上 b,如果a或者b有一個(gè)為1,a||b結(jié)果為1,表示真。
條件操作符一般來構(gòu)建從兩個(gè)輸入中選擇一個(gè)作為輸出的條件選擇結(jié)構(gòu),功能等同于 always中的if-else語句,如下表所示。
表 7.2.4 條件運(yùn)算符
符號(hào) 使用方法 說明
? : a ? b : c 如果 a 為真,就選擇 b,否則選擇 c
位運(yùn)算符是一類最基本的運(yùn)算符,可以認(rèn)為它們直接對(duì)應(yīng)數(shù)字邏輯中的與、或、非門等邏輯門。常用的位運(yùn)算符如下表所示。
表 7.2.5 位運(yùn)算符
符號(hào) 使用方法 說明
~ ~a 將 a 的每個(gè)位進(jìn)行取反
& a & b 將 a 的每個(gè)位與 b 相同的位進(jìn)行相與
| a | b 將 a 的每個(gè)位與 b 相同的位進(jìn)行相或
^ a ^ b 將 a 的每個(gè)位與 b 相同的位進(jìn)行異或
位運(yùn)算符的與、或、非與邏輯運(yùn)算符邏輯與、邏輯或、邏輯非使用時(shí)候容易混淆,邏輯運(yùn)算符一般用在條件判斷上,位運(yùn)算符一般用在信號(hào)賦值上。
移位運(yùn)算符包括左移位運(yùn)算符和右移位運(yùn)算符,這兩種移位運(yùn)算符都用0來填補(bǔ)移出的空位。如下表所示。
表 7.2.6 移位運(yùn)算符
符號(hào) 使用方法 說明
<< a << b 將 a 左移 b 位
a >> b 將 a 右移 b 位
假設(shè)a有8bit數(shù)據(jù)位寬,那么a<<2,表示a左移2bit,a還是8bit數(shù)據(jù)位寬,a的最高2bit數(shù)據(jù)被移位丟棄了,最低2bit數(shù)據(jù)固定補(bǔ)0。如果a是3(二進(jìn)制:00000011),那么3左移2bit,3<<2,就是12(二進(jìn)制:00001100)。一般使用左移位運(yùn)算代替乘法,右移位運(yùn)算代替除法,但是這種也只能表示2的指數(shù)次冪的乘除法。
Verilog中有一個(gè)特殊的運(yùn)算符是C語言中沒有的,就是位拼接運(yùn)算符。用這個(gè)運(yùn)算符可以把兩個(gè)或多個(gè)信號(hào)的某些位拼接起來進(jìn)行運(yùn)算操作。如下表所示。
表 7.2.7 位拼接運(yùn)算符
符號(hào) 使用方法 說明
{} {a,b} 將 a 和 b 拼接起來,作為一個(gè)新信號(hào)
介紹完了這么多運(yùn)算符,大家可能會(huì)想到究竟哪個(gè)運(yùn)算符高,哪個(gè)運(yùn)算符低。為了便于大家查看這些運(yùn)算符的優(yōu)先級(jí),我們將它們制作成了表格,如下表所示。
表 7.2.8 運(yùn)算符的優(yōu)先級(jí)
運(yùn)算符 優(yōu)先級(jí)
!、 ~ 最高
*、 /、 % 次高
+、 -
<<、 >>
<、 <=、 >、 >=
==、 !=、 =、 !
&
^、 ^~
|
&&
|| 次低
? 最低
1.3 Verilog程序框架
在介紹Verilog程序框架之前,我們先來看下Verilog一些基本語法,基礎(chǔ)語法主要包括注釋和關(guān)鍵字。
1.3.1 注釋
Verilog HDL中有兩種注釋的方式,一種是以“/”符號(hào)開始,“/”結(jié)束,在兩個(gè)符號(hào)之間的語句都是注釋語句,因此可擴(kuò)展到多行。如:
/* statement1 ,
statement2,
…
statementn */
以上n個(gè)語句都是注釋語句。
另一種是以//開頭的語句,它表示以//開始到本行結(jié)束都屬于注釋語句。如:
//statement1
我們建議的寫法:使用//作為注釋。
1.3.2 關(guān)鍵字
Verilog和C語言類似,都因編寫需要定義了一系列保留字,叫做關(guān)鍵字(或關(guān)鍵詞)。這些保留字是識(shí)別語法的關(guān)鍵。我們給大家列出了Verilog中的關(guān)鍵字,如下表所示。
表 7.3.1 Verilog的所有關(guān)鍵字
雖然上表列了很多,但是實(shí)際經(jīng)常使用的不是很多,實(shí)際經(jīng)常使用的主要如下表所示。
表 7.3.2 Verilog常用的關(guān)鍵字
關(guān)鍵字 含義
module 模塊開始定義
input 輸入端口定義
output 輸出端口定義
inout 雙向端口定義
parameter 信號(hào)的參數(shù)定義
wire wire信號(hào)定義
reg reg信號(hào)定義
always 產(chǎn)生reg信號(hào)語句的關(guān)鍵字
assign 產(chǎn)生wire信號(hào)語句的關(guān)鍵字
begin 語句的起始標(biāo)志
end 語句的結(jié)束標(biāo)志
posedge/negedge 時(shí)序電路的標(biāo)志
case Case語句起始標(biāo)記
default Case語句的默認(rèn)分支標(biāo)志
endcase Case語句結(jié)束標(biāo)記
if if/else語句標(biāo)記
else if/else語句標(biāo)記
for for語句標(biāo)記
endmodule 模塊結(jié)束定義
注意只有小寫的關(guān)鍵字才是保留字。例如,標(biāo)識(shí)符always(這是個(gè)關(guān)鍵詞)與標(biāo)識(shí)符ALWAYS(非關(guān)鍵詞)是不同的。
1.3.3 程序框架
我們以LED燈閃爍程序?yàn)槔齺斫o大家展示Verilog的程序框架,代碼如下所示(注意:代碼中前面的行號(hào)只是為了方便大家閱讀代碼與快速定位到行號(hào)的位置,在實(shí)際編寫代碼時(shí)不可以添加行號(hào),否則編譯代碼時(shí)會(huì)報(bào)錯(cuò))。
首先//開頭的都是注釋,這個(gè)之前我們講解過了。下面我們來看下具體的解釋。
第1行為模塊定義,模塊定義以module開始,endmodule結(jié)束,如59行所示。
其次2到5行為端口定義,需要定義led模塊的輸入信號(hào)和輸出信號(hào),此處輸入信號(hào)為系統(tǒng)時(shí)鐘和復(fù)位信號(hào),輸出為led控制信號(hào)。
7到9行為參數(shù)parameter定義,語法如7到9行所示,定義parameter的好處是可以靈活改變參數(shù)數(shù)字就能控制一些計(jì)數(shù)器最大計(jì)數(shù)值或者信號(hào)位寬的最大位寬。
12到14行為reg信號(hào)定義,reg信號(hào)一般情況下代表寄存器,比如此處控制0.5秒使能信號(hào)的計(jì)數(shù)器counter。
16到17行為wire信號(hào)定義,wire信號(hào)就是硬件連線,比如此處的counter_en,代表計(jì)數(shù)到最大值時(shí)產(chǎn)生高電平使能,本質(zhì)上是一個(gè)硬件連線,其實(shí)代表的是一些計(jì)數(shù)器/寄存器做邏輯判斷的結(jié)果。
19到21行為moudle開始的注釋,不添加工具綜合也不會(huì)報(bào)錯(cuò),但是我們推薦添加,作為一個(gè)良好的編程規(guī)范。
23到24行為assign語句的樣式,條件成立選擇1,否則選擇0。
26到34行是always語句的樣式,27行代表在時(shí)鐘上升沿或者復(fù)位的下降沿進(jìn)行信號(hào)觸發(fā)。begin/end代表語句的開始和結(jié)束。28到33行為if/else語句,和C語言是比較類似的。29行的“<=”標(biāo)記代表信號(hào)是非阻塞賦值,信號(hào)賦值有非阻塞賦值和阻塞賦值兩個(gè)方式,這個(gè)我們后面會(huì)詳細(xì)解釋。
36和42行也是一個(gè)always語句,和26到34行類似。
44和57行也是一個(gè)always語句,不過這個(gè)always語句中嵌入了一個(gè)case語句,case語句的語法如49到55行所示,需要一個(gè)case關(guān)鍵字開始,endcase關(guān)鍵字結(jié)束,default作為默認(rèn)分支,和C語言也是類似的。當(dāng)然case語句也可以用在不帶時(shí)鐘的always語句中,不過本例子的always都是帶有時(shí)鐘的。不帶時(shí)鐘的always和帶時(shí)鐘的always語句的差異這個(gè)我們后面也會(huì)詳細(xì)解釋。
59行是endmodule標(biāo)記,代表模塊的結(jié)束。
在這里需要補(bǔ)充一點(diǎn)的是,一些初學(xué)者可能會(huì)有這樣一個(gè)疑問,在always語句中編寫if語句或else語句時(shí),后面需要加begin和end嗎?其實(shí)這個(gè)主要看if條件后面跟著幾條賦值語句,如果只有一條賦值語句時(shí),if后面可以加begin和end,也可以不加;如果超過一條賦值語句時(shí),就必須加上begin和end。
if條件只有一條賦值語句時(shí),下面兩種寫法都是可以的,這里更推薦第一種寫法,因?yàn)榈诙N寫法會(huì)占用更多的行號(hào),代碼如下所示:
好了,程序框架就講解完了,大家是不是覺得也很簡單呢?這些都是基本的語法規(guī)范,希望大家能記住這些基礎(chǔ)的知識(shí)點(diǎn)。如果有些地方大家還是覺得比較抽象,很難理解,沒有關(guān)系,相信大家會(huì)在后面的學(xué)習(xí)中,會(huì)慢慢理解的。
1.4 Verilog高級(jí)知識(shí)點(diǎn)
前幾節(jié)主要介紹了Verilog一些基礎(chǔ)的知識(shí)點(diǎn)和程序框架,本節(jié)給大家介紹一些高級(jí)的知識(shí)點(diǎn)。高級(jí)知識(shí)點(diǎn)包括阻塞賦值和非阻塞賦值、assign和always語句差異、什么是鎖存器、狀態(tài)機(jī)、模塊化設(shè)計(jì)等。
1.4.1 阻塞賦值(Blocking)
阻塞賦值,顧名思義,即在一個(gè)always塊中,后面的語句會(huì)受到前語句的影響,具體來說,在同一個(gè)always中,一條阻塞賦值語句如果沒有執(zhí)行結(jié)束,那么該語句后面的語句就不能被執(zhí)行,即被“阻塞”。也就是說always塊內(nèi)的語句是一種順序關(guān)系,這里和C語言很類似。符號(hào)“=”用于阻塞的賦值(如:b = a;),阻塞賦值“=”在begin和end之間的語句是順序執(zhí)行,屬于串行語句。
在這里定義兩個(gè)縮寫:
RHS:賦值等號(hào)右邊的表達(dá)式或變量可以寫作RHS表達(dá)式或RHS變量;
LHS:賦值等號(hào)左邊的表達(dá)式或變量可以寫作LHS表達(dá)式或LHS變量;
阻塞賦值的執(zhí)行可以認(rèn)為是只有一個(gè)步驟的操作,即計(jì)算RHS的值并更新LHS,此時(shí)不允許任何其他語句的干擾,所謂的阻塞的概念就是值在同一個(gè)always塊中,其后面的賦值語句從概念上來講是在前面一條語句賦值完成后才執(zhí)行的。
為了方便大家理解阻塞賦值的概念以及阻塞賦值和非阻塞賦值的區(qū)別,我們這里以在時(shí)序邏輯下使用阻塞賦值為例來實(shí)現(xiàn)這樣一個(gè)功能:在復(fù)位的時(shí)候,a=1,b=2,c=3;而在沒有復(fù)位的時(shí)候,a的值清零,同時(shí)將a的值賦值給b,b的值賦值給c,代碼以及信號(hào)波形圖如下圖所示:
圖 7.4.1.1 阻塞賦值代碼
圖 7.4.1.2 阻塞賦值的信號(hào)波形圖
代碼中使用的是阻塞賦值語句,從波形圖中可以看到,在復(fù)位的時(shí)候(rst_n=0),a=1,b=2,c=3;而結(jié)束復(fù)位之后(波形圖中的0時(shí)刻),當(dāng)clk的上升沿到來時(shí)(波形圖中的2時(shí)刻),a=0,b=0,c=0。這是因?yàn)樽枞x值是在當(dāng)前語句執(zhí)行完成之后,才會(huì)執(zhí)行后面的賦值語句,因此首先執(zhí)行的是a=0,賦值完成后將a的值賦值給b,由于此時(shí)a的值已經(jīng)為0,所以b=a=0,最后執(zhí)行的是將b的值賦值給c,而b的值已經(jīng)賦值為0,所以c的值同樣等于0。
1.4.2 非阻塞賦值(Non-Blocking)
符號(hào)“<=”用于非阻塞賦值(如:b <= a;),非阻塞賦值是由時(shí)鐘節(jié)拍決定,在時(shí)鐘上升到來時(shí),執(zhí)行賦值語句右邊,然后將begin-end之間的所有賦值語句同時(shí)賦值到賦值語句的左邊,注意:是begin—end之間的所有語句,一起執(zhí)行,且一個(gè)時(shí)鐘只執(zhí)行一次,屬于并行執(zhí)行語句。這個(gè)是和C語言最大的一個(gè)差異點(diǎn),大家要逐步理解并行執(zhí)行的概念。
非阻塞賦值的操作過程可以看作兩個(gè)步驟:
(1)賦值開始的時(shí)候,計(jì)算RHS;
(2)賦值結(jié)束的時(shí)候,更新LHS。
所謂的非阻塞的概念是指,在計(jì)算非阻塞賦值的RHS以及LHS期間,允許其它的非阻塞賦值語句同時(shí)計(jì)算RHS和更新LHS。
我們下面使用非阻塞賦值同樣來實(shí)現(xiàn)這樣一個(gè)功能:在復(fù)位的時(shí)候,a=1,b=2,c=3;而在沒有復(fù)位的時(shí)候,a的值清零,同時(shí)將a的值賦值給b,b的值賦值給c,代碼以及信號(hào)波形圖如下圖所示:
圖 7.4.2.1 非阻塞賦值代碼
圖 7.4.2.2 非阻塞賦值的信號(hào)波形圖
代碼中使用的是非阻塞賦值語句,從波形圖中可以看到,在復(fù)位的時(shí)候(rst_n=0),a=1,b=2,c=3;而結(jié)束復(fù)位之后(波形圖中的0時(shí)刻),當(dāng)clk的上升沿到來時(shí)(波形圖中的2時(shí)刻),a=0,b=1,c=2。這是因?yàn)榉亲枞x值在計(jì)算RHS和更新LHS期間,允許其它的非阻塞賦值語句同時(shí)計(jì)算RHS和更新LHS。在波形圖中的2時(shí)刻,RHS的表達(dá)是0、a、b,分別等于0、1、2,這三條語句是同時(shí)更新LHS,所以a、b、c的值分別等于0、1、2。
在了解了阻塞賦值和非阻塞賦值的區(qū)別之后,有些朋友可能還是對(duì)什么時(shí)候使用阻塞賦值,什么時(shí)候使用非阻塞賦值有些疑惑,在這里給大家總結(jié)如下。
在描述組合邏輯電路的時(shí)候,使用阻塞賦值,比如assign賦值語句和不帶時(shí)鐘的always賦值語句,這種電路結(jié)構(gòu)只與輸入電平的變化有關(guān)系,代碼如下:
示例1:assign賦值語句
assign data = (data_en == 1’b1) ? 8’d255 : 8’d0;
示例2:不帶時(shí)鐘的always語句
在描述時(shí)序邏輯的時(shí)候,使用非阻塞賦值,綜合成時(shí)序邏輯的電路結(jié)構(gòu),比如帶時(shí)鐘的always語句;這種電路結(jié)構(gòu)往往與觸發(fā)沿有關(guān)系,只有在觸發(fā)沿時(shí)才可能發(fā)生賦值的變化,代碼如下:
示例3:
1.4.3 assign和always區(qū)別
assign語句和always語句是Verilog中的兩個(gè)基本語句,這兩個(gè)都是經(jīng)常使用的語句。
assign語句使用時(shí)不能帶時(shí)鐘。
always語句可以帶時(shí)鐘,也可以不帶時(shí)鐘。在always不帶時(shí)鐘時(shí),邏輯功能和assign完全一致,都是只產(chǎn)生組合邏輯。比較簡單的組合邏輯推薦使用assign語句,比較復(fù)雜的組合邏輯推薦使用always語句。示例如下:
24 assign counter_en = (counter == (COUNT_MAX - 1’b1)) ? 1’b1 : 1’b0;
1.4.4 帶時(shí)鐘和不帶時(shí)鐘的always
always語句可以帶時(shí)鐘,也可以不帶時(shí)鐘。在always不帶時(shí)鐘時(shí),邏輯功能和assign完全一致,雖然產(chǎn)生的信號(hào)定義還是reg類型,但是該語句產(chǎn)生的還是組合邏輯。
在always帶時(shí)鐘信號(hào)時(shí),這個(gè)邏輯語句才能產(chǎn)生真正的寄存器,如下示例counter就是真正的寄存器。
26 //用于產(chǎn)生0.5秒使能信號(hào)的計(jì)數(shù)器 27 always @(posedge sys_clk or negedge sys_rst_n) begin 28 if (sys_rst_n == 1'b0) 29 counter <= 1'b0; 30 else if (counter_en) 31 counter <= 1'b0; 32 else 33 counter <= counter + 1'b1; 34 end1.4.5 什么是latch
latch是指鎖存器,是一種對(duì)脈沖電平敏感的存儲(chǔ)單元電路。鎖存器和寄存器都是基本存儲(chǔ)單元,鎖存器是電平觸發(fā)的存儲(chǔ)器,寄存器是邊沿觸發(fā)的存儲(chǔ)器。兩者的基本功能是一樣的,都可以存儲(chǔ)數(shù)據(jù)。鎖存器是組合邏輯產(chǎn)生的,而寄存器是在時(shí)序電路中使用,由時(shí)鐘觸發(fā)產(chǎn)生的。
latch的主要危害是會(huì)產(chǎn)生毛刺(glitch),這種毛刺對(duì)下一級(jí)電路是很危險(xiǎn)的。并且其隱蔽性很強(qiáng),不易查出。因此,在設(shè)計(jì)中,應(yīng)盡量避免latch的使用。
代碼里面出現(xiàn)latch的兩個(gè)原因是在組合邏輯中,if或者case語句不完整的描述,比如if缺少else分支,case缺少default分支,導(dǎo)致代碼在綜合過程中出現(xiàn)了latch。解決辦法就是if必須帶else分支,case必須帶default分支。
大家需要注意下,只有不帶時(shí)鐘的always語句if或者case語句不完整才會(huì)產(chǎn)生latch,帶時(shí)鐘的語句if或者case語句不完整描述不會(huì)產(chǎn)生latch。
下面為缺少else分支的帶時(shí)鐘的always語句和不帶時(shí)鐘的always語句,通過實(shí)際產(chǎn)生的電路圖可以看到第二個(gè)是有一個(gè)latch的,第一個(gè)仍然是普通的帶有時(shí)鐘的寄存器。
圖 7.4.5.1 缺少else的帶時(shí)鐘的always語句電路圖
圖 7.4.5.2 缺少else的不帶時(shí)鐘的always語句電路圖
1.4.6 狀態(tài)機(jī)
Verilog是硬件描述語言,硬件電路是并行執(zhí)行的,當(dāng)需要按照流程或者步驟來完成某個(gè)功能時(shí),代碼中通常會(huì)使用很多個(gè)if嵌套語句來實(shí)現(xiàn),這樣就增加了代碼的復(fù)雜度,以及降低了代碼的可讀性,這個(gè)時(shí)候就可以使用狀態(tài)機(jī)來編寫代碼。狀態(tài)機(jī)相當(dāng)于一個(gè)控制器,它將一項(xiàng)功能的完成分解為若干步,每一步對(duì)應(yīng)于二進(jìn)制的一個(gè)狀態(tài),通過預(yù)先設(shè)計(jì)的順序在各狀態(tài)之間進(jìn)行轉(zhuǎn)換,狀態(tài)轉(zhuǎn)換的過程就是實(shí)現(xiàn)邏輯功能的過程。
狀態(tài)機(jī),全稱是有限狀態(tài)機(jī)(Finite State Machine,縮寫為FSM),是一種在有限個(gè)狀態(tài)之間按一定規(guī)律轉(zhuǎn)換的時(shí)序電路,可以認(rèn)為是組合邏輯和時(shí)序邏輯的一種組合。狀態(tài)機(jī)通過控制各個(gè)狀態(tài)的跳轉(zhuǎn)來控制流程,使得整個(gè)代碼看上去更加清晰易懂,在控制復(fù)雜流程的時(shí)候,狀態(tài)機(jī)優(yōu)勢(shì)明顯,因此基本上都會(huì)用到狀態(tài)機(jī),如SDRAM控制器等。在本手冊(cè)提供的例程中,會(huì)有多個(gè)用到狀態(tài)機(jī)設(shè)計(jì)的例子,希望大家能夠慢慢體會(huì)和理解,并且能夠熟練掌握。
根據(jù)狀態(tài)機(jī)的輸出是否與輸入條件相關(guān),可將狀態(tài)機(jī)分為兩大類,即摩爾(Moore)型狀態(tài)機(jī)和米勒(Mealy)型狀態(tài)機(jī)。
? Mealy狀態(tài)機(jī):組合邏輯的輸出不僅取決于當(dāng)前狀態(tài),還取決于輸入狀態(tài)。
? Moore狀態(tài)機(jī):組合邏輯的輸出只取決于當(dāng)前狀態(tài)。
米勒狀態(tài)機(jī)的模型如下圖所示,模型中第一個(gè)方框是指產(chǎn)生下一狀態(tài)的組合邏輯F,F是當(dāng)前狀態(tài)和輸入信號(hào)的函數(shù),狀態(tài)是否改變、如何改變,取決于組合邏輯F的輸出;第二框圖是指狀態(tài)寄存器,其由一組觸發(fā)器組成,用來記憶狀態(tài)機(jī)當(dāng)前所處的狀態(tài),狀態(tài)的改變只發(fā)生在時(shí)鐘的跳邊沿;第三個(gè)框圖是指產(chǎn)生輸出的組合邏輯G,狀態(tài)機(jī)的輸出是由輸出組合邏輯G提供的,G也是當(dāng)前狀態(tài)和輸入信號(hào)的函數(shù)。
圖 7.4.6.1 Mealy狀態(tài)機(jī)模型
2) Moore狀態(tài)機(jī)
摩爾狀態(tài)機(jī)的模型如下圖所示,對(duì)比米勒狀態(tài)機(jī)的模型可以發(fā)現(xiàn),其區(qū)別在于米勒狀態(tài)機(jī)的輸出由當(dāng)前狀態(tài)和輸入條件決定的,而摩爾狀態(tài)機(jī)的輸出只取決于當(dāng)前狀態(tài)。
圖 7.4.6.2 Moore狀態(tài)機(jī)模型
3) 三段式狀態(tài)機(jī)
根據(jù)狀態(tài)機(jī)的實(shí)際寫法,狀態(tài)機(jī)還可以分為一段式、二段式和三段式狀態(tài)機(jī)。
一段式:整個(gè)狀態(tài)機(jī)寫到一個(gè)always模塊里面,在該模塊中既描述狀態(tài)轉(zhuǎn)移,又描述狀態(tài)的輸入和輸出。不推薦采用這種狀態(tài)機(jī),因?yàn)閺拇a風(fēng)格方面來講,一般都會(huì)要求把組合邏輯和時(shí)序邏輯分開;從代碼維護(hù)和升級(jí)來說,組合邏輯和時(shí)序邏輯混合在一起不利于代碼維護(hù)和修改,也不利于約束。
二段式:用兩個(gè)always模塊來描述狀態(tài)機(jī),其中一個(gè)always模塊采用同步時(shí)序描述狀態(tài)轉(zhuǎn)移;另一個(gè)模塊采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件,描述狀態(tài)轉(zhuǎn)移規(guī)律以及輸出。不同于一段式狀態(tài)機(jī)的是,它需要定義兩個(gè)狀態(tài),現(xiàn)態(tài)和次態(tài),然后通過現(xiàn)態(tài)和次態(tài)的轉(zhuǎn)換來實(shí)現(xiàn)時(shí)序邏輯。
三段式:在兩個(gè)always模塊描述方法基礎(chǔ)上,使用三個(gè)always模塊,一個(gè)always模塊采用同步時(shí)序描述狀態(tài)轉(zhuǎn)移,一個(gè)always采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件,描述狀態(tài)轉(zhuǎn)移規(guī)律,另一個(gè)always模塊描述狀態(tài)輸出(可以用組合電路輸出,也可以時(shí)序電路輸出)。
實(shí)際應(yīng)用中三段式狀態(tài)機(jī)使用最多,因?yàn)槿问綘顟B(tài)機(jī)將組合邏輯和時(shí)序分開,有利于綜合器分析優(yōu)化以及程序的維護(hù);并且三段式狀態(tài)機(jī)將狀態(tài)轉(zhuǎn)移與狀態(tài)輸出分開,使代碼看上去更加清晰易懂,提高了代碼的可讀性,推薦大家使用三段式狀態(tài)機(jī),本文也著重講解三段式。
三段式狀態(tài)機(jī)的基本格式是:
第一個(gè)always語句實(shí)現(xiàn)同步狀態(tài)跳轉(zhuǎn);
第二個(gè)always語句采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件;
第三個(gè)always語句描述狀態(tài)輸出(可以用組合電路輸出,也可以時(shí)序電路輸出)。
在開始編寫狀態(tài)機(jī)代碼之前,一般先畫出狀態(tài)跳轉(zhuǎn)圖,這樣在編寫代碼時(shí)思路會(huì)比較清晰,下面以一個(gè)7分頻為例(對(duì)于分頻等較簡單的功能,可以不使用狀態(tài)機(jī),這里只是演示狀態(tài)機(jī)編寫的方法),狀態(tài)跳轉(zhuǎn)圖如下圖所示:
圖 7.4.6.3 七分頻狀態(tài)跳轉(zhuǎn)圖
狀態(tài)跳轉(zhuǎn)圖畫完之后,接下來通過parameter來定義各個(gè)不同狀態(tài)的參數(shù),如下代碼所示:
這里是使用獨(dú)熱碼的方式來定義狀態(tài)機(jī),每個(gè)狀態(tài)只有一位為1,當(dāng)然也可以直接定義成十進(jìn)制的0,1,2……7。
因?yàn)槲覀兌x成獨(dú)熱碼的方式,每一個(gè)狀態(tài)的位寬為7位,接下來還需要定義兩個(gè)7位的寄存器,一個(gè)用來表示當(dāng)前狀態(tài),另一個(gè)用來表示下一個(gè)狀態(tài),如下所示:
接下來就可以使用三個(gè)always語句來開始編寫狀態(tài)機(jī)的代碼,第一個(gè)always采用同步時(shí)序描述狀態(tài)轉(zhuǎn)移,第二個(gè)always采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件,第三個(gè)always是描述狀態(tài)輸出,一個(gè)完整的三段式狀態(tài)機(jī)的例子如下代碼所示:
1 module divider7_fsm ( 2 //系統(tǒng)時(shí)鐘與復(fù)位 3 input sys_clk , 4 input sys_rst_n , 5 6 //輸出時(shí)鐘 7 output reg clk_divide_7 8 ); 9 10 //parameter define 11 parameter S0 = 7'b0000001; //獨(dú)熱碼定義方式 12 parameter S1 = 7'b0000010; 13 parameter S2 = 7'b0000100; 14 parameter S3 = 7'b0001000; 15 parameter S4 = 7'b0010000; 16 parameter S5 = 7'b0100000; 17 parameter S6 = 7'b1000000; 18 19 //reg define 20 reg [6:0] curr_st ; //當(dāng)前狀態(tài) 21 reg [6:0] next_st ; //下一個(gè)狀態(tài) 22 23 //***************************************************** 24 //** main code 25 //***************************************************** 26 27 //狀態(tài)機(jī)的第一段采用同步時(shí)序描述狀態(tài)轉(zhuǎn)移 28 always @(posedge sys_clk or negedge sys_rst_n) begin 29 if (!sys_rst_n) 30 curr_st <= S0; 31 else 32 curr_st <= next_st; 33 end 34 35 //狀態(tài)機(jī)的第二段采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件 36 always @(*) begin 37 case (curr_st) 38 S0: next_st = S1; 39 S1: next_st = S2; 40 S2: next_st = S3; 41 S3: next_st = S4; 42 S4: next_st = S5; 43 S5: next_st = S6; 44 S6: next_st = S0; 45 default: next_st = S0; 46 endcase 47 end 48 49 //狀態(tài)機(jī)的第三段描述狀態(tài)輸出(這里采用時(shí)序電路輸出) 50 always @(posedge sys_clk or negedge sys_rst_n) begin 51 if (!sys_rst_n) 52 clk_divide_7 <= 1'b0; 53 else if ((curr_st == S0) | (curr_st == S1) | (curr_st == S2) | (curr_st == S3)) 54 clk_divide_7 <= 1'b0; 55 else if ((curr_st == S4) | (curr_st == S5) | (curr_st == S6)) 56 clk_divide_7 <= 1'b1; 57 else 58 ; 59 end 60 61 endmodule在編寫狀態(tài)機(jī)代碼時(shí)首先要定義狀態(tài)變量(代碼中的參數(shù)S0~S6)與狀態(tài)寄存器(curr_st、next_st),如代碼中第10行至第21行所示;接下來使用三個(gè)always語句來實(shí)現(xiàn)三段狀態(tài)機(jī),第一個(gè)always語句實(shí)現(xiàn)同步狀態(tài)跳轉(zhuǎn)(如代碼的第27至第33行所示),在復(fù)位的時(shí)候,當(dāng)前狀態(tài)處在S0狀態(tài),否則將下一個(gè)狀態(tài)賦值給當(dāng)前狀態(tài);第二個(gè)always采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件(如代碼的第35行至第47行代碼所示),這里每一個(gè)狀態(tài)只保持一個(gè)時(shí)鐘周期,也就是直接跳轉(zhuǎn)到下一個(gè)狀態(tài),在實(shí)際應(yīng)用中,一般根據(jù)輸入的條件來判斷是否跳轉(zhuǎn)到其它狀態(tài)或者停留在當(dāng)前轉(zhuǎn)態(tài),最后在case語句后面增加一個(gè)default語句,來防止?fàn)顟B(tài)機(jī)處在異常的狀態(tài);第三個(gè)always輸出分頻后的時(shí)鐘(如代碼的第49至第59行代碼所示),狀態(tài)機(jī)的第三段可以使用組合邏輯電路輸出,也可以使用時(shí)序邏輯電路輸出,一般推薦使用時(shí)序電路輸出,因?yàn)闋顟B(tài)機(jī)的設(shè)計(jì)和其它設(shè)計(jì)一樣,最好使用同步時(shí)序方式設(shè)計(jì),以提高設(shè)計(jì)的穩(wěn)定性,消除毛刺。
從代碼中可以看出,輸出的分頻時(shí)鐘clk_divide_7只與當(dāng)前狀態(tài)(curr_st)有關(guān),而與輸入狀態(tài)無關(guān),所以屬于摩爾型狀態(tài)機(jī)。狀態(tài)機(jī)的第一段對(duì)應(yīng)摩爾狀態(tài)機(jī)模型的狀態(tài)寄存器,用來記憶狀態(tài)機(jī)當(dāng)前所處的狀態(tài);狀態(tài)機(jī)的第二段對(duì)應(yīng)摩爾狀態(tài)機(jī)模型產(chǎn)生下一狀態(tài)的組合邏輯F;狀態(tài)機(jī)的第三段對(duì)應(yīng)摩爾狀態(tài)機(jī)產(chǎn)生輸出的組合邏輯G,因?yàn)椴捎脮r(shí)序電路輸出有很大的優(yōu)勢(shì),所以這里第三段狀態(tài)機(jī)是由時(shí)序電路輸出的。
狀態(tài)機(jī)采用時(shí)序邏輯輸出的狀態(tài)機(jī)模型如下圖所示:
圖 7.4.6.4 狀態(tài)機(jī)時(shí)序電路輸出模型
采用這種描述方法雖然代碼結(jié)構(gòu)復(fù)雜了一些,但是這樣做的好處是可以有效地濾去組合邏輯輸出的毛刺,同時(shí)也可以更好的進(jìn)行時(shí)序計(jì)算與約束,另外對(duì)于總線形式的輸出信號(hào)來說,容易使總線數(shù)據(jù)對(duì)齊,減小總線數(shù)據(jù)間的偏移,從而降低接收端數(shù)據(jù)采樣出錯(cuò)的頻率。
1.4.7 模塊化設(shè)計(jì)
模塊化設(shè)計(jì)是FPGA設(shè)計(jì)中一個(gè)很重要的技巧,它能夠使一個(gè)大型設(shè)計(jì)的分工協(xié)作、仿真測(cè)試更加容易,代碼維護(hù)或升級(jí)更加便利,當(dāng)更改某個(gè)子模塊時(shí),不會(huì)影響其它模塊的實(shí)現(xiàn)結(jié)果。進(jìn)行模塊化、標(biāo)準(zhǔn)化設(shè)計(jì)的最終目的就是提高設(shè)計(jì)的通用性,減少不同項(xiàng)目中同一功能設(shè)計(jì)和驗(yàn)證引入的工作量。劃分模塊的基本原則是子模塊功能相對(duì)獨(dú)立、模塊內(nèi)部聯(lián)系盡量緊密、模塊間的連接盡量簡單。
在進(jìn)行模塊化設(shè)計(jì)中,對(duì)于復(fù)雜的數(shù)字系統(tǒng),我們一般采用自頂向下的設(shè)計(jì)方式。可以把系統(tǒng)劃分成幾個(gè)功能模塊,每個(gè)功能模塊再劃分成下一層的子模塊;每個(gè)模塊的設(shè)計(jì)對(duì)應(yīng)一個(gè)module,一個(gè)module設(shè)計(jì)成一個(gè)Verilog程序文件。因此,對(duì)一個(gè)系統(tǒng)的頂層模塊,我們采用結(jié)構(gòu)化的設(shè)計(jì),即頂層模塊分別調(diào)用了各個(gè)功能模塊。
下圖是模塊化設(shè)計(jì)的功能框圖,一般整個(gè)設(shè)計(jì)的頂層模塊只做例化(調(diào)用其它模塊),不做邏輯。頂層下面會(huì)有模塊A、模塊B、模塊C等,模塊A/B/C又可以分多個(gè)子模塊實(shí)現(xiàn)。
圖 7.4.7.1 模塊化設(shè)計(jì)框圖
在這里我們補(bǔ)充一個(gè)概念,就是Verilog語法中的模塊例化。FPGA邏輯設(shè)計(jì)中通常是一個(gè)大的模塊中包含了一個(gè)或多個(gè)功能子模塊,Verilog通過模塊調(diào)用或稱為模塊實(shí)例化的方式來實(shí)現(xiàn)這些子模塊與高層模塊的連接,有利于簡化每一個(gè)模塊的代碼,易于維護(hù)和修改。
下面以一個(gè)實(shí)例(靜態(tài)數(shù)碼管顯示實(shí)驗(yàn))來說明模塊和模塊之間的例化方法。
在靜態(tài)數(shù)碼管顯示實(shí)驗(yàn)中,我們根據(jù)功能將FPGA頂層例化了以下兩個(gè)模塊:計(jì)時(shí)模塊(time_count)和數(shù)碼管靜態(tài)顯示模塊(seg_led_static),如下圖所示:
圖 7.4.7.2 靜態(tài)數(shù)碼管顯示模塊框圖
計(jì)時(shí)模塊部分代碼如下所示:
數(shù)碼管靜態(tài)顯示模塊部分代碼如下所示:
1 module seg_led_static ( 2 input clk , // 時(shí)鐘信號(hào) 3 input rst_n , // 復(fù)位信號(hào)(低有效) 4 5 input add_flag, // 數(shù)碼管變化的通知信號(hào) 6 output reg [5:0] sel , // 數(shù)碼管位選 7 output reg [7:0] seg_led // 數(shù)碼管段選 8 ); …… 66 endmodule頂層模塊代碼如下所示:
1 module seg_led_static_top ( 2 input sys_clk , // 系統(tǒng)時(shí)鐘 3 input sys_rst_n, // 系統(tǒng)復(fù)位信號(hào)(低有效) 4 5 output [5:0] sel , // 數(shù)碼管位選 6 output [7:0] seg_led // 數(shù)碼管段選 7 8 ); 9 10 //parameter define 11 parameter TIME_SHOW = 25'd25000_000; // 數(shù)碼管變化的時(shí)間間隔0.5s 12 13 //wire define 14 wire add_flag; // 數(shù)碼管變化的通知信號(hào) 15 16 //***************************************************** 17 //** main code 18 //***************************************************** 19 20 //例化計(jì)時(shí)模塊 21 time_count #( 22 .MAX_NUM (TIME_SHOW) 23 ) u_time_count( 24 .clk (sys_clk ), 25 .rst_n (sys_rst_n), 26 27 .flag (add_flag ) 28 ); 29 30 //例化數(shù)碼管靜態(tài)顯示模塊 31 seg_led_static u_seg_led_static ( 32 .clk (sys_clk ), 33 .rst_n (sys_rst_n), 34 35 .add_flag (add_flag ), 36 .sel (sel ), 37 .seg_led (seg_led ) 38 ); 39 40 endmodule我們上面貼出了頂層模塊的完整代碼,子模塊只貼出了模塊的端口和參數(shù)定義的代碼。這是因?yàn)轫攲幽K對(duì)子模塊做例化時(shí),只需要知道子模塊的端口信號(hào)名,而不用關(guān)心子模塊內(nèi)部具體是如何實(shí)現(xiàn)的。如果子模塊內(nèi)部使用parameter定義了一些參數(shù),Verilog也支持對(duì)參數(shù)的例化(也叫參數(shù)的傳遞),即頂層模塊可以通過例化參數(shù)來修改子模塊內(nèi)定義的參數(shù)。
我們先來看一下頂層模塊是如何例化子模塊的,例化方法如下圖所示:
圖 7.4.7.3 模塊的例化
上圖右側(cè)是例化的數(shù)碼管靜態(tài)顯示模塊,子模塊名是指被例化模塊的模塊名,而例化模塊名相當(dāng)于標(biāo)識(shí),當(dāng)例化多個(gè)相同模塊時(shí),可以通過例化名來識(shí)別哪一個(gè)例化,我們一般命名為“u_”+“子模塊名”。信號(hào)列表中“.”之后的信號(hào)是數(shù)碼管靜態(tài)顯示模塊定義的端口信號(hào),括號(hào)內(nèi)的信號(hào)則是頂層模塊聲明的信號(hào),這樣就將頂層模塊的信號(hào)與子模塊的信號(hào)一一對(duì)應(yīng)起來,同時(shí)需要注意信號(hào)的位寬要保持一致。
接下來再來介紹一下參數(shù)的例化,參數(shù)的例化是在模塊例化的基礎(chǔ)上,增加了對(duì)參數(shù)的信號(hào)定義,如下圖所示:
圖 7.4.7.4 模塊參數(shù)的例化
在對(duì)參數(shù)進(jìn)行例化時(shí),在模塊名的后面加上“#”,表示后面跟著的是參數(shù)列表。計(jì)時(shí)模塊定義的MAX_NUM和頂層模塊的TIME_SHOW都是等于25000_000,當(dāng)在頂層模塊定義TIME_SHOW=12500_000時(shí),那么子模塊的MAX_NUM的值實(shí)際上是也等于12500_000。當(dāng)然即使子模塊包含參數(shù),在做模塊的例化時(shí)也可以不添加對(duì)參數(shù)的例化,這樣的話,子模塊的參數(shù)值等于該模塊內(nèi)部實(shí)際定義的值。
值得一提的是,Verilog語法中的localparam代表的意思同樣是參數(shù)定義,用法和parameter基本一致,區(qū)別在于parameter定義的參數(shù)可以做例化,而localparam定義的參數(shù)是指本地參數(shù),上層模塊不可以對(duì)localparam定義的參數(shù)做例化。
1.5 Verilog編程規(guī)范
本節(jié)主要給大家介紹下編程規(guī)范,良好的編程規(guī)范是一個(gè)FPGA工程師必備的素質(zhì)。
1.5.1 編程規(guī)范重要性
當(dāng)前數(shù)字電路設(shè)計(jì)越來越復(fù)雜,一個(gè)項(xiàng)目需要的人越來越多,當(dāng)幾十號(hào)設(shè)計(jì)同事完成同一個(gè)項(xiàng)目時(shí)候,大家需要互相檢視對(duì)方代碼,如果沒有一個(gè)統(tǒng)一的編程規(guī)范,那么是不可想象的。大家的風(fēng)格都不一樣,如果不統(tǒng)一的話,后續(xù)維護(hù)、重用等會(huì)有很大的困難,即使是自己寫的代碼,幾個(gè)月后再看也會(huì)變的很陌生,也會(huì)看不懂(您可能不相信,不過筆者和同事交流發(fā)現(xiàn)大家都是這樣的,時(shí)間長不看就忘記了),所以編程規(guī)范的重要性顯而易見。
另外養(yǎng)成良好的編程規(guī)范,對(duì)于個(gè)人的工作習(xí)慣、思路等都有非常大的好處。可以讓新人盡快融入項(xiàng)目中,讓大家更容易看懂您的代碼。
1.5.2 工程組織形式
工程的組織形式一般包括如下幾個(gè)部分,分別是doc、par、rtl和sim四個(gè)部分。
XX工程名
|–doc
|–par
|–rtl
|–sim
doc:一般存放工程相關(guān)的文檔,包括該項(xiàng)目用到的datasheet(數(shù)據(jù)手冊(cè))、設(shè)計(jì)方案等。不過為了便于大家查看,我們開發(fā)板文檔是統(tǒng)一匯總存放在資料盤下的;
par:主要存放工程文件和使用到的一些IP文件;
rtl:主要存放工程的rtl代碼,這是工程的核心,文件名與module名稱應(yīng)當(dāng)一致,建議按照模塊的層次分開存放;
sim:主要存放工程的仿真代碼,復(fù)雜的工程里面,仿真也是不可或缺的部分,可以極大減少調(diào)試的工作量。
1.5.3 文件頭聲明
每一個(gè)Verilog文件的開頭,都必須有一段聲明的文字。包括文件的版權(quán),作者,創(chuàng)建日期以及內(nèi)容介紹等,如下表所示。
//Copyright ?***********************************//
//原子哥在線教學(xué)平臺(tái):www.yuanzige.com
//技術(shù)支持:www.openedv.com
//淘寶店鋪:http://openedv.taobao.com
//關(guān)注微信公眾平臺(tái)微信號(hào):“正點(diǎn)原子”,免費(fèi)獲取ZYNQ & FPGA & STM32 & LINUX資料。
//版權(quán)所有,盜版必究。
//Copyright? 正點(diǎn)原子 2018-2028
//All rights reserved
//----------------------------------------------------------------------------------------
// File name: led_twinkle
// Last modified Date: 2019/4/14 10:55:56
// Last Version: V1.0
// Descriptions: LED燈閃爍
//----------------------------------------------------------------------------------------
// Created by: 正點(diǎn)原子
// Created date: 2019/4/14 10:55:56
// Version: V1.0
// Descriptions: The original version
//
//----------------------------------------------------------------------------------------
//************************************************//
我們建議一個(gè).V只包括一個(gè)module,這樣模塊會(huì)比較清晰易懂。
1.5.4 輸入輸出定義
端口的輸入輸出有Verilog 95和2001兩種格式,推薦大家采用Verilog 2001語法格式。下面是Verilog 2001語法的一個(gè)例子,包括module名字、輸入輸出、信號(hào)名字、輸出類型、注釋。
1 module led(
2 input sys_clk , //系統(tǒng)時(shí)鐘
3 input sys_rst_n, //系統(tǒng)復(fù)位,低電平有效
4 output reg [3:0] led //4位LED燈
5 );
我們建議如下幾點(diǎn):
① 一行只定義一個(gè)信號(hào);
② 信號(hào)全部對(duì)齊;
③ 同一組的信號(hào)放在一起。
1.5.5 parameter定義
我們建議如下幾點(diǎn):
① module中的parameter聲明,不建議隨處亂放;
② 將parameter定義放在緊跟著module的輸入輸出定義之后;
③ parameter等常量命名全部使用大寫。
7 //parameter define
8 parameter WIDTH = 25 ;
9 parameter COUNT_MAX = 25_000_000; //板載50M時(shí)鐘=20ns,0.5s/20ns=25000000,需要25bit
10 //位寬
1.5.6 wire/reg定義
一個(gè)module中的wire/reg變量聲明需要集中放在一起,不建議隨處亂放。
因此,我們建議如下:
① 將reg與wire的定義放在緊跟著parameter之后;
② 建議具有相同功能的信號(hào)集中放在一起;
③ 信號(hào)需要對(duì)齊,reg和位寬需要空2格,位寬和信號(hào)名字至少空四格;
④ 位寬使用降序描述,[6:0];
⑤ 時(shí)鐘使用前綴clk,復(fù)位使用后綴rst;
⑥ 不能使用Verilog關(guān)鍵字作為信號(hào)名字;
⑦ 一行只定義一個(gè)信號(hào)。
12 //reg define
13 reg [WIDTH-1:0] counter ;
14 reg [1:0] led_ctrl_cnt;
15
16 //wire define
17 wire counter_en ;
1.5.7 信號(hào)命名
大家對(duì)信號(hào)命名可能都有不同的喜好,我們建議如下:
① 信號(hào)命名需要體現(xiàn)其意義,比如fifo_wr代表FIFO讀寫使能;
② 可以使用“_”隔開信號(hào),比如sys_clk;
③ 內(nèi)部信號(hào)不要使用大寫,也不要使用大小寫混合,建議全部使用小寫;
④ 模塊名字使用小寫;
⑤ 低電平有效的信號(hào),使用_n作為信號(hào)后綴;
⑥ 異步信號(hào),使用_a作為信號(hào)后綴;
⑦ 純延遲打拍信號(hào)使用_dly作為后綴。
1.5.8 always塊描述方式
always塊的編程規(guī)范,我們建議如下:
① if需要空四格;
② 一個(gè)always需要配一個(gè)begin和end;
③ always前面需要有注釋;
④ beign建議和always放在同一行;
⑤ 一個(gè)always和下一個(gè)always空一行即可,不要空多行;
⑥ 時(shí)鐘復(fù)位觸發(fā)描述使用posedge sys_clk和negedge sys_rst_n
⑦ 一個(gè)always塊只包含一個(gè)時(shí)鐘和復(fù)位;
⑧ 時(shí)序邏輯使用非阻塞賦值。
26 //用于產(chǎn)生0.5秒使能信號(hào)的計(jì)數(shù)器
27 always @(posedge sys_clk or negedge sys_rst_n) begin
28 if (sys_rst_n == 1’b0)
29 counter <= 1’b0;
30 else if (counter_en)
31 counter <= 1’b0;
32 else
33 counter <= counter + 1’b1;
34 end
1.5.9 assign塊描述方式
assign塊的編程規(guī)范,我們建議如下:
① assign的邏輯不能太復(fù)雜,否則易讀性不好;
② assign前面需要有注釋;
③ 組合邏輯使用阻塞賦值。
23 //計(jì)數(shù)到最大值時(shí)產(chǎn)生高電平使能信號(hào)
24 assign counter_en = (counter == (COUNT_MAX - 1’b1)) ? 1’b1 : 1’b0;
1.5.10 空格和TAB
由于不同的解釋器對(duì)于TAB翻譯不一致,所以建議不使用TAB,全部使用空格。
1.5.11 注釋
添加注釋可以增加代碼的可讀性,易于維護(hù)。我們建議規(guī)范如下:
① 注釋描述需要清晰、簡潔;
② 注釋描述不要廢話,冗余;
③ 注釋描述需要使用“//”;
④ 注釋描述需要對(duì)齊;
⑤ 核心代碼和信號(hào)定義之間需要增加注釋。
26 //用于產(chǎn)生0.5秒使能信號(hào)的計(jì)數(shù)器
27 always @(posedge sys_clk or negedge sys_rst_n) begin
28 if (sys_rst_n == 1’b0)
29 counter <= 1’b0;
30 else if (counter_en) // counter_en為1時(shí),counter清0
31 counter <= 1’b0;
32 else
33 counter <= counter + 1’b1;
34 end
1.5.12 模塊例化
模塊例化我們建議規(guī)范如下:
① moudle模塊例化使用u_xx表示。
20 //例化計(jì)時(shí)模塊
21 time_count #(
22 .MAX_NUM (TIME_SHOW)
23 ) u_time_count(
24 .clk (sys_clk ),
25 .rst_n (sys_rst_n),
26
27 .flag (add_flag )
28 );
29
30 //例化數(shù)碼管靜態(tài)顯示模塊
31 seg_led_static u_seg_led_static (
32 .clk (sys_clk ),
33 .rst_n (sys_rst_n),
34
35 .add_flag (add_flag ),
36 .sel (sel ),
37 .seg_led (seg_led )
38 );
1.5.13 其他注意事項(xiàng)
其他注意事項(xiàng)如下:
① 代碼寫的越簡單越好,方便他人閱讀和理解;
② 不使用repeat等循環(huán)語句;
③ RTL級(jí)別代碼里面不使用initial語句,仿真代碼除外;
④ 避免產(chǎn)生Latch鎖存器,比如組合邏輯里面的if不帶else分支、case缺少default語句;
⑤ 避免使用太復(fù)雜和少見的語法,可能造成語法綜合器優(yōu)化力度較低。
良好的編程規(guī)范是大家走向?qū)I(yè)FPGA工程師的必備素質(zhì),希望大家都能養(yǎng)成良好的編程規(guī)范。
總結(jié)
以上是生活随笔為你收集整理的【正点原子FPGA连载】第七章 Verilog HDL语 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: ET工业大脑开放平台,全球首个工业智能的
- 下一篇: 关于TCOOP-M005降压模块的详细介