SystemVerilog 代码风格指南

SystemVerilog 代码风格指南

前言

代码被阅读的频率远远超过编写的频率。在团队中保持一致的编码风格能够显著提升代码的可读性,这是节省工程时间最有效(也是最简单)的方法之一。

在众多编程语言中,Python 可以说是最优雅的。阅读他人编写的 Python 代码非常轻松,即使是复杂的代码逻辑也不会让人望而却步。更重要的是,初学者编写的代码与核心开发者的代码在风格上高度一致。这主要归功于 PEP8 这一 Python 代码风格指南,整个社区对这份文档的采纳程度令人惊叹。

本风格指南借鉴了 PEP8 的成功经验和部分结构,同时结合了 UVM 库的最佳实践,避免重复造轮子。

PEP8 核心理念

风格指南的本质在于一致性。遵循本指南的一致性很重要,项目内部的一致性更重要,单个模块或功能内的一致性是最重要的。

但要知道何时打破常规——有时风格指南的建议并不适用。遇到疑惑时,请运用最佳判断。

代码布局规范

缩进规则

基本原则: 每个缩进级别使用 4 个空格。

✅ 推荐写法:

// 参数换行时,第二行从函数名下方开始
foo = long_function_name(
        var_one, var_two, var_three,
        var_four);

// 或者与第一个参数对齐(4空格缩进可选)
foo = long_function_name(var_one, var_two,
                        var_three, var_four);

// 函数声明中增加额外缩进,区分函数体
function void long_function_name(var_one, var_two,
        var_three, var_four);
    int x;
    // ...函数体...
endfunction: long_function_name

// 条件表达式换行时增加缩进
if (expr_one && expr_two &&
        expr_three) begin
    do_something();
end

❌ 不推荐写法:

// 第二行参数位置不当
foo = long_function_name(var_one, var_two,
    var_three, var_four);

// 函数声明缩进不清晰,容易与函数体混淆
function void long_function_name(var_one, var_two,
    var_three, var_four);
    int x;
    // ...
endfunction: long_function_name

制表符与空格的选择

推荐使用空格进行缩进。 制表符仅在与已有制表符缩进的代码保持兼容时使用。

编辑器配置示例:

Vi/Vim 配置:

" 将以下内容添加到 ~/.vimrc
set tabstop=4
set shiftwidth=4
set expandtab

Emacs 配置:

