一:子產品的組成和例化
- 在了解子產品的例化這一操作前,我們先需要了解硬體描述語言中子產品到底是什麼。硬體描述語言的宗旨是用代碼去描述電路的功能,而再複雜的電路都是由一塊一塊簡單的電路組成的,硬體描述過程也是如此。程式員通常先從最底層的基礎子產品開始書寫代碼,然後逐層地在更高地層級中對低一層級的子產品進行調用(可以想象一下把已經封裝好的已知内部邏輯和輸入輸出端口的晶片一片一片地插在電路闆上,這些晶片也就是“子產品”)。當然,在更高一級的電路中,不僅包含有低層級的子產品(即執行個體化模組化),還會有當下電路中的資料流模組化(多以assign連續指派語句出現)和行為級模組化(以always語句等形式出現)。
-
子產品的執行個體化是一種非常高效的電路調用方法。如:
(1)描述一個4位相加的加法器,就可以用4個一位全加器進行執行個體化來實作;
(2)描述一個16位BCD加法器,就可以用四個四位BCD全加器執行個體化來實作;
更加詳細抽象的例子就不加贅述,總之,子產品的執行個體化無論是在verilog還是在vhdl中都是一種相當高效的電路描述方式。
-
例化的操作方法在實際電路中又是什麼樣的呢?
在第一段中我們通過一種比較通俗的方法來了解例化的過程。實際上,例化是指通過硬體描述語言來實作子產品端口或者是線網間的映射關系。
執行個體化的兩種方法
以以下一段已經給出子產品的代碼作為引例:
/*四位加法器子產品*/
module BCD_4bit(A,B,cin,cout,sum);
input [3:0]A,B;
input cin;
output cout;
output [3:0]sum;
reg [4:0]middle;//用于存放中間計算結果
reg cout;
reg [3:0]sum;
always @(A,B,cin) begin
middle=A+B+cin;
if (middle>9)
{cout,sum}=middle+6;
else
{cout,sum}=middle;
end
endmodule
觀察上面這段代碼可以發現,在不具體了解代碼功能(也就是内特性)的時候,我們依舊可以通過觀察知道其外特性(也就是端口特性),可以發現:這個電路子產品有三個輸入端口和兩個輸出端口,并且其各自的端口名已經在子產品内部定義(關于BCD四位加法器的代碼原理不再詳述,筆者會在文末附件中作簡單的解釋),那麼在使用到這個子產品的電路時就需要為這些内部端口與外部進行列連接配接,以筆者這次碰到的題目(題目傳送門:HDLBits中16位BCD加法器)為例。題目:使用已寫好的四位BCD加法器來實作16位加法器。
- 分析:整個16位BCD加法器的外特性為:
input [15:0] a, b,
input cin,
output cout,
output [15:0] sum );
先看最低的四位BCD加法器輸入,它們的組成除了兩個被加數的低四位A[3:0]和B[3:0]以外,還有一個一位的進位輸入cin,而它們的加法結果輸出經過要調用的***第一個4位BCD加法器(也就是第一個BCD_4bit)*** 的邏輯運算後,輸出構成了sum[3:0]的結果,同時還有一個一位進位輸出"cout"(此處加雙引号的目的是cout此時作為内部端口名出現)。根據我們對加法器的了解,低位的進位輸出就是高位的進位輸入(也就是下一個4位BCD加法器的進位輸入端)。但這時我們還需要申請線網變量矢量reg來存放進位輸入和輸出進位位的中間變量,此時觀察可以發現,由于隻需要四個四位BCD加法器子產品就可以構成16位BCD加法器,裝載輸入輸出進位位的 中間變量矢量寬度為3 即可,因為最後一位的内部端口(“cout”)就是整個16位加法器電路子產品的進位輸出cout。
-
現在我們可以以解決問題為導向正式進入執行個體化的過程。子產品的例化有兩種方法,一種是名字映射,一種是位置映射
(1)名稱映射
名稱映射就是把執行個體外部的信号(signal)直接對應到要例化的 子產品的各個端口上,這種端口對應的順序的方式是任意的,其格式如下:
子產品名稱 執行個體名稱(
.子產品内部端口名稱a (執行個體外部信号a),
.子產品内部端口名稱b (執行個體外部信号b),
...
);
在本題中,頂層子產品根據前文分析後代碼如下(本段代碼原理比較簡單,是以直接參考了解析 傳送門☞4-digit BCD adder):
module top_module(
input [15:0] a, b,
input cin,
output cout,
output [15:0] sum );
wire [3:0] cout_temp;
//例化4次,注意前一次計算的cout為後一次計算的cin
BCD_4bit inst1_bcd_fadd(
.a(a[3:0]),
.b(b[3:0]),
.cin(cin),
.cout(cout_temp[0]),
.sum(sum[3:0])
);
BCD_4bit inst2_bcd_fadd(
.a(a[7:4]),
.b(b[7:4]),
.cin(cout_temp[0]),
.cout(cout_temp[1]),
.sum(sum[7:4])
);
BCD_4bit inst3_bcd_fadd(
.a(a[11:8]),
.b(b[11:8]),
.cin(cout_temp[1]),
.cout(cout_temp[2]),
.sum(sum[11:8])
);
BCD_4bit inst4_bcd_fadd(
.a(a[15:12]),
.b(b[15:12]),
.cin(cout_temp[2]),
.cout(cout_temp[3]),
.sum(sum[15:12])
);
assign cout = cout_temp[3];
endmodule
(2)位置映射
位置對應方式就是在子產品執行個體化的時候外部信号或者說外部線網需要按照該子產品端口聲明時的順序一一對應。如此次4位BCD加法器的底層子產品其聲明的端口順序為:
是以在進行頂層子產品書寫時,代碼如下:
module top_module(
input [15:0] a, b,
input cin,
output cout,
output [15:0] sum );
reg [2:0]m_cout;//m_cout就是用來裝載進位位的中間矢量
BCD_4bit BCDa(a[3:0],b[3:0],cin,m_cout[0],sum[3:0]);
BCD_4bit BCDb(a[7:4],b[7:4],m_cout[0],m_cout[1],sum[7:4]);
BCD_4bit BCDc(a[11:8],b[11:8],m_cout[1],m_cout[2],sum[11:8]);
BCD_4bit BCDd(a[15:12],b[15:12],m_cout[2],cout,sum[15:12]);
endmodule
-
兩種方法的總結:
名稱映射 能夠更加清晰的顯示端口的輸入輸出關系,但是所占篇幅較大,在例化對象規模較大時更加不适合使用;
位置映射 需要操作者能夠熟悉各個端口的輸入輸出關系,但是節省空間使用高效簡潔。
- 在這個部分的最後我們可以看一下Quartus給出的RTL仿真結果:

