天天看點

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

本文主要參考了锆石FPGA文字教程《硬體文法篇》和夏宇聞《Verilog數字系統設計教程》(第三版)

目錄

一、Verilog的基礎知識

1.Verilog的四值邏輯系統

2.Verilog的資料類型

2.1寄存器資料類型

2.2線網資料類型

2.3參數資料類型 

3.Verilog的基本運算符

3.1算數運算符

3.2關系運算符

3.3邏輯運算符

3.4條件運算符

3.5位運算符 

3.6移位運算符

3.7拼接運算符

3.8運算符的優先級别

二、Verilog的基礎文法 

1.Verilog的關鍵字

2.Verilog的基本程式架構

三、Verilog中的關鍵問題

1.Verilog的抽象級别

1.1結構化描述方式

1.2資料流描述方式

1.3行為級描述方式

2.Verilog的子產品化設計

3.給端口選擇正确的資料類型

4.Latch的産生

5.組合邏輯回報環

6.阻塞指派和非阻塞指派

7.狀态機

7.1狀态機的設計步驟

7.2狀态機的狀态編碼

7.3狀态機的描述方法

一、Verilog的基礎知識

1.Verilog的四值邏輯系統

Verilog的四值邏輯系統如下圖所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 在Verilog的邏輯系統中有四種值,對應着四種狀态:邏輯0:表示低電平,也就對應電路中的GND;邏輯1:表示高電平,也就是對應電路中的VCC;邏輯X:表示未知,有可能是高電平,也有可能是低電平;邏輯Z:表示高阻态,外部沒有激勵信号是一個懸空狀态。

2.Verilog的資料類型

Verilog語言中,主要有三大資料類型,即寄存器資料類型、線網資料類型(實體連線)和參數資料類型(常量)。是以,真正在數字電路中起作用的資料類型是寄存器資料類型和線網資料類型,它們共同遵守Verilog的四值邏輯系統。

2.1寄存器資料類型

寄存器資料類型就是表示一個抽象的資料存儲單元,它隻能在always語句和initial語句等過程語句中被指派,它的預設值為X。在實際的數字電路中,如果該過程語句描述的是時序邏輯,則該寄存器變量對應為寄存器;如果該過程語句描述的是組合邏輯;則該寄存器變量對應為硬體連線;如果該過程語句描述的是不完全組合邏輯,那麼該寄存器變量也可以對應為鎖存器。由此可見,寄存器類型的變量不一定會綜合為寄存器。寄存器資料類型有很多種,如reg、integer、real等,其中最常用的就是reg類型(預設1bit),reg類型的使用方法如下:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

2.2線網資料類型

線網資料類型就是表示Verilog結構化元件間的實體連線。它的值由驅動元件的值決定,例如連續指派與門的輸出。如果沒有驅動原件連接配接到線網,線網的預設值為Z。線網資料類型同寄存器資料類型一樣也是有很多種,如tri、wand 和wire等,其中最常用的就是wire類型(預設1bit),wire類型的使用方法如下:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

2.3參數資料類型 

參數類型其實就是一個常量,通常出現在module 内部,常被用于定義狀态機的狀态、資料位寬和延遲大小等,由于它可以在編譯時修改參數的值,是以它又常被用于一些參數可調的子產品中,使使用者在執行個體化子產品時,可以根據需要配置參數。在定義

參數時,我們可以一次定義多個參數,參數與參數之間需要用逗号隔開。這裡我們需要注意的是參數的定義是局部的,隻在目前子產品中有效,參數類型的使用方法如下:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

3.Verilog的基本運算符

 Verilog 硬體描述語言的運算符範圍很廣,其運算符按其功能可分為以下八類:1、算術運算符、2、關系運算符、3、邏輯運算符、4、條件運算符、5、位運算符、6、移位運算符、7、拼接運算符。

3.1算數運算符

所謂算術邏輯運算符就是常說的加、減、乘、除等,這類運算符的抽象層級較高,從數字邏輯電路實作上來看,它們都是基于與、或、非等基礎門邏輯組合實作的,如下表所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

3.2關系運算符

關系運算符主要是用來做一些條件判斷用的,在進行關系運算符時,如果聲明的關系是假的,則傳回值是0,如果聲明的關系是真的,則傳回值是1;所有的關系運算符有着相同的優先級别,關系運算符的優先級别低于算術運算符的優先級别。關系運算符如下表所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

3.3邏輯運算符

邏輯運算符包括與、或和非,是連接配接多個關系表達式用的,可實作更加複雜的判斷,一般不單獨使用,都需要配合具體語句來實作完整的意思,邏輯運算符如下表所示: 

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

3.4條件運算符

 Verilog語言為了讓連續指派的功能更加完善,于是又從C語言中引入了條件操作符(是一個三目運算符)來建構從兩個輸入中選擇一個作為輸出的條件選擇結構,功能等同于always 中的if-else 語句,條件運算符如下表所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