; 将以下内容添加到 ~/.emacs
(setq-default indent-tabs-mode nil)
(setq-default tab-width 4)
(setq indent-line-function 'insert-tab)

最大行长度限制

建议将所有行(包括注释)限制为最多 100 个字符。

传统建议是 80 个字符,但考虑到 UVM 的长宏定义特点:

`uvm_object_utils
`uvm_info(get_name(), "detailed message here", UVM_MEDIUM)

在这些声明中,仅宏和函数名就占用了约 30 个字符。如果严格遵循 80 字符限制,会频繁遇到换行困扰。因此,100 字符的限制更为实用和合理。

重要提醒: 确保换行时进行适当的缩进对齐。

begin & end 语句块

  • begin 与它所属的块语句在同一行
  • end 独占一行

✅ 推荐写法:

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

强烈建议: 始终与条件语句一起使用 begin/end。不使用花括号是产生 bug 的温床。

❌ 危险的写法:

// 避免这种写法
if (big_endian == 1)
    m_bits[count+i] = value[size-1-i];
else
    m_bits[count+i] = value[i];

// 特别要避免嵌套时不用花括号:
// 虽然代码能正常工作,但其他人可能会在 else 后添加代码
// 并误以为会在 else 条件下执行,从而引入难以发现的 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];

空行使用原则

  • 用空行包围类、函数和任务定义
  • 相关的单行代码之间可以省略空行
  • 在函数和任务中谨慎使用空行来区分逻辑段落

空格使用规范

函数与任务调用

基本规则: 函数名与左括号之间不加空格,左括号与第一个参数之间也不加空格。

✅ 推荐写法:

function void foo(x, y, z); 
foo(x, y, z);

❌ 不推荐写法:

function void foo (x, y, z);
foo (x, y, z);
foo( x, y, z );

默认参数处理: 默认参数值的等号周围不加空格。

✅ 推荐写法:

function void foo(name="foo", x=1, y=20);

❌ 不推荐写法:

function void foo(name = "foo", x = 1, y = 20);

赋值和运算符规范

基本原则: 不要为了对齐而在赋值运算符周围添加多余的空格。

✅ 推荐写法:

x = 1;
y = 2;
long_variable = 3;

❌ 不推荐写法:

x             = 1;
y             = 2;
long_variable = 3;

运算符空格规则: 以下二元运算符两侧应该始终保持空格:

  • 赋值操作符: =
  • 复合赋值: +=, -=, *=, /=
  • 比较操作符: ==, ===, <, >, !=, !==, <=, >=
  • 逻辑操作符: &, &&, |, ||
// 正确的操作符使用
result = (a + b) * c;
if (count >= max_value && enable == 1'b1) begin
    status += increment_value;
end

循环和条件语句

基本规则:

  • if, for, while 等关键字与左括号之间加一个空格
  • for 循环中的各个部分之间保持空格: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++)

复合语句: 通常不建议在同一行写多个语句。

❌ 不推荐写法:

if (foo == 1) $display("bar");

always 块的格式

✅ 推荐写法:

always_ff @(posedge clk) begin
    // 时序逻辑
end

always_comb begin
    // 组合逻辑
end

注释规范

关于注释的重要提醒

与代码相矛盾的注释比没有注释更糟糕。当代码更改时,始终优先保持注释的及时更新!

注释基本规范:

  • 注释应该是完整的句子
  • 如果注释是短语或句子,第一个单词应大写(除非是小写标识符)
  • 短注释可以省略末尾的句点
  • 块注释通常由完整句子组成,每句都应以句点结尾

版权横幅

对于许可/版权横幅,请使用以下样式注释块:

/***********************************************************************
 * 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(文档字符串)是位于文件顶部的注释,提供该文件中代码功能的高级描述。将文档字符串放在版权横幅的正后方。不要将它们混合在一起。对此段使用以下样式:

 * 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.
 */

块注释

块注释通常适用于其后面的某些(或全部)代码,并与该代码保持相同的缩进级别。

块注释的每一行都以 // 和一个空格开头(除非是注释内的缩进文本)。块注释中的段落由包含单个 // 的行分隔。

你也可以使用多行块注释格式 /* */

// 这是块注释的第一行
// 这是第二行
//
// 这是块注释的第二段

/* 
 * 这种注释描述了
 * 下面代码的功能
 */
 foo = bar + 1;

内联注释

避免使用内联注释,它们会影响代码的整洁性:

// 不要这样做
x = x + 1;    // 增加数据包计数

注释使用建议

为了赶上项目截止日期,注释往往会被忽略。但当你在一段时间后重新审视代码时,总是会后悔这个决定。因此,现在花一点时间写注释,能为将来省去很多痛苦。未来的你会感谢现在的你。

避免使用注释围栏,例如:

  • /***********************/
  • //######################
  • //////////////

这些围栏会让代码显得杂乱,实际帮助有限。良好的块注释就能提供清晰的代码分隔效果。只有版权横幅应该使用围栏格式。

命名约定

为了统一理解,我们先定义几种常见的命名约定:

  • PascalCase - 每个单词的第一个字母都大写
  • camelCase - 每个单词的第一个字母(除第一个单词外)均大写
  • lowercase_with_underscores - 小写字母加下划线
  • UPPERCASE_WITH_UNDERSCORES - 大写字母加下划线

文件名

文件名应使用 lowercase_with_underscore

crc_generator.sv
tb_defines.svh
module_specification.docx
input_message_buffer.sv

类和模块

类和模块名称应使用 lowercase_with_underscore。如果文件中只有一个类或模块,则其名称应与文件名相同。

class packet_parser_agent;
endclass: packet_parser_agent

module packet_parser_engine;
endmodule: packet_parser_engine

类实例应被视为变量,并应使用 lowercase_with_underscore 格式。模块实例应使用纯驼峰命名法,不带任何下划线。

// 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" 结尾
  • 接口实例以 "_if" 结尾
  • clocking 块使用 camelCase
  • modport 最好只是一个词 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

变量

变量名称应始终使用 lowercase_with_underscore

ethernet_agent eth_agent;
int count_packets, count_errors;
logic [15:0] some_long_var;

如有必要,使用前缀轻松识别和分组变量:

logic [31:0] pe_counter_0;
logic [31:0] pe_counter_1;
logic [31:0] pe_counter_2;

结构、联合和枚举

typedef 所有结构、联合和枚举。他们应该使用驼峰命名法,并具有以下区别:

  • 结构体以 _s 结尾
  • 联合以 _u 结尾
  • 枚举以 _e 结尾。此外,枚举应使用 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;

类型变量名称

类型变量名称应为 UPPERCASE,最好只有一个单词。

// 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;

宏命名规范

宏的命名需要根据用途进行区分:

  • 函数/任务宏: 使用 UPPERCASE 宏名称和 lowercase 参数名称
  • 类/代码片段宏: 使用 lowercase 宏名称和 UPPERCASE 参数名称
  • 单词分隔: 统一使用下划线分隔
// 函数/任务宏:UPPERCASE 宏名 + lowercase 参数  
`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 参数
`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 参数
`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

扩展阅读

想了解更多 SystemVerilog 宏的使用技巧,可以参考宏使用详细指南

结束标识符

在适用的情况下始终使用结束标识符:

endclass: driver_agent
endmodule: potato_block
endinterface: memory_io
endtask: cowboy_bebop

编程实践建议

由于 SystemVerilog 横跨设计和验证两个领域,拥有丰富的语言特性。对于本风格指南中未明确提及的其他语言结构,如断言、覆盖率、约束、时序控制等,都可以参照以上原则进行相应扩展。

结语

编写优雅的代码并非易事。在紧张的项目交付期限和繁重的工作任务中,很难有精力去关注、回顾、重构和优化匆忙产生的代码。在这样的时刻,一些前辈的智慧话语能够帮助我们坚持编写优雅代码的初心:

让我们改变对程序构建的传统态度:与其想象我们的主要任务是指示计算机做什么,不如让我们专注于向人类解释我们希望计算机做什么。

— 唐纳德·克努斯

所以,漂亮的代码是清晰的,易于阅读和理解;它的组织、形状、架构和声明性语法一样揭示了意图。每个小部分都是连贯的,其目的是单一的,尽管所有这些小部分都像复杂马赛克的碎片一样组合在一起,但当一个元素需要更改或替换时,它们很容易分开。

— 维克拉姆·钱德拉 (Vikram Chandra) 作者《Geek Sublime》

参考资料

posted @ 2025-09-23 16:08  LeslieQ  阅读(15)  评论(0)    收藏  举报