圖中的綠框就是封裝好的隻顯示輸入輸出端口的加法器子產品,在RTL仿真圖中點選➕号可以進入檢視其内特性(見下圖)
二:生成塊語句(generate)的簡單應用和使用規範
- 本部分前言: 試想以上的内容僅僅是基于16位的BCD加法器,如果要實作100位,200位甚至400位呢,明顯一行一行地例化這一操作就會非常耗時且麻煩,這時我們就會用到新的語句------ 生成塊語句(generate)
- 本次程式使用了generate(生成塊)語句,其主要功能是對已經寫好的module\reg\assign\task等語句進行複制。如果需要實作的是400位的BCD加法器,那麼其代碼如下(總體設計思路其實與16位的并無分别):
module BCD_addtop(a,b,cin,cout,sum);
input [399:0] a, b;
input cin;
output cout;
output [399:0] sum;
wire [98:0]r_cout;//作為子產品例化使用的中間變量,r_cout一定要定義為wire類型否則會報錯
genvar i;
BCD_4bit bcd_fadd_0(a[3:0],b[3:0],cin,r_cout[0],sum[3:0]);
BCD_4bit bcd_fadd_99(a[399:396],b[399:396],r_cout[98],cout,sum[399:396]);
generate for(i=1;i<99;i=i+1)
begin:bcd_1
BCD_4bit bcd_fadd_i(a[4*i+3:4*i],b[4*i+3:4*i],r_cout[i-1],r_cout[i],sum[4*i+3:4*i]);
end
endgenerate
endmodule
-
generate語句使用最多的是generate for,其中還有generate_if和generate_case。現在此詳細介紹一下generate語句尤其是應用最為廣泛的generate for語句的使用規範:
1)for循環中的 循環變量(i為例)需要在生成塊以外聲明(如genvar i)。(genvar是generate語句中的一種變量類型,用在generate_for中聲明正整數變量。
-
需要複制的語句必須寫到begin_end語句裡面(就算隻有一句!!!)
3)begin_end需要有一個類似于子產品名的名字(如本例中的begin:addbit)
本次部落格基于自己在寫HDLBits題庫時遇到的一些問題和思考,關于generate語句的使用細節借鑒了csdn上其它優秀答主的回答verilog文法之generate語句的基本認識 By小蔣啊 。讀者在閱讀時如果碰到什麼問題或者發現什麼筆誤都歡迎在評論區指正,也希望這篇部落格能夠給在學習fpga的小夥伴帶來一些幫助!!!
(另:答主有時間會附上BCD加法器的詳解還有例化過程中常見的幾種報錯提示和解決辦法)