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 知识体系总览

下面这张图,帮你把今天讲的内容串起来:

命名规范知识体系 信号命名 模块命名 参数命名 来源 + 功能 + 类型 常用后缀:_clk, _rst_n, _en 避坑:不用拼音、不用过度缩写 功能 + 类型 示例:i2c_master, fifo_ctrl 文件名与模块名保持一致 全大写 + 下划线 示例:DATA_WIDTH, FIFO_DEPTH localparam 用于内部常量 核心目标:自注释,让代码自己说话

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代码。你想想看,如果每个模块都这么写,项目维护起来得多省心。

一句话总结:命名规范不是形式主义,它是你代码质量的「门面」。好的命名,能让你少加班、少背锅。

可可精品资料库