3.5位運算符 

位運算符是一類最基本的運算符,可以認為它們直接對應數字邏輯中的與、或、非門等邏輯門。位運算符的與、或、非與邏輯運算符邏輯與、邏輯或、邏輯非,雖然它們處理的資料類型不一樣,但是從硬體實作角度上來說,它們沒有差別的,位運算符如下表所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

3.6移位運算符

在Verilog 中有兩種移位運算符:左移位運算符和右移位運算符,這兩種移位運算符都用0來填補移出的空位(相當于移動二進制中的“1”),移位運算符如下表所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

3.7拼接運算符

 在Verilog中有一個特殊的運算符,就是位拼接運算符。用這個運算符可以把兩個或多個信号的某些位拼接起來進行運算操作,拼接運算符如下表所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

3.8運算符的優先級别

 運算符一多,必然涉及到優先級的問題,運算符的優先級别制成表如下表所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

二、Verilog的基礎文法 

雖然Verilog 硬體描述語言有很完整的文法結構和系統,這些文法結構的應用給設計描述帶來很多友善。但是Verilog是描述硬體電路的,它是建立在硬體電路的基礎上的。有些文法結構是不能與實際硬體電路對應起來的,比如for 循環,它是不能映射成實際的硬體電路的,是以,Verilog 硬體描述語言分為可綜合和不可綜合語言。

下面簡單的介紹一下可綜合與不可綜合。

(1)可綜合,就是說編寫的Verilog代碼能夠被綜合器轉化為相應的電路結構。是以,常用可綜合語句來描述數字硬體電路。

(2)不可綜合,就是說編寫的Verilog 代碼無法綜合生成實際的電路。是以,不可綜合語句一般在描述數字硬體電路中是用不到的,不過,可以用它來仿真、驗證所描述的數字硬體電路。

1.Verilog的關鍵字

Verilog是由C語言衍生而來,但是卻有着比C語言更多的關鍵字,但是其中絕大部分是很少用到的,是以隻要熟練掌握其中的可綜合的關鍵字就足夠了,常用的可綜合關鍵字如下表所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 module和endmodule,它們是成對使用的,子產品是Verilog 設計中基本功能塊,一個最簡單的子產品是由子產品命名、端口清單兩個部分組成。整個子產品是由module開頭,endmodule結尾,module後面緊跟着的是子產品名,每個子產品都有它自己的名字。這些關鍵詞的基本使用方法如下所示(關鍵詞的學習不是孤立的,需要結合執行個體進行學習和熟悉):

input、output和inout 用于端口定義。

wire 和reg是用來聲明資料類型,parameter是用來聲明參數類型。

always 是過程指派語句,assign是連續指派語句。

if 和else成對使用,是條件判斷語句,和C語言中的if和else是一樣的功能。

begin和end也是成對使用,相當于C 語言中的大括号。

case、endcase 和default 成對使用,是一個多分支條件語句,和C 語言中的switch 一樣的功

能。

posedge、negedge 和or 這三個關鍵字是和always 關鍵字聯合使用的,posedge 是上升沿觸發,negedge 是下降沿觸發,posedge or negedge是既有上升沿又有下降沿。

2.Verilog的基本程式架構

以最簡單的門電路為例簡介Verilog的基本程式架構,門電路的Verilog代碼如下所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 上述代碼中,a和b是與門的輸入,c是與門的輸出,也就是說,上述代碼實作的是一個兩輸入的與門的邏輯功能。

  • Verilog HDL程式是由子產品構成的,每個子產品的内容都是嵌在module和endmodule兩個語句之間
  • 每個子產品要進行端口定義,并說明輸入輸出口,然後對子產品的功能進行行為邏輯描述
  • Verilog HDL程式的書寫格式自由,一行可以寫幾個語句,一個語句也可以分寫多行
  • 除了endmodule 語句外,每個語句和資料定義的最後必須有分号

三、Verilog中的關鍵問題

1.Verilog的抽象級别

所謂抽象級别,實際上是指同一個實體電路,可以在不同的層次上用Verilog語言來描述它。Verilog硬體描述語言支援以下五種級别(抽象級别由高到低):(1)系統級;(2)算法級;(3)RTL級;(4)門級;(5)開關級。

其中,系統級和算法級屬于行為級描述;RTL級又稱為資料流描述方式;門級和開關級屬于結構化描述方式。

1.1結構化描述方式

結構化描述方式是最原始的描述方式,也是抽象級别最低的描述方式,不過,它卻是最接近于實際的硬體結構的描述方式。因為采用結構化的描述方式來編寫Verilog 代碼,其思路就跟在面包闆上搭建數字電路時一樣的,唯一的不同點就是通過Verilog 的形式來描述數字電路都需要哪些元器件以及它們之間的連接配接關系是怎麼樣的(實際上就相當于直接在描述邏輯電路)。

