Lab_5 硬體描述語言Verilog
1.1 Verilog是什麼?
Verilog是一種用來描述硬體的語言,它的語法與C語言相似,易學易用,而且能夠允許在同一個模組中有不同層次的表示法共同存在,設計者可以在同一個模組中混合使用:
a.電晶體層次(Transistor Model) PS.不建議使用此層次
b.邏輯閘層次模型(Gate Level Model)
c.暫存器轉移層次(Register Transfer Level)
d.行為模型(Behavioral Model)
等4種不同層次的表示法來描述所設計的電路。
1.2 為何要用Verilog來描述硬體以及模擬硬體呢?
首先我要說的是在這裡所謂的“硬體”指的是“數位電路”,因為超大型積體電路(VLSI)設計技術與半導體製造工業的快速成長、使得數系統愈來愈複雜,以及電子產品的生命週期子於設計時所花的時間,往使用的「全訂製」(Full Custom)IC的設計流程並未能符合市埸的快速變化、因而未能廣泛使用於電子產品的設計中。因此數位系統設計人員及數位電路工程師急迫地需要一種能夠模擬數位電路或是系統的語言好用來驗証以及模擬數位電路的正確性以加快數位電路的設計,而Verilog就是用來模擬數位電路的動作。你可以把數位電路模擬用的Verilog與Full Custom裡與Hspice的做個相對應的比較。
1.3 數位電路的設計流程(See Figure 1.1)
第一步、「功能模擬」(Function Simulation)階段
◆ 1.Verilog電路描述檔案(*.v檔)
我們將Verilog電路描述檔案(*.v檔)作為設計的輸入。
◆ 6.功能模擬(Function Simulation)及電路的測試碼(Verilog Test Drive)
配合您所提供用於測試該模組電路的測試碼(Verilog Test Drive),執行CAD軟體所提供的「功能模擬」(Function Simulation)用以確保模組的訊號輸出與預期的結果在訊號波形(Function)及時序(Timing)…等方面是否相同。
第二步、「邏輯閘層次模擬」(Gate Level Simulation)階段
◆1.Verilog電路描述檔案(*.v檔)
我們將Verilog電路描述檔案(*.v檔)作為設計的輸入。
◆2.Verilog語法檢查(Synnopsys HDL Compiler)
SynopsysDesign Analyer的讀入Verilog電路描述檔(*.v)的過程會檢查其是否符合Verilog的語法格式。
◆3.Verilog電路合成(Synopsys Design Compiler)
Synopsys的Design Analyzer可依照您對該模組或是電路所下的限制條件(Constraints)、例如:面積(area)、效能(speed)…等等、作為電路合成的要求目標。
◆4.邏輯閘層次描述(Gate Level Description)
完成步驟「3.Verilog電路合成(Synopsys Design Compiler)」之後會得到「邏輯閘層次描述(Gate Level Description)」
◆5.邏輯閘層次模擬(Gate Level Simulation)及7.電路的測試碼(Verilog Test Driver)
配合您所提供用於測試該模組電路的測試碼(Verilog Test
Drive),執行CAD軟體所提供的「邏輯閘層次模擬模擬」(Gate Level Simulation)用以確保模組的訊號輸出與預期的結果在訊號波形(Function)及時序(Timing)…等方面是否相同。
第三步、「模擬結果」(Simulation Output
Comparison)階段
◆8.功能及時序模擬結果比較(Compare Output)
將「功能模擬」(Function Simulation)階段的模擬結果以及「邏輯閘層次模擬」(Gate Level
Simulation)階段的模擬結果,比較他們之間的訊號輸出與預期的結果在訊號的波形(Function及時序(Timing)…等方面是否相同。這二個階段的模擬結果未必都會相同, 尤其是在訊號的時序(Timing)方面或多或少會有誤差的。因為在「功能模擬」(Function
Simulation)階段是比較接近理想的狀態下的模擬結果,。而在「邏緝閘層次模擬」階段因為已經合成出邏緝閘層次的電路了,所以是會比較接近最後所需電路(實際狀態下)的模擬結果。
數位電路設計流程圖:
Figure 1.1
二. 如何編寫Verilog硬體描述語言
2.1 Verilog的語法協定
◆識別字(Identifiers) ,是在Verilog電路描述中所給予物件的名稱。識別字的第一個字元必須為字母,第二個之後的字元可為字母,數字,底線“ˍ”或是錢字號“$”所組成。識別字的大小寫是有所分別的,例Data與data是不同的二個名字;也就是說在Verilog語言是可以分辨大小寫的要注意哦!!
◆關鍵字(Keyword)是一組特殊的定義名稱,其功用是用來定義Verilog電路描述的架構,所有的關鍵字都必需使用小寫來表示,也就是說在Verilog用到的指令都是小寫的。
◆註解(Comments),在Verilog中有二種的註解方法,
第一種為「單行註解」(One Line Comment)是以“//”為開頭的註解寫法:
例: wire a = b & c; //宣告1條接線a,並指定他為b&c
第二種為「多行註解」(Multiple Line Comment),以“/*”為開頭,用“*/”當作結尾的註解寫法:
例:
/*
若op=ADD,則Result=a+b; 若op=SUB,則Result=a-b;
若op=AND,則Result=a&b; 若op=OR,則Result=a|b;
*/
◆數值(Numbers)表示
數值表示有二種的表示法,一為定長度(Sized)表示,另一種為不定長度(Unsized)表示二種。
◆定長度(Sized)數值表示:
是以<size>’<base
format><number>來表示。其中<size>是以十進位來表示數字的位元數(Bits),<base format>用來定義此值是用16進位(’H或’h)、十進位(’D或’d)、八進位(’O或’o)或是使用二進位(’B或’b)。
例:
wire [3:0] Out1,Out2,Out3,Out4;
assign Out1 = 4’hd13;
// 將十進位表示的13設給Out1
assign Out2 = 4’b1101;
// 將二進位表示的1101設給Out2
assign Out3= 4’o15;
// 將八進位表示的15設給Out3
assign Out4 = 4’hD;
// 將十六進位表示的D設給Out4
上面這個例子中Out1到Out4裡頭其實都是設定到同一個值,只不過是在不同基底(base format)下去設定。
◆不定長度(Unsized)的數值表示
不定長度數字不必使用<size>來規定數字之位元數大小,而是使用HDL編譯器內定的長度(通常為32bit的寬度)。其中若沒有寫明<base format>的部份,則以十進制來表示。
2.2開始Verilog的電路描述
一個電路的行為我們大體可以將它分為二個部分,一是邏輯電路的部分,另一個是循序電路的部分,第一部分首先我們先來看如何來描述一個邏輯的電路行為,第二部分我們在來討論循序電路的電路描述。
2.2.1邏輯電路的描述
若這時我要舉一些例子來做電路描述的說明:
例1.如下圖Fig2.1所示:
Fig 2.1
它的Verilog語言描述有下面二種方式,第一種是使用關鍵字assign描述,另一種使用關鍵字always來描述。
第一種方式,當我們使用關鍵字assign來做指描述時,我們要先宣告它所指定的識別字要為(wire)接線形態,而在等號右半邊的運算元(a或b)則沒有限制。其上面電路描述如下:
wire c ; //宣告c 為一條接線
assign c = a & b ; //將a與b作邏輯AND運算後指定給接線c
注意:其中a或b可以是wire(接線)形態或是reg(暫存器)形態,但等號的左半邊c一定要為wire(接線)形態。
第二種方式,使用always來描述,我們要宣告它所指定的識別字為reg(暫存器)形態,相同的在等號右半邊的運算元則沒有限制。電路描述如下:
reg c ;
always @(a or b)
begin
c
= a & b ;
end
注意:其中等號的右半邊a或b可以是wire(接線)形態或是reg(暫存器)形態,但等號的左半邊c一定要為reg(暫存器)形態,在使用always時,記得要加“@(要察看的訊諕)”這一項的敘述,且在此always內有二個以上的reg(暫存器)被指定時,在always敘述要以begin為開始,end作為結尾。
接下來我們將導入一個向量(Vector)的宣告方法,去宣告wire(接線)或是reg(暫存器),若有一個Verilog語言寫法如下所示:
wire [3:0] a
;
則它是代表有一寬度為4bit的wire(接線)a被宣告,相同的當要宣告寬度為4bit的reg(暫存器)b時可寫成如下:
reg [3:0] b
;
我們在這裡在舉一個簡單的例子:
例2.如下圖Fig2.2所示:
Fig2.2
這一個例子,大家可以想一想如何利用上面的向量(Vector)宣告,配合assign或always來描述;請先試著寫看看,再參考下一頁的答案。
使用assign描述如下:
wire [3:0] c
; //宣告c為寬度4bit的接線
assign c = a & b ; //將a和b作邏輯AND後指定給接線 c
注意:其中的a或b均是寬度為4bit的wire(接線)或是reg(暫存器)
使用always描述如下:
reg [3:0] c
;
always @(a or b)
begin
//## always開始 #####
c
= a & b; //其中a , b是運算元而&(and)為運算子
end //## always結束 #####
在Verilog中運算元的資料型態包括常數、整數、實數、接點、暫存器、時間、一個向量接點或是一個向量暫存器中的一個位元或是部分的位元、記憶體或是函數的回傳值…等等。
以下將列出Verilog所支援的運算子,這裡還要注意一件事那就是Verilog所支援的運算子不代表它都可以合成也就是實體化,在最後一欄有標示。
運算子種類
|
符號
|
運算功能
|
運算元數目
|
否可合成
|
算術運算符號
|
*
|
乘法
|
2
|
可
|
/
|
除法
|
2
|
不可
|
|
+
|
加法
|
2
|
可
|
|
-
|
減法
|
2
|
可
|
|
%
|
取餘數
|
2
|
不可
|
|
邏輯運算符號
|
!
|
邏輯上的 " NOT"
|
1
|
可
|
&&
|
邏輯上的 "AND"
|
2
|
可
|
|
||
|
邏輯上的 "OR"
|
2
|
可
|
|
比較符號
用在if-else
判斷式
|
>
|
大於
|
2
|
可
|
<
|
小於
|
2
|
可
|
|
>=
|
大於或等於
|
2
|
可
|
|
<=
|
小於或等於
|
2
|
可
|
|
相等符號
用在if-else
判斷式
|
==
|
等於
|
2
|
可
|
!=
|
不等於
|
2
|
可
|
|
===
|
事件上的等於
|
2
|
不可
|
|
!==
|
事件上的不等於
|
2
|
不可
|
|
位元運算符號
|
~
|
取 1 的補數
|
1
|
可
|
&
|
對相對位元 "AND"
|
2
|
可
|
|
|
|
對相對位元 "OR"
|
2
|
可
|
|
^
|
對相對位元 "XOR"
|
2
|
可
|
|
^~
或 ~^
|
對相對位元 "XNOR"
|
2
|
可
|
|
簡化的位元運算符號
|
&
|
簡化的 "AND"
|
1
|
可
|
~&
|
簡化的 "NAND"
|
1
|
可
|
|
|
|
簡化的 "OR"
|
1
|
可
|
|
~|
|
簡化的 "NOR"
|
1
|
可
|
|
^
|
簡化的 "XOR"
|
1
|
可
|
|
^~
或 ~^
|
簡化的 "XNOR"
|
1
|
可
|
|
移位運算符號
|
>>
|
向右移位
|
2
|
可
|
<<
|
向左移位
|
2
|
可
|
|
連結運算符號
|
{}
|
連結
|
任意數目
|
可
|
{{}}
|
重複
|
任意數目
|
不可
|
|
判斷運算符號
|
?:
|
做判斷運算
|
3
|
可
|
接著我們來看邏輯閘層次模型與行為模型描述電路時他們的差異,在這裡我們舉下面(例3)一個全加器來討論。
例3:我們用邏輯閘層次模型來描述一個電路必需知道他的邏輯閘電路才能去描述,如下:
我們可以依照上圖寫出下面的邏輯閘模型描述
|
|
或
若是使用行為模型來描述的話,我們只需要對它的行為做描述就可以了,而不用知道它的邏輯閘情形,其描述如下:
reg Sun,Cout; //宣告Sun,Cout為暫存器型態
always @(A or B or Cin)
begin //always開始
{Cout,Sum}
= A + B + Cin; //全加器的行為描述
end //always結束
其中的“{ }”為Verilog關鍵字可以用來連結訊號用,舉個例子若a為2bit的訊號而b為3bit的訊號,所以{a,b}就會一個5bit的訊號了。
接下來我們來看常用到的if…else…,case 和for的敘述,一樣的我們用例子來一一看這些的敘述關鍵字。
例子四:若有一個電路如下圖(Fig2.3)如示,是一個可以利用訊號Sel來選擇做二個8bit寬度A和B作加的動作或是減的動作:
Fig2.3
此時我們可以利用if…else…敘述來描述之,其描述如下
reg [8:0] C;
always @(A or B or Sel) //在等號右半邊出現過的都要寫進來
begin //當然判斷式裡的訊號也要記的寫進去
if(Sel==1’b1) C
= A – B ; //Sel = 1’b1時作這一行動作
else C
= A + B ; //否則就作這一行
end
注意:if…else…可以是有巢狀結構的,也就是在if…else…裡也還可以在使用if…else…敘述,和always一樣只要有二個以上的指定動作一定要begin開始並用end結束。還有要注意的是當if…else…使用巢狀結構時,它是有優先權的觀念,也就它的設定是有先後關係的,這一點與case的敘述是有所不同的。
對了case以及for敘述都是可以用巢狀結構的。
例子四:看圖Fig2.4,如何去描述這一個電路,它能夠由OP去選擇所要做的動作其動作表如Table 1:
Fig2.4 Table 1
下面就是利用case敘述來描述這一個電路的動作
reg [7:0] C
;
always @(A or B or OP )
begin
case(OP)
3’b000
: C = A+B ;
3’b001
: C = A-B ;
3’b010
: C = A&B ;
3’b011
: C = A|B ;
3’b100
: C = A ^ B ;
3’b101
: C = A ;
3’b110
: C = B ;
3’b111
: C = 8’h00 ;
endcase
end
注意:case的敘述記得用endcase來作結束;case敘述除了case以外還有casex和casez其用法請去看參考書籍。
從這些例子中,大家是否有感受到Verilog行為模型描述電路的威力,試想若上面的那些電路,如果每一個部分都要自己去設計其邏輯閘架構然後依照其位置去做連接那會是一件多累的事,我們的時間應多花在電路的系統架構下,而不是退而求其次的去做電路內部邏輯閘的設計,除非你的設計重點是邏輯閘層次的設計。
接著我們來談另一個Verilog常用的關鍵字for,for敘述主要是提供一個簡單的方法來描述貝有重覆特性的敘述。像下面這一個例子就可以利用for的敘述來簡化它的描述。
reg [3:0] Out ;
reg c
;
always @(a or b)
begin
Out[0]
= a[0] ^ b[0] ;
Out[1]
= a[1] ^ b[1];
Out[2]
= a[2] ^ b[2] ;
Out[3]
= a[3] ^ b[3] ;
c
= (a[0] | b[0])&(a[1] | b[1])&(a[2] | b[2])&(a[3] | b[3]) ;
end
上面的電路利用for描述後如下:
reg [3:0] Out;
reg c;
always @(a or b)
begin
integer i ; //宣告i為一個整數形態
for(i=0;i<=3;i=i+1) //利用這一個for迥圈去描述整在電路
begin
Out[i]
= a[i] & b[i] ;
c
= (a[i] | b[i]) & c ;
end
end
其中的for loop內的敘述在編譯(Compile Time)時會被展開成等效的敘述如上一個描述。比較一下二者的描述,我們可以發現當電路很煩瑣的重覆描述時,我們可以利用for來簡潔描述之,且在適當的描述後亦可增加程式可讀性。
2.2.2循序電路的描述
其實循序電路可以簡單看成是一個邏輯電路加上一些儲存元件去實現。它和邏輯電路的不同是它的輸出需要參考之前的輸出,不單單只看現在的輸入,所以它是需要暫存器去儲存之前的輸出好來當下一次的輸出的輸入。常看到的循序電路有計數器(Counter)、有限狀態機器(Finite State Machine,FSMs)。下面是一個簡單循序電路的示意圖:
在數位邏輯電路中,具有儲存數位訊號能力元件稱為「記憶元件」,包括閂鎖(Latch)以及正反器(Filp-Flop)。接著我們將來說明如何去描述閂鎖(Latch)與正反器(Flip-Flop)。首先我們先來談閂鎖是如何用Verilog來描述,在前面的部分我們曾用always這一個敘述關鍵字來描述一個組合邏輯(Combination Logic)電路,其中可以利用if…else…去描述我們所要的組合邏輯電路,那時的描述都是全整的描述電路行為,但如果發生描述不完全時這時就會有閂鎖(Latch)的產生,我們利用下面的例子來做說明。
例五:
reg q;
always @(q or d or Sel)
begin
if(Sel) q
= d ; //當sel=1時q = d
else q
= q ; //否則q = q;
end
上面為一完整的描述故電路合成後會如下圖所示為一個多功器,其一輸入為輸出回授接入:
但當它的描述是不完全時,則電路合成後會是一個閂鎖(Latch), 下面是一個不完整的描述,及其合成後的電路即一為閂鎖。
reg q;
always @(q or Sel)
begin
if(Sel)
q = d ; //只有if的描述,少了else的描述
end
其電路成合後如示所示為一個閂鎖(Latch):
我們在這裡作一個結論,那就是在always @(訊號 or <訊號> …)敘述,只要是完整的描述其合成出來的電路將會是組合邏輯,而當其之間的描述是不完整的話其電路將會有閂鎖產生。
接著我們來講正反器(Filp-Flop)其Verilog是如何描述之,試想一下Filp-Flop的特性它是利用Clock的上升邊緣或是下降邊緣作訊號的轉換,在Verilog裡是利用@(posedge clk)或@(negedge clk)去描述的。和前面一樣我將會一些例子來說明,以下就是其說明的例子:
例六:一個D Filp-Flop 的描述
reg q;
always @(posedge clk) //以clk上升緣作觸發
begin
q = d ;
end
其電路如下:
下面我們舉一個利用計數器(Counter)來實現一除頻器為例子,每當計數器數到3時其訊號i就會反相(1變0 ,0變1),下面就是其Verilog描述方法:
例七:
reg i;
reg [1:0] Count;
always @(posedge Reset or posedge
Clk)
begin //請看有底色的部分,此為
if(Reset) //D Flip-Flop的非同步重置(Reset)的描述方法
begin
Counter = 0;
i = 0;
end
else
begin
i
=(Counter==3)? ~i : i ;
//判斷運算若Counter==3成立的話 i = ~i (“~”為反相)
//否則 i = i ;
Counter
= Counter +1; //計數器的描述
end
end
Clk
Reset
Counter 0
1 2 3
0 1 2
3 0 1
2 3 0
1 2 3
0 1 2 3
i
上面就是電路執行後的時序圖,大家可以比對一下電路描述與波形圖
2.3 整個Verilog語言的完整敘述
- 瞭解 Verilog 中模組的定義,如模組的名稱,埠列 ( port list ) ,參數、變數的宣告,陳述資料的處理程序,行為模式的陳述,取用低階模組的別名,任務 (Tasks)與函數 (functions )。
- 瞭解在 Verilog 中如何定義一個模組的埠列。
- 瞭解在一個模組的別名與另一個別名,埠與埠之間相互連接的規則。
- 瞭解如何藉由依照順序或是指定名稱的方式來連接不與外部的輸入訊號。
- 解釋在 Verilog 中階層化名稱的架構。
2.3.1模組
- 在 Verilog 中一個模組其架構與組成如圖 2.4 所示。
Figure 2.4 Verilog 模組的組成元件
- 一個模組都是以一組關鍵字 module 與 endmodule 包裝起來的,開頭永遠是一個關鍵字 module。接下來是模組的名稱,結尾一定是 endmodule 。且只有這三部份是必要的。
- 圖2.4 的其他部份則視設計的需要而加入,且在排列上沒有一定的順序,可以任意排列。
- 模組中的每一個描述的結尾都要加上分號,以示結束,包括模組一開始以 module 開頭的那一行,只有結束時的 endmodule 不用。
- 底下以一個全加器,來說明圖2.4中各個部分。
例5: 全加器的各個部分
// 這個例題主要是在說明一個模組中的不同元件
// 模組名稱與埠列
// 模組名稱為 fa32
module fa32(a,b,cin,sum,cout);
// 埠的宣告
input a,b,cin;
output sum,cout;
// 模組名稱為 fa32
module fa32(a,b,cin,sum,cout);
// 埠的宣告
input a,b,cin;
output sum,cout;
// 取用低階層次模組的別名
// 注意訊號線連接的方式
sum sum1(a,b,cin,sum);
carry carry1(a,b,cin,cout);
// 注意訊號線連接的方式
sum sum1(a,b,cin,sum);
carry carry1(a,b,cin,cout);
// 關鍵字
endmodule
endmodule
endmodule
// 模組名稱與埠列
// 觸發模組
module test;
// 觸發模組
module test;
// 宣告 wire reg 與其他變數
wire sum,cout;
reg a,b,cin;
// 取用低階層次模組的別名
// 取用 fa32 模組並取別名為 fa1
fa32 fa1(a,b,cin);
wire sum,cout;
reg a,b,cin;
// 取用低階層次模組的別名
// 取用 fa32 模組並取別名為 fa1
fa32 fa1(a,b,cin);
2.3.2 埠(port)
- 埠提供一個模組與外界溝通的介面,好比一個晶片的輸出、輸入腳一樣。
- 外界僅能由埠來跟一個模組溝通,而一個模組內部的詳細情況外界是無法得知的,這點提供設計的人對於模組的內部有較大的修改空間。
2.3.2.1埠列
- 一個模組通常經由一系列的埠來與外界溝通,相反若一個模組不需要與外界溝通自然就沒有埠。
例6: 埠列
module fa32(sum,c_out,a,b,c_in) ; // 有埠列的模組
module test; // 沒有埠列的模組,通常用在模擬方塊
module test; // 沒有埠列的模組,通常用在模擬方塊
2.4 埠的宣告
- 在埠列中的埠都必須要在模組中宣告。埠的宣告有以下幾種類別:
Verilog 關鍵字
|
埠的類別(依方向來分別)
|
input
|
輸入埠
|
ouput
|
輸出埠
|
inout
|
雙向埠
|
例題 4-3 埠的宣告
Module fulladd4 (sum,c_out,a,b,c_in) ;
// 開始宣告埠
output [ 3: 0] sum ;
output c_cout ;
input [3:0] a, b;
input c_in ;
// 結束埠的宣告
...
<module internals>
...
endmodule
// 開始宣告埠
output [ 3: 0] sum ;
output c_cout ;
input [3:0] a, b;
input c_in ;
// 結束埠的宣告
...
<module internals>
...
endmodule
- 在 Verilog 中內定的埠的宣告種類為 wire ,因此假若在埠的宣告中只有宣告 output、input 或是 inout,則皆將其視為 wire,然而假如需要將訊號的值儲存起來,就要將埠的種類宣告為 reg。
例6: DFF 埠的宣告
module DFF (q,d,clk,reset) ;
output q ;
reg q ;
output q ;
reg q ;
// 輸出埠 q 需要儲存資料,所以宣告成 reg 型態的變數
input d, clk, reset ;
...
...
endmodule
input d, clk, reset ;
...
...
endmodule
- 注意!input 與 inout 型態的埠不能被宣告為 reg。因為 reg 儲存值,而輸入訊號只是表現出外來訊號的改變情況,所以不能儲存其值。
2.5 埠的連接規定
- 埠分為模組內外相互連接的兩部分,在 Verilog 中埠的內與外部的連接則必須要遵守某些規定,如圖 4-4 所示:
圖 2.5 埠的相連規定
- 輸入
- 在模組的內部,輸入埠永遠只是一個接點 ( net )。而由外部到輸入埠的訊號則可以是暫存器 ( reg ) 或是一個接點 ( net )。
- 輸出
- 在模組的內部輸出訊號可以是暫存器或是接點的形式,接到外面的訊號就必須是一個接點,不可以為暫存器的型態。
- 雙向
- 雙向埠不管是在模組內或外都必須是接點的型態。
- 埠的寬度
- 在 Verilog 中允許埠的內外寬度不同,但模擬器會發出警告的訊號。
˙ 埠的浮接
Verilog 允許埠可以浮接,如在一個用來除錯的埠在正常工作的時候不需要接到任何的訊號,就可以將它浮接。底下是一個浮接的例子:
Fulladd4 fa0 (SUM, ,A,B,C_IN) ; // 輸出埠 c_out 浮接
非法的埠連接例題,底下在例題7
將以一個非法的例題來說明埠的連接規定,如下所示:
例7:非法的埠連接
module Top;
// 宣告連接用的變數
reg [3:0] A, B ;
reg C_IN ;
reg [3:0] SUM ;
wire C_OUT ;
// 引用模組 fulladd4 並取別名為 fa0
fulladd4 fa0 (SUM,C_OUT,A,B,C_IN ) ;
// 這是一個非法的連結,因為模組 fulladd4 的輸出埠 連接到一個
// 型態為 reg 的 SUM 變數上
…
…
< stimulus >
…
…
endmodule
// 宣告連接用的變數
reg [3:0] A, B ;
reg C_IN ;
reg [3:0] SUM ;
wire C_OUT ;
// 引用模組 fulladd4 並取別名為 fa0
fulladd4 fa0 (SUM,C_OUT,A,B,C_IN ) ;
// 這是一個非法的連結,因為模組 fulladd4 的輸出埠 連接到一個
// 型態為 reg 的 SUM 變數上
…
…
< stimulus >
…
…
endmodule
在這例題中我們可以看到 fa0 的輸出埠 sum 輸出到一個暫存器 SUM 是錯誤的,因此必須要把 SUM 的型態改成接點 ( net )。
2.6埠與外部訊號連接的方法
將一個模組的埠與外部訊號連接的方法有兩種:
◆依照定義模組時埠列的順序來連接
例8:依照埠列順序連接
Module Top;
// 宣告用來連接的變數
reg [3:0] A, B ;
reg C_IN ;
wire [3:0] SUM ;
wire C_OUT ;
// 引用模組 fulladd4 並取別名為 fa_ordered.
// 訊號依照埠列宣告的順序連接
fulladd4 fa_ordered ( SUM,C_OUT,A,B,C_IN ) ;
...
< stimulus >
...
// 宣告用來連接的變數
reg [3:0] A, B ;
reg C_IN ;
wire [3:0] SUM ;
wire C_OUT ;
// 引用模組 fulladd4 並取別名為 fa_ordered.
// 訊號依照埠列宣告的順序連接
fulladd4 fa_ordered ( SUM,C_OUT,A,B,C_IN ) ;
...
< stimulus >
...
endmodule
module
fulladd4 ( sum, c_out, a,
b, c_in ) ;
output [3:0] sum;
output c_cout;
input [3:0] a, b;
input c_in ;
...
< module internals >
...
endmodule
output [3:0] sum;
output c_cout;
input [3:0] a, b;
input c_in ;
...
< module internals >
...
endmodule
◆用指定名稱的方法 (
Connecting ports by name )
// 訊號依照指定埠的名稱的方式連接
fulladd4 fa_byname ( .c_out ( C_OUT )
, .sum ( SUM ) , .b ( B ),
.c_in (
C_IN ) , .a( A ) ) ;
注意:只有需要與外部連接的埠才被指名連接,其餘不需要連接的埠的名稱就可以不用寫出,如上面例子中若埠 c_out 不想與外面的訊號相連,只要不寫出來即可,如下面所示。
// 訊號依照指定埠的名稱的方式連接
fulladd4 fa_byaname ( .sum ( SUM ), .b ( B ), .c_in ( C_IN ), .a ( A ) ) ;
fulladd4 fa_byaname ( .sum ( SUM ), .b ( B ), .c_in ( C_IN ), .a ( A ) ) ;
2.7總結
- 模組定義包含以下各個部份,其中關鍵字 module 與 endmodule 和模組名稱是一定要的,其餘的部份包含有:埠列 ( port list ),埠的宣告 ( port declarations ),變數與訊號的宣告 ( variable and signal declarations ),資料處理 ( dataflow statments ),行為模式 ( behavioral blocks ),低階模組的引用 ( lower-level module instantiations ),任務 (task ) 或是函數 (functions ),這些部份是視設計的需要再加入即可。
- 埠提供一個模組與周遭環境的溝通介面。一個需要與外界訊號溝通得模組皆有一個埠列,在埠列中的每個埠依照其方向分別宣告為 input、output 或是 inout,當引用一個模組時對於埠與訊號的連接必須遵守 Verilog 的規定。
- 埠的連接方法有依照埠列的順序與指定名稱兩種方式。
- 在設計中任何一個變數、訊號或是別名都有一個單一階層化名稱,藉由這個階層化的名稱,我們可以在其他的等級訂位到這個我們所需要的別名、訊號或是變數。
補充範例:
(1)non-blocking and blocking
(2)4位元串進串出,左移,移位暫存器
module
SISO_shift_register(clock,clear,SI,SO);
input clock,clear,SI;
output SO;
reg SO;
reg [3:0] reg4;
always @(posedge
clock)
begin
if(clear)
reg4=4’b0;
else
begin
SO<=reg4[3];
reg4[3]<=reg4[2];
reg4[[2]<=reg4[1];
reg4[1]<=reg4[0];
reg4[0]<=SI;
end
end
endmodule
(3)外部module呼叫
(4)內部function呼叫
module
Fun_AS(a,b,Add,Inc,switch,c);
input [3:0]a,b;
input
ADD,Sub,Inc,Switch;
output [3:0] c;
reg
[3:0] c;
always@(a or b or Add or Sub or Inc or Switch)
begin
if(Add)
//相加
c=a+b;
else
if(Switch)
//交換 a,b
c=Sub_Inc_Dec(b,a,Sub,Inc);
else
c=Sub_Inc_Dec(a,b,Sub,Inc);
end
function
[3:0] Sub_Inc_Dec;
input
[3:0]a,b;
input
Sub,Inc;
begin
if(Sub)
//相減
Sub_Inc_Dec=(a-b);
else
if(Inc)
//相加
Sub_Inc_Dec=(a+b);
else
Sub_Inc_Dec=(a-1’b1);
end
endfunction
endmodule
(5)case statement
(6)善用您的括號
out=((a+(b+c))+d+e)+f
(7)測試檔讀取資料檔資料,輸出結果儲存檔案形式。
`timescale 1ns/1ns //設定debussy的時間單位為1ns
`define INFILE "inreal.txt" //定義讀取的資料檔為inreal.txt與inimag.txt
`define INFILE1 "inimag.txt"
module fft; //開始module
parameter INPUT_DATA = `INFILE;
//定義INPUT_DATA為INFILE
parameter INPUT_DATA1 = `INFILE1; //定義INPUT_DATA為INFILE1
parameter length = 15; //定義length為15
reg
t_ck,t_reset,t_fftifftsel1,t_fftifftsel2;
reg [length-1:0]
t_x,t_xi,t_p,t_pi;
reg [14:0] pat_in_file [0:384-1]; //定義pat_in_file為15bit共384筆
reg [14:0] pat_in_file1 [0:384-1];
wire [length-1:0] t_xo,t_xio,t_po,t_pio;
integer i,out_f,out_f1; //定義整數變數
FFT248
m(.x(t_x),.xi(t_xi),.p(t_p),.pi(t_pi),.ck(t_ck),.xo(t_xo),.xio(t_xio),.po(t_po),.pio(t_pio),.reset(t_reset),.fftifftsel1(t_fftifftsel1),.fftifftsel2(t_fftifftsel2));
//initial $sdf_annotate("9.sdf",
m);
initial $readmemb(INPUT_DATA, pat_in_file);
//讀取INPOUT_DATA資料放於pat_in_file
initial $readmemb(INPUT_DATA1,
pat_in_file1);
initial
begin
out_f = $fopen("fftreal_out.dat"); //開起一文字檔fftreal_out.dat以存輸出資料
if
(out_f == 0) //out_f為0有誤
begin
$display("Output file open error !"); //如果錯誤顯示訊習息
$finish;
end
end
initial
begin
out_f1 = $fopen("fftimag_out.dat");
if
(out_f1 == 0)
begin
$display("Output file open error !");
$finish;
end
end
always #25
t_ck=~t_ck;
//integer
i,j,out_f;
initial
begin
#0
t_ck=1'b1;
t_reset=1'b0;
t_fftifftsel1=1'b0;
t_fftifftsel2=1'b0;
#45
t_reset=1'b1;
#50
t_reset=1'b0;
#5
t_x=pat_in_file [0]; //將pat_in_file[0]放入t_x
t_xi=pat_in_file1 [0];
t_p=1;
t_pi=1;
for (i=1;i<=384;i=i+1) //將pat_in_file[x]即1至384筆資料讀入t_x
begin
#50
t_x=pat_in_file [i];
t_xi=pat_in_file1 [i];
t_p=i+1;
t_pi=i+1;
end
#50
t_x=15'bxxxxxxxxxxxxxxx;
t_xi=15'bxxxxxxxxxxxxxxx;
t_p=15'bxxxxxxxxxxxxxxx;
t_pi=15'bxxxxxxxxxxxxxxx;
end
always @(posedge t_ck)
//每一個正緣觸發將t_xo的值放入out_f中
begin
$fdisplay(out_f,"%b",t_xo);
//%b為二進制形式
$fdisplay(out_f1,"%b",t_xio);
end
initial
begin
$dumpfile("64ffts.vcd");
$dumpvars("");
#150000 $dumpoff;
#10 $finish;
end
endmodule
三、如何在工作站上做Verilog編寫與模擬
首先我們先Login工作站,然後按mouse的右鍵選Tools在選Terminal把Terminal打開,如下圖所示。
接著我們在Terminal下指令 textedit &將文字編輯器打開,如下圖:
而在編輯器中去編寫Verilog程式,我們在這舉一個上面提過的例四。
首先我們先儲存此Verilog檔如下:
如下圖所示,在Save AS欄填上是儲存的檔案名子alu.v
然後我們就開始編寫之前例四的Verilog程式如下圖所示:
編寫好後記得要在儲存檔案一次哦!用mouse點選File à Save這樣我們就編寫完一個Verilog檔。接著我們做一個初步的Verilog語法驗証看看我們所寫的Verilog語法是否正確。在Terminal下我們下Verilog -c alu.v
來驗証語法正確性,如下圖所示:
執行完我們會看到下面的結果。
由上圖我們可以看到驗証結果看到第40行有一個錯誤,所以我們回去看我們所編寫的alu.v檔的內容找第40行,結果我們發現在此處我們忘了加上case的結束描述,如下圖:
|
少了case結束的描述”endcase”
所以我們在此行加入case結束描述”endcase”如下圖所示:
加入後我們存檔,然後在執行一次Verilog -c
alu.v看語法驗証後的結果,如下:
結果我們發現共有8個錯誤,而且全都是同一個訊號Carry_Out的指定語法不正確,這時我們回去看看alu.v裡的Carry_Out是否有宣告錯誤,結果我們發現我們忘了宣告Carry_Out這個訊號,所以我們在將這一個宣告加入如下圖所示: 在此處加入Carry_Out的宣告
整個alu.v程式如上圖,我們儲存後在做一個的語法驗証Verilog -c
alu.v我們可以得到下面的結果。
如圖如示,此alu.v 的語法沒有任何的錯誤。
在Verilog中有二種方式來作驗証,一種是純文字介面下另一種是圖形介面下,在邏輯驗証上通常只需在純文字介面下在驗証就夠了,至於圖形介面是拿來作循序電路的行為驗証。
在上面的alu.v只是一個邏輯電路,所以在此我們先用純文字介面來做驗証;
alu.v只是我們描述的一個電路,我們必需給它輸入訊號它才會有相對應輸出訊號,此時我們要編寫一個測試檔來測試我們所描述的電路行為是不是符合我們的要求的,一樣的我們要先打開文字編輯器“textedit
&”然後我們給它一個名字接著我們存檔,如下圖我們叫這個測試檔為alu.vt如下圖所示:
存檔後我們就開始編寫這一個測試檔alu.vt,如下圖:
上面有一個新的識別字是“initial”以及幾個Verilog系統函式分別為“$display”,“$monitor”,“$finish”以及在測試檔常用的時間延遲指定符號 “#”。只要在Verilog檔裡看到由$(錢字號)開頭的識別字全都是Verilog的系統函式,它是那來做一特定的動作,如上面的$display就是把在括號( )裡的內容列印到Terminal上,而$monitor也是一樣將括號內容列印到Terminal上,不同的是$display只會執行一次,而$monitor只要括號內的訊號改變它就會將其括號內容列印出。至於“#”你可以把它看成是在這個時間點上要延遲多久,而識別字“initial”相對於“always”,在於它裡面的敘述都只執行一次而不是像“always”一樣重覆執行,至於系統函式“$finish”則是結束此模擬動作。
我們現在就來作alu.v的模擬測試,我們在Terminal下鍵內以下的字
verilog alu.v
alu.vt 然後按下Enter如下圖所示:
則我們可以得到下圖所示的結果:
我們可以看到經由系統函式$dipslay及以$monitor已將模擬結果清楚的列印在Terminal上。大家可以看看其模擬結果是否與自己預想的一樣。
加入圈號之程式去使用debussy
產生debussy所需之alu.vcd波形檔,即可產生debussy所需波形,定義如下:
initial //初始
begin //開始
$dumpfile("alu.vcd"); //產生alu.vcd檔
$dumpvars(""); //設定一般使用””為空內容
#150000 $dumpoff;
//時間長度150000
#10 $finish;
//初始完成
end
/ /結束
鍵入vfast alu.vcd 轉換成debussy可以看的alu.vcd.fsdb檔
鍵入debussy &
開出debussy 軟體
按下波形按鈕,產生如下視窗:
選擇file à open
選擇alu.vcd.fsdb為所需波形檔,按下ok
選擇get signal按鈕。
選出所想要的訊號,大家就可以看到我們所模擬的結果秀在圖上了如下圖所示:
我們利用上面例七的例子做一個延伸來描述一個除4以及除8的電路,一樣的我們使用textedit
&來做編寫的工具下圖是這個除頻電路的Verilog描述檔。
接著我們編寫它的測試檔div_freq.vt如下圖所示:
其中always #10 Clk = ~Clk的描述是來達成一個週期為20個時間單位的時脈(Clock),但記得要給初始值要不然會產生不確定值(unknown),其初始值我們可以發現我們是寫在initial裡了。還有一點要注意此電路動作之前要記得作重置(Reset)的動作,這裡才會確保電路工作正確。
和上例子一樣我們現在可以來跑模擬,我們在Terminal下鍵入
verilog
div_freq.v div_freq.vt按Enter後會產生freq.vcd目錄,我們在Terminal下鍵入vfast
freq.vcd,接著我們在Terminal下鍵入debussy & 按Enter去啟動debussy
用它來秀出我們所模擬出來的結果。接著我們按照之前所講的首先去找file ->open 目錄點進去後選freq.vcd.fsb按OK然後在get
signals去選下面所要看的訊號如下圖:
我們可以看到模擬結果,它正符合我們所要求的會產生一個除8以及除32的訊號。
我在舉一例子它是利用有限機器(FSM)來完成控制加法共用4bit乘法器,其控制流程圖如下如示:
我們先來說明其工作原理,一個4bit乘4bit的作動作如下如示:
B = 1 0 1 0
X) A = 1 0 1 1
-------------------------------------------------
1) M = 0 0 0 0 1 0 1 0
-------------------------------------------------------------------- shift
2) M = 0 0 0 1 0 1 0 0
-------------------------------------------------------------------- shift
0 0 1 0 1 0 0 0
+) 1 0 1 0
--------------------------------------------------------------------
3) M = 0 0 1 1 0 0 1 0
-------------------------------------------------------------------- shift
0 1 1 0 0 1 0 0
+) 1 0 1 0
--------------------------------------------------------------------
4) M = 0 1 1 0 1 1 1 0 à finish result
就是利用上面這一個演算法我們設計這一個有限機(FSM)去做這一個乘法器的控制,以下就是它的Verilog硬體描述Serial_Mult4x4.v:
在上面有一個新的識別字“parameter”出現,這一個識別字只不過為了讓程式有更高的可看度它會將等號左半邊的符號用右半邊的值去代替它,也是是在程式中若看到有用parameter定義過的用其右半邊的值去代表。
下面的圖即是此乘法器的測試檔Serial_Mult4x4.vt:
和之前一樣我們在Terminal下verilog Serial_Mult4x4.v Serial_Mult4x4.vt
按Enter去作模擬,我們可得到一個Serial_Mult4x4.shm目錄,然後用signalscan &去開啟這一個模擬結果,其結果如下:
我們可以利用ZoomIn來放大其模擬波形放大後如下圖所示:
然後利用下面的遊標來拉到所要看的時間點,對了此時的訊號基底為16進制,在看時不夠直覺故我們將其改為十進制基底。我們先去點選 waveform在去點選Value Radix然後選擇Decimal如下圖所示:
此時我們就可以得到下面十進位的模擬結果:
我們可以看到在這一個時間範圍內輸入為十進位4與6其輸出為十進位的24我們可以看來其結果是正確。
沒有留言:
張貼留言