SystemVerilog 代碼風(fēng)格指南
SystemVerilog 代碼風(fēng)格指南
前言
代碼被閱讀的頻率遠(yuǎn)遠(yuǎn)超過編寫的頻率。在團(tuán)隊(duì)中保持一致的編碼風(fēng)格能夠顯著提升代碼的可讀性,這是節(jié)省工程時(shí)間最有效(也是最簡單)的方法之一。
在眾多編程語言中,Python 可以說是最優(yōu)雅的。閱讀他人編寫的 Python 代碼非常輕松,即使是復(fù)雜的代碼邏輯也不會(huì)讓人望而卻步。更重要的是,初學(xué)者編寫的代碼與核心開發(fā)者的代碼在風(fēng)格上高度一致。這主要?dú)w功于 PEP8 這一 Python 代碼風(fēng)格指南,整個(gè)社區(qū)對這份文檔的采納程度令人驚嘆。
本風(fēng)格指南借鑒了 PEP8 的成功經(jīng)驗(yàn)和部分結(jié)構(gòu),同時(shí)結(jié)合了 UVM 庫的最佳實(shí)踐,避免重復(fù)造輪子。
PEP8 核心理念
風(fēng)格指南的本質(zhì)在于一致性。遵循本指南的一致性很重要,項(xiàng)目內(nèi)部的一致性更重要,單個(gè)模塊或功能內(nèi)的一致性是最重要的。
但要知道何時(shí)打破常規(guī)——有時(shí)風(fēng)格指南的建議并不適用。遇到疑惑時(shí),請運(yùn)用最佳判斷。
代碼布局規(guī)范
縮進(jìn)規(guī)則
基本原則: 每個(gè)縮進(jìn)級別使用 4 個(gè)空格。
? 推薦寫法:
// 參數(shù)換行時(shí),第二行從函數(shù)名下方開始
foo = long_function_name(
var_one, var_two, var_three,
var_four);
// 或者與第一個(gè)參數(shù)對齊(4空格縮進(jìn)可選)
foo = long_function_name(var_one, var_two,
var_three, var_four);
// 函數(shù)聲明中增加額外縮進(jìn),區(qū)分函數(shù)體
function void long_function_name(var_one, var_two,
var_three, var_four);
int x;
// ...函數(shù)體...
endfunction: long_function_name
// 條件表達(dá)式換行時(shí)增加縮進(jìn)
if (expr_one && expr_two &&
expr_three) begin
do_something();
end
? 不推薦寫法:
// 第二行參數(shù)位置不當(dāng)
foo = long_function_name(var_one, var_two,
var_three, var_four);
// 函數(shù)聲明縮進(jìn)不清晰,容易與函數(shù)體混淆
function void long_function_name(var_one, var_two,
var_three, var_four);
int x;
// ...
endfunction: long_function_name
制表符與空格的選擇
推薦使用空格進(jìn)行縮進(jìn)。 制表符僅在與已有制表符縮進(jìn)的代碼保持兼容時(shí)使用。
編輯器配置示例:
Vi/Vim 配置:
" 將以下內(nèi)容添加到 ~/.vimrc
set tabstop=4
set shiftwidth=4
set expandtab
Emacs 配置:
; 將以下內(nèi)容添加到 ~/.emacs
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq indent-line-function 'insert-tab)
最大行長度限制
建議將所有行(包括注釋)限制為最多 100 個(gè)字符。
傳統(tǒng)建議是 80 個(gè)字符,但考慮到 UVM 的長宏定義特點(diǎn):
`uvm_object_utils
`uvm_info(get_name(), "detailed message here", UVM_MEDIUM)
在這些聲明中,僅宏和函數(shù)名就占用了約 30 個(gè)字符。如果嚴(yán)格遵循 80 字符限制,會(huì)頻繁遇到換行困擾。因此,100 字符的限制更為實(shí)用和合理。
重要提醒: 確保換行時(shí)進(jìn)行適當(dāng)?shù)目s進(jìn)對齊。
begin & end 語句塊
begin與它所屬的塊語句在同一行end獨(dú)占一行
? 推薦寫法:
always_ff @(posedge clk) begin
// 邏輯代碼
end
if (big_endian == 1) begin
m_bits[count+i] = value[size-1-i];
end
else begin
m_bits[count+i] = value[i];
end
for (int i = 0; i < size; i++) begin
if (big_endian == 1) begin
m_bits[count+i] = value[size-1-i];
end
else begin
m_bits[count+i] = value[i];
end
end
if & else 條件語句
else 從新行開始
? 推薦寫法:
if (big_endian == 1) begin
m_bits[count+i] = value[size-1-i];
end
else begin
m_bits[count+i] = value[i];
end
? 不推薦寫法:
if (big_endian == 1) begin
m_bits[count+i] = value[size-1-i];
end else begin
m_bits[count+i] = value[i];
end
強(qiáng)烈建議: 始終與條件語句一起使用 begin/end。不使用花括號是產(chǎn)生 bug 的溫床。
? 危險(xiǎn)的寫法:
// 避免這種寫法
if (big_endian == 1)
m_bits[count+i] = value[size-1-i];
else
m_bits[count+i] = value[i];
// 特別要避免嵌套時(shí)不用花括號:
// 雖然代碼能正常工作,但其他人可能會(huì)在 else 后添加代碼
// 并誤以為會(huì)在 else 條件下執(zhí)行,從而引入難以發(fā)現(xiàn)的 bug
for (int i = 0; i < size; i++)
if (big_endian == 1)
m_bits[count+i] = value[size-1-i];
else
m_bits[count+i] = value[i];
空行使用原則
- 用空行包圍類、函數(shù)和任務(wù)定義
- 相關(guān)的單行代碼之間可以省略空行
- 在函數(shù)和任務(wù)中謹(jǐn)慎使用空行來區(qū)分邏輯段落
空格使用規(guī)范
函數(shù)與任務(wù)調(diào)用
基本規(guī)則: 函數(shù)名與左括號之間不加空格,左括號與第一個(gè)參數(shù)之間也不加空格。
? 推薦寫法:
function void foo(x, y, z);
foo(x, y, z);
? 不推薦寫法:
function void foo (x, y, z);
foo (x, y, z);
foo( x, y, z );
默認(rèn)參數(shù)處理: 默認(rèn)參數(shù)值的等號周圍不加空格。
? 推薦寫法:
function void foo(name="foo", x=1, y=20);
? 不推薦寫法:
function void foo(name = "foo", x = 1, y = 20);
賦值和運(yùn)算符規(guī)范
基本原則: 不要為了對齊而在賦值運(yùn)算符周圍添加多余的空格。
? 推薦寫法:
x = 1;
y = 2;
long_variable = 3;
? 不推薦寫法:
x = 1;
y = 2;
long_variable = 3;
運(yùn)算符空格規(guī)則: 以下二元運(yùn)算符兩側(cè)應(yīng)該始終保持空格:
- 賦值操作符:
= - 復(fù)合賦值:
+=,-=,*=,/=等 - 比較操作符:
==,===,<,>,!=,!==,<=,>= - 邏輯操作符:
&,&&,|,||
// 正確的操作符使用
result = (a + b) * c;
if (count >= max_value && enable == 1'b1) begin
status += increment_value;
end
循環(huán)和條件語句
基本規(guī)則:
if,for,while等關(guān)鍵字與左括號之間加一個(gè)空格- for 循環(huán)中的各個(gè)部分之間保持空格:
int i = 0; i < 10; i++
? 推薦寫法:
if (x == 10)
for (int ii = 0; ii < 20; ii++)
while (condition_true)
? 不推薦寫法:
if(x == 10)
if( x == 10 )
for(int ii=0;ii<20;ii++)
復(fù)合語句: 通常不建議在同一行寫多個(gè)語句。
? 不推薦寫法:
if (foo == 1) $display("bar");
always 塊的格式
? 推薦寫法:
always_ff @(posedge clk) begin
// 時(shí)序邏輯
end
always_comb begin
// 組合邏輯
end
注釋規(guī)范
關(guān)于注釋的重要提醒
與代碼相矛盾的注釋比沒有注釋更糟糕。當(dāng)代碼更改時(shí),始終優(yōu)先保持注釋的及時(shí)更新!
注釋基本規(guī)范:
- 注釋應(yīng)該是完整的句子
- 如果注釋是短語或句子,第一個(gè)單詞應(yīng)大寫(除非是小寫標(biāo)識(shí)符)
- 短注釋可以省略末尾的句點(diǎn)
- 塊注釋通常由完整句子組成,每句都應(yīng)以句點(diǎn)結(jié)尾
版權(quán)橫幅
對于許可/版權(quán)橫幅,請使用以下樣式注釋塊:
/***********************************************************************
* Copyright 2007-2011 Mentor Graphics Corporation
* Copyright 2007-2010 Cadence Design Systems, Inc.
* Copyright 2010 Synopsys, Inc.
* Copyright 2013 NVIDIA Corporation
* All Rights Reserved Worldwide
*
* Licensed under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in
* writing, software distributed under the License is
* distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
* CONDITIONS OF ANY KIND, either express or implied. See
* the License for the specific language governing
* permissions and limitations under the License.
**********************************************************************/
文檔字符串
Docstring(文檔字符串)是位于文件頂部的注釋,提供該文件中代碼功能的高級描述。將文檔字符串放在版權(quán)橫幅的正后方。不要將它們混合在一起。對此段使用以下樣式:
* Ending of copyright banner
**********************************************************************/
/*
* Module `ABC`
*
* This is the 1st paragraph. Separate paragraphs with
* an empty line with just the ` *` character.
*
* This is the 2nd paragraph. Do not fence the docstring
* in a banner of `*****`. Only the copyright segment
* above the docstring gets a fence.
*/
塊注釋
塊注釋通常適用于其后面的某些(或全部)代碼,并與該代碼保持相同的縮進(jìn)級別。
塊注釋的每一行都以 // 和一個(gè)空格開頭(除非是注釋內(nèi)的縮進(jìn)文本)。塊注釋中的段落由包含單個(gè) // 的行分隔。
你也可以使用多行塊注釋格式 /* */:
// 這是塊注釋的第一行
// 這是第二行
//
// 這是塊注釋的第二段
/*
* 這種注釋描述了
* 下面代碼的功能
*/
foo = bar + 1;
內(nèi)聯(lián)注釋
避免使用內(nèi)聯(lián)注釋,它們會(huì)影響代碼的整潔性:
// 不要這樣做
x = x + 1; // 增加數(shù)據(jù)包計(jì)數(shù)
注釋使用建議
為了趕上項(xiàng)目截止日期,注釋往往會(huì)被忽略。但當(dāng)你在一段時(shí)間后重新審視代碼時(shí),總是會(huì)后悔這個(gè)決定。因此,現(xiàn)在花一點(diǎn)時(shí)間寫注釋,能為將來省去很多痛苦。未來的你會(huì)感謝現(xiàn)在的你。
避免使用注釋圍欄,例如:
/***********************///######################//////////////
這些圍欄會(huì)讓代碼顯得雜亂,實(shí)際幫助有限。良好的塊注釋就能提供清晰的代碼分隔效果。只有版權(quán)橫幅應(yīng)該使用圍欄格式。
命名約定
為了統(tǒng)一理解,我們先定義幾種常見的命名約定:
- PascalCase - 每個(gè)單詞的第一個(gè)字母都大寫
- camelCase - 每個(gè)單詞的第一個(gè)字母(除第一個(gè)單詞外)均大寫
- lowercase_with_underscores - 小寫字母加下劃線
- UPPERCASE_WITH_UNDERSCORES - 大寫字母加下劃線
文件名
文件名應(yīng)使用 lowercase_with_underscore
crc_generator.sv
tb_defines.svh
module_specification.docx
input_message_buffer.sv
類和模塊
類和模塊名稱應(yīng)使用 lowercase_with_underscore。如果文件中只有一個(gè)類或模塊,則其名稱應(yīng)與文件名相同。
class packet_parser_agent;
endclass: packet_parser_agent
module packet_parser_engine;
endmodule: packet_parser_engine
類實(shí)例應(yīng)被視為變量,并應(yīng)使用 lowercase_with_underscore 格式。模塊實(shí)例應(yīng)使用純駝峰命名法,不帶任何下劃線。
// Class
packet_parser_agent parser_agent;
parser_agent = new();
// Module
packet_parser_engine ppe0(.*);
packet_parser_engine packetParserEngine4a(.*);
packet_parser_engine packetParserEngine4b(.*);
接口
- 接口定義使用
lowercase_with_underscores以 "_io" 結(jié)尾 - 接口實(shí)例以 "_if" 結(jié)尾
- clocking 塊使用
camelCase - modport 最好只是一個(gè)詞
lowercase
interface bus_io(input bit clk);
logic vld;
logic [7:0] addr, data;
clocking ioDrv @posedge(clk);
input addr;
output vld;
output data;
endclocking: ioDrv
modport dut(input addr, output vld, data);
modport tb(clocking ioDrv);
endinterface: bus_io
module tb_top;
bus_io bus_if(clk);
endmodule: tb_top
變量
變量名稱應(yīng)始終使用 lowercase_with_underscore
ethernet_agent eth_agent;
int count_packets, count_errors;
logic [15:0] some_long_var;
如有必要,使用前綴輕松識(shí)別和分組變量:
logic [31:0] pe_counter_0;
logic [31:0] pe_counter_1;
logic [31:0] pe_counter_2;
結(jié)構(gòu)、聯(lián)合和枚舉
typedef 所有結(jié)構(gòu)、聯(lián)合和枚舉。他們應(yīng)該使用駝峰命名法,并具有以下區(qū)別:
- 結(jié)構(gòu)體以
_s結(jié)尾 - 聯(lián)合以
_u結(jié)尾 - 枚舉以
_e結(jié)尾。此外,枚舉應(yīng)使用UPPERCASE_WITH_UNDERSCORES
typedef struct packed {
logic [47:0] macda;
logic [47:0] macsa;
logic [15:0] etype;
} ethPacket_s;
typedef union packed {
logic [15:0] tx_count;
logic [15:0] rx_count;
} dataPacketCount_u;
typedef enum logic [1:0] {
IPV4_TCP,
IPV4_UDP,
IPV6_TCP,
IPV6_UDP
} packetType_e;
類型變量名稱
類型變量名稱應(yīng)為 UPPERCASE,最好只有一個(gè)單詞。
// Following examples were extracted from the UVM code base.
// The file path where they can be found is also mentioned.
// tlm1/uvm_exports.svh
class uvm_get_peek_export #(type T=int);
class uvm_blocking_master_export #(type REQ=int, type RSP=REQ);
// base/uvm_traversal.svh
virtual class uvm_visitor_adapter #(type STRUCTURE=uvm_component,
VISITOR=uvm_visitor#(STRUCTURE)) extends uvm_object;
宏命名規(guī)范
宏的命名需要根據(jù)用途進(jìn)行區(qū)分:
- 函數(shù)/任務(wù)宏: 使用 UPPERCASE 宏名稱和 lowercase 參數(shù)名稱
- 類/代碼片段宏: 使用 lowercase 宏名稱和 UPPERCASE 參數(shù)名稱
- 單詞分隔: 統(tǒng)一使用下劃線分隔
// 函數(shù)/任務(wù)宏:UPPERCASE 宏名 + lowercase 參數(shù)
`define PRINT_BYTES(arr, startbyte, numbytes) \
function print_bytes(logic[7:0] arr[], int startbyte, int numbytes); \
for (int ii=startbyte; ii<startbyte+numbytes; ii++) begin \
if ((ii != 0) && (ii % 16 == 0)) \
$display("\n"); \
$display("0x%x ", arr[ii]); \
end \
endfunction: print_bytes
// 類定義宏:lowercase 宏名 + UPPERCASE 參數(shù)
`define uvm_analysis_imp_decl(SFX) \
class uvm_analysis_imp``SFX #(type T=int, type IMP=int) \
extends uvm_port_base #(uvm_tlm_if_base #(T,T)); \
`UVM_IMP_COMMON(`UVM_TLM_ANALYSIS_MASK,`"uvm_analysis_imp``SFX`",IMP) \
function void write( input T t); \
m_imp.write``SFX( t); \
endfunction \
endclass
// 代碼片段宏:lowercase 宏名 + UPPERCASE 參數(shù)
`define uvm_create_on(SEQ_OR_ITEM, SEQR) \
begin \
uvm_object_wrapper w_; \
w_ = SEQ_OR_ITEM.get_type(); \
$cast(SEQ_OR_ITEM , create_item(w_, SEQR, `"SEQ_OR_ITEM`"));\
end
擴(kuò)展閱讀
想了解更多 SystemVerilog 宏的使用技巧,可以參考宏使用詳細(xì)指南。
結(jié)束標(biāo)識(shí)符
在適用的情況下始終使用結(jié)束標(biāo)識(shí)符:
endclass: driver_agent
endmodule: potato_block
endinterface: memory_io
endtask: cowboy_bebop
編程實(shí)踐建議
由于 SystemVerilog 橫跨設(shè)計(jì)和驗(yàn)證兩個(gè)領(lǐng)域,擁有豐富的語言特性。對于本風(fēng)格指南中未明確提及的其他語言結(jié)構(gòu),如斷言、覆蓋率、約束、時(shí)序控制等,都可以參照以上原則進(jìn)行相應(yīng)擴(kuò)展。
結(jié)語
編寫優(yōu)雅的代碼并非易事。在緊張的項(xiàng)目交付期限和繁重的工作任務(wù)中,很難有精力去關(guān)注、回顧、重構(gòu)和優(yōu)化匆忙產(chǎn)生的代碼。在這樣的時(shí)刻,一些前輩的智慧話語能夠幫助我們堅(jiān)持編寫優(yōu)雅代碼的初心:
讓我們改變對程序構(gòu)建的傳統(tǒng)態(tài)度:與其想象我們的主要任務(wù)是指示計(jì)算機(jī)做什么,不如讓我們專注于向人類解釋我們希望計(jì)算機(jī)做什么。
— 唐納德·克努斯
所以,漂亮的代碼是清晰的,易于閱讀和理解;它的組織、形狀、架構(gòu)和聲明性語法一樣揭示了意圖。每個(gè)小部分都是連貫的,其目的是單一的,盡管所有這些小部分都像復(fù)雜馬賽克的碎片一樣組合在一起,但當(dāng)一個(gè)元素需要更改或替換時(shí),它們很容易分開。
— 維克拉姆·錢德拉 (Vikram Chandra) 作者《Geek Sublime》
參考資料
- UVM 源代碼
- PEP8 - Python 代碼風(fēng)格指南
- PEP7 - C 代碼風(fēng)格指南
- 《極客崇高》- 維克拉姆·錢德拉
- 《美麗的代碼》- 安迪·奧拉姆、格雷格·威爾遜
- 空白大辯論

浙公網(wǎng)安備 33010602011771號