以三人表決器的結構化描述方式為例:

1  module Example_Structure //Example_Structure,即子產品的開始,
2  (
3      //輸入端口
4      A,B,C,
5      //輸出端口
6      L
7  );
8
9  input A; //子產品的輸入端口A
10 input B; //子產品的輸入端口B
11 input C; //子產品的輸入端口C
12 output L; //子產品的輸出端口L
13
14 wire AB,BC,AC; //内部信号聲明AB,BC,AC
15
16 and U1(AB,A,B); //與門(A,B 信号進入)(A 與B 信号即AB 輸出)
17 and U2(BC,B,C); //與門 同上
18 and U3(AC,A,C); //與門 同上
19
20 or U4(L,AB,BC,AC); //或門 同上
21
22 endmodule //子產品的結束
           

1.2資料流描述方式

資料流描述方式要比結構化描述方式的抽象級别高一些,因為它不再需要清晰地刻畫出具體的數字電路架構,而是可以比較直覺地表達底層邏輯的行為。基于資料流的描述方式,形象點來說,每個子產品就好比一個容器,大量外部資訊從子產品的輸入端口流入,相應的,大量的處理後資訊也會從子產品的輸出端口流出(實際上就相當于直接在描述邏輯表達式),是以,基于這種思路編寫的Verilog代碼被稱為資料流描述方式。資料流描述方式又可稱為RTL級描述方式,即寄存器傳輸級描述。

三人表決器的資料流描述方式的Verilog代碼如下:

1  module Example_Dataflow //Example_Dataflow,即子產品的開始
2  (
3      //輸出端口
4      A,B,C,
5      //輸入端口
6      L
7  );
8
9  input A; //子產品的輸入端口A
10 input B; //子產品的輸入端口B
11 input C; //子產品的輸入端口C
12 output L; //子產品的輸出端口L
13
14 assign L = ((!A) & B & C) | (A & (!B) & C) | (A & B & (!C)) | (A & B & C);
15
16 endmodule //子產品的結束
           

1.3行為級描述方式

和前面兩種描述方式比起來,行為級描述方式的抽象級别最高,概括力也最強,是以規模稍大些的設計,往往都是以行為級描述方式為主。進階語言的執行思路都是串行的,例如C語言。順序執行的語句更容易幫助我們來表達我們的設計思想,尤其是使描述時序邏輯變得容易。雖然FPGA的設計思路都是并行的,但是Verilog中還是支援大量的串行語句元素。由此可見,行為級描述方式的主要載體就是串行語句,同時輔以并行語句用于描述各個算法之間的連接配接關系。

三人表決器的行為級描述方式的Verilog代碼如下:

1  module Example_Behavior //Example_Behavior,即子產品的開始
2  (
3      //輸入端口
4      A,B,C,
5      //輸出端口
6      L
7  );
8
9  input A; //子產品的輸入端口A
10 input B; //子產品的輸入端口B
11 input C; //子產品的輸入端口C
12 output reg L; //子產品的輸出端口L
13
14 always @ (A,C,B) //always 在組合邏輯中的用法
15 begin //always @ (A,B,C)解析:隻要A,B,C
16     case({A,B,C}) //其中有一個信号有變化便會執行begin 中的case 語句
17         3'b000: L = 1'b0; //也可以寫成always @ (*),與always @ (A,B,C)功能相同
18         3'b001: L = 1'b0; //{A,B,C}解析:把A,B,C 三條線合成一條總線
19         3'b010: L = 1'b0; //舉例說明:{1'b1,1'b0}=2'b10
20         3'b011: L = 1'b1; //
21         3'b100: L = 1'b0;
22         3'b101: L = 1'b1;
23         3'b110: L = 1'b1;
24         3'b111: L = 1'b1;
25         default:L = 1'bx; //不要省略
26     endcase //case 語句的結束
27 end //begin 語句的結束
28
29 endmodule //module 語句的結束
           

2.Verilog的子產品化設計

子產品化設計是FPGA設計中一個很重要的技巧(一般整個設計的頂層隻做例化,不做邏輯),它能夠使一個大型設計的分工協作和仿真測試更加容易,使代碼維護和更新更加便利。所謂子產品化設計,就是将一個比較複雜的系統按照一定的規則劃分為多個小子產品,然後再分别對每個小子產品進行設計,當這些小子產品全都完成以後,再将這些小子產品有機的組合起來,最終就能夠完成整個複雜系統的設計。

