故事是這樣子的,在實作硬體的時候,verilog 大多是第一選擇(至少在亞洲區跟美洲區是這樣,VHDL那是另一個故事了), 而 verilog 作為古老的硬體設計語言,從各角度上來看都不足以稱為一款足夠現代的語言,整體的撰寫邏輯上仍然相當不抽象, 即便是細微的修改都可能導致大量的修正工作,即便透過 SystemVerilog 引入些許改進, 受限於開發工具(特別是開源相關的工具)與標準的匱乏,許多有用的功能仍然相當受限。
在 verilog 開發中,有一部分需要大量時間的工作就是接線,也就是將設計好的子模組,在上層模組 (下層 top module,儘管它們不一定是真的最頂層的模組)中實例化,每個模組的 input, output port 要宣告接到某些線, 這些線會通往 0/1 固定值,連接到其他子模組,或是變成 top module 的 input, output port,以 RSA256.sv 模組, 省略掉模組中的邏輯如下所示:
module RSA (
input clk,
input rst_n,
input i_valid,
output i_ready,
input RSAModIn i_in,
output o_valid,
input o_ready,
output RSAModOut o_out
);
KeyType msg, key, modulus;
logic i_en;
logic s1_i_valid, s1_i_ready;
logic s1_o_valid, s1_o_ready;
Pipeline pipeline_input (
.clk(clk),
.rst_n(rst_n),
.i_valid(i_valid),
.i_ready(i_ready),
.o_en(i_en),
.o_valid(s1_i_valid),
.o_ready(s1_i_ready)
);
IntType power;
assign power = MOD_WIDTH * 2;
KeyType packed_val;
logic dist_valid[2], dist_ready[2];
logic comb_valid[2], comb_ready[2];
logic s1_en;
KeyType s1_msg, s1_key, s1_modulus;
PipelineDistribute #(.N(2)) i_dist (
.clk(clk),
.rst_n(rst_n),
.i_valid(s1_i_valid),
.i_ready(s1_i_ready),
.o_valid(dist_valid),
.o_ready(dist_ready)
);
Pipeline pipeline_stg1 (
.clk(clk),
.rst_n(rst_n),
.i_valid(dist_valid[0]),
.i_ready(dist_ready[0]),
.o_en(s1_en),
.o_valid(comb_valid[0]),
.o_ready(comb_ready[0])
);
TwoPower i_twopower (
.clk(clk),
.rst_n(rst_n),
.i_valid(dist_valid[1]),
.i_ready(dist_ready[1]),
.i_in({power, modulus}),
.o_valid(comb_valid[1]),
.o_ready(comb_ready[1]),
.o_out(packed_val)
);
PipelineCombine #(.N(2)) i_comb (
.i_valid(comb_valid),
.i_ready(comb_ready),
.o_valid(s1_o_valid),
.o_ready(s1_o_ready)
);
RSAMont i_RSAMont (
.clk(clk),
.rst_n(rst_n),
.i_valid(s1_o_valid),
.i_ready(s1_o_ready),
.i_in({packed_val, s1_msg, s1_key, s1_modulus}),
.o_valid(o_valid),
.o_ready(o_ready),
.o_out(o_out)
);
這裡我們只實例化五個模組,在稍有規模的 verilog project,top module 實例化 10-20 個模組也不算太少見, 這會需要花費大量的工夫在接線上。
這篇文章我們就來介紹兩個開源的接線方案,兩者的功能大同小異,使用者可以在 verilog code 中插入註解, 工具自動剖析模組檔案,透過接線命名判斷哪些模組需要連接在一起,哪些線接到 top module 的 input/output, 哪些又需要額外宣告,以及它們宣告的型別,兩款分別是:
兩者都是開源工具,誤接或是更大的問題一定有,下面介紹後就會一併列出,我想在商用上一定有更完整的解決方案,
就我所知的話 G 社裡面應該也有比 verilogpp 更完整的工具可以使用。
這也是沒辦法,畢竟自己實作過 verilog 就會覺得它根本一團爛泥…要什麼沒什麼,
真虧大家可以用這種東西打造出產值幾兆美元的硬體產業。
verilog-mode
Install
首先 verilog-mode 是一套 emacs 下的巨集工具,所以必須先裝 emacs。
直接來到 verilog-mode github
,使用 ELPA 的方式進行安裝。
Comment Format
verilog-mode 使用方式,可以參考這篇 Emacs verilog-mode 的使用 ,我的經驗有用到的就下面五個:
- /*AUTOINPUT*/ 生成 top module 的 input port
- /*AUTOOUTPUT*/ 生成 top module 的 output port
- /*AUTOREG*/ 生成 top module 接線需要的 logic
- /*AUTOWIRE*/ 生成 top module 接線需要的 wire
- /*AUTOINST*/ 實例化每個子模組
操作也不用記什麼,用 emacs 打開文件:
- Ctrl+C Ctrl+K 把生成內容全砍掉
- Ctrl+C Ctrl+A 重新生出內容
- Ctrl+X Ctrl+S 存檔
- Ctrl+X Ctrl+C 關掉檔案。
不想記也沒關係,用滑鼠點也可以,準備好要讓 verilog-mode 接線前的檔案會長這樣:
import RSA_pkg::*;
module RSA (
// input
input clk,
input rst_n,
input RSAModIn i_in,
output RSAModOut o_out,
/*AUTOINPUT*/
/*AUTOOUTPUT*/
);
IntType power;
assign power = MOD_WIDTH * 2;
KeyType msg, key, modulus;
logic i_en;
logic stg_en;
/*AUTOREG*/
/*AUTOWIRE*/
/* Pipeline AUTO_TEMPLATE (
.o_valid(s1_valid),
.o_ready(s1_ready),
.o_en(i_en),
);
*/
Pipeline pipe1 (/*AUTOINST*/);
/* PipelineDistribute AUTO_TEMPLATE (
.i_valid(s1_valid),
.i_ready(s1_ready),
.o_valid(dist_valid),
.o_ready(dist_ready),
);
*/
PipelineDistribute #(.N(2)) i_dist (/*AUTOINST*/);
/* Pipeline AUTO_TEMPLATE (
.i_valid(dist_valid[0]),
.i_ready(dist_ready[0]),
.o_en(stg_en),
.o_valid(comb_valid[0]),
.o_ready(comb_ready[0]),
);
*/
Pipeline pipe2 (/*AUTOINST*/);
KeyType packed_val;
KeyType s1_msg, s1_key, s1_modulus;
/* TwoPower AUTO_TEMPLATE (
.i_valid(dist_valid[1]),
.i_ready(dist_ready[1]),
.i_in({power, modulus}),
.o_valid(comb_valid[1]),
.o_ready(comb_ready[1]),
.o_out(packed_val),
);
*/
TwoPower i_twopower (/*AUTOINST*/);
/* PipelineCombine AUTO_TEMPLATE (
.i_valid(comb_valid),
.i_ready(comb_ready),
.o_valid(s2_valid),
.o_ready(s2_ready),
);
*/
PipelineCombine i_comb (/*AUTOINST*/);
RSAMontModIn rsain;
assign rsain = {packed_val, s1_msg, s1_key, s1_modulus};
/* RSAMont AUTO_TEMPLATE (
.i_valid(s2_valid),
.i_ready(s2_ready),
.i_in(rsain),
);
*/
RSAMont i_RSAMont (/*AUTOINST*/);
endmodule
概述如下:
用 AUTOINST 生成所有實例,對需要客製化的部分使用 AUTO_TEMPLATE。
AUTO_TEMPLATE 內有兩種特殊符號可以使用:
- @ 會代換成實例名字的一部分,預設是實例名稱最後的數字,這個設計的用意是取代 verilog 那個難用到爆炸的 generate。
- 承上,AUTO_TEMPLATE 支援一小段的正規表示式規範 @ 被代換的內容,也就是上例中出現的 “_([a-z]+)”
- [] 會代換成對應線的寬度,應該是把寬度不一樣的線連接起來的時候,它會自動加入線的寬度, 在 systemverilog 支援 type 的狀況下,這個用處不大;另外現有的 linter 工具多半能找出類似的問題。
使用意見
在使用上 verilog-code 會有以下的問題:
- verilog-mode 只適用在各模組間接線的狀況,也就是說你的模組裡面不能有任何其他邏輯,只能有子模組的宣告, 也因此 RSA256.sv 並不是 verilog-mode 合格的適用對象。
- verilog-mode 無法處理 systemverilog 的型別,top module input/output 的 RSAModIn i_in 和 RSAModOut o_out,這兩個訊號雖然沒有接進其他的模組,理應是 top module input/output 的一部分, 但 verilog-mode 受到自訂型別的關係無法成功接線。
- verilog-mode 無法處理 systemverilog 的 unpack array,例如我內部模組的宣告是 input o_ready [2], verilog-mode 外部的接線會宣告為 logic [1:0] ,甚至只宣告為 logic。
- 理論上我已經宣告了
i_en
stg_en
兩條線,希望這兩條線從 submodule 出來之後可以導入我 top module 的邏輯, 但 verilog-mode 不會處理其他宣告,下面有線沒接就是往 top 的 interface 丟,實在是有點太…沒彈性了。
Verilogpp
Install
第二款工具是 Google 出的,用 perl 寫的工具,載下來 放到可執行的地方就可以使用了,比較可惜的是已經七年沒有更新了,雖然考慮到 verilog/systemverilog 更新的頻率, 七年不更新好像也還好(蓋章
使用方式大致就三種:
- verilogpp <target.sv>:剖析 target.sv 並接線
- verilogpp -t <target.sv>:刪掉 target.sv 中所有接線
- verilogpp <target.svpp>:生成 target.sv 並接線,不會動到 target.svpp 的內容, 壞處是 .svpp 的 extension 在編輯器裡沒有 verilog 程式碼格式的設定,要額外設定。
Comment Format
會用到的註解比 verilog-mode 還要少:
- /**AUTOINTERFACE**/ 等同於 verilog-mode 的 AUTOINPUT + AUTOOUTPUT
- /**AUTONET –warn**/ 等同於 verilog-mode 的 AUTOREG + AUTOWIRE
- /**INST instance.sv i_instance**/ 等同 verilog-mod 的 AUTOINST 跟 AUTO_TEMPLATE
產生的內容會用在 PPSTART PPEND comment 包起來 /*PPSTART*/ /*PPEND*/
相比 verilog-mode 的話,它在 INST 不用寫明用哪個模組,而是直接寫出要接的檔案路徑跟實例的名字即可,路徑也支援資料夾路徑。
如果使用 verilogpp 來接線的話,完成體大概會長這樣:
import RSA_pkg::*;
module RSA (
/**AUTOINTERFACE**/
);
logic i_en;
RSAMontModIn mont_in;
TwoPowerIn s1_modulus;
logic s2_en;
TwoPowerOut twopower_out;
/**AUTONET --warn**/
IntType power;
assign power = MOD_WIDTH * 2;
/**INST Pipeline.sv pipe1
.o_en(i_en)
s/^o_/s1_/;
**/
/**INST PipelineDistribute.sv i_dist
parameter N 2
s/^i_/s1_/;
s/^o_/dist_/;
**/
/**INST Pipeline.sv pipe2
.o_en(s2_en)
s/^i_(.*)/dist_$1 [1]/;
s/^o_(.*)/comb_$1 [1]/;
**/
/**INST TwoPower.sv i_twopower
.i_in(s1_modulus)
.o_out(twopower_out)
s/^i_(.*)/dist_$1 [0]/;
s/^o_(.*)/comb_$1 [0]/;
**/
/**INST PipelineCombine.sv i_comb
parameter N 2
s/^i_(valid|ready)/comb_$1/;
s/^o_(valid|ready)/mont_$1/;
**/
RSAMontModIn mont_in = {twopower_out, s1_key, s1_msg, s1_modulus},
/**INST RSAMont.sv i_mont
s/^i_/mont_/;
**/
endmodule
使用意見
分析來看,verilogpp 跟 verilog-mode 在缺點上幾乎是一致的,畢竟兩者設計的邏輯是一樣的,不過相比之下仍然有較優的地方:
- verilogpp 可以理解 systemverilog type,在 top module 的輸出入可以正常加入 RSAModIn i_in 和 RSAModOut o_out
- 跟第一點一樣,verilogpp 可以理解 unpack array 的 port,宣告接線子模組的 unpack array port 在 top module 能正常生成
- verilogpp 在宣告子模組時可以明確指定路徑檔名,比 verilog-mode 只寫模組名稱卻不知道他去哪裡找模組來得清楚
- verilogpp 在註解的形式上比 verilog-mode 更直覺些,雖然只要扯到正規表示式都不會太漂亮。
與 verilog-mode 相同,所有缺點都可以直接複製:
- verilogpp 接線的檔案同樣不能有任何的邏輯,只能有一堆子模組的宣告。
- verilogpp 一樣無法處理 unpack array 的接線,範例中的 dist_valid, comb_valid 等信號,宣告為 logic [2], 但分別把 [0] 跟 [1] 接到 pipeline 和 TwoPower 模組,verilogpp 無法判斷 dist_valid, comb_valid 有接到其他模組,就會把他們接去 top module 的 port。
- 同理 verilogpp 不會理你已有的宣告,所以處理完在 top module 的 interface 就會出現一堆奇怪的線。
結論
本文介紹了兩款 verilog 的接線工具:verilog-mode 和 verilogpp。 就如上所述,這些工具的使用情境都是在一個 top module 只有一堆子模組宣告的情況下,自動生成所有模組的實例, 並把對應的線連接在一起;由於工具不會分析內部本來的邏輯,只要你的模組裡有一丁點自己的邏輯,就立刻不適用這些工具。
其次,由於一般命名的一致性,很多模組都會有重複的命名,例如 RSA256 幾乎都是同一套命名邏輯:i_valid, i_ready, i_in, o_valid, o_ready, o_out
,
兩套工具把 同名 線接起來的設計邏輯,無法簡單寫完所有模組宣告就了事,
會像上面兩套範例一樣,必須加入大量註解標示接入這些 port 的線的名字,導致使用這些工具跟用手寫的效率差不多。
各種 systemverilog 的特性,這些工具全都無法處理,症狀是生成完之後 top module 的 interface 會有額外的 port, 生成完後要再手動移去模組內的變數宣告區。
綜上所述,我覺得這些工具的使用情境只有兩種:
- 設計的時候就把 port 命名弄好,模組不要用 i_valid/ready 這種通用名,完全為可以使用接線工具為目標做整體設計。
- 照常設計,用接線工具幫你產生各子模組實例,代入 port 資訊,後續接線還是切回人工編輯,這樣至少省下打開每個檔案複製定義、刪除 input/output 關鍵字的時間。
在以上幾種使用情境中,我個人認為 verilogpp 較 verilog-mode 優,可以處理 systemverilog 的型別,
另外子模組可以給定路徑,註解指定接線名稱的方式也較 verilog-mode 直覺好懂。
至於兩者共同的缺點也不怪他們,要處理上述那些問題就必須進到語法分析等階段,不是簡單的 script 工具能做到的了
展望未來
本文介紹了兩套不同的 verilog 接線工具,很可惜的兩套都離完美相差甚遠,以下我提出兩個可能的隱藏參賽者。
verilog/systemverilog language server
打造一套能分析專案中模組的 verilog 檔案的 language server 可能是比較泛用的解決方案,一般接線最煩的,就只有:
- 找到每個想實例的模組
- 複製該模組的定義
- 刪除定義前的 input/output 關鍵字,加上小括號開始接線
如果使用一個 language server 分析過整個專案中的檔案,應該是能輕鬆完成上述的工作; 透過名字接線因為高重複率效率很差,實務上也真的沒必要。
AI 工具
最近 Github-Copilot 開放大家免費使用了,我在 vscode 上安裝,試著對上述的 RSA256 進行接線。
成果我覺得有點差強人意,確實 AI 在辨識程式碼 pattern 頗有一套,例如宣告完 logic modulus
,
下一行打 always_ff 時,自動補齊已經生好全套從 if (!rst_n)
到結尾的程式碼,並且變數已經代入 modulus,十分貼心,
這是以往 snippet 工具無法作到的客製化插入程式碼。
但在接線上顯然還力有未逮,理論上 Copilot 應該是能看到整個專案的檔案, 但它顯然還未意識到實例模組與在其他檔案中的模組定義的關係。
使用上出現的都是:
- 先寫好 Module 1 的實例,定義有 Port A, B。
- 準備實例 Module 2,定義有 Port A, B, C。 AI 工具會把實例 Module 2 與 Module 1 作連結,以致在 Module 2 下面生成 Part A, B 而忽略 Port C,甚至在 Port D, E 的狀況下, 仍然生出 Port A, B。
如果比較兩個隱藏參賽者的話,我覺得比較踏實的解法是發展一套 verilog 的 language server,現下雖然有 一些 verilog/systemverilog 的實作 , 但我還沒完整試用過,應該都還沒達到足以擔當接線的大任。
AI 工具的話,我不諱言這也許只是 Copilot 在 Github 看了比較少的 verilog project,如果給 AI 工具足夠的訓練資料,
把每個模組的 input, output port 複製過來這種事,對 AI 工具來說應該是小菜一碟吧?
AI 工具在發展潛力上比 language server 更多更大,也許會出現超過現有接線工具的 AI 工具也說不定。
為了拯救廣大的 verilog 工程師們,就讓我們拭目以待這兩種隱藏工具的未來發展吧。