第2章 Verilog/VHDL编码规范:命名、文件组织与注释
说实话,做了十几年数字芯片,我见过太多因为命名混乱导致项目delay的惨案。你想想看,一个模块叫 tmp,另一个叫 tmp2,三个月后连写代码的人自己都分不清。这一章我就把压箱底的命名习惯、文件结构、注释套路拿出来,都是我在流片项目里踩过坑之后总结的。
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 / _valid | wr_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检查前,我都会让团队过一遍注释是否过时。