以半加器為例,采用子產品化設計的半加器的結構圖如下所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 從上圖中可以看出,采用子產品化設計的方法将半加器分成了與門子產品和異或子產品,接下來隻需要完成實作與門子產品和異或子產品,我們再将實作的與門子產品和異或子產品相結合,最終就能實作半加器。首先給出半加器頂層子產品代碼,如下所示:

1  module Example_Module
2  (
3      input a,
4      input b,
5      output s,
6      output c
7  );
8
9  Yumen_Module Yumen_Init
10 (
11     .yumen_a(a),
12     .yumen_b(b),
13     .yumen_c(c)
14 );
15
16 Yihuo_Module Yihuo_Init
17 (
18     .yihuo_a(a),
19     .yihuo_b(b),
20     .yihuo_s(s)
21 );
22
23 endmodule
           

從上述代碼中可以看出, 第1行是子產品的開始,子產品名為Example_Module。第2至7行是端口聲明,一共定義了2個輸入端口a和b,2個輸出端口s和c。其中a和b代表兩個加數,s代表兩個加數的和,c代表進位。第9行是例化與門子產品,其Yumen_Module 是Yumen_Module.v子產品裡相對應的子產品名,Yumen_Init可以任意命名,它主要是用來區分例化多個相同的子產品。第10至14行是信号的例化,其中.yumen_a是與門子產品中的信号,它必須和與門子產品中的信号名一緻才行,(a) 是頂層子產品中的信号,它必須和頂層子產品中的信号名一緻才行。第16至21行是例化異或子產品,與例化與門同理。看完了頂層子產品,下面再來看下與門子產品的代碼,如下所示:

1  module Yumen_Module
2  (
3      input yumen_a,
4      input yumen_b,
5      output yumen_c
6  );
7
8  assign yumen_c = yumen_a && yumen_b;
9
10 endmodule
           

下面是異或子產品的代碼:

1  module Yihuo_Module
2  (
3      input yihuo_a,
4      input yihuo_b,
5      output yihuo_s
6  );
7
8  assign yihuo_s = yihuo_a ^ yihuo_b;
9
10 endmodule
           

 通過子產品化設計的方法不僅層次清晰,而且思路明确。從半加器的RTL視圖更能深刻感受到子產品化設計帶來的層次感,半加器的RTL視圖如下圖所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

3.給端口選擇正确的資料類型

Verilog中關于reg和wire的資料類型的使用常常讓人對此感到疑惑,以下是一個綜合出的實際電路子產品。

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

從上圖中可以看出, 對于輸入A和輸入B這兩個端口來說,隻能使用線網類型,但是,輸入A和輸入B這兩個端口可以由寄存器和線網所驅動,也就是說,寄存器和線網可以連接配接到這兩個輸入端口上作為輸入源。對于輸出端口Y 來說,它可以是線網,也可以是寄存器類型,但是,對于輸出端口Y,它隻能驅動線網類型。

以下述代碼為例:

1  module Example_Datatype
2  (
3      a,b,c,d,o1,o2
4  );
5
6  input a,b,c,d;
7  output o1,o2;
8
9  reg c,d;
10 reg o1;
11 reg o2;
12
13 assign o2 = c && d;
14
15 always @ (a or b)
16 begin
17     if(a)
18         o1 = b;
19     else
20         o1 = 1'b0;
21 end
22
23 endmodule
           

在編譯的過程中出現如下錯誤:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 根據上圖可以看出,c不能被申明為reg類型(2001Verilog标準中,已經将register更改為variable變量類型),原因是,輸入端口不能被定義為reg(寄存器類型),隻能被定義為wire型。對代碼進行修改,将對c和d的reg聲明注釋掉(預設就是wire型),修改完成後,仍出現如下報錯:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 從上圖可以看出,o2必須為線網類型,但是前面所述的是對于輸出端口,它既可以是wire(線網類型),也可以是reg(寄存器類型),這裡出所的原因是o2是由關鍵字assign指定的。對于assign這種連續指派語句,必須是wire型。而對于o1,由于o1是在always中指派的,是以必須為reg型。是以,将o2聲明為reg型注釋掉,再次編譯,通過。

結論為:assign連續指派語句隻能聲明為wire;always中隻能聲明為reg。

以下是上述代碼的RTL視圖:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

從上圖中可以看出, 盡管将o1聲明為了reg類信号,但是它并沒有綜合成觸發器。這是因為,對于寄存器reg類型,在always等過程塊中被指派的信号,如果該always子產品描述的是時序邏輯電路,那麼該信号常常被綜合為D觸發器,如果該always子產品描述的是組合邏輯電路,那麼該信号會被綜合成連線(詳見2.1)。

4.Latch的産生

當在always過程塊中描述組合邏輯電路時,如果條件語句中沒有說明全部的條件,那麼就會有可能産生鎖存器,也就是所謂的latch(隻有在always中描述組合邏輯電路才可能産生latch)。

