第2章 Verilog/VHDL编码规范:命名、文件组织与注释

说实话,做了十几年数字芯片,我见过太多因为命名混乱导致项目delay的惨案。你想想看,一个模块叫 tmp,另一个叫 tmp2,三个月后连写代码的人自己都分不清。这一章我就把压箱底的命名习惯、文件结构、注释套路拿出来,都是我在流片项目里踩过坑之后总结的。

📌 编码规范核心体系 命名规范 • 信号命名:小写+下划线 • 模块命名:大写驼峰 • 参数/常量:全大写 • 时钟:clk_xxx • 复位:rst_n (低有效) • 避免缩写歧义 • 后缀 _n / _p 表示极性 文件组织结构 • 每个模块一个文件 • 文件名 = 模块名 • 目录按功能划分 • 统一文件头模板 • 版本/作者/修改记录 • 避免单文件过长 • 仿真文件与设计分离 注释规范 • 文件头注释(必写) • 端口/参数说明 • 关键逻辑段落注释 • 时序/状态机说明 • 避免废话注释 • TODO/FIXME 标记 • 与代码同步更新 相互影响 协同一致

2.1 命名规范 —— 一眼看懂信号是干什么的

我个人习惯用 小写字母加下划线 命名信号。比如 data_valid、wr_en。模块名用大写驼峰,像 AhbMaster、UartRx。参数呢?全大写加下划线,DATA_WIDTH、FIFO_DEPTH。这样看代码时,一眼就能区分信号、模块和常量。

✅ 推荐命名风格
// 信号
wire        clk_sys;
wire        rst_n;          // 低电平复位
reg  [31:0] data_out;
wire        fifo_full;

// 模块实例
AhbMaster u_ahb_master (
    .clk        (clk_sys),
    .rst_n      (rst_n),
    .haddr      (addr_bus)
);

// 参数
parameter ADDR_WIDTH = 12;
localparam IDLE = 2'b00;

我曾经在一个IP里看到信号叫 s1, s2, tmp_reg…… 三个月后做Lint检查,根本不知道哪个是状态哪个是数据。后来我们团队硬性规定:时钟信号必须以 clk_ 开头,复位以 rst_ 开头,低有效加 _n 后缀。你想想看,如果所有模块都统一这个规则,跨模块debug会轻松多少。

类型命名风格示例
模块名大写驼峰DmaController, SpiSlave
信号/线网小写+下划线addr_valid, wr_data
参数/常量全大写+下划线DATA_WIDTH, MAX_BURST
时钟clk_ 前缀clk_sys, clk_48m
复位 (低有效)rst_n 后缀rst_n, rst_ahb_n
使能/有效标志_en / _validwr_en, data_valid
⚠️ 我曾经踩过的坑: 把低有效复位写成 rst 而不是 rst_n,综合时工具默认高有效,导致整个芯片复位极性反了。从那以后,我坚持所有低有效信号必须加 _n,没有例外。

2.2 文件组织结构 —— 让工程像图书馆一样清晰

每个模块一个文件,文件名必须和模块名一致。比如模块 AhbMaster 就放在 AhbMaster.v 里。别把五个模块塞进一个文件,那会让Lint工具和代码审查都痛苦不堪。

我习惯的目录结构长这样:

rtl/
├── ahb/
│   ├── AhbMaster.v
│   ├── AhbSlave.v
│   └── AhbArbiter.v
├── uart/
│   ├── UartTx.v
│   └── UartRx.v
├── common/
│   ├── fifo.v
│   └── sync.v
└── top/
    └── chip_top.v

每个文件头部必须包含统一模板:模块名、作者、创建日期、修改记录、简要功能描述。别小看这个头,我在一个五年老项目里,靠文件头的修改记录找到了三年前引入的bug。

💡 我的小技巧: 在文件头里加一行 // Lint: 本模块已通过SpyGlass检查,这样后续维护的人一眼就知道这个模块的Lint状态。

2.3 代码注释规范 —— 写给未来的自己

注释不是写给编译器看的,是写给三个月后的你。我见过最没用的注释就是 // 计数器加1 —— 这不明摆着吗?注释应该说明 为什么 这么做,而不是 做了什么。

// 好的注释:解释意图
// 等待FIFO非空且至少有一个完整包,才开始读取
if (!fifo_empty && (pkt_len >= MIN_PKT_SIZE)) begin
    read_en <= 1'b1;
end

// 差的注释:废话
// 如果fifo不空并且包长大于最小包长,则拉高读使能
if (!fifo_empty && (pkt_len >= MIN_PKT_SIZE)) begin
    read_en <= 1'b1;
end

我个人习惯在关键状态机、跨时钟域处理、复杂组合逻辑前加一段段落注释。比如:

// ------------------------------------------------------------
// 状态机:AHB写传输
// 状态: IDLE -> SETUP -> DATA -> RESP
// 注意:在RESP阶段等待hready拉高后才能跳回IDLE
// ------------------------------------------------------------
📌 注释规范清单
  • 每个文件头部必须有模块功能、作者、日期、修改历史
  • 每个端口和参数在声明处加一行注释
  • always块或assign语句超过5行,前面加说明
  • 状态机用表格或文字描述状态转移条件
  • TODO 和 FIXME 必须标注日期和责任人
  • 不要注释掉大段代码 —— 用版本控制,不要用注释

嗯,这里要注意:注释要和代码同步更新。我见过最坑的是注释说“低有效复位”,代码里却用了高有效。这种不一致比没有注释更可怕。所以每次Lint检查前,我都会让团队过一遍注释是否过时。