模塊與端口
- 關(guān)鍵詞:模塊,端口饭冬,雙向端口嘀韧,PAD
結(jié)構(gòu)建模方式有 3 類描述語句: Gate(門級(jí))例化語句秤标,UDP (用戶定義原語)例化語句和 module (模塊) 例化語句绝淡。本次主要講述使用最多的模塊級(jí)例化語句。
- 模塊
模塊是 Verilog 中基本單元的定義形式苍姜,是與外界交互的接口牢酵。
模塊格式定義如下:
module module_name
#(parameter_list)
(port_list) ;
Declarations_and_Statements ;
endmodule
模塊定義必須以關(guān)鍵字 module 開始,以關(guān)鍵字 endmodule 結(jié)束衙猪。
模塊名馍乙,端口信號(hào)布近,端口聲明和可選的參數(shù)聲明等,出現(xiàn)在設(shè)計(jì)使用的 Verilog 語句(圖中 Declarations_and_Statements)之前潘拨。
模塊內(nèi)部有可選的 5 部分組成吊输,分別是變量聲明,數(shù)據(jù)流語句铁追,行為級(jí)語句季蚂,低層模塊例化及任務(wù)和函數(shù),如下圖表示琅束。這 5 部分出現(xiàn)順序扭屁、出現(xiàn)位置都是任意的。但是涩禀,各種變量都應(yīng)在使用之前聲明料滥。變量具體聲明的位置不要求,但必須保證在使用之前的位置艾船。
端口
端口是模塊與外界交互的接口葵腹。對(duì)于外部環(huán)境來說,模塊內(nèi)部是不可見的屿岂,對(duì)模塊的調(diào)用只能通過端口連接進(jìn)行践宴。端口列表
模塊的定義中包含一個(gè)可選的端口列表,一般將不帶類型爷怀、不帶位寬的信號(hào)變量羅列在模塊聲明里阻肩。下面是一個(gè) PAD 模型的端口列表:
module pad(
DIN, OEN, PULL,
DOUT, PAD);
一個(gè)模塊如果和外部環(huán)境沒有交互,則可以不用聲明端口列表运授。例如之前我們仿真時(shí) test.sv 文件中的 test 模塊都沒有聲明具體端口烤惊。
module test ; //直接分號(hào)結(jié)束
...... //數(shù)據(jù)流或行為級(jí)描述
endmodule
- 端口聲明
(1) 端口信號(hào)在端口列表中羅列出來以后,就可以在模塊實(shí)體中進(jìn)行聲明了吁朦。
根據(jù)端口的方向柒室,端口類型有 3 種: 輸入(input),輸出(output)和雙向端口(inout)喇完。
input伦泥、inout 類型不能聲明為 reg 數(shù)據(jù)類型,因?yàn)?reg 類型是用于保存數(shù)值的锦溪,而輸入端口只能反映與其相連的外部信號(hào)的變化,不能保存這些信號(hào)的值府怯。
output 可以聲明為 wire 或 reg 數(shù)據(jù)類型刻诊。
上述例子中 pad 模塊的端口聲明,在 module 實(shí)體中就可以表示如下:
//端口類型聲明
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
//端口數(shù)據(jù)類型聲明
wire DIN, OEN ;
wire [1:0] PULL ;
wire PAD ;
reg DOUT ;
(2) 在 Verilog 中牺丙,端口隱式的聲明為 wire 型變量则涯,即當(dāng)端口具有 wire 屬性時(shí)复局,不用再次聲明端口類型為 wire 型。但是粟判,當(dāng)端口有 reg 屬性時(shí)亿昏,則 reg 聲明不可省略。
上述例子中的端口聲明档礁,則可以簡(jiǎn)化為:
//端口類型聲明
input DIN, OEN ;
input [1:0] PULL ;
inout PAD ;
output DOUT ;
reg DOUT ;
(3) 當(dāng)然角钩,信號(hào) DOUT 的聲明完全可以合并成一句:
output reg DOUT ;
(4) 還有一種更簡(jiǎn)潔且常用的方法來聲明端口,即在 module 聲明時(shí)就陳列出端口及其類型呻澜。reg 型端口要么在 module 聲明時(shí)聲明递礼,要么在 module 實(shí)體中聲明,例如以下 2 種寫法是等效的羹幸。
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 ;
模塊例化
關(guān)鍵字:例化脊髓,generate,全加器栅受,層次訪問
在一個(gè)模塊中引用另一個(gè)模塊将硝,對(duì)其端口進(jìn)行相關(guān)連接,叫做模塊例化屏镊。模塊例化建立了描述的層次依疼。信號(hào)端口可以通過位置或名稱關(guān)聯(lián),端口連接也必須遵循一些規(guī)則闸衫。命名端口連接
這種方法將需要例化的模塊端口與外部信號(hào)按照其名字進(jìn)行連接涛贯,端口順序隨意,可以與引用 module 的聲明端口順序不一致蔚出,只要保證端口名字與外部信號(hào)匹配即可弟翘。
下面是例化一次 1bit 全加器的例子:
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]));
如果某些輸出端口并不需要在外部連接,例化時(shí) 可以懸空不連接骄酗,甚至刪除稀余。一般來說,input 端口在例化時(shí)不能刪除趋翻,否則編譯報(bào)錯(cuò)睛琳,output 端口在例化時(shí)可以刪除踏烙。例如:
//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));
- 順序端口連接
這種方法將需要例化的模塊端口按照模塊聲明時(shí)端口的順序與外部信號(hào)進(jìn)行匹配連接,位置要嚴(yán)格保持一致黍少。例如例化一次 1bit 全加器的代碼可以改為:
full_adder1 u_adder1(
a[1], b[1], co_temp[0], so_bit1, co_temp[1]);
雖然代碼從書寫上可能會(huì)占用相對(duì)較少的空間菩掏,但代碼可讀性降低砸紊,也不易于調(diào)試。有時(shí)候在大型的設(shè)計(jì)中可能會(huì)有很多個(gè)端口系草,端口信號(hào)的順序時(shí)不時(shí)的可能也會(huì)有所改動(dòng)廊酣,此時(shí)再利用順序端口連接進(jìn)行模塊例化,顯然是不方便的戒职。所以平時(shí)乳乌,建議采用命名端口方式對(duì)模塊進(jìn)行例化。
- 端口連接規(guī)則
- 輸入端口
模塊例化時(shí)其弊,從模塊外部來講仰担, input 端口可以連接 wire 或 reg 型變量。這與模塊聲明是不同的拌滋,從模塊內(nèi)部來講,input 端口必須是 wire 型變量。
- 輸出端口
模塊例化時(shí),從模塊外部來講铸敏,output 端口必須連接 wire 型變量桩撮。這與模塊聲明是不同的,從模塊內(nèi)部來講旱爆,output 端口可以是 wire 或 reg 型變量脆烟。
- 輸入輸出端口
模塊例化時(shí)拜鹤,從模塊外部來講,inout 端口必須連接 wire 型變量。這與模塊聲明是相同的。
- 懸空端口
模塊例化時(shí)咙边,如果某些信號(hào)不需要與外部信號(hào)進(jìn)行連接交互市殷,我們可以將其懸空带迟,即端口例化處保留空白即可,上述例子中有提及翠语。
output 端口正常懸空時(shí)啡专,我們甚至可以在例化時(shí)將其刪除。
input 端口正常懸空時(shí)制圈,懸空信號(hào)的邏輯功能表現(xiàn)為高阻狀態(tài)(邏輯值為 z)。但是畔况,例化時(shí)一般不能將懸空的 input 端口刪除鲸鹦,否則編譯會(huì)報(bào)錯(cuò)眯停,例如:
//下述代碼編譯會(huì)報(bào)Warning
full_adder4 u_adder4(
.a (a),
.b (b),
.c (),
.so (so),
.co (co));
//如果模塊full_adder4有input端口c慎宾,則下述代碼編譯是會(huì)報(bào)Error
full_adder4 u_adder4(
.a (a),
.b (b),
.so (so),
.co (co));
一般來說泼舱,建議 input 端口不要做懸空處理铃诬,無其他外部連接時(shí)賦值其常量闸迷,例如:
full_adder4 u_adder4(
.a (a),
.b (b),
.c (1'b0),
.so (so),
.co (co));
- 位寬匹配
當(dāng)例化端口與連續(xù)信號(hào)位寬不匹配時(shí)妖谴,端口會(huì)通過無符號(hào)數(shù)的右對(duì)齊或截?cái)喾绞竭M(jìn)行匹配挪哄。
假如在模塊 full_adder4 中,端口 a 和端口 b 的位寬都為 4bit,則下面代碼的例化結(jié)果會(huì)導(dǎo)致:
u_adder4.a = {2'bzz, a[1:0]}, u_adder4.b = b[3:0] 舰褪。
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));
端口連續(xù)信號(hào)類型
連接端口的信號(hào)類型可以是占拍,1)標(biāo)識(shí)符折汞,2)位選擇暴备,3)部分選擇悠瞬,4)上述類型的合并,5)用于輸入端口的表達(dá)式涯捻。
當(dāng)然浅妆,信號(hào)名字可以與端口名字一樣,但他們的意義是不一樣的障癌,分別代表的是 2 個(gè)模塊內(nèi)的信號(hào)凌外。
用 generate 進(jìn)行模塊例化
當(dāng)例化多個(gè)相同的模塊時(shí),一個(gè)一個(gè)的手動(dòng)例化會(huì)比較繁瑣涛浙。用 generate 語句進(jìn)行多個(gè)模塊的重復(fù)例化康辑,可大大簡(jiǎn)化程序的編寫過程摄欲。
重復(fù)例化 4 個(gè) 1bit 全加器組成一個(gè) 4bit 全加器的代碼如下:
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 ;
//第一個(gè)例化模塊一般格式有所差異,需要單獨(dú)例化
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]), //上一個(gè)全加器的溢位是下一個(gè)的進(jìn)位
.So (so[i]),
.Co (co_temp[i]));
end
endgenerate
assign co = co_temp[3] ;
endmodule
- 層次訪問
每一個(gè)例化模塊的名字疮薇,每個(gè)模塊的信號(hào)變量等胸墙,都使用一個(gè)特定的標(biāo)識(shí)符進(jìn)行定義。在整個(gè)層次設(shè)計(jì)中按咒,每個(gè)標(biāo)識(shí)符都具有唯一的位置與名字迟隅。
Verilog 中,通過使用一連串的 . 符號(hào)對(duì)各個(gè)模塊的標(biāo)識(shí)符進(jìn)行層次分隔連接励七,就可以在任何地方通過指定完整的層次名對(duì)整個(gè)設(shè)計(jì)中的標(biāo)識(shí)符進(jìn)行訪問智袭。
層次訪問多見于仿真中。
例如掠抬,有以下層次設(shè)計(jì)吼野,則葉單元、子模塊和頂層模塊間的信號(hào)就可以相互訪問两波。
//u_n1模塊中訪問u_n3模塊信號(hào):
a = top.u_m2.u_n3.c ;
//u_n1模塊中訪問top模塊信號(hào)
if (top.p == 'b0) a = 1'b1 ;
//top模塊中訪問u_n4模塊信號(hào)
assign p = top.u_m2.u_n4.d ;
帶參數(shù)例化
- 關(guān)鍵詞: defparam瞳步,參數(shù),例化腰奋,ram
當(dāng)一個(gè)模塊被另一個(gè)模塊引用例化時(shí)谚攒,高層模塊可以對(duì)低層模塊的參數(shù)值進(jìn)行改寫。這樣就允許在編譯時(shí)將不同的參數(shù)傳遞給多個(gè)相同名字的模塊氛堕,而不用單獨(dú)為只有參數(shù)不同的多個(gè)模塊再新建文件。
參數(shù)覆蓋有 2 種方式:1)使用關(guān)鍵字 defparam野蝇,2)帶參數(shù)值模塊例化讼稚。
- defparam 語句
可以用關(guān)鍵字 defparam 通過模塊層次調(diào)用的方法,來改寫低層次模塊的參數(shù)值绕沈。
例如對(duì)一個(gè)單口地址線和數(shù)據(jù)線都是 4bit 寬度的 ram 模塊的 MASK 參數(shù)進(jìn)行改寫:
//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 的模型如下:
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
對(duì)此進(jìn)行一個(gè)簡(jiǎn)單的仿真锐想,testbench 編寫如下:
`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
- 帶參數(shù)模塊例化
第二種方法就是例化模塊時(shí),將新的參數(shù)值寫入模塊例化語句乍狐,以此來改寫原有 module 的參數(shù)值赠摇。
例如對(duì)一個(gè)地址和數(shù)據(jù)位寬都可變的 ram 模塊進(jìn)行帶參數(shù)的模塊例化:
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 模型如下:
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
區(qū)別與建議
(1) 和模塊端口實(shí)例化一樣,帶參數(shù)例化時(shí)浅蚪,也可以不指定原有參數(shù)名字藕帜,按順序進(jìn)行參數(shù)例化,例如 u_ram 的例化可以描述為:
ram #(4, 4) u_ram (......) ;
(2) 當(dāng)然惜傲,利用 defparam 也可以改寫模塊在端口聲明時(shí)聲明的參數(shù)洽故,利用帶參數(shù)例化也可以改寫模塊實(shí)體中聲明的參數(shù)。例如 u_ram 和 u_ram_4x4 的例化分別可以描述為:
defparam u_ram.AW = 4 ;
defparam u_ram.DW = 4 ;
ram u_ram(......);
ram_4x4 #(.MASK(7)) u_ram_4x4(......);
(3) 那能不能混合使用這兩種模塊參數(shù)改寫的方式呢盗誊?當(dāng)然能时甚!前提是所有參數(shù)都是模塊在端口聲明時(shí)聲明的參數(shù)或參數(shù)都是模塊實(shí)體中聲明的參數(shù)隘弊,例如 u_ram 的聲明還可以表示為(模塊實(shí)體中參數(shù)可自行實(shí)驗(yàn)驗(yàn)證):
defparam u_ram.AW = 4 ;
ram #(.DW(4)) u_ram (......);
(4) 那如果一個(gè)模塊中既有在模塊在端口聲明時(shí)聲明的參數(shù)共苛,又有在模塊實(shí)體中聲明的參數(shù)搔耕,那這兩種參數(shù)還能同時(shí)改寫么雏婶?例如在 ram 模塊中加入 MASK 參數(shù)姑宽,模型如下:
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
此時(shí)再用 defparam 改寫參數(shù) MASK 值時(shí)魂毁,編譯報(bào) Error:
//都采用defparam時(shí)會(huì)報(bào)Error
defparam u_ram.AW = 4 ;
defparam u_ram.DW = 4 ;
defparam u_ram.MASK = 7 ;
ram u_ram (......);
//模塊實(shí)體中parameter用defparam改寫也會(huì)報(bào)Error
defparam u_ram.MASK = 7 ;
ram #(.AW(4), .DW(4)) u_ram (......);
重點(diǎn)來了W檀痢]锪跪妥!如果你用帶參數(shù)模塊例化的方法去改寫參數(shù) MASK 的值舅列,編譯不會(huì)報(bào)錯(cuò)肌割,MASK 也將被成功改寫!
ram #(.AW(4), .DW(4), .MASK(7)) u_ram (......);
可能的解釋為帐要,在編譯器看來把敞,如果有模塊在端口聲明時(shí)的參數(shù),那么實(shí)體中的參數(shù)將視為localparam 類型榨惠,使用 defparam 將不能改寫模塊實(shí)體中聲明的參數(shù)奋早。
也可能和編譯器有關(guān)系,大家也可以在其他編譯器上實(shí)驗(yàn)赠橙。
(5)建議耽装,對(duì)已有模塊進(jìn)行例化并將其相關(guān)參數(shù)進(jìn)行改寫時(shí),不要采用 defparam 的方法期揪。除了上述缺點(diǎn)外掉奄,defparam 一般也不可綜合。
(6)而且建議凤薛,模塊在編寫時(shí)姓建,如果預(yù)知將被例化且有需要改寫的參數(shù),都將這些參數(shù)寫入到模塊端口聲明之前的地方(用關(guān)鍵字井號(hào) # 表示)缤苫。這樣的代碼格式不僅有很好的可讀性速兔,而且方便調(diào)試。
函數(shù)
- 關(guān)鍵詞:函數(shù)活玲,大小端轉(zhuǎn)換涣狗,數(shù)碼管譯碼
在 Verilog 中,可以利用任務(wù)(關(guān)鍵字為 task)或函數(shù)(關(guān)鍵字為 function)舒憾,將重復(fù)性的行為級(jí)設(shè)計(jì)進(jìn)行提取镀钓,并在多個(gè)地方調(diào)用,來避免重復(fù)代碼的多次編寫珍剑,使代碼更加的簡(jiǎn)潔掸宛、易懂。 函數(shù)
函數(shù)只能在模塊中定義招拙,位置任意唧瘾,并在模塊的任何地方引用措译,作用范圍也局限于此模塊。函數(shù)主要有以下幾個(gè)特點(diǎn):
1)不含有任何延遲饰序、時(shí)序或時(shí)序控制邏輯
2)至少有一個(gè)輸入變量
3)只有一個(gè)返回值领虹,且沒有輸出
4)不含有非阻塞賦值語句
5)函數(shù)可以調(diào)用其他函數(shù),但是不能調(diào)用任務(wù)
Verilog 函數(shù)聲明格式如下:
function [range-1:0] function_id ;
input_declaration ;
other_declaration ;
procedural_statement ;
endfunction
函數(shù)在聲明時(shí)求豫,會(huì)隱式的聲明一個(gè)寬度為 range塌衰、 名字為 function_id 的寄存器變量,函數(shù)的返回值通過這個(gè)變量進(jìn)行傳遞蝠嘉。當(dāng)該寄存器變量沒有指定位寬時(shí)最疆,默認(rèn)位寬為 1。
函數(shù)通過指明函數(shù)名與輸入變量進(jìn)行調(diào)用蚤告。函數(shù)結(jié)束時(shí)努酸,返回值被傳遞到調(diào)用處。
函數(shù)調(diào)用格式如下:
function_id(input1, input2, …);
下面用函數(shù)實(shí)現(xiàn)一個(gè)數(shù)據(jù)大小端轉(zhuǎn)換的功能杜恰。
當(dāng)輸入為 4'b0011 時(shí)获诈,輸出可為 4'b1100。例如:
module endian_rvs
#(parameter N = 4)
(
input en, //enable control
input [N-1:0] a ,
output [N-1:0] b
);
reg [N-1:0] b_temp ;
always @(*) begin
if (en) begin
b_temp = data_rvs(a);
end
else begin
b_temp = 0 ;
end
end
assign b = b_temp ;
//function entity
function [N-1:0] data_rvs ;
input [N-1:0] data_in ;
parameter MASK = 32'h3 ;
integer k ;
begin
for(k=0; k<N; k=k+1) begin
data_rvs[N-k-1] = data_in[k] ;
end
end
endfunction
endmodule
函數(shù)里的參數(shù)也可以改寫心褐,例如:
defparam data_rvs.MASK = 32'd7 ;
但是仿真時(shí)發(fā)現(xiàn)舔涎,此種寫法編譯可以通過,仿真結(jié)果中逗爹,函數(shù)里的參數(shù) MASK 實(shí)際并沒有改寫成功亡嫌,仍然為 32'h3。這可能和編譯器有關(guān)掘而,有興趣的學(xué)者可以用其他 Verilog 編譯器進(jìn)行下實(shí)驗(yàn)昼伴。
函數(shù)在聲明時(shí),也可以在函數(shù)名后面加一個(gè)括號(hào)镣屹,將 input 聲明包起來。
例如上述大小端聲明函數(shù)可以表示為:
function [N-1:0] data_rvs(
input [N-1:0] data_in
......
) ;
- 常數(shù)函數(shù)
常數(shù)函數(shù)是指在仿真開始之前价涝,在編譯期間就計(jì)算出結(jié)果為常數(shù)的函數(shù)女蜈。常數(shù)函數(shù)不允許訪問全局變量或者調(diào)用系統(tǒng)函數(shù),但是可以調(diào)用另一個(gè)常數(shù)函數(shù)色瘩。
這種函數(shù)能夠用來引用復(fù)雜的值伪窖,因此可用來代替常量。
例如下面一個(gè)常量函數(shù)居兆,可以來計(jì)算模塊中地址總線的寬度:
parameter MEM_DEPTH = 256 ;
reg [logb2(MEM_DEPTH)-1: 0] addr ; //可得addr的寬度為8bit
function integer logb2;
input integer depth ;
//256為9bit覆山,我們最終數(shù)據(jù)應(yīng)該是8,所以需depth=2時(shí)提前停止循環(huán)
for(logb2=0; depth>1; logb2=logb2+1) begin
depth = depth >> 1 ;
end
endfunction
- automatic 函數(shù)
在 Verilog 中泥栖,一般函數(shù)的局部變量是靜態(tài)的簇宽,即函數(shù)的每次調(diào)用勋篓,函數(shù)的局部變量都會(huì)使用同一個(gè)存儲(chǔ)空間。若某個(gè)函數(shù)在兩個(gè)不同的地方同時(shí)并發(fā)的調(diào)用魏割,那么兩個(gè)函數(shù)調(diào)用行為同時(shí)對(duì)同一塊地址進(jìn)行操作譬嚣,會(huì)導(dǎo)致不確定的函數(shù)結(jié)果。
Verilog 用關(guān)鍵字 automatic 來對(duì)函數(shù)進(jìn)行說明钞它,此類函數(shù)在調(diào)用時(shí)是可以自動(dòng)分配新的內(nèi)存空間的拜银,也可以理解為是可遞歸的。因此遭垛,automatic 函數(shù)中聲明的局部變量不能通過層次命名進(jìn)行訪問尼桶,但是 automatic 函數(shù)本身可以通過層次名進(jìn)行調(diào)用。
下面用 automatic 函數(shù)锯仪,實(shí)現(xiàn)階乘計(jì)算:
wire [31:0] results3 = factorial(4);
function automatic integer factorial ;
input integer data ;
integer i ;
begin
factorial = (data>=2)? data * factorial(data-1) : 1 ;
end
endfunction // factorial
- 數(shù)碼管譯碼
每位數(shù)碼顯示端有 8 個(gè)光亮控制端(如圖中 a-g 所示)泵督,可以用來控制顯示數(shù)字 0-9 。
而數(shù)碼管有 4 個(gè)片選(如圖中 1-4)卵酪,用來控制此時(shí)哪一位數(shù)碼顯示端應(yīng)該選通幌蚊,即應(yīng)該發(fā)光。倘若在很短的時(shí)間內(nèi)溃卡,依次對(duì) 4 個(gè)數(shù)碼顯示端進(jìn)行片選發(fā)光溢豆,同時(shí)在不同片選下給予不同的光亮控制(各對(duì)應(yīng) 4 位十進(jìn)制數(shù)字),那么在肉眼不能分辨的情況下瘸羡,就達(dá)到了同時(shí)顯示 4 位十進(jìn)制數(shù)字的效果漩仙。
下面,我們用信號(hào) abcdefg 來控制光亮控制端犹赖,用信號(hào) csn 來控制片選队他,4 位 10 進(jìn)制的數(shù)字個(gè)十百千位分別用 4 個(gè) 4bit 信號(hào) single_digit, ten_digit, hundred_digit, kilo_digit 來表示,則一個(gè)數(shù)碼管的顯示設(shè)計(jì)可以描述如下:
module digital_tube
(
input clk ,
input rstn ,
input en ,
input [3:0] single_digit ,
input [3:0] ten_digit ,
input [3:0] hundred_digit ,
input [3:0] kilo_digit ,
output reg [3:0] csn , //chip select, low-available
output reg [6:0] abcdefg //light control
);
reg [1:0] scan_r ; //scan_ctrl
always @ (posedge clk or negedge rstn) begin
if(!rstn)begin
csn <= 4'b1111;
abcdefg <= 'd0;
scan_r <= 3'd0;
end
else if (en) begin
case(scan_r)
2'd0:begin
scan_r <= 3'd1;
csn <= 4'b0111; //select single digit
abcdefg <= dt_translate(single_digit);
end
2'd1:begin
scan_r <= 3'd2;
csn <= 4'b1011; //select ten digit
abcdefg <= dt_translate(ten_digit);
end
2'd2:begin
scan_r <= 3'd3;
csn <= 4'b1101; //select hundred digit
abcdefg <= dt_translate(hundred_digit);
end
2'd3:begin
scan_r <= 3'd0;
csn <= 4'b1110; //select kilo digit
abcdefg <= dt_translate(kilo_digit);
end
endcase
end
end
/*------------ translate function -------*/
function [6:0] dt_translate;
input [3:0] data;
begin
case(data)
4'd0: dt_translate = 7'b1111110; //number 0 -> 0x7e
4'd1: dt_translate = 7'b0110000; //number 1 -> 0x30
4'd2: dt_translate = 7'b1101101; //number 2 -> 0x6d
4'd3: dt_translate = 7'b1111001; //number 3 -> 0x79
4'd4: dt_translate = 7'b0110011; //number 4 -> 0x33
4'd5: dt_translate = 7'b1011011; //number 5 -> 0x5b
4'd6: dt_translate = 7'b1011111; //number 6 -> 0x5f
4'd7: dt_translate = 7'b1110000; //number 7 -> 0x70
4'd8: dt_translate = 7'b1111111; //number 8 -> 0x7f
4'd9: dt_translate = 7'b1111011; //number 9 -> 0x7b
endcase
end
endfunction
endmodule
任務(wù)
關(guān)鍵詞:任務(wù)
-
任務(wù)與函數(shù)的區(qū)別
和函數(shù)一樣峻村,任務(wù)(task)可以用來描述共同的代碼段麸折,并在模塊內(nèi)任意位置被調(diào)用,讓代碼更加的直觀易讀粘昨。函數(shù)一般用于組合邏輯的各種轉(zhuǎn)換和計(jì)算垢啼,而任務(wù)更像一個(gè)過程,不僅能完成函數(shù)的功能张肾,還可以包含時(shí)序控制邏輯芭析。下面對(duì)任務(wù)與函數(shù)的區(qū)別進(jìn)行概括:
任務(wù)
任務(wù)聲明
任務(wù)在模塊中任意位置定義,并在模塊內(nèi)任意位置引用吞瞪,作用范圍也局限于此模塊馁启。
模塊內(nèi)子程序出現(xiàn)下面任意一個(gè)條件時(shí),則必須使用任務(wù)而不能使用函數(shù)芍秆。
1)子程序中包含時(shí)序控制邏輯惯疙,例如延遲翠勉,事件控制等
2)沒有輸入變量
3)沒有輸出或輸出端的數(shù)量大于 1
Verilog 任務(wù)聲明格式如下:
task task_id ;
port_declaration ;
procedural_statement ;
endtask
任務(wù)中使用關(guān)鍵字 input、output 和 inout 對(duì)端口進(jìn)行聲明螟碎。input 眉菱、inout 型端口將變量從任務(wù)外部傳遞到內(nèi)部,output掉分、inout 型端口將任務(wù)執(zhí)行完畢時(shí)的結(jié)果傳回到外部俭缓。
進(jìn)行任務(wù)的邏輯設(shè)計(jì)時(shí),可以把 input 聲明的端口變量看做 wire 型酥郭,把 output 聲明的端口變量看做 reg 型华坦。但是不需要用 reg 對(duì) output 端口再次說明。
對(duì) output 信號(hào)賦值時(shí)也不要用關(guān)鍵字 assign不从。為避免時(shí)序錯(cuò)亂惜姐,建議 output 信號(hào)采用阻塞賦值。
例如椿息,一個(gè)帶延時(shí)的異或功能 task 描述如下:
task xor_oper_iner;
input [N-1:0] numa;
input [N-1:0] numb;
output [N-1:0] numco ;
//output reg [N-1:0] numco ; //無需再注明 reg 類型歹袁,雖然注明也可能沒錯(cuò)
#3 numco = numa ^ numb ;
//assign #3 numco = numa ^ numb ; //不用assign,因?yàn)檩敵瞿J(rèn)是reg
endtask
任務(wù)在聲明時(shí)寝优,也可以在任務(wù)名后面加一個(gè)括號(hào)条舔,將端口聲明包起來。
上述設(shè)計(jì)可以更改為:
task xor_oper_iner(
input [N-1:0] numa,
input [N-1:0] numb,
output [N-1:0] numco ) ;
#3 numco = numa ^ numb ;
endtask
- 任務(wù)調(diào)用
任務(wù)可單獨(dú)作為一條語句出現(xiàn)在 initial 或 always 塊中乏矾,調(diào)用格式如下:
task_id(input1, input2, …,outpu1, output2, …);
任務(wù)調(diào)用時(shí)孟抗,端口必須按順序?qū)?yīng)。
輸入端連接的模塊內(nèi)信號(hào)可以是 wire 型钻心,也可以是 reg 型凄硼。輸出端連接的模塊內(nèi)信號(hào)要求一定是 reg 型,這點(diǎn)需要注意捷沸。
module xor_oper
#(parameter N = 4)
(
input clk ,
input rstn ,
input [N-1:0] a ,
input [N-1:0] b ,
output [N-1:0] co );
reg [N-1:0] co_t ;
always @(*) begin //任務(wù)調(diào)用
xor_oper_iner(a, b, co_t);
end
reg [N-1:0] co_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
co_r <= 'b0 ;
end
else begin
co_r <= co_t ; //數(shù)據(jù)緩存
end
end
assign co = co_r ;
/*------------ task -------*/
task xor_oper_iner;
input [N-1:0] numa;
input [N-1:0] numb;
output [N-1:0] numco ;
#3 numco = numa ^ numb ; //阻塞賦值摊沉,易于控制時(shí)序
endtask
endmodule
- 任務(wù)操作全局變量
因?yàn)槿蝿?wù)可以看做是過程性賦值,所以任務(wù)的 output 端信號(hào)返回時(shí)間是在任務(wù)中所有語句執(zhí)行完畢之后痒给。
任務(wù)內(nèi)部變量也只有在任務(wù)中可見坯钦,如果想具體觀察任務(wù)中對(duì)變量的操作過程,需要將觀察的變量聲明在模塊之內(nèi)侈玄、任務(wù)之外,可謂之"全局變量"吟温。
例如有以下 2 種嘗試?yán)?task 產(chǎn)生時(shí)鐘的描述方式序仙。
//way1 to decirbe clk generating, not work
task clk_rvs_iner ;
output clk_no_rvs ;
# 5 ; clk_no_rvs = 0 ;
# 5 ; clk_no_rvs = 1 ;
endtask
reg clk_test1 ;
always clk_rvs_iner(clk_test1);
//way2: use task to operate global varialbes to generating clk
reg clk_test2 ;
task clk_rvs_global ;
# 5 ; clk_test2 = 0 ;
# 5 ; clk_test2 = 1 ;
endtask // clk_rvs_iner
always clk_rvs_global;
- automatic 任務(wù)
和函數(shù)一樣,Verilog 中任務(wù)調(diào)用時(shí)的局部變量都是靜態(tài)的鲁豪∨说浚可以用關(guān)鍵字 automatic 來對(duì)任務(wù)進(jìn)行聲明律秃,那么任務(wù)調(diào)用時(shí)各存儲(chǔ)空間就可以動(dòng)態(tài)分配,每個(gè)調(diào)用的任務(wù)都各自獨(dú)立的對(duì)自己獨(dú)有的地址空間進(jìn)行操作治唤,而不影響多個(gè)相同任務(wù)調(diào)用時(shí)的并發(fā)執(zhí)行棒动。
如果一任務(wù)代碼段被 2 處及以上調(diào)用,一定要用關(guān)鍵字 automatic 聲明宾添。
當(dāng)沒有使用 automatic 聲明任務(wù)時(shí)船惨,任務(wù)被 2 次調(diào)用,可能出現(xiàn)信號(hào)間干擾缕陕,例如下面代碼描述:
task test_flag ;
input [3:0] cnti ;
input en ;
output [3:0] cnto ;
if (en) cnto = cnti ;
endtask
reg en_cnt ;
reg [3:0] cnt_temp ;
initial begin
en_cnt = 1 ;
cnt_temp = 0 ;
#25 ; en_cnt = 0 ;
end
always #10 cnt_temp = cnt_temp + 1 ;
reg [3:0] cnt1, cnt2 ;
always @(posedge clk) test_flag(2, en_cnt, cnt1); //task(1)
always @(posedge clk) test_flag(cnt_temp, !en_cnt, cnt2);//task(2)
狀態(tài)機(jī)
- 關(guān)鍵詞:狀態(tài)機(jī)粱锐,售賣機(jī)
有限狀態(tài)機(jī)(Finite-State Machine,F(xiàn)SM)扛邑,簡(jiǎn)稱狀態(tài)機(jī)怜浅,是表示有限個(gè)狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動(dòng)作等行為的數(shù)學(xué)模型。狀態(tài)機(jī)不僅是一種電路的描述工具蔬崩,而且也是一種思想方法恶座,在電路設(shè)計(jì)的系統(tǒng)級(jí)和 RTL 級(jí)有著廣泛的應(yīng)用。
狀態(tài)機(jī)類型
Verilog 中狀態(tài)機(jī)主要用于同步時(shí)序邏輯的設(shè)計(jì)沥阳,能夠在有限個(gè)狀態(tài)之間按一定要求和規(guī)律切換時(shí)序電路的狀態(tài)跨琳。狀態(tài)的切換方向不但取決于各個(gè)輸入值,還取決于當(dāng)前所在狀態(tài)沪袭。 狀態(tài)機(jī)可分為 2 類:Moore 狀態(tài)機(jī)和 Mealy 狀態(tài)機(jī)湾宙。Moore 型狀態(tài)機(jī)
Moore 型狀態(tài)機(jī)的輸出只與當(dāng)前狀態(tài)有關(guān),與當(dāng)前輸入無關(guān)冈绊。
輸出會(huì)在一個(gè)完整的時(shí)鐘周期內(nèi)保持穩(wěn)定侠鳄,即使此時(shí)輸入信號(hào)有變化,輸出也不會(huì)變化死宣。輸入對(duì)輸出的影響要到下一個(gè)時(shí)鐘周期才能反映出來伟恶。這也是 Moore 型狀態(tài)機(jī)的一個(gè)重要特點(diǎn):輸入與輸出是隔離開來的。
- Mealy 型狀態(tài)機(jī)
Mealy 型狀態(tài)機(jī)的輸出毅该,不僅與當(dāng)前狀態(tài)有關(guān)博秫,還取決于當(dāng)前的輸入信號(hào)。
Mealy 型狀態(tài)機(jī)的輸出是在輸入信號(hào)變化以后立刻發(fā)生變化眶掌,且輸入變化可能出現(xiàn)在任何狀態(tài)的時(shí)鐘周期內(nèi)挡育。因此,同種邏輯下朴爬,Mealy 型狀態(tài)機(jī)輸出對(duì)輸入的響應(yīng)會(huì)比 Moore 型狀態(tài)機(jī)早一個(gè)時(shí)鐘周期即寒。
- 狀態(tài)機(jī)設(shè)計(jì):3 段式(推薦)
狀態(tài)機(jī)設(shè)計(jì)如下:
(0) 首先,根據(jù)狀態(tài)機(jī)的個(gè)數(shù)確定狀態(tài)機(jī)編碼。利用編碼給狀態(tài)寄存器賦值母赵,代碼可讀性更好逸爵。
(1) 狀態(tài)機(jī)第一段,時(shí)序邏輯凹嘲,非阻塞賦值师倔,傳遞寄存器的狀態(tài)。
(2) 狀態(tài)機(jī)第二段周蹭,組合邏輯趋艘,阻塞賦值,根據(jù)當(dāng)前狀態(tài)和當(dāng)前輸入谷醉,確定下一個(gè)狀態(tài)機(jī)的狀態(tài)致稀。
(3) 狀態(tài)機(jī)第三代,時(shí)序邏輯俱尼,非阻塞賦值抖单,因?yàn)槭?Mealy 型狀態(tài)機(jī),根據(jù)當(dāng)前狀態(tài)和當(dāng)前輸入遇八,確定輸出信號(hào)矛绘。
// vending-machine
// 2 yuan for a bottle of drink
// only 2 coins supported: 5 jiao and 1 yuan
// finish the function of selling and changing
module vending_machine_p3 (
input clk ,
input rstn ,
input [1:0] coin , //01 for 0.5 jiao, 10 for 1 yuan
output [1:0] change ,
output sell //output the drink
);
//machine state decode
parameter IDLE = 3'd0 ;
parameter GET05 = 3'd1 ;
parameter GET10 = 3'd2 ;
parameter GET15 = 3'd3 ;
//machine variable
reg [2:0] st_next ;
reg [2:0] st_cur ;
//(1) state transfer
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
st_cur <= 'b0 ;
end
else begin
st_cur <= st_next ;
end
end
//(2) state switch, using block assignment for combination-logic
//all case items need to be displayed completely
always @(*) begin
//st_next = st_cur ;//如果條件選項(xiàng)考慮不全,可以賦初值消除latch
case(st_cur)
IDLE:
case (coin)
2'b01: st_next = GET05 ;
2'b10: st_next = GET10 ;
default: st_next = IDLE ;
endcase
GET05:
case (coin)
2'b01: st_next = GET10 ;
2'b10: st_next = GET15 ;
default: st_next = GET05 ;
endcase
GET10:
case (coin)
2'b01: st_next = GET15 ;
2'b10: st_next = IDLE ;
default: st_next = GET10 ;
endcase
GET15:
case (coin)
2'b01,2'b10:
st_next = IDLE ;
default: st_next = GET15 ;
endcase
default: st_next = IDLE ;
endcase
end
//(3) output logic, using non-block assignment
reg [1:0] change_r ;
reg sell_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
end
else if ((st_cur == GET15 && coin ==2'h1)
|| (st_cur == GET10 && coin ==2'd2)) begin
change_r <= 2'b0 ;
sell_r <= 1'b1 ;
end
else if (st_cur == GET15 && coin == 2'h2) begin
change_r <= 2'b1 ;
sell_r <= 1'b1 ;
end
else begin
change_r <= 2'b0 ;
sell_r <= 1'b0 ;
end
end
assign sell = sell_r ;
assign change = change_r ;
endmodule
- Verilog 書寫規(guī)范
在編程時(shí)多注意以下幾點(diǎn)刃永,也可以避免大多數(shù)的競(jìng)爭(zhēng)與冒險(xiǎn)問題货矮。
1)時(shí)序電路建模時(shí),用非阻塞賦值斯够。
2)組合邏輯建模時(shí)囚玫,用阻塞賦值。
3)在同一個(gè) always 塊中建立時(shí)序和組合邏輯模型時(shí)读规,用非阻塞賦值抓督。
4)在同一個(gè) always 塊中不要既使用阻塞賦值又使用非阻塞賦值。
5)不要在多個(gè) always 塊中為同一個(gè)變量賦值束亏。
6)避免 latch 產(chǎn)生铃在。
下面,對(duì)以上注意事項(xiàng)逐條分析碍遍。
1)時(shí)序電路建模時(shí)定铜,用非阻塞賦值
前面講述非阻塞賦值時(shí)就陳述過,時(shí)序電路中非阻塞賦值可以消除競(jìng)爭(zhēng)冒險(xiǎn)怕敬。
例如下面代碼描述揣炕,由于無法確定 a 與 b 阻塞賦值的操作順序,就有可能帶來競(jìng)爭(zhēng)冒險(xiǎn)东跪。
always @(posedge clk) begin
a = b ;
b = a ;
end
而使用非阻塞賦值時(shí)畸陡,賦值操作是同時(shí)進(jìn)行的矮烹,所以就不會(huì)帶來競(jìng)爭(zhēng)冒險(xiǎn),如以下代碼描述罩锐。
always @(posedge clk) begin
a <= b ;
b <= a ;
end
2)組合邏輯建模時(shí),用阻塞賦值
例如卤唉,我們想實(shí)現(xiàn) C = A&B, F=C&D 的組合邏輯功能涩惑,用非阻塞賦值語句如下。
兩條賦值語句同時(shí)賦值桑驱,F(xiàn) <= C & D 中使用的是信號(hào) C 的舊值竭恬,所以導(dǎo)致此時(shí)的邏輯是錯(cuò)誤的,F(xiàn) 的邏輯值不等于 A&B&D熬的。
而且痊硕,此時(shí)要求信號(hào) C 具有存儲(chǔ)功能,但不是時(shí)鐘驅(qū)動(dòng)押框,所以 C 可能會(huì)被綜合成鎖存器(latch)岔绸,導(dǎo)致競(jìng)爭(zhēng)冒險(xiǎn)。
always @(*) begin
C <= A & B ;
F <= C & D ;
end
對(duì)代碼進(jìn)行如下修改橡伞,F(xiàn) = C & D 的操作一定是在 C = A & B 之后盒揉,此時(shí) F 的邏輯值等于 A&B&D,符合設(shè)計(jì)兑徘。
always @(*) begin
C = A & B ;
F = C & D ;
end
3)在同一個(gè) always 塊中建立時(shí)序和組合邏輯模型時(shí)刚盈,用非阻塞賦值
雖然時(shí)序電路中可能涉及組合邏輯,但是如果賦值操作使用非阻塞賦值挂脑,仍然會(huì)導(dǎo)致如規(guī)范 1 中所涉及的類似問題藕漱。
例如在時(shí)鐘驅(qū)動(dòng)下完成一個(gè)與門的邏輯功能,代碼參考如下崭闲。
always @(posedge clk or negedge rst_n)
if (!rst_n) begin
q <= 1'b0;
end
else begin
q <= a & b; //即便有組合邏輯肋联,也不要寫成:q = a & b
end
end
4)在同一個(gè) always 塊中不要既使用阻塞賦值又使用非阻塞賦值
always 涉及的組合邏輯中,既有阻塞賦值又有非阻塞賦值時(shí)镀脂,會(huì)導(dǎo)致意外的結(jié)果牺蹄,例如下面代碼描述。
此時(shí)信號(hào) C 阻塞賦值完畢以后薄翅,信號(hào) F 才會(huì)被非阻塞賦值沙兰,仿真結(jié)果可能正確。
但如果 F 信號(hào)有其他的負(fù)載翘魄,F(xiàn) 的最新值并不能馬上傳遞出去鼎天,數(shù)據(jù)有效時(shí)間還是在下一個(gè)觸發(fā)時(shí)刻。此時(shí)要求 F 具有存儲(chǔ)功能暑竟,可能會(huì)被綜合成 latch斋射,導(dǎo)致競(jìng)爭(zhēng)冒險(xiǎn)育勺。
always @(*) begin
C = A & B ;
F <= C & D ;
end
如下代碼描述,仿真角度看罗岖,信號(hào) C 被非阻塞賦值涧至,下一個(gè)觸發(fā)時(shí)刻才會(huì)有效。而 F = C & D 雖然是阻塞賦值桑包,但是信號(hào) C 不是阻塞賦值南蓬,所以 F 邏輯中使用的還是 C 的舊值。
always @(*) begin
C <= A & B ;
F = C & D ;
end
下面分析假如在時(shí)序電路里既有阻塞賦值哑了,又有非阻塞賦值會(huì)怎樣赘方,代碼如下。
假如復(fù)位端與時(shí)鐘同步弱左,那么由于復(fù)位導(dǎo)致的信號(hào) q 為 0窄陡,是在下一個(gè)時(shí)鐘周期才有效。
而如果是信號(hào) a 或 b 導(dǎo)致的 q 為 0拆火,則在當(dāng)期時(shí)鐘周期內(nèi)有效跳夭。
如果 q 還有其他負(fù)載,就會(huì)導(dǎo)致 q 的時(shí)序特別混亂榜掌,顯然不符合設(shè)計(jì)需求优妙。
always @(posedge clk or negedge rst_n)
if (!rst_n) begin //假設(shè)復(fù)位與時(shí)鐘同步
q <= 1'b0;
end
else begin
q = a & b;
end
end
需要說明的是,很多編譯器都支持這么寫憎账,上述的分析也都是建立在仿真角度上套硼。實(shí)際中如果阻塞賦值和非阻塞賦值混合編寫,綜合后的電路時(shí)序?qū)⑹清e(cuò)亂的胞皱,不利于分析調(diào)試邪意。
5)不要在多個(gè) always 塊中為同一個(gè)變量賦值
與 C 語言有所不同,Verilog 中不允許在多個(gè) always 塊中為同一個(gè)變量賦值反砌。此時(shí)信號(hào)擁有多驅(qū)動(dòng)端(Multiple Driver)雾鬼,是禁止的。當(dāng)然宴树,也不允許 assign 語句為同一個(gè)變量進(jìn)行多次連線賦值策菜。 從信號(hào)角度來講,多驅(qū)動(dòng)時(shí)酒贬,同一個(gè)信號(hào)變量在很短的時(shí)間內(nèi)進(jìn)行多次不同的賦值結(jié)果又憨,就有可能產(chǎn)生競(jìng)爭(zhēng)冒險(xiǎn)。
從語法來講锭吨,很多編譯器檢測(cè)到多驅(qū)動(dòng)時(shí)蠢莺,也會(huì)報(bào) Error。
避免 Latch
- 關(guān)鍵詞:觸發(fā)器零如,鎖存器
- Latch 的含義
鎖存器(Latch)躏将,是電平觸發(fā)的存儲(chǔ)單元锄弱,數(shù)據(jù)存儲(chǔ)的動(dòng)作取決于輸入時(shí)鐘(或者使能)信號(hào)的電平值。僅當(dāng)鎖存器處于使能狀態(tài)時(shí)祸憋,輸出才會(huì)隨著數(shù)據(jù)輸入發(fā)生變化会宪。
當(dāng)電平信號(hào)無效時(shí),輸出信號(hào)隨輸入信號(hào)變化蚯窥,就像通過了緩沖器狈谊;當(dāng)電平有效時(shí),輸出信號(hào)被鎖存沟沙。激勵(lì)信號(hào)的任何變化,都將直接引起鎖存器輸出狀態(tài)的改變壁榕,很有可能會(huì)因?yàn)樗矐B(tài)特性不穩(wěn)定而產(chǎn)生振蕩現(xiàn)象矛紫。
觸發(fā)器(flip-flop),是邊沿敏感的存儲(chǔ)單元牌里,數(shù)據(jù)存儲(chǔ)的動(dòng)作(狀態(tài)轉(zhuǎn)換)由某一信號(hào)的上升沿或者下降沿進(jìn)行同步的(限制存儲(chǔ)單元狀態(tài)轉(zhuǎn)換在一個(gè)很短的時(shí)間內(nèi))颊咬。
觸發(fā)器示意圖如下:
寄存器(register),在 Verilog 中用來暫時(shí)存放參與運(yùn)算的數(shù)據(jù)和運(yùn)算結(jié)果的變量牡辽。一個(gè)變量聲明為寄存器時(shí)喳篇,它既可以被綜合成觸發(fā)器,也可能被綜合成 Latch态辛,甚至是 wire 型變量麸澜。但是大多數(shù)情況下我們希望它被綜合成觸發(fā)器,但是有時(shí)候由于代碼書寫問題奏黑,它會(huì)被綜合成不期望的 Latch 結(jié)構(gòu)炊邦。
Latch 的主要危害有:
1)輸入狀態(tài)可能多次變化,容易產(chǎn)生毛刺熟史,增加了下一級(jí)電路的不確定性馁害;
2)在大部分 FPGA 的資源中,可能需要比觸發(fā)器更多的資源去實(shí)現(xiàn) Latch 結(jié)構(gòu)蹂匹;
3)鎖存器的出現(xiàn)使得靜態(tài)時(shí)序分析變得更加復(fù)雜碘菜。
Latch 多用于門控時(shí)鐘(clock gating)的控制,一般設(shè)計(jì)時(shí)限寞,我們應(yīng)當(dāng)避免 Latch 的產(chǎn)生忍啸。
- if 結(jié)構(gòu)不完整
組合邏輯中,不完整的 if - else 結(jié)構(gòu)昆烁,會(huì)產(chǎn)生 latch吊骤。
例如下面的模型,if 語句中缺少 else 結(jié)構(gòu)静尼,系統(tǒng)默認(rèn) else 的分支下寄存器 q 的值保持不變白粉,即具有存儲(chǔ)數(shù)據(jù)的功能传泊,所以寄存器 q 會(huì)被綜合成 latch 結(jié)構(gòu)。
module module1_latch1(
input data,
input en ,
output reg q) ;
always @(*) begin
if (en) q = data ;
end
endmodule
避免此類 latch 的方法主要有 2 種鸭巴,一種是補(bǔ)全 if-else 結(jié)構(gòu)眷细,或者對(duì)信號(hào)賦初值。
例如鹃祖,上面模型中的always語句溪椎,可以改為以下兩種形式:
// 補(bǔ)全條件分支結(jié)構(gòu)
always @(*) begin
if (en) q = data ;
else q = 1'b0 ;
end
//賦初值
always @(*) begin
q = 1'b0 ;
if (en) q = data ; //如果en有效,改寫q的值恬口,否則q會(huì)保持為0
end
但是在時(shí)序邏輯中校读,不完整的 if - else 結(jié)構(gòu),不會(huì)產(chǎn)生 latch祖能,例如下面模型歉秫。
這是因?yàn)椋琿 寄存器具有存儲(chǔ)功能养铸,且其值在時(shí)鐘的邊沿下才會(huì)改變雁芙,這正是觸發(fā)器的特性。
module module1_ff(
input clk ,
input data,
input en ,
output reg q) ;
always @(posedge clk) begin
if (en) q <= data ;
end
endmodule
在組合邏輯中钞螟,當(dāng)條件語句中有很多條賦值語句時(shí)兔甘,每個(gè)分支條件下賦值語句的不完整也是會(huì)產(chǎn)生 latch。
其實(shí)對(duì)每個(gè)信號(hào)的邏輯拆分來看鳞滨,這也相當(dāng)于是 if-else 結(jié)構(gòu)不完整洞焙,相關(guān)寄存器信號(hào)缺少在其他條件下的賦值行為。例如:
module module1_latch11(
input data1,
input data2,
input en ,
output reg q1 ,
output reg q2) ;
always @(*) begin
if (en) q1 = data1 ;
else q2 = data2 ;
end
endmodule
這種情況也可以通過補(bǔ)充完整賦值語句或賦初值來避免 latch拯啦。例如:
always @(*) begin
//q1 = 0; q2 = 0 ; //或在這里對(duì) q1/q2 賦初值
if (en) begin
q1 = data1 ;
q2 = 1'b0 ;
end
else begin
q1 = 1'b0 ;
q2 = data2 ;
end
end
- case 結(jié)構(gòu)不完整
case 語句產(chǎn)生 Latch 的原理幾乎和 if 語句一致闽晦。在組合邏輯中,當(dāng) case 選項(xiàng)列表不全且沒有加 default 關(guān)鍵字提岔,或有多個(gè)賦值語句不完整時(shí)仙蛉,也會(huì)產(chǎn)生 Latch。例如:
module module1_latch2(
input data1,
input data2,
input [1:0] sel ,
output reg q ) ;
always @(*) begin
case(sel)
2'b00: q = data1 ;
2'b01: q = data2 ;
endcase
end
endmodule
當(dāng)然碱蒙,消除此種 latch 的方法也是 2 種荠瘪,將 case 選項(xiàng)列表補(bǔ)充完整,或?qū)π盘?hào)賦初值赛惩。
當(dāng)然哀墓,補(bǔ)充完整 case 選項(xiàng)列表時(shí),可以羅列所有的選項(xiàng)結(jié)果喷兼,也可以用 default 關(guān)鍵字來代替其他選項(xiàng)結(jié)果篮绰。
例如,上述 always 語句有以下 2 種修改方式季惯。
always @(*) begin
case(sel)
2'b00: q = data1 ;
2'b01: q = data2 ;
default: q = 1'b0 ;
endcase
end
always @(*) begin
case(sel)
2'b00: q = data1 ;
2'b01: q = data2 ;
2'b10, 2'b11 :
q = 1'b0 ;
endcase
end
原信號(hào)賦值或判斷
在組合邏輯中吠各,如果一個(gè)信號(hào)的賦值源頭有其信號(hào)本身臀突,或者判斷條件中有其信號(hào)本身的邏輯,則也會(huì)產(chǎn)生 latch贾漏。因?yàn)榇藭r(shí)信號(hào)也需要具有存儲(chǔ)功能候学,但是沒有時(shí)鐘驅(qū)動(dòng)。此類問題在 if 語句纵散、case 語句梳码、問號(hào)表達(dá)式中都可能出現(xiàn),例如:
//signal itself as a part of condition
reg a, b ;
always @(*) begin
if (a & b) a = 1'b1 ; //a -> latch
else a = 1'b0 ;
end
//signal itself are the assigment source
reg c;
wire [1:0] sel ;
always @(*) begin
case(sel)
2'b00: c = c ; //c -> latch
2'b01: c = 1'b1 ;
default: c = 1'b0 ;
endcase
end
//signal itself as a part of condition in "? expression"
wire d, sel2;
assign d = (sel2 && d) ? 1'b0 : 1'b1 ; //d -> latch
避免此類 Latch 的方法伍掀,就只有一種掰茶,即在組合邏輯中避免這種寫法罢艾,信號(hào)不要給信號(hào)自己賦值来涨,且不要用賦值信號(hào)本身參與判斷條件邏輯。
例如害碾,如果不要求立刻輸出瘩例,可以將信號(hào)進(jìn)行一個(gè)時(shí)鐘周期的延時(shí)再進(jìn)行相關(guān)邏輯的組合。上述第一個(gè)產(chǎn)生 Latch 的代碼可以描述為:
reg a, b ;
reg a_r ;
always (@posedge clk)
a_r <= a ;
always @(*) begin
if (a_r & b) a = 1'b1 ; //there is no latch
else a = 1'b0 ;
end
敏感信號(hào)列表不完整
如果組合邏輯中 always@() 塊內(nèi)敏感列表沒有列全甸各,該觸發(fā)的時(shí)候沒有觸發(fā)垛贤,那么相關(guān)寄存器還是會(huì)保存之前的輸出結(jié)果,因而會(huì)生成鎖存器趣倾。
這種情況聘惦,把敏感信號(hào)補(bǔ)全或者直接用 always@(*) 即可消除 latch。
小結(jié)
總之儒恋,為避免 latch 的產(chǎn)生善绎,在組合邏輯中,需要注意以下幾點(diǎn):
1)if-else 或 case 語句诫尽,結(jié)構(gòu)一定要完整
2)不要將賦值信號(hào)放在賦值源頭禀酱,或條件判斷中
3)敏感信號(hào)列表建議多用 always@(*)
仿真激勵(lì)
- 關(guān)鍵詞:testbench,仿真牧嫉,文件讀寫
Verilog 代碼設(shè)計(jì)完成后剂跟,還需要進(jìn)行重要的步驟,即邏輯功能仿真酣藻。仿真激勵(lì)文件稱之為 testbench曹洽,放在各設(shè)計(jì)模塊的頂層,以便對(duì)模塊進(jìn)行系統(tǒng)性的例化調(diào)用進(jìn)行仿真辽剧。
毫不夸張的說送淆,對(duì)于稍微復(fù)雜的 Verilog 設(shè)計(jì),如果不進(jìn)行仿真怕轿,即便是經(jīng)驗(yàn)豐富的老手偷崩,99.9999% 以上的設(shè)計(jì)都不會(huì)正常的工作辟拷。不能說仿真比設(shè)計(jì)更加的重要,但是一般來說环凿,仿真花費(fèi)的時(shí)間會(huì)比設(shè)計(jì)花費(fèi)的時(shí)間要多梧兼。有時(shí)候,考慮到各種應(yīng)用場(chǎng)景智听,testbench 的編寫也會(huì)比 Verilog 設(shè)計(jì)更加的復(fù)雜羽杰。所以,數(shù)字電路行業(yè)會(huì)具體劃分設(shè)計(jì)工程師和驗(yàn)證工程師到推。
下面考赛,對(duì) testbench 做一個(gè)簡(jiǎn)單的學(xué)習(xí)。
-
testbench 結(jié)構(gòu)劃分
testbench 一般結(jié)構(gòu)如下:
其實(shí) testbench 最基本的結(jié)構(gòu)包括信號(hào)聲明莉测、激勵(lì)和模塊例化颜骤。
根據(jù)設(shè)計(jì)的復(fù)雜度,需要引入時(shí)鐘和復(fù)位部分捣卤。當(dāng)然更為復(fù)雜的設(shè)計(jì)忍抽,激勵(lì)部分也會(huì)更加復(fù)雜。根據(jù)自己的驗(yàn)證需求董朝,選擇是否需要自校驗(yàn)和停止仿真部分鸠项。
當(dāng)然,復(fù)位和時(shí)鐘產(chǎn)生部分子姜,也可以看做激勵(lì)祟绊,所以它們都可以在一個(gè)語句塊中實(shí)現(xiàn)。也可以拿自校驗(yàn)的結(jié)果哥捕,作為結(jié)束仿真的條件牧抽。
- testbench 具體分析
1)信號(hào)聲明
testbench 模塊聲明時(shí),一般不需要聲明端口遥赚。因?yàn)榧?lì)信號(hào)一般都在 testbench 模塊內(nèi)部扬舒,沒有外部信號(hào)。
聲明的變量應(yīng)該能全部對(duì)應(yīng)被測(cè)試模塊的端口凫佛。當(dāng)然呼巴,變量不一定要與被測(cè)試模塊端口名字一樣。但是被測(cè)試模塊輸入端對(duì)應(yīng)的變量應(yīng)該聲明為 reg 型御蒲,如 clk衣赶,rstn 等,輸出端對(duì)應(yīng)的變量應(yīng)該聲明為 wire 型厚满,如 dout府瞄,dout_en。
2)時(shí)鐘生成
生成時(shí)鐘的方式有很多種,例如以下兩種生成方式也可以借鑒遵馆。
initial clk = 0 ;
always #(CYCLE_200MHz/2) clk = ~clk;
initial begin
clk = 0 ;
forever begin
#(CYCLE_200MHz/2) clk = ~clk;
end
end
需要注意的是鲸郊,利用取反方法產(chǎn)生時(shí)鐘時(shí),一定要給 clk 寄存器賦初值货邓。
利用參數(shù)的方法去指定時(shí)間延遲時(shí)秆撮,如果延時(shí)參數(shù)為浮點(diǎn)數(shù),該參數(shù)不要聲明為 parameter 類型换况。例如實(shí)例中變量 CYCLE_200MHz 的值為 2.5职辨。如果其變量類型為 parameter,最后生成的時(shí)鐘周期很可能就是 4ns戈二。當(dāng)然舒裤,timescale 的精度也需要提高,單位和精度不能一樣觉吭,否則小數(shù)部分的時(shí)間延遲賦值也將不起作用腾供。
3)復(fù)位生成
復(fù)位邏輯比較簡(jiǎn)單,一般賦初值為 0鲜滩,再經(jīng)過一段小延遲后伴鳖,復(fù)位為 1 即可。
這里大多數(shù)的仿真都是用的低有效復(fù)位徙硅。
4)激勵(lì)部分
激勵(lì)部分該產(chǎn)生怎樣的輸入信號(hào)榜聂,是根據(jù)被測(cè)模塊的需要來設(shè)計(jì)的。
5)模塊例化
這里利用 testbench 開始聲明的信號(hào)變量闷游,對(duì)被測(cè)試模塊進(jìn)行例化連接。
6)自校驗(yàn)
如果設(shè)計(jì)比較簡(jiǎn)單贴汪,完全可以通過輸入脐往、輸出信號(hào)的波形來確定設(shè)計(jì)是否正確,此部分完全可以刪除扳埂。如果數(shù)據(jù)很多业簿,有時(shí)候拿肉眼觀察并不能對(duì)設(shè)計(jì)的正確性進(jìn)行一個(gè)有效判定。此時(shí)加入一個(gè)自校驗(yàn)?zāi)K阳懂,會(huì)是大大增加仿真的效率梅尤。
實(shí)例中,我們會(huì)在數(shù)據(jù)輸出使能 dout_en 有效時(shí)岩调,對(duì)輸出數(shù)據(jù) dout 與參考數(shù)據(jù) read_temp(激勵(lì)部分產(chǎn)生)做一個(gè)對(duì)比巷燥,并將對(duì)比結(jié)果置于信號(hào) err_cnt 中。最后我們就可以通過觀察 err_cnt 信號(hào)是否為 0 來直觀的對(duì)設(shè)計(jì)進(jìn)行判斷号枕。
當(dāng)然如實(shí)例中所示缰揪,我們也可以將數(shù)據(jù)寫入到對(duì)應(yīng)文件中,利用其他方式做對(duì)比葱淳。
7)結(jié)束仿真
如果我們不加入結(jié)束仿真部分钝腺,仿真就會(huì)無限制的運(yùn)行下去抛姑,波形太長(zhǎng)有時(shí)候并不方便分析。Verilog 中提供了系統(tǒng)任務(wù) $finish 來停止仿真艳狐。
- 文件讀寫選項(xiàng)
用于打開文件的系統(tǒng)任務(wù) $fopen 格式如下:
fd = $fopen("<name_of_file>", "mode")
和 C 語言類似定硝,打開方式的選項(xiàng) "mode" 意義如下:
流水線
- 一般乘法器設(shè)計(jì)
也許有人會(huì)問,直接用乘號(hào) * 來完成 2 個(gè)數(shù)的相乘不是更快更簡(jiǎn)單嗎毫目?
如果你有這個(gè)疑問蔬啡,說明你對(duì)硬件描述語言的認(rèn)知還有所不足。就像之前所說蒜茴,Verilog 描述的是硬件電路星爪,直接用乘號(hào)完成相乘過程,編譯器在編譯的時(shí)候也會(huì)把這個(gè)乘法表達(dá)式映射成默認(rèn)的乘法器粉私,但其構(gòu)造不得而知顽腾。
例如,在 FPGA 設(shè)計(jì)中诺核,可以直接調(diào)用 IP 核來生成一個(gè)高性能的乘法器抄肖。在位寬較小的時(shí)候,一個(gè)周期內(nèi)就可以輸出結(jié)果窖杀,位寬較大時(shí)也可以流水輸出漓摩。在能滿足要求的前提下,可以謹(jǐn)慎的用 * 或直接調(diào)用 IP 來完成乘法運(yùn)算入客。
但乘法器 IP 也有很多的缺陷管毙,例如位寬的限制,未知的時(shí)序等桌硫。尤其使用乘號(hào)夭咬,會(huì)為數(shù)字設(shè)計(jì)的不確定性埋下很大的隱瞞。
很多時(shí)候铆隘,常數(shù)的乘法都會(huì)用移位相加的形式實(shí)現(xiàn)卓舵,例如:
A = A<<1 ; //完成A * 2
A = (A<<1) + A ; //對(duì)應(yīng)A * 3
A = (A<<3) + (A<<2) + (A<<1) + A ; //對(duì)應(yīng)A * 15
用一個(gè)移位寄存器和一個(gè)加法器就能完成乘以 3 的操作。但是乘以 15 時(shí)就需要 3 個(gè)移位寄存器和 3 個(gè)加法器(當(dāng)然乘以 15 可以用移位相減的方式)膀钠。
有時(shí)候數(shù)字電路在一個(gè)周期內(nèi)并不能夠完成多個(gè)變量同時(shí)相加的操作掏湾。所以數(shù)字設(shè)計(jì)中,最保險(xiǎn)的加法操作是同一時(shí)刻只對(duì) 2 個(gè)數(shù)據(jù)進(jìn)行加法運(yùn)算肿嘲,最差設(shè)計(jì)是同一時(shí)刻對(duì) 4 個(gè)及以上的數(shù)據(jù)進(jìn)行加法運(yùn)算融击。
如果設(shè)計(jì)中有同時(shí)對(duì) 4 個(gè)數(shù)據(jù)進(jìn)行加法運(yùn)算的操作設(shè)計(jì),那么此部分設(shè)計(jì)就會(huì)有危險(xiǎn)雳窟,可能導(dǎo)致時(shí)序不滿足砚嘴。
此時(shí),設(shè)計(jì)參數(shù)可配、時(shí)序可控的流水線式乘法器就顯得有必要了际长。
設(shè)計(jì)原理
和十進(jìn)制乘法類似耸采,計(jì)算 13 與 5 的相乘過程如下所示:
由此可知,被乘數(shù)按照乘數(shù)對(duì)應(yīng) bit 位進(jìn)行移位累計(jì)工育,便可完成相乘的過程虾宇。
假設(shè)每個(gè)周期只能完成一次累加,那么一次乘法計(jì)算時(shí)間最少的時(shí)鐘數(shù)恰好是乘數(shù)的位寬如绸。所以建議嘱朽,將位寬窄的數(shù)當(dāng)做乘數(shù),此時(shí)計(jì)算周期短怔接。
乘法器設(shè)計(jì)
考慮每次乘法運(yùn)算只能輸出一個(gè)結(jié)果(非流水線設(shè)計(jì))搪泳,設(shè)計(jì)代碼如下。
module mult_low
#(parameter N=4,
parameter M=4)
(
input clk,
input rstn,
input data_rdy , //數(shù)據(jù)輸入使能
input [N-1:0] mult1, //被乘數(shù)
input [M-1:0] mult2, //乘數(shù)
output res_rdy , //數(shù)據(jù)輸出使能
output [N+M-1:0] res //乘法結(jié)果
);
//calculate counter
reg [31:0] cnt ;
//乘法周期計(jì)數(shù)器
wire [31:0] cnt_temp = (cnt == M)? 'b0 : cnt + 1'b1 ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
cnt <= 'b0 ;
end
else if (data_rdy) begin //數(shù)據(jù)使能時(shí)開始計(jì)數(shù)
cnt <= cnt_temp ;
end
else if (cnt != 0 ) begin //防止輸入使能端持續(xù)時(shí)間過短
cnt <= cnt_temp ;
end
else begin
cnt <= 'b0 ;
end
end
//multiply
reg [M-1:0] mult2_shift ;
reg [M+N-1:0] mult1_shift ;
reg [M+N-1:0] mult1_acc ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
mult2_shift <= 'b0 ;
mult2_shift <= 'b0 ;
mult1_acc <= 'b0 ;
end
else if (data_rdy && cnt=='b0) begin //初始化
mult1_shift <= {{(N){1'b0}}, mult1} << 1 ;
mult2_shift <= mult2 >> 1 ;
mult1_acc <= mult2[0] ? {{(N){1'b0}}, mult1} : 'b0 ;
end
else if (cnt != M) begin
mult1_shift <= mult1_shift << 1 ; //被乘數(shù)乘2
mult2_shift <= mult2_shift >> 1 ; //乘數(shù)右移扼脐,方便判斷
//判斷乘數(shù)對(duì)應(yīng)為是否為1岸军,為1則累加
mult1_acc <= mult2_shift[0] ? mult1_acc + mult1_shift : mult1_acc ;
end
else begin
mult2_shift <= 'b0 ;
mult2_shift <= 'b0 ;
mult1_acc <= 'b0 ;
end
end
//results
reg [M+N-1:0] res_r ;
reg res_rdy_r ;
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
res_r <= 'b0 ;
res_rdy_r <= 'b0 ;
end
else if (cnt == M) begin
res_r <= mult1_acc ; //乘法周期結(jié)束時(shí)輸出結(jié)果
res_rdy_r <= 1'b1 ;
end
else begin
res_r <= 'b0 ;
res_rdy_r <= 'b0 ;
end
end
assign res_rdy = res_rdy_r;
assign res = res_r;
endmodule
- 流水線乘法器設(shè)計(jì)
下面對(duì)乘法執(zhí)行過程的中間狀態(tài)進(jìn)行保存,以便流水工作瓦侮,設(shè)計(jì)代碼如下艰赞。
單次累加計(jì)算過程的代碼文件如下(mult_cell.v ):
module mult_cell
#(parameter N=4,
parameter M=4)
(
input clk,
input rstn,
input en,
input [M+N-1:0] mult1, //被乘數(shù)
input [M-1:0] mult2, //乘數(shù)
input [M+N-1:0] mult1_acci, //上次累加結(jié)果
output reg [M+N-1:0] mult1_o, //被乘數(shù)移位后保存值
output reg [M-1:0] mult2_shift, //乘數(shù)移位后保存值
output reg [N+M-1:0] mult1_acco, //當(dāng)前累加結(jié)果
output reg rdy );
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
rdy <= 'b0 ;
mult1_o <= 'b0 ;
mult1_acco <= 'b0 ;
mult2_shift <= 'b0 ;
end
else if (en) begin
rdy <= 1'b1 ;
mult2_shift <= mult2 >> 1 ;
mult1_o <= mult1 << 1 ;
if (mult2[0]) begin
//乘數(shù)對(duì)應(yīng)位為1則累加
mult1_acco <= mult1_acci + mult1 ;
end
else begin
mult1_acco <= mult1_acci ; //乘數(shù)對(duì)應(yīng)位為1則保持
end
end
else begin
rdy <= 'b0 ;
mult1_o <= 'b0 ;
mult1_acco <= 'b0 ;
mult2_shift <= 'b0 ;
end
end
endmodule
頂層例化
多次模塊例化完成多次累加,代碼文件如下(mult_man.v ):
module mult_man
#(parameter N=4,
parameter M=4)
(
input clk,
input rstn,
input data_rdy ,
input [N-1:0] mult1,
input [M-1:0] mult2,
output res_rdy ,
output [N+M-1:0] res );
wire [N+M-1:0] mult1_t [M-1:0] ;
wire [M-1:0] mult2_t [M-1:0] ;
wire [N+M-1:0] mult1_acc_t [M-1:0] ;
wire [M-1:0] rdy_t ;
//第一次例化相當(dāng)于初始化肚吏,不能用 generate 語句
mult_cell #(.N(N), .M(M))
u_mult_step0
(
.clk (clk),
.rstn (rstn),
.en (data_rdy),
.mult1 ({{(M){1'b0}}, mult1}),
.mult2 (mult2),
.mult1_acci ({(N+M){1'b0}}),
//output
.mult1_acco (mult1_acc_t[0]),
.mult2_shift (mult2_t[0]),
.mult1_o (mult1_t[0]),
.rdy (rdy_t[0]) );
//多次模塊例化方妖,用 generate 語句
genvar i ;
generate
for(i=1; i<=M-1; i=i+1) begin: mult_stepx
mult_cell #(.N(N), .M(M))
u_mult_step
(
.clk (clk),
.rstn (rstn),
.en (rdy_t[i-1]),
.mult1 (mult1_t[i-1]),
.mult2 (mult2_t[i-1]),
//上一次累加結(jié)果作為下一次累加輸入
.mult1_acci (mult1_acc_t[i-1]),
//output
.mult1_acco (mult1_acc_t[i]),
.mult1_o (mult1_t[i]), //被乘數(shù)移位狀態(tài)傳遞
.mult2_shift (mult2_t[i]), //乘數(shù)移位狀態(tài)傳遞
.rdy (rdy_t[i]) );
end
endgenerate
assign res_rdy = rdy_t[M-1];
assign res = mult1_acc_t[M-1];
endmodule