以下述産生latch的電路子產品的代碼為例:

1  module Example_Latch
2  (
3      clk,q,data,enable
4  );
5
6  input clk;
7  input data;
8  input enable;
9  output q;
10
11 reg q;
12
13 //always @ (posedge clk)
14 //begin
15 // if(enable)
16 // q <= data;
17 // else
18 // q <= q;
19 //end
20
21 always @ (*)
22 begin
23     if(enable)
24         q = data;
25 //  else
26 //      q = 1'b0;
27 end
28
29 endmodule
           

 上述代碼的功能是用always實作了一個組合電路,當enable為1時才會将data的值賦給q。由于q是在always中進行指派的,是以q為reg類型。生成的RTL視圖如下:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 從上圖可以看出,前述代碼最終産生了latch。原因在于,當enable為1時,它将data端輸入到q;但是當enable為0時,沒有執行任何程式,電路預設情況下也就保持原值不變,是以産生了鎖存器(else沒有發生的情況下,預設是保持原值的,但是由于組合邏輯電路沒有記憶功能,是以隻能生成鎖存器)。将if語句的條件補全,生成RTL視圖如下:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

從上圖可以看出,将條件語句補全以後,電路中不再生成latch,而是生成了一個二選一的資料選擇器。是以,在描述組合邏輯的時候一定要注意,對于if-else語句盡量把它的每種條件都考慮進去,要把條件都寫全,這樣就能避免latch的産生。

需要補充的一點是,當在always過程塊中描述時序邏輯電路時,即使條件語句沒有說明全部的條件,該電路也不會産生鎖存器。将上述代碼中的組合邏輯部分注釋掉(21-27行),将時序邏輯部分(13-19行)解注釋,生成的RTL視圖如下:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

從上圖可以看出, 電路沒有綜合成latch,而是綜合成了D觸發器。對于時序邏輯電路來說,else有和沒有是完全一樣的,因為對于D觸發器來說,它預設情況下就是保持原值的,而enable這個端口相當于一個使能端口。q是保持原值,還是改變為新值,是由使能端口enable決定的。是以Verilog語言中latch的産生一般是組合電路中條件不完整造成的。

當遇到綜合所需case條件與現實條件不一緻,此時不能直接使用綜合器。例如,紅綠燈一共有三種狀态(紅、黃和綠),但是兩bit一共有四種情況,如果在always中隻寫三個條件的話,會産生latch。解決方法是使用綜合器指令full case。

以簡單的三人表決器為例,按鍵用來表示表決輸入,LED燈與按鍵對應(按下按鍵對應LED等點亮),數位管用來顯示表決通過的認數。

以下是case條件齊全,未産生latch的verilog代碼:

module Three_Vote
(
	KEY,LED,SEG_EN,SEG_DATA
);

input 	  [2:0] KEY;
output	  [2:0] LED;
output	  [5:0] SEG_EN;
output reg [7:0] SEG_DATA;

parameter	SEG_NUM0 = 8'hbf,   //數字0
				SEG_NUM1 = 8'h86,   //數字1
				SEG_NUM2 = 8'hdb,   //數字2
				SEG_NUM3 = 8'hcf,   //數字3
				SEG_NUM4 = 8'he6,   //數字4
				SEG_NUM5 = 8'hed,   //數字5
				SEG_NUM6 = 8'hfd,   //數字6
				SEG_NUM7 = 8'h87,   //數字7
				SEG_NUM8 = 8'hff,   //數字8
				SEG_NUM9 = 8'hef,   //數字9
				SEG_NUMA = 8'hf7,   //數字A
				SEG_NUMB = 8'hfc,   //數字B
				SEG_NUMC = 8'hb9,   //數字C
				SEG_NUMD = 8'hde,   //數字D
				SEG_NUME = 8'hf9,   //數字E
				SEG_NUMF = 8'hf1;   //數字F


always @ (*)
begin
	case(KEY)
		3'b000 : SEG_DATA = SEG_NUM0;
		3'b001 : SEG_DATA = SEG_NUM1;
		3'b011 : SEG_DATA = SEG_NUM2;
		3'b010 : SEG_DATA = SEG_NUM1;
		3'b110 : SEG_DATA = SEG_NUM2;
		3'b111 : SEG_DATA = SEG_NUM3;
		3'b101 : SEG_DATA = SEG_NUM2;
		3'b100 : SEG_DATA = SEG_NUM1;
	endcase
end

assign LED = ~ KEY;
assign SEG_EN = 6'b00_0000;

endmodule
           

其對應的RTL視圖如下:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

