3. 命名规范(一):信号命名规则、模块命名规则、参数命名规则
说实话,我在刚入行那会儿,觉得命名这事儿挺虚的。代码能跑通不就行了?直到有一次,我接手了一个前辈留下的项目——信号名全是 a1、b2、tmp 这种,模块名更是天书。我花了整整三天才理清楚一条数据通路。嗯,从那以后,我再也不敢小看命名了。
命名规范,说白了就是给你的代码「上户口」。好的命名,能让别人(包括三个月后的你自己)一眼看懂你的设计意图。今天咱们就聊聊信号、模块和参数这三类核心元素的命名规则。
核心原则:命名要自注释(self-documenting)。看到名字,就该知道它是什么、干什么用的。
3.1 信号命名规则
信号是RTL设计里最基础的单元。我个人的习惯是:信号名要体现「来源 + 功能 + 类型」。举个例子,一个来自CPU写地址总线的信号,我会命名为 cpu_wr_addr,而不是 addr1。
3.1.1 常用信号后缀
这里我整理了一份我团队内部一直在用的信号后缀表,你直接拿去用就行:
| 信号类型 | 推荐后缀 | 示例 | 说明 |
|---|---|---|---|
| 时钟 | _clk |
sys_clk |
全局或模块时钟 |
| 复位 | _rst_n |
sys_rst_n |
低电平有效复位,加 _n 后缀 |
| 使能 | _en |
fifo_wr_en |
使能信号,高有效 |
| 有效指示 | _vld |
data_vld |
数据有效标志 |
| 就绪 | _rdy |
tx_rdy |
发送就绪 |
| 请求 | _req |
dma_req |
请求信号 |
| 应答 | _ack |
dma_ack |
应答信号 |
| 中断 | _irq |
uart_irq |
中断请求 |
| 计数器 | _cnt |
burst_cnt |
计数信号 |
| 状态 | _state |
fsm_state |
状态机当前状态 |
小技巧:低电平有效的信号,统一加 _n 后缀。比如 rst_n、cs_n。这样一看就知道它是「反着来的」。
3.1.2 信号命名避坑指南
- 不要用拼音。我曾经见过一个项目里用
xinhao_1、dizhi_bus,说实话,看着很别扭。统一用英文。 - 不要用缩写过度。比如
addr可以,但adr就有点过了。你想想看,别人看到adr还得猜一下。 - 大小写统一。我建议全部小写,用下划线分隔。比如
write_data,而不是WriteData或writedata。 - 避免歧义。比如
data_in和data_out就很清楚,但data1和data2就让人摸不着头脑。
3.2 模块命名规则
模块名,我习惯用 「功能 + 类型」 的结构。比如一个UART的接收模块,我会命名为 uart_rx,而不是 rx_module。
3.2.1 模块命名格式
我个人推荐这种格式:
<功能描述>_<模块类型>
举个例子:
i2c_master— I2C主控制器spi_slave— SPI从设备fifo_ctrl— FIFO控制器clk_div— 时钟分频器data_align— 数据对齐模块
注意:模块名不要包含版本号或日期。比如 uart_v2、fifo_2024 这种,版本管理应该交给Git,而不是写在名字里。
3.2.2 模块文件命名
模块文件名应该和模块名保持一致。比如模块名叫 uart_rx,那文件名就是 uart_rx.v 或 uart_rx.sv。这样做的好处是:
- 文件系统里一眼就能找到对应的模块
- 编译脚本里不容易出错
- 代码审查时方便定位
3.3 参数命名规则
参数(parameter)在RTL里用来定义常量。我见过有人把参数写成全大写,也有人写成小写。我个人建议:参数名用全大写,下划线分隔。这样一眼就能和信号、变量区分开。
3.3.1 参数命名格式
<模块前缀>_<参数描述>
举个例子:
parameter DATA_WIDTH = 32;
parameter ADDR_WIDTH = 16;
parameter FIFO_DEPTH = 256;
parameter CLK_FREQ_MHZ = 100;
你看,DATA_WIDTH 一看就知道是数据位宽,FIFO_DEPTH 是FIFO深度。全大写的形式在代码里非常醒目。
3.3.2 参数命名避坑指南
- 不要用魔法数字。比如直接在代码里写
#(32),别人根本不知道这个32是什么意思。应该写成#(DATA_WIDTH)。 - 参数名要有意义。比如
DEPTH不如FIFO_DEPTH清楚,WIDTH不如DATA_WIDTH明确。 - 局部参数用 localparam。如果参数只在模块内部使用,用
localparam而不是parameter,防止外部覆盖。
我的习惯:所有可配置的参数用 parameter,内部常量用 localparam。比如状态机的编码值,我就用 localparam 定义。
3.4 知识体系总览
下面这张图,帮你把今天讲的内容串起来:
3.5 一个完整的例子
说了这么多,咱们看一个实际的模块代码。这是一个简单的FIFO控制器,你看看命名是怎么用的:
module fifo_ctrl #(
parameter DATA_WIDTH = 32,
parameter ADDR_WIDTH = 8,
localparam FIFO_DEPTH = 1 << ADDR_WIDTH
)(
input wire clk,
input wire rst_n,
input wire wr_en,
input wire [DATA_WIDTH-1:0] wr_data,
input wire rd_en,
output wire [DATA_WIDTH-1:0] rd_data,
output wire full,
output wire empty,
output wire [ADDR_WIDTH:0] fifo_cnt
);
// 内部信号
reg [ADDR_WIDTH-1:0] wr_ptr;
reg [ADDR_WIDTH-1:0] rd_ptr;
reg [DATA_WIDTH-1:0] mem [0:FIFO_DEPTH-1];
// 写指针逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
wr_ptr <= '0;
else if (wr_en && !full)
wr_ptr <= wr_ptr + 1'b1;
end
// 读指针逻辑
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
rd_ptr <= '0;
else if (rd_en && !empty)
rd_ptr <= rd_ptr + 1'b1;
end
// 空满标志
assign full = (fifo_cnt == FIFO_DEPTH);
assign empty = (fifo_cnt == 0);
endmodule
你看,这个模块里:
- 信号名都带了后缀:
clk、rst_n、wr_en、rd_en - 参数名全大写:
DATA_WIDTH、ADDR_WIDTH - 内部常量用
localparam:FIFO_DEPTH - 模块名
fifo_ctrl清晰表达了功能
嗯,这就是我心目中「干净」的RTL代码。你想想看,如果每个模块都这么写,项目维护起来得多省心。
一句话总结:命名规范不是形式主义,它是你代码质量的「门面」。好的命名,能让你少加班、少背锅。