日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

【正点原子FPGA连载】第七章 Verilog HDL语 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0

發(fā)布時間:2023/12/20 编程问答 41 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【正点原子FPGA连载】第七章 Verilog HDL语 -摘自【正点原子】领航者ZYNQ之FPGA开发指南_V2.0 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

1)實驗平臺:正點原子領(lǐng)航者ZYNQ開發(fā)板
2)平臺購買地址:https://item.taobao.com/item.htm?&id=606160108761
3)全套實驗源碼+手冊+視頻下載地址:http://www.openedv.com/thread-301505-1-1.html
4)對正點原子FPGA感興趣的同學(xué)可以加群討論:994244016
5)關(guān)注正點原子公眾號,獲取最新資料更新

第七章 Verilog HDL語法

Verilog HDL(Hardware Description Language)是在用途最廣泛的C語言的基礎(chǔ)上發(fā)展起來的一種硬件描述語言,具有靈活性高、易學(xué)易用等特點。Verilog HDL可以在較短的時間內(nèi)學(xué)習(xí)和掌握,目前已經(jīng)在FPGA開發(fā)/IC設(shè)計領(lǐng)域占據(jù)絕對的領(lǐng)導(dǎo)地位。
本章包括以下幾個部分:
1.1 Verilog概述
1.2 Verilog基礎(chǔ)知識
1.3 Verilog程序框架
1.4 Verilog高級知識點
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)和行為的語言,用它可以表示邏輯電路圖、邏輯表達式,還可以表示數(shù)字邏輯系統(tǒng)所完成的邏輯功能。
數(shù)字電路設(shè)計者利用這種語言,可以從頂層到底層逐層描述自己的設(shè)計思想,用一系列分層次的模塊來表示極其復(fù)雜的數(shù)字系統(tǒng)。然后利用電子設(shè)計自動化(EDA)工具,逐層進行仿真驗證,再把其中需要變?yōu)閷嶋H電路的模塊組合,經(jīng)過自動綜合工具轉(zhuǎn)換到門級電路網(wǎng)表。接下來,再用專用集成電路ASIC或FPGA自動布局布線工具,把網(wǎng)表轉(zhuǎn)換為要實現(xiàn)的具體電路結(jié)構(gòu)。
Verilog語言最初是于1983年由Gateway Design Automation公司為其模擬器產(chǎn)品開發(fā)的硬件建模語言。由于他們的模擬、仿真器產(chǎn)品的廣泛使用,Verilog HDL作為一種便于使用且實用的語言逐漸為眾多設(shè)計者所接受。在一次努力增加語言普及性的活動中,Verilog HDL語言于1990年被推向公眾領(lǐng)域。Verilog語言于1995年成為IEEE標(biāo)準(zhǔn),稱為IEEE Std1364-1995,也就是通常所說的Verilog-95。
設(shè)計人員在使用Verilog-95的過程中發(fā)現(xiàn)了一些可改進之處。為了解決用戶在使用此版本Verilog過程中反映的問題,Verilog進行了修正和擴展,這個擴展后的版本后來成為了電氣電子工程師學(xué)會Std1364-2001標(biāo)準(zhǔn),即通常所說的Verilog-2001。Verilog-2001是對Verilog-95的一個重大改進版本,它具備一些新的實用功能,例如敏感列表、多維數(shù)組、生成語句塊、命名端口連接等。目前,Verilog-2001是Verilog的最主流版本,被大多數(shù)商業(yè)電子設(shè)計自動化軟件支持。
1.1.2 為什么需要Verilog
在FPGA設(shè)計里面,我們有多種設(shè)計方式,如原理圖設(shè)計方式、編寫描述語言(代碼)等方式。一開始很多工程師對原理圖設(shè)計方式很鐘愛,這種輸入方式能夠很直觀的看到電路結(jié)構(gòu)并快速理解,但是隨著電路設(shè)計規(guī)模的不斷增加,邏輯電路設(shè)計也越來越復(fù)雜,這種設(shè)計方式已經(jīng)越來越不滿足實際的項目需求了。這個時候Verilog語言就取而代之了,目前Verilog已經(jīng)在FPGA開發(fā)/IC設(shè)計領(lǐng)域占據(jù)絕對的領(lǐng)導(dǎo)地位。
1.1.3 Verilog和VHDL區(qū)別
這兩種語言都是用于數(shù)字電路系統(tǒng)設(shè)計的硬件描述語言,而且都已經(jīng)是IEEE的標(biāo)準(zhǔn)。 VHDL 1987年成為標(biāo)準(zhǔn),而Verilog是1995年才成為標(biāo)準(zhǔn)的。這是因為VHDL是美國軍方組織開發(fā)的,而Verilog是由一個公司的私有財產(chǎn)轉(zhuǎn)化而來。為什么Verilog能成為IEEE標(biāo)準(zhǔn)呢?它一定有其獨特的優(yōu)越性才行,所以說Verilog有更強的生命力。
這兩者有其共同的特點:

  • 能形式化地抽象表示電路的行為和結(jié)構(gòu);
  • 支持邏輯設(shè)計中層次與范圍地描述;
  • 可借用高級語言地精巧結(jié)構(gòu)來簡化電路行為和結(jié)構(gòu);
  • 支持電路描述由高層到低層的綜合轉(zhuǎn)換;
  • 硬件描述和實現(xiàn)工藝無關(guān)。
    但是兩者也各有特點。Verilog推出已經(jīng)有20年了,擁有廣泛的設(shè)計群體,成熟的資源,且Verilog容易掌握,只要有C語言的編程基礎(chǔ),通過比較短的時間,經(jīng)過一些實際的操作,可以在1個月左右掌握這種語言。而VHDL設(shè)計相對要難一點,這個是因為VHDL不是很直觀,一般認為至少要半年以上的專業(yè)培訓(xùn)才能掌握。
    近10年來,EDA界一直在對數(shù)字邏輯設(shè)計中究竟用哪一種硬件描述語言爭論不休,目前在美國,高層次數(shù)字系統(tǒng)設(shè)計領(lǐng)域中,應(yīng)用Verilog和VHDL的比率是80%和20%;日本與中國臺灣和美國差不多;而在歐洲VHDL發(fā)展的比較好;在中國很多集成電路設(shè)計公司都采用Verilog。我們推薦大家學(xué)習(xí)Verilog,本教程全部的例程都是使用Verilog開發(fā)的。
    1.1.4 Verilog和C的區(qū)別
    Verilog是硬件描述語言,在編譯下載到FPGA之后,會生成電路,所以Verilog全部是并行處理與運行的;C語言是軟件語言,編譯下載到單片機/CPU之后,還是軟件指令,而不會根據(jù)你的代碼生成相應(yīng)的硬件電路,而單片機/CPU處理軟件指令需要取址、譯碼、執(zhí)行,是串行執(zhí)行的。
    Verilog和C的區(qū)別也是FPGA和單片機/CPU的區(qū)別,由于FPGA全部并行處理,所以處理速度非常快,這個是FPGA的最大優(yōu)勢,這一點是單片機/CPU替代不了的。
    1.2 Verilog基礎(chǔ)知識
    本節(jié)主要講解了Verilog的基礎(chǔ)知識,包括5個小節(jié),下面我們分別給大家介紹這5個小節(jié)的內(nèi)容。
    1.2.1 Verilog的邏輯值
    我們先看下邏輯電路中有四種值,即四種狀態(tài):
    邏輯 0:表示低電平,也就是對應(yīng)我們電路的GND;
    邏輯 1:表示高電平,也就是對應(yīng)我們電路的VCC;
    邏輯 X:表示未知,有可能是高電平,也有可能是低電平;
    邏輯 Z:表示高阻態(tài),外部沒有激勵信號是一個懸空狀態(tài)。
    如下圖所示:
  • 圖 7.2.1.1 Verilog邏輯值
    1.2.2 Verilog的標(biāo)識符
    定義
    標(biāo)識符(identifier)用于定義模塊名、端口名和信號名等。Verilog的標(biāo)識符可以是任意一組字母、數(shù)字、和(下劃線)符號的組合,但標(biāo)識符的第一個字符必須是字母或者下劃線。另外,標(biāo)識符是區(qū)分大小寫的。以下是標(biāo)識符的幾個例子:CountCOUNT//與Count不同。R5668FIVE和_(下劃線)符號的組合,但標(biāo)識符的第一個字符必須是字母或者下劃線。另外,標(biāo)識符是區(qū)分大小寫的。以下是標(biāo)識符的幾個例子: Count COUNT //與Count不同。 R56_68 FIVE(?)標(biāo)標(biāo)區(qū)標(biāo)CountCOUNT//CountR566?8FIVE
    雖然標(biāo)識符寫法很多,但是要簡潔、清晰、易懂,推薦寫法如下:
    count
    fifo_wr
    不建議大小寫混合使用,普通內(nèi)部信號建議全部小寫,參數(shù)定義建議大寫,另外信號命名最好體現(xiàn)信號的含義。
    規(guī)范建議
    以下是一些書寫規(guī)范的要求:
    1、用有意義的有效的名字如sum、cpu_addr等。
    2、用下劃線區(qū)分詞語組合,如cpu_addr。
    3、采用一些前綴或后綴,比如:時鐘采用clk前綴:clk_50m,clk_cpu;低電平采用_n后綴:enable_n;
    4、統(tǒng)一縮寫,如全局復(fù)位信號rst。
    5、同一信號在不同層次保持一致性,如同一時鐘信號必須在各模塊保持一致。
    6、自定義的標(biāo)識符不能與保留字(關(guān)鍵詞)同名。
    7、參數(shù)統(tǒng)一采用大寫,如定義參數(shù)使用SIZE。
    1.2.3 Verilog的數(shù)字進制格式
    Verilog數(shù)字進制格式包括二進制、八進制、十進制和十六進制,一般常用的為二進制、十進制和十六進制。
    二進制表示如下:4’b0101表示4位二進制數(shù)字0101;
    十進制表示如下:4’d2表示4位十進制數(shù)字2(二進制0010);
    十六進制表示如下:4’ha表示4位十六進制數(shù)字a(二進制1010),十六進制的計數(shù)方式為0,1,2…9,a,b,c,d,e,f,最大計數(shù)為f(f:十進制表示為15)。
    當(dāng)代碼中沒有指定數(shù)字的位寬與進制時,默認為32位的十進制,比如100,實際上表示的值為32’d100。
    1.2.4 Verilog的數(shù)據(jù)類型
    在Verilog語法中,主要有三大類數(shù)據(jù)類型,即寄存器類型、線網(wǎng)類型和參數(shù)類型。從名稱中,我們可以看出,真正在數(shù)字電路中起作用的數(shù)據(jù)類型應(yīng)該是寄存器類型和線網(wǎng)類型。

  • 寄存器類型
    寄存器類型表示一個抽象的數(shù)據(jù)存儲單元,它只能在always語句和initial語句中被賦值,并且它的值從一個賦值到另一個賦值過程中被保存下來。如果該過程語句描述的是時序邏輯,即always語句帶有時鐘信號,則該寄存器變量對應(yīng)為寄存器;如果該過程語句描述的是組合邏輯,即always語句不帶有時鐘信號,則該寄存器變量對應(yīng)為硬件連線;寄存器類型的缺省值是x(未知狀態(tài))。
    寄存器數(shù)據(jù)類型有很多種,如reg、integer、real等,其中最常用的就是reg類型,它的使用方法如下:
  • //reg define reg [31:0] delay_cnt; //延時計數(shù)器 reg key_flag ; //按鍵標(biāo)志
  • 線網(wǎng)類型
    線網(wǎng)表示Verilog結(jié)構(gòu)化元件間的物理連線。它的值由驅(qū)動元件的值決定,例如連續(xù)賦值或門的輸出。如果沒有驅(qū)動元件連接到線網(wǎng),線網(wǎng)的缺省值為z(高阻態(tài))。線網(wǎng)類型同寄存器類型一樣也是有很多種,如tri和wire等,其中最常用的就是wire類型,它的使用方法如下:
    //wire define
    wire data_en; //數(shù)據(jù)使能信號
    wire [7:0] data ; //數(shù)據(jù)
  • 參數(shù)類型
    我們再來看下參數(shù)類型,參數(shù)其實就是一個常量,常被用于定義狀態(tài)機的狀態(tài)、數(shù)據(jù)位寬和延遲大小等,由于它可以在編譯時修改參數(shù)的值,因此它又常被用于一些參數(shù)可調(diào)的模塊中,使用戶在實例化模塊時,可以根據(jù)需要配置參數(shù)。在定義參數(shù)時,我們可以一次定義多個參數(shù),參數(shù)與參數(shù)之間需要用逗號隔開。這里我們需要注意的是參數(shù)的定義是局部的,只在當(dāng)前模塊中有效。它的使用方法如下:
    //parameter define
    parameter DATA_WIDTH = 8; //數(shù)據(jù)位寬為8位
    1.2.5 Verilog的運算符
    大家看完了Verilog的數(shù)據(jù)類型,我們再來介紹下Verilog的運算符。Verilog中的運算符按照功能可以分為下述類型:1、算術(shù)運算符、 2、關(guān)系運算符、3、邏輯運算符、 4、條件運算符、 5、位運算符、 6、移位運算符、 7、拼接運算符。下面我們分別對這些運算符進行介紹。
  • 算術(shù)運算符
    算術(shù)運算符,簡單來說,就是數(shù)學(xué)運算里面的加減乘除,數(shù)字邏輯處理有時候也需要進行數(shù)字運算,所以需要算術(shù)運算符。常用的算術(shù)運算符主要包括加減乘除和模除(模除運算也叫取余運算)如下表所示:
    表 7.2.1 算術(shù)運算符
    符號 使用方法 說明
    • a + b a 加上 b
    • a - b a 減去 b
    • a * b a 乘以 b
      / a / b a 除以 b
      % a % b a 模除 b
      大家要注意下,Verilog實現(xiàn)乘除比較浪費組合邏輯資源,尤其是除法。一般2的指數(shù)次冪的乘除法使用移位運算來完成運算,詳情可以看移位運算符章節(jié)。非2的指數(shù)次冪的乘除法一般是調(diào)用現(xiàn)成的IP,QUARTUS/ISE等工具軟件會有提供,不過這些工具軟件提供的IP也是由最底層的組合邏輯(與或非門等)搭建而成的。
  • 關(guān)系運算符
    關(guān)系運算符主要是用來做一些條件判斷用的,在進行關(guān)系運算符時,如果聲明的關(guān)系是假的,則返回值是0,如果聲明的關(guān)系是真的,則返回值是1;所有的關(guān)系運算符有著相同的優(yōu)先級別,關(guān)系運算符的優(yōu)先級別低于算術(shù)運算符的優(yōu)先級別如下表所示。
    表 7.2.2 關(guān)系運算符
    符號 使用方法 說明
  • a > b a 大于 b
    < a < b a 小于 b
    = a >= b a 大于等于 b
    <= a <= b a 小于等于 b
    == a == b a 等于 b
    != a != b a 不等于 b

  • 邏輯運算符
    邏輯運算符是連接多個關(guān)系表達式用的,可實現(xiàn)更加復(fù)雜的判斷,一般不單獨使用,都需要配合具體語句來實現(xiàn)完整的意思,如下表所示。
    表 7.2.3 邏輯運算符
    符號 使用方法 說明
    ! !a a的非,如果a為0,那么a的非是1。
    && a && b a 與上 b,如果a和b都為1,a&&b結(jié)果才為1,表示真。
    || a || b a 或上 b,如果a或者b有一個為1,a||b結(jié)果為1,表示真。
  • 條件運算符
    條件操作符一般來構(gòu)建從兩個輸入中選擇一個作為輸出的條件選擇結(jié)構(gòu),功能等同于 always中的if-else語句,如下表所示。
    表 7.2.4 條件運算符
    符號 使用方法 說明
    ? : a ? b : c 如果 a 為真,就選擇 b,否則選擇 c
  • 位運算符
    位運算符是一類最基本的運算符,可以認為它們直接對應(yīng)數(shù)字邏輯中的與、或、非門等邏輯門。常用的位運算符如下表所示。
    表 7.2.5 位運算符
    符號 使用方法 說明
    ~ ~a 將 a 的每個位進行取反
    & a & b 將 a 的每個位與 b 相同的位進行相與
    | a | b 將 a 的每個位與 b 相同的位進行相或
    ^ a ^ b 將 a 的每個位與 b 相同的位進行異或
    位運算符的與、或、非與邏輯運算符邏輯與、邏輯或、邏輯非使用時候容易混淆,邏輯運算符一般用在條件判斷上,位運算符一般用在信號賦值上。
  • 移位運算符
    移位運算符包括左移位運算符和右移位運算符,這兩種移位運算符都用0來填補移出的空位。如下表所示。
    表 7.2.6 移位運算符
    符號 使用方法 說明
    << 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ù)固定補0。如果a是3(二進制:00000011),那么3左移2bit,3<<2,就是12(二進制:00001100)。一般使用左移位運算代替乘法,右移位運算代替除法,但是這種也只能表示2的指數(shù)次冪的乘除法。

  • 拼接運算符
    Verilog中有一個特殊的運算符是C語言中沒有的,就是位拼接運算符。用這個運算符可以把兩個或多個信號的某些位拼接起來進行運算操作。如下表所示。
    表 7.2.7 位拼接運算符
    符號 使用方法 說明
    {} {a,b} 將 a 和 b 拼接起來,作為一個新信號
  • 運算符的優(yōu)先級
    介紹完了這么多運算符,大家可能會想到究竟哪個運算符高,哪個運算符低。為了便于大家查看這些運算符的優(yōu)先級,我們將它們制作成了表格,如下表所示。
    表 7.2.8 運算符的優(yōu)先級
    運算符 優(yōu)先級
    !、 ~ 最高
    *、 /、 % 次高
    +、 -
  • <<、 >>
    <、 <=、 >、 >=
    ==、 !=、 =、 !
    &
    ^、 ^~
    |
    &&
    || 次低
    ? 最低
    1.3 Verilog程序框架
    在介紹Verilog程序框架之前,我們先來看下Verilog一些基本語法,基礎(chǔ)語法主要包括注釋和關(guān)鍵字。
    1.3.1 注釋
    Verilog HDL中有兩種注釋的方式,一種是以“/”符號開始,“/”結(jié)束,在兩個符號之間的語句都是注釋語句,因此可擴展到多行。如:
    /* statement1 ,
    statement2,

    statementn */
    以上n個語句都是注釋語句。
    另一種是以//開頭的語句,它表示以//開始到本行結(jié)束都屬于注釋語句。如:
    //statement1
    我們建議的寫法:使用//作為注釋。
    1.3.2 關(guān)鍵字
    Verilog和C語言類似,都因編寫需要定義了一系列保留字,叫做關(guān)鍵字(或關(guān)鍵詞)。這些保留字是識別語法的關(guān)鍵。我們給大家列出了Verilog中的關(guān)鍵字,如下表所示。
    表 7.3.1 Verilog的所有關(guān)鍵字

    and always assign begin buf bufif0 bufif1 case casex casez cmos deassign default defparam disable edge else end endcase endfunction endprimitive endmodule endspecify endtable endtask event for force forever fork function highz0 highz1 if ifnone initial inout input integer join large macromodule medium module nand negedge nor not notif0 notif1 nmos or output parameter pmos posedge primitive pulldown pullup pull0 pull1 rcmos real realtime reg release repeat rnmos rpmos rtran rtranif0 rtranif1 scalared small specify specparam strength strong0 strong1 supply0 supply1 table task tran tranif0 tranif1 time tri triand trior trireg tri0 tri1 vectored wait wand weak0 weak1 while wire wor xnor xor

    雖然上表列了很多,但是實際經(jīng)常使用的不是很多,實際經(jīng)常使用的主要如下表所示。
    表 7.3.2 Verilog常用的關(guān)鍵字
    關(guān)鍵字 含義
    module 模塊開始定義
    input 輸入端口定義
    output 輸出端口定義
    inout 雙向端口定義
    parameter 信號的參數(shù)定義
    wire wire信號定義
    reg reg信號定義
    always 產(chǎn)生reg信號語句的關(guān)鍵字
    assign 產(chǎn)生wire信號語句的關(guān)鍵字
    begin 語句的起始標(biāo)志
    end 語句的結(jié)束標(biāo)志
    posedge/negedge 時序電路的標(biāo)志
    case Case語句起始標(biāo)記
    default Case語句的默認分支標(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)識符always(這是個關(guān)鍵詞)與標(biāo)識符ALWAYS(非關(guān)鍵詞)是不同的。
    1.3.3 程序框架
    我們以LED燈閃爍程序為例來給大家展示Verilog的程序框架,代碼如下所示(注意:代碼中前面的行號只是為了方便大家閱讀代碼與快速定位到行號的位置,在實際編寫代碼時不可以添加行號,否則編譯代碼時會報錯)。

    1 module led( 2 input sys_clk , //系統(tǒng)時鐘 3 input sys_rst_n, //系統(tǒng)復(fù)位,低電平有效 4 output reg [3:0] led //4位LED燈 5 ); 6 7 //parameter define 8 parameter WIDTH = 25 ; 9 parameter COUNT_MAX = 25_000_000; //板載50M時鐘=20ns,0.5s/20ns=25000000,需要25bit 10 //位寬 11 12 //reg define 13 reg [WIDTH-1:0] counter ; 14 reg [1:0] led_ctrl_cnt; 15 16 //wire define 17 wire counter_en ; 18 19 //*********************************************************************************** 20 //** main code 21 //*********************************************************************************** 22 23 //計數(shù)到最大值時產(chǎn)生高電平使能信號 24 assign counter_en = (counter == (COUNT_MAX - 1'b1)) ? 1'b1 : 1'b0; 25 26 //用于產(chǎn)生0.5秒使能信號的計數(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 35 36 //led流水控制計數(shù)器 37 always @(posedge sys_clk or negedge sys_rst_n) begin 38 if (sys_rst_n == 1'b0) 39 led_ctrl_cnt <= 2'b0; 40 else if (counter_en) 41 led_ctrl_cnt <= led_ctrl_cnt + 2'b1; 42 end 43 44 //通過控制IO口的高低電平實現(xiàn)發(fā)光二極管的亮滅 45 always @(posedge sys_clk or negedge sys_rst_n) begin 46 if (sys_rst_n == 1'b0) 47 led <= 4'b0; 48 else begin 49 case (led_ctrl_cnt) 50 2'd0 : led <= 4'b0001; 51 2'd1 : led <= 4'b0010; 52 2'd2 : led <= 4'b0100; 53 2'd3 : led <= 4'b1000; 54 default : ; 55 endcase 56 end 57 end 58 59 endmodule

    首先//開頭的都是注釋,這個之前我們講解過了。下面我們來看下具體的解釋。
    第1行為模塊定義,模塊定義以module開始,endmodule結(jié)束,如59行所示。
    其次2到5行為端口定義,需要定義led模塊的輸入信號和輸出信號,此處輸入信號為系統(tǒng)時鐘和復(fù)位信號,輸出為led控制信號。
    7到9行為參數(shù)parameter定義,語法如7到9行所示,定義parameter的好處是可以靈活改變參數(shù)數(shù)字就能控制一些計數(shù)器最大計數(shù)值或者信號位寬的最大位寬。
    12到14行為reg信號定義,reg信號一般情況下代表寄存器,比如此處控制0.5秒使能信號的計數(shù)器counter。
    16到17行為wire信號定義,wire信號就是硬件連線,比如此處的counter_en,代表計數(shù)到最大值時產(chǎn)生高電平使能,本質(zhì)上是一個硬件連線,其實代表的是一些計數(shù)器/寄存器做邏輯判斷的結(jié)果。
    19到21行為moudle開始的注釋,不添加工具綜合也不會報錯,但是我們推薦添加,作為一個良好的編程規(guī)范。
    23到24行為assign語句的樣式,條件成立選擇1,否則選擇0。
    26到34行是always語句的樣式,27行代表在時鐘上升沿或者復(fù)位的下降沿進行信號觸發(fā)。begin/end代表語句的開始和結(jié)束。28到33行為if/else語句,和C語言是比較類似的。29行的“<=”標(biāo)記代表信號是非阻塞賦值,信號賦值有非阻塞賦值和阻塞賦值兩個方式,這個我們后面會詳細解釋。
    36和42行也是一個always語句,和26到34行類似。
    44和57行也是一個always語句,不過這個always語句中嵌入了一個case語句,case語句的語法如49到55行所示,需要一個case關(guān)鍵字開始,endcase關(guān)鍵字結(jié)束,default作為默認分支,和C語言也是類似的。當(dāng)然case語句也可以用在不帶時鐘的always語句中,不過本例子的always都是帶有時鐘的。不帶時鐘的always和帶時鐘的always語句的差異這個我們后面也會詳細解釋。
    59行是endmodule標(biāo)記,代表模塊的結(jié)束。
    在這里需要補充一點的是,一些初學(xué)者可能會有這樣一個疑問,在always語句中編寫if語句或else語句時,后面需要加begin和end嗎?其實這個主要看if條件后面跟著幾條賦值語句,如果只有一條賦值語句時,if后面可以加begin和end,也可以不加;如果超過一條賦值語句時,就必須加上begin和end。
    if條件只有一條賦值語句時,下面兩種寫法都是可以的,這里更推薦第一種寫法,因為第二種寫法會占用更多的行號,代碼如下所示:

    if(en == 1'b1)a <= 1'b1; 或者 if(en == 1'b1) begina <= 1'b1; end 對于if條件超過一條賦值語句的情況,必須添加begin和end,代碼如下所示: if(en == 1'b1) beginb <= 1'b1;c <= 1'b1; end

    好了,程序框架就講解完了,大家是不是覺得也很簡單呢?這些都是基本的語法規(guī)范,希望大家能記住這些基礎(chǔ)的知識點。如果有些地方大家還是覺得比較抽象,很難理解,沒有關(guān)系,相信大家會在后面的學(xué)習(xí)中,會慢慢理解的。
    1.4 Verilog高級知識點
    前幾節(jié)主要介紹了Verilog一些基礎(chǔ)的知識點和程序框架,本節(jié)給大家介紹一些高級的知識點。高級知識點包括阻塞賦值和非阻塞賦值、assign和always語句差異、什么是鎖存器、狀態(tài)機、模塊化設(shè)計等。
    1.4.1 阻塞賦值(Blocking)
    阻塞賦值,顧名思義,即在一個always塊中,后面的語句會受到前語句的影響,具體來說,在同一個always中,一條阻塞賦值語句如果沒有執(zhí)行結(jié)束,那么該語句后面的語句就不能被執(zhí)行,即被“阻塞”。也就是說always塊內(nèi)的語句是一種順序關(guān)系,這里和C語言很類似。符號“=”用于阻塞的賦值(如:b = a;),阻塞賦值“=”在begin和end之間的語句是順序執(zhí)行,屬于串行語句。
    在這里定義兩個縮寫:
    RHS:賦值等號右邊的表達式或變量可以寫作RHS表達式或RHS變量;
    LHS:賦值等號左邊的表達式或變量可以寫作LHS表達式或LHS變量;
    阻塞賦值的執(zhí)行可以認為是只有一個步驟的操作,即計算RHS的值并更新LHS,此時不允許任何其他語句的干擾,所謂的阻塞的概念就是值在同一個always塊中,其后面的賦值語句從概念上來講是在前面一條語句賦值完成后才執(zhí)行的。
    為了方便大家理解阻塞賦值的概念以及阻塞賦值和非阻塞賦值的區(qū)別,我們這里以在時序邏輯下使用阻塞賦值為例來實現(xiàn)這樣一個功能:在復(fù)位的時候,a=1,b=2,c=3;而在沒有復(fù)位的時候,a的值清零,同時將a的值賦值給b,b的值賦值給c,代碼以及信號波形圖如下圖所示:

    圖 7.4.1.1 阻塞賦值代碼

    圖 7.4.1.2 阻塞賦值的信號波形圖
    代碼中使用的是阻塞賦值語句,從波形圖中可以看到,在復(fù)位的時候(rst_n=0),a=1,b=2,c=3;而結(jié)束復(fù)位之后(波形圖中的0時刻),當(dāng)clk的上升沿到來時(波形圖中的2時刻),a=0,b=0,c=0。這是因為阻塞賦值是在當(dāng)前語句執(zhí)行完成之后,才會執(zhí)行后面的賦值語句,因此首先執(zhí)行的是a=0,賦值完成后將a的值賦值給b,由于此時a的值已經(jīng)為0,所以b=a=0,最后執(zhí)行的是將b的值賦值給c,而b的值已經(jīng)賦值為0,所以c的值同樣等于0。
    1.4.2 非阻塞賦值(Non-Blocking)
    符號“<=”用于非阻塞賦值(如:b <= a;),非阻塞賦值是由時鐘節(jié)拍決定,在時鐘上升到來時,執(zhí)行賦值語句右邊,然后將begin-end之間的所有賦值語句同時賦值到賦值語句的左邊,注意:是begin—end之間的所有語句,一起執(zhí)行,且一個時鐘只執(zhí)行一次,屬于并行執(zhí)行語句。這個是和C語言最大的一個差異點,大家要逐步理解并行執(zhí)行的概念。
    非阻塞賦值的操作過程可以看作兩個步驟:
    (1)賦值開始的時候,計算RHS;
    (2)賦值結(jié)束的時候,更新LHS。
    所謂的非阻塞的概念是指,在計算非阻塞賦值的RHS以及LHS期間,允許其它的非阻塞賦值語句同時計算RHS和更新LHS。
    我們下面使用非阻塞賦值同樣來實現(xiàn)這樣一個功能:在復(fù)位的時候,a=1,b=2,c=3;而在沒有復(fù)位的時候,a的值清零,同時將a的值賦值給b,b的值賦值給c,代碼以及信號波形圖如下圖所示:

    圖 7.4.2.1 非阻塞賦值代碼

    圖 7.4.2.2 非阻塞賦值的信號波形圖
    代碼中使用的是非阻塞賦值語句,從波形圖中可以看到,在復(fù)位的時候(rst_n=0),a=1,b=2,c=3;而結(jié)束復(fù)位之后(波形圖中的0時刻),當(dāng)clk的上升沿到來時(波形圖中的2時刻),a=0,b=1,c=2。這是因為非阻塞賦值在計算RHS和更新LHS期間,允許其它的非阻塞賦值語句同時計算RHS和更新LHS。在波形圖中的2時刻,RHS的表達是0、a、b,分別等于0、1、2,這三條語句是同時更新LHS,所以a、b、c的值分別等于0、1、2。
    在了解了阻塞賦值和非阻塞賦值的區(qū)別之后,有些朋友可能還是對什么時候使用阻塞賦值,什么時候使用非阻塞賦值有些疑惑,在這里給大家總結(jié)如下。
    在描述組合邏輯電路的時候,使用阻塞賦值,比如assign賦值語句和不帶時鐘的always賦值語句,這種電路結(jié)構(gòu)只與輸入電平的變化有關(guān)系,代碼如下:
    示例1:assign賦值語句
    assign data = (data_en == 1’b1) ? 8’d255 : 8’d0;
    示例2:不帶時鐘的always語句

    always @(*) beginif (en) begina = a0;b = b0;endelse begina = a1;b = b1;end end

    在描述時序邏輯的時候,使用非阻塞賦值,綜合成時序邏輯的電路結(jié)構(gòu),比如帶時鐘的always語句;這種電路結(jié)構(gòu)往往與觸發(fā)沿有關(guān)系,只有在觸發(fā)沿時才可能發(fā)生賦值的變化,代碼如下:
    示例3:

    always @(posedge sys_clk or negedge sys_rst_n) begin if (!sys_rst_n) begina <= 1'b0;b <= 1'b0;endelse begina <= c;b <= d;end end

    1.4.3 assign和always區(qū)別
    assign語句和always語句是Verilog中的兩個基本語句,這兩個都是經(jīng)常使用的語句。
    assign語句使用時不能帶時鐘。
    always語句可以帶時鐘,也可以不帶時鐘。在always不帶時鐘時,邏輯功能和assign完全一致,都是只產(chǎn)生組合邏輯。比較簡單的組合邏輯推薦使用assign語句,比較復(fù)雜的組合邏輯推薦使用always語句。示例如下:
    24 assign counter_en = (counter == (COUNT_MAX - 1’b1)) ? 1’b1 : 1’b0;

    45 always @(*) begin 49 case (led_ctrl_cnt) 50 2'd0 : led = 4'b0001; 51 2'd1 : led = 4'b0010; 52 2'd2 : led = 4'b0100; 53 2'd3 : led = 4'b1000; 54 default : led = 4'b0000; 55 endcase 57 end

    1.4.4 帶時鐘和不帶時鐘的always
    always語句可以帶時鐘,也可以不帶時鐘。在always不帶時鐘時,邏輯功能和assign完全一致,雖然產(chǎn)生的信號定義還是reg類型,但是該語句產(chǎn)生的還是組合邏輯。

    44 reg [3:0] led; 45 always @(*) begin 49 case (led_ctrl_cnt) 50 2'd0 : led = 4'b0001; 51 2'd1 : led = 4'b0010; 52 2'd2 : led = 4'b0100; 53 2'd3 : led = 4'b1000; 54 default : led = 4'b0000; 55 endcase 57 end

    在always帶時鐘信號時,這個邏輯語句才能產(chǎn)生真正的寄存器,如下示例counter就是真正的寄存器。

    26 //用于產(chǎn)生0.5秒使能信號的計數(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.4.5 什么是latch
    latch是指鎖存器,是一種對脈沖電平敏感的存儲單元電路。鎖存器和寄存器都是基本存儲單元,鎖存器是電平觸發(fā)的存儲器,寄存器是邊沿觸發(fā)的存儲器。兩者的基本功能是一樣的,都可以存儲數(shù)據(jù)。鎖存器是組合邏輯產(chǎn)生的,而寄存器是在時序電路中使用,由時鐘觸發(fā)產(chǎn)生的。
    latch的主要危害是會產(chǎn)生毛刺(glitch),這種毛刺對下一級電路是很危險的。并且其隱蔽性很強,不易查出。因此,在設(shè)計中,應(yīng)盡量避免latch的使用。
    代碼里面出現(xiàn)latch的兩個原因是在組合邏輯中,if或者case語句不完整的描述,比如if缺少else分支,case缺少default分支,導(dǎo)致代碼在綜合過程中出現(xiàn)了latch。解決辦法就是if必須帶else分支,case必須帶default分支。
    大家需要注意下,只有不帶時鐘的always語句if或者case語句不完整才會產(chǎn)生latch,帶時鐘的語句if或者case語句不完整描述不會產(chǎn)生latch。
    下面為缺少else分支的帶時鐘的always語句和不帶時鐘的always語句,通過實際產(chǎn)生的電路圖可以看到第二個是有一個latch的,第一個仍然是普通的帶有時鐘的寄存器。

    圖 7.4.5.1 缺少else的帶時鐘的always語句電路圖

    圖 7.4.5.2 缺少else的不帶時鐘的always語句電路圖
    1.4.6 狀態(tài)機
    Verilog是硬件描述語言,硬件電路是并行執(zhí)行的,當(dāng)需要按照流程或者步驟來完成某個功能時,代碼中通常會使用很多個if嵌套語句來實現(xiàn),這樣就增加了代碼的復(fù)雜度,以及降低了代碼的可讀性,這個時候就可以使用狀態(tài)機來編寫代碼。狀態(tài)機相當(dāng)于一個控制器,它將一項功能的完成分解為若干步,每一步對應(yīng)于二進制的一個狀態(tài),通過預(yù)先設(shè)計的順序在各狀態(tài)之間進行轉(zhuǎn)換,狀態(tài)轉(zhuǎn)換的過程就是實現(xiàn)邏輯功能的過程。
    狀態(tài)機,全稱是有限狀態(tài)機(Finite State Machine,縮寫為FSM),是一種在有限個狀態(tài)之間按一定規(guī)律轉(zhuǎn)換的時序電路,可以認為是組合邏輯和時序邏輯的一種組合。狀態(tài)機通過控制各個狀態(tài)的跳轉(zhuǎn)來控制流程,使得整個代碼看上去更加清晰易懂,在控制復(fù)雜流程的時候,狀態(tài)機優(yōu)勢明顯,因此基本上都會用到狀態(tài)機,如SDRAM控制器等。在本手冊提供的例程中,會有多個用到狀態(tài)機設(shè)計的例子,希望大家能夠慢慢體會和理解,并且能夠熟練掌握。
    根據(jù)狀態(tài)機的輸出是否與輸入條件相關(guān),可將狀態(tài)機分為兩大類,即摩爾(Moore)型狀態(tài)機和米勒(Mealy)型狀態(tài)機。
    ? Mealy狀態(tài)機:組合邏輯的輸出不僅取決于當(dāng)前狀態(tài),還取決于輸入狀態(tài)。
    ? Moore狀態(tài)機:組合邏輯的輸出只取決于當(dāng)前狀態(tài)。

  • Mealy狀態(tài)機
    米勒狀態(tài)機的模型如下圖所示,模型中第一個方框是指產(chǎn)生下一狀態(tài)的組合邏輯F,F是當(dāng)前狀態(tài)和輸入信號的函數(shù),狀態(tài)是否改變、如何改變,取決于組合邏輯F的輸出;第二框圖是指狀態(tài)寄存器,其由一組觸發(fā)器組成,用來記憶狀態(tài)機當(dāng)前所處的狀態(tài),狀態(tài)的改變只發(fā)生在時鐘的跳邊沿;第三個框圖是指產(chǎn)生輸出的組合邏輯G,狀態(tài)機的輸出是由輸出組合邏輯G提供的,G也是當(dāng)前狀態(tài)和輸入信號的函數(shù)。
  • 圖 7.4.6.1 Mealy狀態(tài)機模型
    2) Moore狀態(tài)機
    摩爾狀態(tài)機的模型如下圖所示,對比米勒狀態(tài)機的模型可以發(fā)現(xiàn),其區(qū)別在于米勒狀態(tài)機的輸出由當(dāng)前狀態(tài)和輸入條件決定的,而摩爾狀態(tài)機的輸出只取決于當(dāng)前狀態(tài)。

    圖 7.4.6.2 Moore狀態(tài)機模型
    3) 三段式狀態(tài)機
    根據(jù)狀態(tài)機的實際寫法,狀態(tài)機還可以分為一段式、二段式和三段式狀態(tài)機。
    一段式:整個狀態(tài)機寫到一個always模塊里面,在該模塊中既描述狀態(tài)轉(zhuǎn)移,又描述狀態(tài)的輸入和輸出。不推薦采用這種狀態(tài)機,因為從代碼風(fēng)格方面來講,一般都會要求把組合邏輯和時序邏輯分開;從代碼維護和升級來說,組合邏輯和時序邏輯混合在一起不利于代碼維護和修改,也不利于約束。
    二段式:用兩個always模塊來描述狀態(tài)機,其中一個always模塊采用同步時序描述狀態(tài)轉(zhuǎn)移;另一個模塊采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件,描述狀態(tài)轉(zhuǎn)移規(guī)律以及輸出。不同于一段式狀態(tài)機的是,它需要定義兩個狀態(tài),現(xiàn)態(tài)和次態(tài),然后通過現(xiàn)態(tài)和次態(tài)的轉(zhuǎn)換來實現(xiàn)時序邏輯。
    三段式:在兩個always模塊描述方法基礎(chǔ)上,使用三個always模塊,一個always模塊采用同步時序描述狀態(tài)轉(zhuǎn)移,一個always采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件,描述狀態(tài)轉(zhuǎn)移規(guī)律,另一個always模塊描述狀態(tài)輸出(可以用組合電路輸出,也可以時序電路輸出)。
    實際應(yīng)用中三段式狀態(tài)機使用最多,因為三段式狀態(tài)機將組合邏輯和時序分開,有利于綜合器分析優(yōu)化以及程序的維護;并且三段式狀態(tài)機將狀態(tài)轉(zhuǎn)移與狀態(tài)輸出分開,使代碼看上去更加清晰易懂,提高了代碼的可讀性,推薦大家使用三段式狀態(tài)機,本文也著重講解三段式。
    三段式狀態(tài)機的基本格式是:
    第一個always語句實現(xiàn)同步狀態(tài)跳轉(zhuǎn);
    第二個always語句采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件;
    第三個always語句描述狀態(tài)輸出(可以用組合電路輸出,也可以時序電路輸出)。
    在開始編寫狀態(tài)機代碼之前,一般先畫出狀態(tài)跳轉(zhuǎn)圖,這樣在編寫代碼時思路會比較清晰,下面以一個7分頻為例(對于分頻等較簡單的功能,可以不使用狀態(tài)機,這里只是演示狀態(tài)機編寫的方法),狀態(tài)跳轉(zhuǎn)圖如下圖所示:

    圖 7.4.6.3 七分頻狀態(tài)跳轉(zhuǎn)圖
    狀態(tài)跳轉(zhuǎn)圖畫完之后,接下來通過parameter來定義各個不同狀態(tài)的參數(shù),如下代碼所示:

    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;

    這里是使用獨熱碼的方式來定義狀態(tài)機,每個狀態(tài)只有一位為1,當(dāng)然也可以直接定義成十進制的0,1,2……7。
    因為我們定義成獨熱碼的方式,每一個狀態(tài)的位寬為7位,接下來還需要定義兩個7位的寄存器,一個用來表示當(dāng)前狀態(tài),另一個用來表示下一個狀態(tài),如下所示:

    reg [6:0] curr_st ; //當(dāng)前狀態(tài) reg [6:0] next_st ; //下一個狀態(tài)

    接下來就可以使用三個always語句來開始編寫狀態(tài)機的代碼,第一個always采用同步時序描述狀態(tài)轉(zhuǎn)移,第二個always采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件,第三個always是描述狀態(tài)輸出,一個完整的三段式狀態(tài)機的例子如下代碼所示:

    1 module divider7_fsm ( 2 //系統(tǒng)時鐘與復(fù)位 3 input sys_clk , 4 input sys_rst_n , 5 6 //輸出時鐘 7 output reg clk_divide_7 8 ); 9 10 //parameter define 11 parameter S0 = 7'b0000001; //獨熱碼定義方式 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 ; //下一個狀態(tài) 22 23 //***************************************************** 24 //** main code 25 //***************************************************** 26 27 //狀態(tài)機的第一段采用同步時序描述狀態(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)機的第二段采用組合邏輯判斷狀態(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)機的第三段描述狀態(tài)輸出(這里采用時序電路輸出) 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)機代碼時首先要定義狀態(tài)變量(代碼中的參數(shù)S0~S6)與狀態(tài)寄存器(curr_st、next_st),如代碼中第10行至第21行所示;接下來使用三個always語句來實現(xiàn)三段狀態(tài)機,第一個always語句實現(xiàn)同步狀態(tài)跳轉(zhuǎn)(如代碼的第27至第33行所示),在復(fù)位的時候,當(dāng)前狀態(tài)處在S0狀態(tài),否則將下一個狀態(tài)賦值給當(dāng)前狀態(tài);第二個always采用組合邏輯判斷狀態(tài)轉(zhuǎn)移條件(如代碼的第35行至第47行代碼所示),這里每一個狀態(tài)只保持一個時鐘周期,也就是直接跳轉(zhuǎn)到下一個狀態(tài),在實際應(yīng)用中,一般根據(jù)輸入的條件來判斷是否跳轉(zhuǎn)到其它狀態(tài)或者停留在當(dāng)前轉(zhuǎn)態(tài),最后在case語句后面增加一個default語句,來防止?fàn)顟B(tài)機處在異常的狀態(tài);第三個always輸出分頻后的時鐘(如代碼的第49至第59行代碼所示),狀態(tài)機的第三段可以使用組合邏輯電路輸出,也可以使用時序邏輯電路輸出,一般推薦使用時序電路輸出,因為狀態(tài)機的設(shè)計和其它設(shè)計一樣,最好使用同步時序方式設(shè)計,以提高設(shè)計的穩(wěn)定性,消除毛刺。
    從代碼中可以看出,輸出的分頻時鐘clk_divide_7只與當(dāng)前狀態(tài)(curr_st)有關(guān),而與輸入狀態(tài)無關(guān),所以屬于摩爾型狀態(tài)機。狀態(tài)機的第一段對應(yīng)摩爾狀態(tài)機模型的狀態(tài)寄存器,用來記憶狀態(tài)機當(dāng)前所處的狀態(tài);狀態(tài)機的第二段對應(yīng)摩爾狀態(tài)機模型產(chǎn)生下一狀態(tài)的組合邏輯F;狀態(tài)機的第三段對應(yīng)摩爾狀態(tài)機產(chǎn)生輸出的組合邏輯G,因為采用時序電路輸出有很大的優(yōu)勢,所以這里第三段狀態(tài)機是由時序電路輸出的。
    狀態(tài)機采用時序邏輯輸出的狀態(tài)機模型如下圖所示:

    圖 7.4.6.4 狀態(tài)機時序電路輸出模型
    采用這種描述方法雖然代碼結(jié)構(gòu)復(fù)雜了一些,但是這樣做的好處是可以有效地濾去組合邏輯輸出的毛刺,同時也可以更好的進行時序計算與約束,另外對于總線形式的輸出信號來說,容易使總線數(shù)據(jù)對齊,減小總線數(shù)據(jù)間的偏移,從而降低接收端數(shù)據(jù)采樣出錯的頻率。
    1.4.7 模塊化設(shè)計
    模塊化設(shè)計是FPGA設(shè)計中一個很重要的技巧,它能夠使一個大型設(shè)計的分工協(xié)作、仿真測試更加容易,代碼維護或升級更加便利,當(dāng)更改某個子模塊時,不會影響其它模塊的實現(xiàn)結(jié)果。進行模塊化、標(biāo)準(zhǔn)化設(shè)計的最終目的就是提高設(shè)計的通用性,減少不同項目中同一功能設(shè)計和驗證引入的工作量。劃分模塊的基本原則是子模塊功能相對獨立、模塊內(nèi)部聯(lián)系盡量緊密、模塊間的連接盡量簡單。
    在進行模塊化設(shè)計中,對于復(fù)雜的數(shù)字系統(tǒng),我們一般采用自頂向下的設(shè)計方式。可以把系統(tǒng)劃分成幾個功能模塊,每個功能模塊再劃分成下一層的子模塊;每個模塊的設(shè)計對應(yīng)一個module,一個module設(shè)計成一個Verilog程序文件。因此,對一個系統(tǒng)的頂層模塊,我們采用結(jié)構(gòu)化的設(shè)計,即頂層模塊分別調(diào)用了各個功能模塊。
    下圖是模塊化設(shè)計的功能框圖,一般整個設(shè)計的頂層模塊只做例化(調(diào)用其它模塊),不做邏輯。頂層下面會有模塊A、模塊B、模塊C等,模塊A/B/C又可以分多個子模塊實現(xiàn)。

    圖 7.4.7.1 模塊化設(shè)計框圖
    在這里我們補充一個概念,就是Verilog語法中的模塊例化。FPGA邏輯設(shè)計中通常是一個大的模塊中包含了一個或多個功能子模塊,Verilog通過模塊調(diào)用或稱為模塊實例化的方式來實現(xiàn)這些子模塊與高層模塊的連接,有利于簡化每一個模塊的代碼,易于維護和修改。
    下面以一個實例(靜態(tài)數(shù)碼管顯示實驗)來說明模塊和模塊之間的例化方法。
    在靜態(tài)數(shù)碼管顯示實驗中,我們根據(jù)功能將FPGA頂層例化了以下兩個模塊:計時模塊(time_count)和數(shù)碼管靜態(tài)顯示模塊(seg_led_static),如下圖所示:

    圖 7.4.7.2 靜態(tài)數(shù)碼管顯示模塊框圖
    計時模塊部分代碼如下所示:

    1 module time_count( 2 input clk , // 時鐘信號 3 input rst_n , // 復(fù)位信號 4 5 output reg flag // 一個時鐘周期的脈沖信號 6 ); 7 8 //parameter define 9 parameter MAX_NUM = 25000_000; // 計數(shù)器最大計數(shù)值 …… 34 endmodule

    數(shù)碼管靜態(tài)顯示模塊部分代碼如下所示:

    1 module seg_led_static ( 2 input clk , // 時鐘信號 3 input rst_n , // 復(fù)位信號(低有效) 4 5 input add_flag, // 數(shù)碼管變化的通知信號 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)時鐘 3 input sys_rst_n, // 系統(tǒng)復(fù)位信號(低有效) 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ù)碼管變化的時間間隔0.5s 12 13 //wire define 14 wire add_flag; // 數(shù)碼管變化的通知信號 15 16 //***************************************************** 17 //** main code 18 //***************************************************** 19 20 //例化計時模塊 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ù)定義的代碼。這是因為頂層模塊對子模塊做例化時,只需要知道子模塊的端口信號名,而不用關(guān)心子模塊內(nèi)部具體是如何實現(xiàn)的。如果子模塊內(nèi)部使用parameter定義了一些參數(shù),Verilog也支持對參數(shù)的例化(也叫參數(shù)的傳遞),即頂層模塊可以通過例化參數(shù)來修改子模塊內(nèi)定義的參數(shù)。
    我們先來看一下頂層模塊是如何例化子模塊的,例化方法如下圖所示:

    圖 7.4.7.3 模塊的例化
    上圖右側(cè)是例化的數(shù)碼管靜態(tài)顯示模塊,子模塊名是指被例化模塊的模塊名,而例化模塊名相當(dāng)于標(biāo)識,當(dāng)例化多個相同模塊時,可以通過例化名來識別哪一個例化,我們一般命名為“u_”+“子模塊名”。信號列表中“.”之后的信號是數(shù)碼管靜態(tài)顯示模塊定義的端口信號,括號內(nèi)的信號則是頂層模塊聲明的信號,這樣就將頂層模塊的信號與子模塊的信號一一對應(yīng)起來,同時需要注意信號的位寬要保持一致。
    接下來再來介紹一下參數(shù)的例化,參數(shù)的例化是在模塊例化的基礎(chǔ)上,增加了對參數(shù)的信號定義,如下圖所示:

    圖 7.4.7.4 模塊參數(shù)的例化
    在對參數(shù)進行例化時,在模塊名的后面加上“#”,表示后面跟著的是參數(shù)列表。計時模塊定義的MAX_NUM和頂層模塊的TIME_SHOW都是等于25000_000,當(dāng)在頂層模塊定義TIME_SHOW=12500_000時,那么子模塊的MAX_NUM的值實際上是也等于12500_000。當(dāng)然即使子模塊包含參數(shù),在做模塊的例化時也可以不添加對參數(shù)的例化,這樣的話,子模塊的參數(shù)值等于該模塊內(nèi)部實際定義的值。
    值得一提的是,Verilog語法中的localparam代表的意思同樣是參數(shù)定義,用法和parameter基本一致,區(qū)別在于parameter定義的參數(shù)可以做例化,而localparam定義的參數(shù)是指本地參數(shù),上層模塊不可以對localparam定義的參數(shù)做例化。
    1.5 Verilog編程規(guī)范
    本節(jié)主要給大家介紹下編程規(guī)范,良好的編程規(guī)范是一個FPGA工程師必備的素質(zhì)。
    1.5.1 編程規(guī)范重要性
    當(dāng)前數(shù)字電路設(shè)計越來越復(fù)雜,一個項目需要的人越來越多,當(dāng)幾十號設(shè)計同事完成同一個項目時候,大家需要互相檢視對方代碼,如果沒有一個統(tǒng)一的編程規(guī)范,那么是不可想象的。大家的風(fēng)格都不一樣,如果不統(tǒng)一的話,后續(xù)維護、重用等會有很大的困難,即使是自己寫的代碼,幾個月后再看也會變的很陌生,也會看不懂(您可能不相信,不過筆者和同事交流發(fā)現(xiàn)大家都是這樣的,時間長不看就忘記了),所以編程規(guī)范的重要性顯而易見。
    另外養(yǎng)成良好的編程規(guī)范,對于個人的工作習(xí)慣、思路等都有非常大的好處。可以讓新人盡快融入項目中,讓大家更容易看懂您的代碼。
    1.5.2 工程組織形式
    工程的組織形式一般包括如下幾個部分,分別是doc、par、rtl和sim四個部分。
    XX工程名
    |–doc
    |–par
    |–rtl
    |–sim
    doc:一般存放工程相關(guān)的文檔,包括該項目用到的datasheet(數(shù)據(jù)手冊)、設(shè)計方案等。不過為了便于大家查看,我們開發(fā)板文檔是統(tǒng)一匯總存放在資料盤下的;
    par:主要存放工程文件和使用到的一些IP文件;
    rtl:主要存放工程的rtl代碼,這是工程的核心,文件名與module名稱應(yīng)當(dāng)一致,建議按照模塊的層次分開存放;
    sim:主要存放工程的仿真代碼,復(fù)雜的工程里面,仿真也是不可或缺的部分,可以極大減少調(diào)試的工作量。
    1.5.3 文件頭聲明
    每一個Verilog文件的開頭,都必須有一段聲明的文字。包括文件的版權(quán),作者,創(chuàng)建日期以及內(nèi)容介紹等,如下表所示。
    //Copyright ?***********************************//
    //原子哥在線教學(xué)平臺:www.yuanzige.com
    //技術(shù)支持:www.openedv.com
    //淘寶店鋪:http://openedv.taobao.com
    //關(guān)注微信公眾平臺微信號:“正點原子”,免費獲取ZYNQ & FPGA & STM32 & LINUX資料。
    //版權(quán)所有,盜版必究。
    //Copyright? 正點原子 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: 正點原子
    // Created date: 2019/4/14 10:55:56
    // Version: V1.0
    // Descriptions: The original version
    //
    //----------------------------------------------------------------------------------------
    //
    ************************************************//
    我們建議一個.V只包括一個module,這樣模塊會比較清晰易懂。
    1.5.4 輸入輸出定義
    端口的輸入輸出有Verilog 95和2001兩種格式,推薦大家采用Verilog 2001語法格式。下面是Verilog 2001語法的一個例子,包括module名字、輸入輸出、信號名字、輸出類型、注釋。
    1 module led(
    2 input sys_clk , //系統(tǒng)時鐘
    3 input sys_rst_n, //系統(tǒng)復(fù)位,低電平有效
    4 output reg [3:0] led //4位LED燈
    5 );
    我們建議如下幾點:
    ① 一行只定義一個信號;
    ② 信號全部對齊;
    ③ 同一組的信號放在一起。
    1.5.5 parameter定義
    我們建議如下幾點:
    ① module中的parameter聲明,不建議隨處亂放;
    ② 將parameter定義放在緊跟著module的輸入輸出定義之后;
    ③ parameter等常量命名全部使用大寫。
    7 //parameter define
    8 parameter WIDTH = 25 ;
    9 parameter COUNT_MAX = 25_000_000; //板載50M時鐘=20ns,0.5s/20ns=25000000,需要25bit
    10 //位寬
    1.5.6 wire/reg定義
    一個module中的wire/reg變量聲明需要集中放在一起,不建議隨處亂放。
    因此,我們建議如下:
    ① 將reg與wire的定義放在緊跟著parameter之后;
    ② 建議具有相同功能的信號集中放在一起;
    ③ 信號需要對齊,reg和位寬需要空2格,位寬和信號名字至少空四格;
    ④ 位寬使用降序描述,[6:0];
    ⑤ 時鐘使用前綴clk,復(fù)位使用后綴rst;
    ⑥ 不能使用Verilog關(guān)鍵字作為信號名字;
    ⑦ 一行只定義一個信號。
    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 信號命名
    大家對信號命名可能都有不同的喜好,我們建議如下:
    ① 信號命名需要體現(xiàn)其意義,比如fifo_wr代表FIFO讀寫使能;
    ② 可以使用“_”隔開信號,比如sys_clk;
    ③ 內(nèi)部信號不要使用大寫,也不要使用大小寫混合,建議全部使用小寫;
    ④ 模塊名字使用小寫;
    ⑤ 低電平有效的信號,使用_n作為信號后綴;
    ⑥ 異步信號,使用_a作為信號后綴;
    ⑦ 純延遲打拍信號使用_dly作為后綴。
    1.5.8 always塊描述方式
    always塊的編程規(guī)范,我們建議如下:
    ① if需要空四格;
    ② 一個always需要配一個begin和end;
    ③ always前面需要有注釋;
    ④ beign建議和always放在同一行;
    ⑤ 一個always和下一個always空一行即可,不要空多行;
    ⑥ 時鐘復(fù)位觸發(fā)描述使用posedge sys_clk和negedge sys_rst_n
    ⑦ 一個always塊只包含一個時鐘和復(fù)位;
    ⑧ 時序邏輯使用非阻塞賦值。
    26 //用于產(chǎn)生0.5秒使能信號的計數(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 //計數(shù)到最大值時產(chǎn)生高電平使能信號
    24 assign counter_en = (counter == (COUNT_MAX - 1’b1)) ? 1’b1 : 1’b0;
    1.5.10 空格和TAB
    由于不同的解釋器對于TAB翻譯不一致,所以建議不使用TAB,全部使用空格。
    1.5.11 注釋
    添加注釋可以增加代碼的可讀性,易于維護。我們建議規(guī)范如下:
    ① 注釋描述需要清晰、簡潔;
    ② 注釋描述不要廢話,冗余;
    ③ 注釋描述需要使用“//”;
    ④ 注釋描述需要對齊;
    ⑤ 核心代碼和信號定義之間需要增加注釋。
    26 //用于產(chǎn)生0.5秒使能信號的計數(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時,counter清0
    31 counter <= 1’b0;
    32 else
    33 counter <= counter + 1’b1;
    34 end
    1.5.12 模塊例化
    模塊例化我們建議規(guī)范如下:
    ① moudle模塊例化使用u_xx表示。
    20 //例化計時模塊
    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 其他注意事項
    其他注意事項如下:
    ① 代碼寫的越簡單越好,方便他人閱讀和理解;
    ② 不使用repeat等循環(huán)語句;
    ③ RTL級別代碼里面不使用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)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。