現在将case的條件任一注釋(這裡選擇注釋最後一個:3'b100) ,生成的RTL視圖如下:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

顯然,用always塊描述時序邏輯電路條件不完全導緻了latch的産生。下面,在代碼中case處添加“//synopsys full_case”(這裡的引号僅作示意),變動後的case語句代碼如下(其餘部分不變):

case(KEY)//synopsys full_case
	3'b000 : SEG_DATA = SEG_NUM0;
	3'b001 : SEG_DATA = SEG_NUM1;
	3'b011 : SEG_DATA = SEG_NUM2;
	3'b010 : SEG_DATA = SEG_NUM1;
	3'b110 : SEG_DATA = SEG_NUM2;
	3'b111 : SEG_DATA = SEG_NUM3;
	3'b101 : SEG_DATA = SEG_NUM2;
	//3'b100 : SEG_DATA = SEG_NUM1;
endcase
           

 生成的RTL視圖如下:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

顯然,在告知綜合器case語句已經完全的情況下,電路中不會再産生latch。 

注:常用的綜合器指令還有parallel case,用于條件分支不互斥的情況(正常的條件分支都是互斥的),如果case語句的條件不互斥,則會存在優先級,就可以使用parallel case的原語告知DC(Design Compiler)所有條件互斥,且并行,無優先級。指令為:“//synopsys parallel_case”如“//synopsys full_case“一樣添加在case語句旁。

5.組合邏輯回報環

組合邏輯回報環路是數字同步邏輯設計的大忌,它最容易因振蕩、毛刺、時序違規等問題引起整個系統的不穩定和不可靠。

示例如下:

1  module Example_Feedback
2  (
3      data_in1,data_in2,data_out
4  );
5
6  input data_in1;
7  input data_in2;
8  output data_out;
9
10 assign data_out = (data_in2) ? data_in1 : (~data_out | data_in1);
11
12 endmodule
           

 可以看到,這就是一個典型的組合邏輯回報環電路。簡單的介紹一下該代碼,第1至8行是用來端口聲明的,第10行是一個assign連續指派語句,當data_in2為1 時,就将data_in1指派給data_out,當data_in2為0時,便将(~data_out | data_in1)指派給

data_out。由于data_out 是由assign關鍵字指定的,是以data_out為wire 類型。介紹完了代碼,接下來看下它的RTL視圖。

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 上述電路的輸出端口data_out直接通過組合邏輯回報到與門的輸入端口上,此時,假設data_in1和data_in2均都為1,那麼毋容置疑,電路會輸出1;而如果data_in1和data_in2都為0,這時候,就需要判斷data_out這個回報信号,如果此時data_out為1,輸出就為0,但是如果此時data_out為0,那麼輸出就為1,是以可以看出,它沒有一個穩定的狀态,電路存在不确定性。

将上述代碼在Quartus II中編譯,出現如下警告:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 從上圖可以看到,Quartus II 軟體提示說Found combinational loop of 2 nodes。當然這種警告不是緻命錯誤,它不影響編譯。通過組合邏輯電路回報環來實作這種功能的話是不允許這樣做的,如果确實需要這樣做的話,那麼可以通過時序電路同步回報來實作。下面将上述的組合邏輯電路改為時序邏輯電路,代碼如下所示:

1  module Example_Feedback
2  (
3      data_in1,data_in2,data_out,clk,rst_n
4  );
5
6  input clk;
7  input rst_n;
8  input data_in1;
9  input data_in2;
10 output data_out;
11
12 reg data_out_r;
13
14 always @ (posedge clk or negedge rst_n)
15 begin
16     if(!rst_n)
17         data_out_r <= 1'b0;
18     else
19         data_out_r <= (data_in2) ? (data_in1) : (~data_out_r | data_in1);
20 end
21
22 assign data_out = data_out_r;
23
24 endmodule
           

 由于該代碼與前面的代碼功能是一樣的,是以這裡就不再進行介紹了,需要說明的是,由于data_out_r是在always子產品中指派的,是以需要将data_out_r定義成reg類型,由于data_out是由assign指定的,是以需要将data_out定義成wire類型。如果将該代碼放到Quartus II軟體中進行編譯,那麼在警告視窗中是看不到combinational loop警告資訊的。邏輯時序電路生成的RTL視圖如下所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

是以,如果需要将輸出信号回報到輸入端,必須通過時序電路實作。

6.阻塞指派和非阻塞指派

阻塞指派使用等号(=)來表示,非阻塞指派使用的是小于等于号(<=)來表示。

結合執行個體分析,采用阻塞指派和非阻塞指派示例的頂層子產品代碼如下所示:

1  module Example_Block
2  (
3      /* 時鐘和複位端口 */
4      clk,rst_n,
5      /* 資料輸入端口 */
6      data_in,
7      /* 阻塞端口 */
8      block_out1,block_out2,
9      /* 非阻塞端口 */
10     no_block_out1,no_block_out2
11 );
12
13 input clk;
14 input rst_n;
15 input data_in;
16 output block_out1;
17 output block_out2;
18 output no_block_out1;
19 output no_block_out2;
20
21 Block_Module Block_Init
22 (
23     .clk (clk ),
24     .rst_n (rst_n ),
25     .block_in (data_in ),
26     .block_out1 (block_out1 ),
27     .block_out2 (block_out2 )
28 );
29
30 No_Block_Module No_Block_Init
31 (
32     .clk (clk ),
33     .rst_n (rst_n ),
34     .no_block_in (data_in ),
35     .no_block_out1 (no_block_out1 ),
36     .no_block_out2 (no_block_out2 )
37 );
38
39 endmodule
           

從上述代碼可以知道,頂層子產品Example_Block中包含了兩個子產品,一個子產品是Block_Module也就是阻塞指派,另一個子產品是No_Block_Module也就是非阻塞指派,這個代碼很簡單,沒有任何邏輯設計。

阻塞指派Block_Module代碼如下所示:

1  module Block_Module
2  (
3      /* 時鐘和複位端口 */
4      clk,rst_n,
5      /* 阻塞輸入端口 */
6      block_in,
7      /* 阻塞輸出端口 */
8      block_out1,block_out2
9  );
10
11 input clk;
12 input rst_n;
13 input block_in;
14 output block_out1;
15 output block_out2;
16
17 reg block_out1;
18 reg block_out2;
19
20 always @ (posedge clk or negedge rst_n)
21 begin
22     if(!rst_n)
23         begin
24         block_out1 = 1'b0;
25         block_out2 = 1'b0;
26         end
27     else
28         begin
29         block_out1 = block_in;
30         block_out2 = block_out1;
31         end
32 end
33
34 endmodule
           

從上述代碼中可以知道, 第1至18行是端口的聲明,一共聲明了5個端口,其中3個輸入端口,2個輸出端口;第20至32行是一個always子產品,在always子產品中,先将輸入端口block_in指派給block_out1,然後我們再将block_out1指派給block_out2。,這裡需要注意的是,使用的是阻塞指派(=)。

非阻塞指派No_Block_Module代碼如下所示:

1  module No_Block_Module
2  (
3      /* 時鐘和複位端口 */
4      clk,rst_n,
5      /* 非阻塞輸入端口 */
6      no_block_in,
7      /* 非阻塞輸出端口 */
8      no_block_out1,no_block_out2
9  );
10
11 input clk;
12 input rst_n;
13 input no_block_in;
14 output no_block_out1;
15 output no_block_out2;
16
17 reg no_block_out1;
18 reg no_block_out2;
19
20 always @ (posedge clk or negedge rst_n)
21 begin
22     if(!rst_n)
23         begin
24         no_block_out1 <= 1'b0;
25         no_block_out2 <= 1'b0;
26         end
27     else
28         begin
29         no_block_out1 <= no_block_in;
30         no_block_out2 <= no_block_out1;
31     end
32 end
33
34 endmodule
           

 分别通過Block_Module和No_Block_Module實作了阻塞指派和非阻塞指派的例化,頂層子產品生成的RTL視圖如下所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

盡管這兩個子產品的代碼基本上一樣,但是它們兩個實作的功能卻是完全不同。從上述RTL視圖可以看出,使用阻塞指派生成的電路是兩個并聯的D觸發器;使用非阻塞指派生成的電路是兩個串聯的D觸發器,生成的電路是完全不同的。

注:這裡需要注意的是,阻塞指派意味着語句是順序執行的,并不代表電路是串聯的,這兩者沒有必然關系。由于阻塞指派中是先将block_in指派給block_out1,再将block_out1指派給block_out2,實際上相當于同時将block_in指派給block_out1和block_out2,是以在阻塞指派的RTL視圖中對應的電路是并聯的;而非阻塞指派中同時将no_block_in和no_block_out1分别指派給no_block_out1和no_block_out2,是以在非阻塞指派的RTL視圖中對應的電路是串聯的,級聯意味着第二個觸發器的輸入是第一個觸發器原本狀态的輸出no_block_out1,而不是将第一個D觸發器現态的輸出直接送到第二個D觸發器的輸入端。

可以想象它們所産生的波形也是完全不同的,下面給出頂層子產品的仿真波形圖。

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 從該仿真波形圖中我們可以看出,block_out1、block_out2 和no_block_out1,它們三個的波形是一緻的,但是no_block_out2波形有很大不同。出現這種情況的原因如下:

(1) 阻塞指派(=):寫在前面的語句先執行,寫在後面的語句後執行,也就是說,它是順序執行的,一般用于組合邏輯電路。

(2) 非阻塞指派(<=):寫在前面的語句與後面的語句同時執行,也就是說,它是并行執行的,跟書寫順序沒有關系,一般用于時序邏輯電路。

下面是一個簡單的阻塞指派和非阻塞指派的示例(僅示意)。

初始化m=1,n=2,p=3,執行以下語句

begin        begin
                                m=n;         m<=n;
                                n=p;         n<=p;
                                p=m;         p<=m;
                                end          end
           

 結果分别是:m=2,n=3,p=2和m=2,n=3,p=1。

7.狀态機

Verilog中程式都是并行執行的,但是在一些在實際的工程應用中往往需要實作一些具有一定順序的工作,是以就需要狀态機來解決上述問題。

所謂狀态機就是根據控制信号按照預先設定的狀态進行狀态轉移,是協調相關信号、完成特定操作的控制中心。通過狀态機這種方法,可降低抽象難度,同時也可提高代碼的可讀性及維護性。狀态機是表示多個狀态以及這些狀态之間轉移和動作的數學模型。狀态存儲關于過去的資訊,它反映從系統到現在時刻輸入的變化;轉移訓示狀态變更,用必須滿足來确使轉移發生的條件來描述它;動作是給定時刻要進行的活動描述。

狀态機簡寫為FSM(Finite State Machine),主要分為兩大類:

(1) 輸出隻和狀态有關而與輸入無關,則稱為摩爾(Moore)狀态機;

(2) 輸出不僅和狀态有關而且和輸入有關系,則稱為米利(Mealy)狀态機。

 狀态機通常包括組合邏輯和時序邏輯,其中,時序邏輯由一組觸發器組成,用來記憶目前的狀态;而組合邏輯又可以分為次态邏輯和輸出邏輯兩部分,次态邏輯的功能是确定有限狀态機的下一個狀态,輸出邏輯的功能是确定有限狀态機的輸出。

7.1狀态機的設計步驟

通常設計一個狀态機分為以下四個步驟:

(1) 根據實際使用需要來選擇狀态機的結構,确定采用摩爾型狀态機還是米裡型狀态機;

(2) 根據實際情況分析設計要求并列出狀态機的所有狀态,然後對每個狀态進行狀态編碼;

(3) 根據狀态轉移關系和輸出函數畫出狀态圖。這裡我們需要注意的是,對同一個設計問題來說,不同的人可能會構造出不同的狀态圖。狀态圖直覺地反映了狀态機各個狀态之間的轉換關系以及轉換條件,因而有利于了解狀态機的工作原理,但此時要求設計的狀态個數不能太多對于狀态個數較多的狀态機一般采用狀态表的方法列出狀态機的轉移條件。

(4) 根據所畫的狀态圖,采用硬體描述語言對狀态機進行描述。

7.2狀态機的狀态編碼

所謂狀态編碼就是通過不同的編碼值去區分各個不同的狀态,使得每一個狀态都有唯一的辨別。在使用Verilog描述狀态機時,通常用參數定義語句parameter指定狀态編碼。常用的狀态編碼有三種分别是:遞增二進制編碼,格雷編碼和one-hot編碼,如下表所示:

Verilog的基礎知識和關鍵問題一、Verilog的基礎知識 二、Verilog的基礎文法  三、Verilog中的關鍵問題

 (1) 遞增二進制編碼:這種編碼方式的效率很高,但是在譯碼過程中需要讓所有的二進制位參與譯碼,在狀态較多且狀态跳轉條件比較複雜時會導緻很大的組合邏輯。

(2) 格雷編碼:這種編碼方式能夠避免進入錯誤的狀态,常用于高可靠性設計。

(3)one-hot編碼:這種編碼方式所占用的D觸發器資源通常要比遞增二進制編碼多一些,狀态數等于觸發器的資料,備援的觸發器使得譯碼電路比較簡單,是以它的速度非常快。

注:需要注意的是,不管使用哪種編碼方式,狀态機中的各個狀态都應使用符号常量(parmeter),而不應該直接使用編碼數值,賦予各狀态有意義的名字對與設計的驗證和代碼的可讀性都是有益的。

7.3狀态機的描述方法

狀态機的描述代碼風格有三種:分别是一段式狀态機、二段式狀态機、三段式狀态機。對于這三種方法下面分别進行介紹。

(1) 一段式狀态機:是将所有邏輯都寫在了一個always子產品中,雖然這種寫法從功能上來說并沒有錯誤,但是它的可讀性差,在編寫的時候容易出錯,往往不利于維護;

(2) 二段式狀态機:是将組合邏輯和時序邏輯分開,具有較好的可讀性,相對容易維護,不過組合邏輯輸出較易出現毛刺等常見問題;

(3) 三段式狀态機:除了具有兩段式的優點(将組合邏輯和時序邏輯分開),還對狀态輸出進行了寄存,可以有效地濾除毛刺(推薦使用三段式狀态機,養成良好的代碼風格)。