owned this note
owned this note
Published
Linked with GitHub
# 5. Verilog 模塊與端口
[參考網址:Verilog 中文詳細教程](https://www.runoob.com/w3cnote/verilog-tutorial.html)
###### tags: `verilog` `IC 設計`
---
[toc]
---
關鍵詞:模塊,端口,雙向端口,PAD
結構建模方式有 3 類描述語句: Gate(門級)例化語句,UDP (用戶定義原語)例化語句和 module (模塊) 例化語句。本次主要講述使用最多的模塊級例化語句。
### :pushpin: 模塊
模塊是 Verilog 中基本單元的定義形式,是與外界交互的接口。 模塊格式定義如下:
```verilog=
module module_name
#(parameter_list)
(port_list) ;
Declarations_and_Statements ;
endmodule
```
模塊定義必須以關鍵字 module 開始,以關鍵字 endmodule 結束。 模塊名,端口信號,端口聲明和可選的參數聲明等,出現在設計使用的 Verilog 語句(圖中 Declarations_and_Statements)之前。 模塊內部有可選的 5 部分組成,分別是變量聲明,數據流語句,行為級語句,低層模塊例化及任務和函數,如下圖表示。這 5 部分出現順序、出現位置都是任意的。但是,各種變量都應在使用之前聲明。變量具體聲明的位置不要求,但必須保證在使用之前的位置。

前面大多數仿真代碼都會用到 module 聲明,大家可以自行參考,這裡不再做具體舉例。下面介紹端口時,再做詳細的仿真。
### :pushpin: 端口
端口是模塊與外界交互的接口。對於外部環境來說,模塊內部是不可見的,對模塊的調用只能通過端口連接進行。
### :pushpin: 端口列表
模塊的定義中包含一個可選的端口列表,一般將不帶類型、不帶位寬的信號變量羅列在模塊聲明里。下面是一個 PAD 模型的端口列表:
```verilog=
module pad(
DIN, OEN, PULL,
DOUT, PAD);
```
一個模塊如果和外部環境沒有交互,則可以不用聲明端口列表。例如之前我們仿真時 test.sv 文件中的 test 模塊都沒有聲明具體端口。
```verilog=
module test ; //直接分号结束
...... //数据流或行为级描述
endmodule
```
### :pushpin: 端口聲明
(1) 端口信號在端口列表中羅列出來以後,就可以在模塊實體中進行聲明了。 根據端口的方向,端口類型有 3 種: 輸入(input),輸出(output)和雙向端口(inout)。 input、inout 類型不能聲明為 reg 數據類型,因為 reg 類型是用於保存數值的,而輸入端口只能反映與其相連的外部信號的變化,不能保存這些信號的值。 output 可以聲明為 wire 或 reg 數據類型。 上述例子中 pad 模塊的端口聲明,在 module 實體中就可以表示如下:
```verilog=
//端口类型声明
input DIN, OEN ;
input [1:0] PULL ; //(00,01-dispull, 11-pullup, 10-pulldown)
inout PAD ; //pad value
output DOUT ; //pad load when pad configured as input
//端口数据类型声明
wire DIN, OEN ;
wire [1:0] PULL ;
wire PAD ;
reg DOUT ;
```
(2) 在 Verilog 中,端口隱式的聲明為 wire 型變量,即當端口具有 wire 屬性時,不用再次聲明端口類型為 wire 型。但是,當端口有 reg 屬性時,則 reg 聲明不可省略。 上述例子中的端口聲明,則可以簡化為:
```verilog=
//端口类型声明
input DIN, OEN ;
input [1:0] PULL ;
inout PAD ;
output DOUT ;
reg DOUT ;
```
(3) 當然,信號 DOUT 的聲明完全可以合併成一句:
```verilog=
output reg DOUT ;
```
(4) 還有一種更簡潔且常用的方法來聲明端口,即在 module 聲明時就陳列出端口及其類型。 reg 型端口要么在 module 聲明時聲明,要么在 module 實體中聲明,例如以下 2 種寫法是等效的。
```verilog=
module pad(
input DIN, OEN ,
input [1:0] PULL ,
inout PAD ,
output reg DOUT
);
module pad(
input DIN, OEN ,
input [1:0] PULL ,
inout PAD ,
output DOUT
);
reg DOUT ;
```
### :pushpin: inout 端口仿真
對包含有 inout 端口類型的 pad 模型進行仿真。 pad 模型完整代碼如下:
```verilog=
module pad(
//DIN, pad driver when pad configured as output
//OEN, pad direction(1-input, o-output)
input DIN, OEN ,
//pull function (00,01-dispull, 10-pullup, 11-pulldown)
input [1:0] PULL ,
inout PAD ,
//pad load when pad configured as input
output reg DOUT
);
//input:(not effect pad external input logic), output: DIN->PAD
assign PAD = OEN? 'bz : DIN ;
//input:(PAD->DOUT)
always @(*) begin
if (OEN == 1) begin //input
DOUT = PAD ;
end
else begin
DOUT = 'bz ;
end
end
//use tristate gate in Verilog to realize pull up/down function
bufif1 puller(PAD, PULL[0], PULL[1]);
endmodule
```
testbench代碼如下:
```verilog=
`timescale 1ns/1ns
module test ;
reg DIN, OEN ;
reg [1:0] PULL ;
wire PAD ;
wire DOUT ;
reg PAD_REG ;
assign PAD = OEN ? PAD_REG : 1'bz ; //
initial begin
PAD_REG = 1'bz ; //pad with no dirve at first
OEN = 1'b1 ; //input simulation
#0 ; PULL = 2'b10 ; //pull down
#20 ; PULL = 2'b11 ; //pull up
#20 ; PULL = 2'b00 ; //dispull
#20 ; PAD_REG = 1'b0 ;
#20 ; PAD_REG = 1'b1 ;
#30 ; OEN = 1'b0 ; //output simulation
DIN = 1'bz ;
#15 ; DIN = 1'b0 ;
#15 ; DIN = 1'b1 ;
end
pad u_pad(
.DIN (DIN) ,
.OEN (OEN) ,
.PULL (PULL) ,
.PAD (PAD) ,
.DOUT (DOUT)
);
initial begin
forever begin
#100;
if ($time >= 1000) $finish ;
end
end
endmodule // test
```
仿真結果如下:

仿真結果分析如下:
當 PAD 方向為 input 且沒有驅動時,pull 功能能通過 PAD 的值而體現。
前 60ns 內,PAD 的驅動端 PAD_REG 為 z, 可認為沒有驅動,所以開始時 PULL=2, 下拉,PAD值為 0; 20ns 時,PULL=3,上拉,PAD 值為 1; 40ns 時,PULL=0,沒有 pull 功能,PAD 值輸入為 z。
60ns~100ns 後,PAD 的驅動端 PAD_REG 開始正常驅動。此時相當於 PAD 直接與 PAD_REG 相連,所以 PAD 值與其驅動值保持一致。
以上分析,PAD 方向都是 input,所有輸出端 DOUT 與 PAD 值保持一致。 當 PAD 方向為 output 時,即 120ns 時 OEN= 0,PAD 值與輸入端 DIN 值保持一致。
# 5.2 Verilog 模塊例化
關鍵字:例化,generate,全加器,層次訪問
在一個模塊中引用另一個模塊,對其端口進行相關連接,叫做模塊例化。模塊例化建立了描述的層次。信號端口可以通過位置或名稱關聯,端口連接也必須遵循一些規則。
### :pushpin: 命名端口連接
這種方法將需要例化的模塊端口與外部信號按照其名字進行連接,端口順序隨意,可以與引用 module 的聲明端口順序不一致,只要保證端口名字與外部信號匹配即可。 下面是例化一次 1bit 全加器的例子:
```verilog=
full_adder1 u_adder0(
.Ai (a[0]),
.Bi (b[0]),
.Ci (c==1'b1 ? 1'b0 : 1'b1),
.So (so_bit0),
.Co (co_temp[0]));
```
如果某些輸出端口並不需要在外部連接,例化時 可以懸空不連接,甚至刪除。一般來說,input 端口在例化時不能刪除,否則編譯報錯,output 端口在例化時可以刪除。例如:
```verilog=
//output 端口 Co 悬空
full_adder1 u_adder0(
.Ai (a[0]),
.Bi (b[0]),
.Ci (c==1'b1 ? 1'b0 : 1'b1),
.So (so_bit0),
.Co ());
//output 端口 Co 删除
full_adder1 u_adder0(
.Ai (a[0]),
.Bi (b[0]),
.Ci (c==1'b1 ? 1'b0 : 1'b1),
.So (so_bit0));
```
### :pushpin: 順序端口連接
這種方法將需要例化的模塊端口按照模塊聲明時端口的順序與外部信號進行匹配連接,位置要嚴格保持一致。例如例化一次 1bit 全加器的代碼可以改為:
```verilog=
full_adder1 u_adder1(
a[1], b[1], co_temp[0], so_bit1, co_temp[1]);
```
雖然代碼從書寫上可能會佔用相對較少的空間,但代碼可讀性降低,也不易於調試。有時候在大型的設計中可能會有很多個端口,端口信號的順序時不時的可能也會有所改動,此時再利用順序端口連接進行模塊例化,顯然是不方便的。所以平時,建議採用命名端口方式對模塊進行例化。
### :pushpin: 端口連接規則
**輸入端口**
模塊例化時,從模塊外部來講, input 端口可以連接 wire 或 reg 型變量。這與模塊聲明是不同的,從模塊內部來講,input 端口必須是 wire 型變量。
**輸出端口**
模塊例化時,從模塊外部來講,output 端口必須連接 wire 型變量。這與模塊聲明是不同的,從模塊內部來講,output 端口可以是 wire 或 reg 型變量。
**輸入輸出端口**
模塊例化時,從模塊外部來講,inout 端口必須連接 wire 型變量。這與模塊聲明是相同的。
**懸空端口**
模塊例化時,如果某些信號不需要與外部信號進行連接交互,我們可以將其懸空,即端口例化處保留空白即可,上述例子中有提及。 output 端口正常懸空時,我們甚至可以在例化時將其刪除。 input 端口正常懸空時,懸空信號的邏輯功能表現為高阻狀態(邏輯值為 z)。但是,例化時一般不能將懸空的 input 端口刪除,否則編譯會報錯,例如:
```verilog=
//下述代码编译会报Warning
full_adder4 u_adder4(
.a (a),
.b (b),
.c (),
.so (so),
.co (co));
```
```verilog=
//如果模块full_adder4有input端口c,则下述代码编译是会报Error
full_adder4 u_adder4(
.a (a),
.b (b),
.so (so),
.co (co));
```
一般來說,建議 input 端口不要做懸空處理,無其他外部連接時賦值其常量,例如:
```verilog=
full_adder4 u_adder4(
.a (a),
.b (b),
.c (1'b0),
.so (so),
.co (co));
```
### :pushpin: 位寬匹配
當例化端口與連續信號位寬不匹配時,端口會通過無符號數的右對齊或截斷方式進行匹配。 假如在模塊 full_adder4 中,端口 a 和端口 b 的位寬都為 4bit,則下面代碼的例化結果會導致:u_adder4.a = {2'bzz, a[1:0]}, u_adder4.b = b[3:0] 。
```verilog=
full_adder4 u_adder4(
.a (a[1:0]), //input a[3:0]
.b (b[5:0]), //input b[3:0]
.c (1'b0),
.so (so),
.co (co));
```
### :pushpin: 端口連續信號類型
連接端口的信號類型可以是,1)標識符,2)位選擇,3)部分選擇,4)上述類型的合併,5)用於輸入端口的表達式。 當然,信號名字可以與端口名字一樣,但他們的意義是不一樣的,分別代表的是 2 個模塊內的信號。
### :pushpin: 用 generate 進行模塊例化
當例化多個相同的模塊時,一個一個的手動例化會比較繁瑣。用 generate 語句進行多個模塊的重複例化,可大大簡化程序的編寫過程。 重複例化 4 個 1bit 全加器組成一個 4bit 全加器的代碼如下:
```verilog=
module full_adder4(
input [3:0] a , //adder1
input [3:0] b , //adder2
input c , //input carry bit
output [3:0] so , //adding result
output co //output carry bit
);
wire [3:0] co_temp ;
//第一个例化模块一般格式有所差异,需要单独例化
full_adder1 u_adder0(
.Ai (a[0]),
.Bi (b[0]),
.Ci (c==1'b1 ? 1'b1 : 1'b0),
.So (so[0]),
.Co (co_temp[0]));
genvar i ;
generate
for(i=1; i<=3; i=i+1) begin: adder_gen
full_adder1 u_adder(
.Ai (a[i]),
.Bi (b[i]),
.Ci (co_temp[i-1]), //上一个全加器的溢位是下一个的进位
.So (so[i]),
.Co (co_temp[i]));
end
endgenerate
assign co = co_temp[3] ;
endmodule
```
testbench 如下:
```verilog=
`timescale 1ns/1ns
module test ;
reg [3:0] a ;
reg [3:0] b ;
//reg c ;
wire [3:0] so ;
wire co ;
//简单驱动
initial begin
a = 4'd5 ;
b = 4'd2 ;
#10 ;
a = 4'd10 ;
b = 4'd8 ;
end
full_adder4 u_adder4(
.a (a),
.b (b),
.c (1'b0), //端口可以连接常量
.so (so),
.co (co));
initial begin
forever begin
#100;
if ($time >= 1000) $finish ;
end
end
endmodule // test
```
仿真結果如下,可知 4bit 全加器工作正常:

### :pushpin: 層次訪問
每一個例化模塊的名字,每個模塊的信號變量等,都使用一個特定的標識符進行定義。在整個層次設計中,每個標識符都具有唯一的位置與名字。 Verilog 中,通過使用一連串的 . 符號對各個模塊的標識符進行層次分隔連接,就可以在任何地方通過指定完整的層次名對整個設計中的標識符進行訪問。 層次訪問多見於仿真中。 例如,有以下層次設計,則葉單元、子模塊和頂層模塊間的信號就可以相互訪問。
```verilog=
//u_n1模块中访问u_n3模块信号:
a = top.u_m2.u_n3.c ;
//u_n1模块中访问top模块信号
if (top.p == 'b0) a = 1'b1 ;
//top模块中访问u_n4模块信号
assign p = top.u_m2.u_n4.d ;
```

前面章節的仿真中,或多或少的也進行過相關的層次訪問。例如《過程連續賦值》一節中,在頂層仿真激勵 test 模塊中使用瞭如下語句:
```verilog=
wait (test.u_counter.cnt_temp == 4'd4) ;
```
# 5.3 Verilog 帶參數例化
關鍵詞: defparam,參數,例化,ram
當一個模塊被另一個模塊引用例化時,高層模塊可以對低層模塊的參數值進行改寫。這樣就允許在編譯時將不同的參數傳遞給多個相同名字的模塊,而不用單獨為只有參數不同的多個模塊再新建文件。 參數覆蓋有 2 種方式:1)使用關鍵字 defparam,2)帶參數值模塊例化。
### :pushpin: defparam 語句
可以用關鍵字 defparam 通過模塊層次調用的方法,來改寫低層次模塊的參數值。 例如對一個單口地址線和數據線都是 4bit 寬度的 ram 模塊的 MASK 參數進行改寫:
```verilog=
//instantiation
defparam u_ram_4x4.MASK = 7 ;
ram_4x4 u_ram_4x4
(
.CLK (clk),
.A (a[4-1:0]),
.D (d),
.EN (en),
.WR (wr), //1 for write and 0 for read
.Q (q) );
```
ram_4x4 的模型如下:
```verilog=
module ram_4x4
(
input CLK ,
input [4-1:0] A ,
input [4-1:0] D ,
input EN ,
input WR , //1 for write and 0 for read
output reg [4-1:0] Q );
parameter MASK = 3 ;
reg [4-1:0] mem [0:(1<<4)-1] ;
always @(posedge CLK) begin
if (EN && WR) begin
mem[A] <= D & MASK;
end
else if (EN && !WR) begin
Q <= mem[A] & MASK;
end
end
endmodule
```
對此進行一個簡單的仿真,testbench 編寫如下:
```verilog=
`timescale 1ns/1ns
module test ;
parameter AW = 4 ;
parameter DW = 4 ;
reg clk ;
reg [AW:0] a ;
reg [DW-1:0] d ;
reg en ;
reg wr ;
wire [DW-1:0] q ;
//clock generating
always begin
#15 ; clk = 0 ;
#15 ; clk = 1 ;
end
initial begin
a = 10 ;
d = 2 ;
en = 'b0 ;
wr = 'b0 ;
repeat(10) begin
@(negedge clk) ;
en = 1'b1;
a = a + 1 ;
wr = 1'b1 ; //write command
d = d + 1 ;
end
a = 10 ;
repeat(10) begin
@(negedge clk) ;
a = a + 1 ;
wr = 1'b0 ; //read command
end
end // initial begin
//instantiation
defparam u_ram_4x4.MASK = 7 ;
ram_4x4 u_ram_4x4
(
.CLK (clk),
.A (a[AW-1:0]),
.D (d),
.EN (en),
.WR (wr), //1 for write and 0 for read
.Q (q)
);
//stop simulation
initial begin
forever begin
#100;
if ($time >= 1000) $finish ;
end
end
endmodule // test
```
仿真結果如下:
圖中黃色部分,當地址第一次為 c 時寫入數據 4, 當第二次地址為 c 時讀出數據為 4;可知此時 ram 行為正確,且 MASK 不為 3。因為 ram 的 Q 端 bit2 沒有被屏蔽。 當第一次地址為 1 時寫入數據為 9,第二次地址為 1 時讀出的數據卻是 1,因為此時 MASK 為 7,ram 的 Q 端信號 bit3 被屏蔽。由此可知,MASK 參數被正確改寫。

### :pushpin: 帶參數模塊例化
第二種方法就是例化模塊時,將新的參數值寫入模塊例化語句,以此來改寫原有 module 的參數值。 例如對一個地址和數據位寬都可變的 ram 模塊進行帶參數的模塊例化:
```verilog=
ram #(.AW(4), .DW(4))
u_ram
(
.CLK (clk),
.A (a[AW-1:0]),
.D (d),
.EN (en),
.WR (wr), //1 for write and 0 for read
.Q (q)
);
```
ram 模型如下:
```verilog=
module ram
#( parameter AW = 2 ,
parameter DW = 3 )
(
input CLK ,
input [AW-1:0] A ,
input [DW-1:0] D ,
input EN ,
input WR , //1 for write and 0 for read
output reg [DW-1:0] Q
);
reg [DW-1:0] mem [0:(1<<AW)-1] ;
always @(posedge CLK) begin
if (EN && WR) begin
mem[A] <= D ;
end
else if (EN && !WR) begin
Q <= mem[A] ;
end
end
endmodule
```
仿真時,只需在上一例的 testbench 中,將本次例化的模塊 u_ram 覆蓋掉 u_ram_4x4, 或重新添加之即可。 仿真結果如下。由圖可知,ram 模塊的參數 AW 與 DW 均被改寫為 4, 且 ram 行為正確。

### :pushpin: 區別與建議
(1) 和模塊端口實例化一樣,帶參數例化時,也可以不指定原有參數名字,按順序進行參數例化,例如 u_ram 的例化可以描述為:
```verilog=
ram #(4, 4) u_ram (......) ;
```
(2) 當然,利用 defparam 也可以改寫模塊在端口聲明時聲明的參數,利用帶參數例化也可以改寫模塊實體中聲明的參數。例如 u_ram 和 u_ram_4x4 的例化分別可以描述為:
```verilog=
defparam u_ram.AW = 4 ;
defparam u_ram.DW = 4 ;
ram u_ram(......);
ram_4x4 #(.MASK(7)) u_ram_4x4(......);
```
(3) 那能不能混合使用這兩種模塊參數改寫的方式呢?當然能!前提是所有參數都是模塊在端口聲明時聲明的參數或參數都是模塊實體中聲明的參數,例如 u_ram 的聲明還可以表示為(模塊實體中參數可自行實驗驗證):
```verilog=
defparam u_ram.AW = 4 ;
ram #(.DW(4)) u_ram (......); //也只有我这么无聊才会实验这种写法
```
(4) 那如果一個模塊中既有在模塊在端口聲明時聲明的參數,又有在模塊實體中聲明的參數,那這兩種參數還能同時改寫麼?例如在 ram 模塊中加入 MASK 參數,模型如下:
```verilog=
module ram
#( parameter AW = 2 ,
parameter DW = 3 )
(
input CLK ,
input [AW-1:0] A ,
input [DW-1:0] D ,
input EN ,
input WR , //1 for write and 0 for read
output reg [DW-1:0] Q );
parameter MASK = 3 ;
reg [DW-1:0] mem [0:(1<<AW)-1] ;
always @(posedge CLK) begin
if (EN && WR) begin
mem[A] <= D ;
end
else if (EN && !WR) begin
Q <= mem[A] ;
end
end
endmodule
```
此時再用 defparam 改寫參數 MASK 值時,編譯報 Error:
```verilog=
//都采用defparam时会报Error
defparam u_ram.AW = 4 ;
defparam u_ram.DW = 4 ;
defparam u_ram.MASK = 7 ;
ram u_ram (......);
//模块实体中parameter用defparam改写也会报Error
defparam u_ram.MASK = 7 ;
ram #(.AW(4), .DW(4)) u_ram (......);
```
重點來了! ! !如果你用帶參數模塊例化的方法去改寫參數 MASK 的值,編譯不會報錯,MASK 也將被成功改寫!
```verilog=
ram #(.AW(4), .DW(4), .MASK(7)) u_ram (......);
```
可能的解釋為,在編譯器看來,如果有模塊在端口聲明時的參數,那麼實體中的參數將視為 localparam 類型,使用 defparam 將不能改寫模塊實體中聲明的參數。 也可能和編譯器有關係,大家也可以在其他編譯器上實驗。
(5)建議,對已有模塊進行例化並將其相關參數進行改寫時,不要採用 defparam 的方法。除了上述缺點外,defparam 一般也不可綜合。
(6)而且建議,模塊在編寫時,如果預知將被例化且有需要改寫的參數,都將這些參數寫入到模塊端口聲明之前的地方(用關鍵字井號 # 表示)。這樣的代碼格式不僅有很好的可讀性,而且方便調試。
### :pushpin: 階段總結
其實,介紹到這裡,大家完全可以用前面學習到的 Verilog 語言知識,去搭建硬件電路的小茅草屋。對,是小茅草屋。因為硬件語言對應實際硬件電路的這種特殊性,在用 Verilog 建立各種模型時必須考慮實際生成的電路是什麼樣子的,是否符合實際要求。有時候 rtl 仿真能通過,但是最後生成的實際電路可能會工作異常。 所以,要為你的小茅草屋添磚蓋瓦,還需要再學習下進階部分。當然,進階部分也只能讓你的小茅草屋變成硬朗的磚瓦房,能抵擋風雪交加,可能遇到地震還是會垮塌。
如果你想鞏固下你的磚瓦房,去建一套別墅,那你需要再學習下 Verilog 高級篇知識,例如 PLI(編程語言接口)、UDP(用戶自定義原語),時序約束和時序分析等,還需要多參與項目工程積累經驗,特別注意一些設計技巧,例如低功耗設計、異步設計等。當然學會用 SystemVerilog 去全面驗證,又會讓你的建築增加一層防護盾。
但是如果你想把數字電路、Verilog 所有的知識學完,去築一套防砲彈的總統府,那真的是愛莫能助。因為,學海無涯,回頭沒岸哪。 限於篇幅,這裡只介紹下進階篇。有機會,高級篇,技巧篇,也一併補上。