?
老衲第一次學習 Verilog 語言,基本就到前面幾講的程度,頂多加上了解`define 宏定義。于是對于能設計 IP 核的人,那是佩服的五體投地,如黃河泛濫一發(fā)不可收拾。直到 Verilog 2001 出了參數(shù)(parameter)和生成塊(generate)功能,做 IP 核就成了人人可以掌握的技能了。對頭,下面老僧就和施主們講這些內容。
同樣的代碼 / 模塊要簡化不難,這就是所謂模塊化的作用。但是類似的模塊 ---- 例如同樣是計數(shù)器,只是內部 D 觸發(fā)器位寬的不同 ---- 要聚類就需要一些技巧了,因為電路是不支持結構靈活變化的。為了能夠合并類似模塊,Verilog 發(fā)展出了宏和參數(shù)這兩個玩意。
最后呢,在 Verilog 2001 里面,設計了塊生成的功能。這個功能使得用戶可以更加簡單的根據(jù)外部的輸入,靈活的產生最佳的電路結構。
1. 參數(shù)定義,結構變化
參數(shù)的定義方法和傳遞方式見表 1 所示。定義有兩種方法,傳遞也有兩種方法,在加上參數(shù)列表的兩種表示,又是一個“一題多解”。那種方式好?沒標準答案,按照施主喜歡的方式來即可。但是一個項目里面,最好選擇一種組合模式,方便其他人閱讀。
表 1 參數(shù)的定于與傳遞
“parameter_list”為參數(shù)列表,參數(shù)之間用逗號“,”隔開;其中,“parameter_name”是用戶定義的參數(shù)名稱,建議采用容易閱讀的命名方式;“parameter_initial_value”為該參數(shù)對應的初始值,在本模塊的實例未被定義參數(shù)的時候使用。
“parameter_list_seperated”為獨立于 module 定義之外的參數(shù)列表,需要在模塊行為描述之前書寫。它由若干個關鍵字“parameter”開頭的參數(shù)定義構成;如果一個“parameter”帶多個參數(shù)定義,則這些參數(shù)需要被逗號“,”隔離;每個“parameter”開頭的定義,末尾用分號“;”表示結束。
“parameter_assignment_list”是參數(shù)賦值列表,由關鍵字“defparam”開頭,后面是各個參數(shù)的賦值。對于每個參數(shù),參數(shù)名稱需要用“module_instance_index”指定是哪一個例化模塊對應的參數(shù);模塊索引“module_instance_index”的格式是按照最高模塊例化名稱到最底層例化的順序排列,模塊例化名稱之間用點“.”隔離;“parameter_value”是對應參數(shù)的值;參數(shù)賦值之間用逗號“,”隔離,末尾需要有分號“;”。
“parameter_assignment_list_sequence”是按照定義順序的參數(shù)傳遞列表,其中各個對應參數(shù)值“parameter_value”的順序必須和定義時候的順序一致。
“parameter_assignment_list_named”是按照指定名稱的參數(shù)傳遞列表,其中各個對應參數(shù)值“parameter_value”有前面帶點“.”的參數(shù)名稱“parameter_name”指定。所以在參數(shù)的排列順序上可以任意。
當在實例化模塊的同時傳遞參數(shù)的時候,請注意模塊名稱之后是參數(shù)傳遞列表,然后才是模塊的實例名等,這個順序不能搞錯。參數(shù)傳遞列表被#(…)標記出來。
參數(shù)的值可以說明位寬,但是一般而言,參數(shù)的值是一個常數(shù)不需要定義位寬的。
代碼中引用參數(shù)的時候,直接使用其名稱,無需象宏定義那樣用“`”開頭。
例 2 給出了一個位寬和最大值參數(shù)化的計數(shù)器的例子,請參考。例子中,這個計數(shù)器完成到達最大值就清零的不停的計數(shù)功能。頂層模塊調用了兩個參數(shù)不同的計數(shù)器。
?
【例 2】位寬和最大值參數(shù)化的計數(shù)器
`define WIDTH_1 8
`define WIDTH_2 4
`define WIDTH_3 3
//Bit width for different sub modules
`define MAX_1 200
`define MAX_2 13
`define MAX_3 5
//Bit width for different sub modules
module top_counter_parameter
? (
??? input clk, RST,
??? output[`WIDTH_1 - 1:0] counter1,
??? output[`WIDTH_2 - 1:0] counter2,
??? output[`WIDTH_3 - 1:0] counter3??????
? );
//Load other module(s)
counter_parameter C1(.clk(clk), .RST(RST), .counter(counter1));
counter_parameter C2(.clk(clk), .RST(RST), .counter(counter2));
??? defparam? C2.WIDTH = `WIDTH_2, C2.MAX_VALUE = `MAX_2;
counter_parameter #(.WIDTH(`WIDTH_3), .MAX_VALUE(`MAX_3))
????????????????? C3(.clk(clk), .RST(RST), .counter(counter3));
//Definition for Variables in the module
//Logic
endmodule
……
module counter_parameter
#(parameter WIDTH = 8,
//Bit width for output
MAX_VALUE = 200)
//Maximun value for counter)
? (
??? input clk, RST,
??? output reg[WIDTH - 1:0] counter
? );
?
//Load other module(s)
//Definition for Variables in the module
//Logic
always @(posedge clk or negedge RST)
begin
??? if (!RST)
??? begin
??????? counter <= 1'h0;
??? end
??? else if (counter < MAX_VALUE)
??? begin
?????? counter <= counter + 1'h1;????
??? end
??? else
??? begin
?????? counter <= 1'h0;????
??? end
end
endmodule
參數(shù)型常數(shù)常用于定義延遲時間和變量寬度,在模塊和實例引用時,可通過參數(shù)傳遞改變在被引用模塊或實例中已定義的參數(shù)。參數(shù)在被綜合的時候必須是常數(shù)值,這點要強調一下,變結構的電路是不可能被綜合的。這個常數(shù)值可以是已知的常數(shù),也可以通過常數(shù)之間的計算得到。
參數(shù)是給綜合軟件用的,所以在實現(xiàn)的電路里面不可能明顯的看到參數(shù)的值。
除了參數(shù),Verilog 2001 里面還定義了一個很多工程師都不知道干嘛用的本地參數(shù)“l(fā)ocalparam”。本地參數(shù)的定義方法和參數(shù)一樣,作用域也是相同的。這兩類參數(shù)的區(qū)別在于,本地參數(shù)不同通過參數(shù)傳遞方式進行修改。
?
2. 生成有塊,更加靈活
一般而言,生成塊要和參數(shù)功能合作,完成動態(tài)產生電路的作用。當然這個功能也可以不動態(tài)產生電路,但是這樣相當于用寶劍來做木匠活,不僅大材小用還不方便使用。
生成塊的關鍵詞是“generate”,英文產生的意思。一個生成塊被
generate
operations
endgenerate
這樣的框架包裹,其中“operations”是快生成的功能部分,用來描述實際有用的邏輯。生成塊功能分為:條件、case 和循環(huán)三個類型,待貧僧一一道來。
條件嘛,莫過于就是“if…else if…”的樣子了,看到這里的觀眾們應該可以耳熟能詳了。但是注意,生成塊的條件里面不是什么都可以裝的,可以用于生成塊的條件功能的內容僅限于:模塊(例化)、UDP、Verilog 門原語、連續(xù)賦值,initial 塊和 always 塊等。這個要注意,否則報錯是不可避免的。
生成塊的語法結構,想必大伙兒也猜得出:
generate
if (condition)
??? operation_1
else
??? operation_2
endgenerate
其中,“condition”是邏輯表達式,是判決條件。當判決條件為真的時候,進行“operation_1”的操作;否則,進行“operation_2”的操作。
就和 if 與 case 的關系一般,生成塊里面既然有 if 也少不得 case 來搭配。生成塊的 case 的語法結構是:
generate
case (constant_express)
value_1: operation_1
value_2: operation_2
……
value_n: operation_n
default: operation_default
endgenerate
如何使用,不必啰嗦。生成塊的 case 里面可以包含的內容和生成塊 if 的一樣的。強調一下,僅僅包括:模塊(例化)、UDP、Verilog 門原語、連續(xù)賦值,initial 塊和 always 塊。
如何選擇 if 和 case 的應用場景也是類似的情況,老衲也不多嘴。
看到上面的內容,施主們一定會產生輕敵的思想:生成塊莫過如此,easy!兵法有云:“驕兵必敗”,這個想法要不得。下面給大伙說說循環(huán)類型的生成塊,這個很容易引起歧義,老衲需要細細講解。
欲說循環(huán)生成,先要介紹生成索引變量“genvar”,其語法結構是:
genvar genvar_name_1, genvar_name_2, ……, genvar_name_n;
其中,“genvar_name”是不同循環(huán)索引變量的名稱,要求符合 Verilog 對于變量名名的要求。這個變量是和循環(huán)生成共生的,看起來像是循環(huán)里面的循環(huán)變量。實際中,它比循環(huán)變量的應用范圍專業(yè),只用于循環(huán)生成的電路模塊的“索引”。具體啥叫“索引”和“索引”啥,先買個關子,后面再說。
循環(huán)式生成塊的語法結構是
generate
genvar genvar;
??? for (genvar = start_value; end_condition; circle_express)
??? begin:? instant_name
??????? operations
??? end
endgenerate
其中, “start_vlue”、“end_condition”、“circle_expree”是和循環(huán)語句 for 是一樣一樣的含義?!皁perations”是每次循環(huán)的操作,這只能是變量聲明、模塊(例化)、UDP、Verilog 門原語、連續(xù)賦值,initial 塊和 always 塊這幾個里面的一個或者幾個。“genvar”就是前面說到的生成索引變量。最大的不同是操作一定要有“begin……end”括在內部,而且“begin”之后要有“:? instant_name”這個結構。冒號表示風格,不多說;“instant_name”就是所謂的生成索引的名字。換句話說,“instant_name”表示 for 內部實現(xiàn)的模塊、變量的名稱,以防止混淆。
這里說了這么許多,很多施主肯定已經迷糊了,下來給個例子十分必要。
?
看一個簡單的例子,用循環(huán)生成做一個位寬參數(shù)化的加法鏈,如例 2 所示。由于最低比特是一個半加器,這個在代理里面特別處理了。特別說明一下,for 循環(huán)里面實現(xiàn)了若干個全加器,這些全加器的被命名為:full_adder[0].F,full_adder[1].F……這就是所謂的生成索引。
【例 2】位寬參數(shù)化的加法鏈(半加器外置)
代碼 |
電路代碼 |
綜合軟件內的代碼 |
module? adder_line_generate #(parameter WIDTH = 8) ?( ??? input[WIDTH - 1 :0] a0, a1, ??? output[WIDTH :0] sum ? ); //Definition for Variables in the module wire[WIDTH - 1:0]? c; //Carried bits in the line //Load other module(s) half_adder HALF_ADDER(.a0(a0[0]), .a1(a1[0]), ????????? .s(sum[0]),.c1(c[0])); //First bit: half adder ????????? generate //Other bits: full_adder genvar loop; begin ??? for (loop = 1; loop < WIDTH; loop = loop + 1) ??? begin: FULL_ADDER ??????? full_adder F(.a0(a0[loop]), .a1(a1[loop]), .c0(c[loop - 1]), ???????????????? .s(sum[loop]), .c1(c[loop]) ); ? ??end end endgenerate //Logic assign sum[WIDTH] = c[WIDTH-1]; //Carried bit for the result endmodule |
module? adder_line_generate #(parameter WIDTH = 8) ?( ??? input[WIDTH - 1 :0] a0, a1, ??? output[WIDTH :0] sum ? ); //Definition for Variables in the module wire[WIDTH - 1:0]? c; //Carried bits in the line //Load other module(s) half_adder HALF_ADDER(.a0(a0[0]), .a1(a1[0]), ????????? .s(sum[0]),.c1(c[0])); //First bit: half adder ?????????
//Other bits: full_adder
??? begin: FULL_ADDER ??????? full_adder F(.a0(a0[loop]), .a1(a1[loop]), .c0(c[loop - 1]), ???????????????? .s(sum[loop]), .c1(c[loop]) );
//Logic assign sum[WIDTH] = c[WIDTH-1]; //Carried bit for the result endmodule |
//Definition for Variables in the module
//Carried bits in the line //Load other module(s)
//First bit: half adder ????????? generate //Other bits: full_adder genvar loop; begin ??? for (loop = 1; loop < WIDTH; loop = loop + 1) ??? begin: FULL_ADDER
??? end end endgenerate //Logic
//Carried bit for the result
|
施主們就看到了生成塊是可以嵌套的,這個一般的代碼類似。還是一句順口溜:嵌套用得好,寫核難不倒。意思是說:生成塊的嵌套用好了,自己寫 IP 核就是探囊取物一般簡單。這方面的資料相對較少,所以老僧就不厭其煩再給幾個例子。
還是例 3 場景,實現(xiàn)位寬參數(shù)化的加法鏈。
【例 3】位寬參數(shù)化的加法鏈(全加器 / 半加器合并)
module? adder_line_generate #(parameter WIDTH = 8)
(
??? input[WIDTH - 1 :0] a0, a1,
??? output[WIDTH :0] sum
? );
//Definition for Variables in the module
?????????
generate
//Other bits: full adder
genvar loop;
begin
??? for (loop = 0; loop < WIDTH; loop = loop + 1)
??? begin: ADDER
?????? wire c;
?????? //Carried bit in the loop named ADDER[loop].c
?????? if (loop == 0)
?????? begin
?????????? half_adder h(.a0(a0[loop]), .a1(a1[loop]),
???????????????????????????? .s(sum[loop]), .c1(c) );
?????? end
?????? else
?????? begin
?????????? if (loop == WIDTH - 1)
?????????? begin
??????????????????????????? full_adder F(.a0(a0[loop]), .a1(a1[loop]), .c0(ADDER[loop-1].c),
???????????????????????????????? .s(sum[loop]), .c1(sum[WIDTH]) );
??????????? end
??????????? else
??????????? begin
??????????????? full_adder F(.a0(a0[loop]), .a1(a1[loop]), .c0(ADDER[loop-1].c),
???????????????????? .s(sum[loop]), .c1(c) );????????????
??????????? end
??????? end
??? end
end
endgenerate
//Logic
endmodule
生成塊和 Verilog 語句里面對應的 if、case 和 for 很容易混淆,在最后貧僧在表 3 里面幫大家做了一個總結,請參考。
表 3 生成塊和 Verilog 語句的區(qū)別
? |
生成塊:if 與 case |
Verilog 語句的 if 與 case |
功能 |
綜合軟件根據(jù)條件,判斷選擇的電路器件 |
直接產生電路 |
電路映射 |
不產生電路,隱式體現(xiàn) |
產生電路,一般為選擇器 |
內部可包含 |
模塊(例化)、UDP、Verilog 門原語、連續(xù)賦值,initial 塊和 always 塊 |
Verilog 語句 |
條件表達式 |
參數(shù)化的常數(shù)表達式 |
常數(shù)表達式(不一定參數(shù)化)或者變量 |
可綜合性要求 |
內部為模塊(例化)、連續(xù)賦值和 / 或 always 塊,并且內部語句可綜合 |
內部語句可綜合 |
? |
生成塊:for |
Verilog 語句的 for |
功能 |
綜合軟件循環(huán)次數(shù),實現(xiàn)電路 |
綜合軟件循環(huán)次數(shù),實現(xiàn)電路 |
電路映射 |
不產生電路,隱式體現(xiàn) |
不產生電路,隱式體現(xiàn) |
內部可包含 |
變量聲明、模塊(例化)、UDP、Verilog 門原語、連續(xù)賦值,initial 塊和 always 塊 |
其他可綜合語句(不能實現(xiàn)模塊例化等) |
信號、模塊數(shù)量 |
可參數(shù)化生成 |
規(guī)模固定[1] |
循環(huán)變量類型 |
生成索引變量 |
一般變量 |
格式 |
必須由“begin …… end”括住,而且 begin 后面需要“:? instant_name”結構 |
如果是單獨語句語法上可以沒有“begin …… end”括住[2] |
可綜合性要求 |
|
|
[1]Verilog 語句 for 可以使得部分信號、模塊的實例在實際中不被使用,但是理論上這些信號、模塊依然被語言約束存在于電路中。當然好的綜合軟件會優(yōu)化掉這些無用的信號、模塊,但是這不是語法要求的。
[2]工程代碼里面不建議這樣的寫法。
由此可見,雖然生成塊和 Verilog 語句里面 if、case 和 for 的樣子完全一樣,但是應用場合卻是大相徑庭的。施主們一定要注意區(qū)別對待,用錯了地方可是要鬧笑話的。
這正是:
“
禹王量水號神針,大圣降妖棒一根。如今列位有福分,語言里面塊生成。
模塊選擇參數(shù)能,數(shù)目大小循環(huán)認。靈活運用仙界登,不怕小核把亂生。
”
與非網原創(chuàng)內容,謝絕轉載!
系列匯總:
之二:Verilog 編程無法一蹴而就,語言層次講究“名正則言順”
之三:數(shù)字邏輯不容小窺,電路門一統(tǒng)江湖