4. 模块命名规范:顶层模块命名、子模块命名、实例化命名、信号命名前缀/后缀规则

模块命名这事儿,看着简单,其实特别容易踩坑。我见过不少项目,代码写得很漂亮,但一看模块名,乱七八糟的,根本分不清谁是谁。说白了,命名规范就是给代码贴标签,标签贴好了,后面的人一看就懂,不用猜。

我个人习惯,命名规范要解决三个问题:这个模块是干什么的?它在哪个层级?它跟谁连? 下面我一个个说。

4.1 顶层模块命名:一眼看出这是老大

顶层模块,就是整个芯片或整个子系统的入口。命名要体现它的功能归属和层级地位。

我建议的格式:<项目名>_<功能名>_top

举个例子:

// 项目名:pll_clk_gen
// 功能:时钟生成
// 顶层命名
module pll_clk_gen_top (
    input  wire clk_ref,
    input  wire rst_n,
    output wire pll_clk_out,
    output wire pll_locked
);

为什么加 _top?因为你在仿真脚本、综合脚本里,一眼就能找到顶层。我在项目中遇到过,有人把顶层模块叫 pll_clk_gen,结果子模块也叫 pll_clk_gen,两个名字一模一样,综合工具直接报错。嗯,这种低级错误,加个 _top 就能避免。

核心原则: 顶层模块名必须唯一,且能体现它是整个设计的入口。

4.2 子模块命名:层级清晰,功能明确

子模块命名,我习惯用 <父模块名>_<子功能名> 的格式。这样从名字就能看出它在哪个层级下面。

比如,PLL 时钟生成模块下面,可能有这几个子模块:

// 父模块:pll_clk_gen_top
// 子模块命名
module pll_clk_gen_pll_core   ( ... );  // PLL 核心
module pll_clk_gen_divider    ( ... );  // 分频器
module pll_clk_gen_glitch_free( ... );  // 无毛刺切换
module pll_clk_gen_status     ( ... );  // 状态监控

你看,每个子模块前面都带着 pll_clk_gen_,一看就知道是同一个父模块下的。你想想看,如果子模块叫 divider,那这个 divider 到底是哪个模块的?分不清。

我曾经在一个项目里,看到有人把子模块命名为 u_div、u_pll,结果代码里一堆 u_xxx,根本不知道哪个是哪个。后来重构的时候,光改名字就花了两天。所以,子模块名一定要带父模块前缀。

4.3 实例化命名:跟模块名区分开

实例化命名,说白了就是给模块的“实例”起个名字。很多人喜欢用 u_xxx 的格式,我个人不太推荐。为什么?因为 u_ 太通用,容易重复。

我建议的格式:u_<模块名>,但模块名要去掉父模块前缀。

看个例子:

// 模块定义
module pll_clk_gen_divider ( ... );

// 实例化
pll_clk_gen_divider u_divider (
    .clk_in (clk_pll),
    .clk_out(clk_div)
);

这里实例名叫 u_divider,而不是 u_pll_clk_gen_divider。为什么?因为实例名太长,代码可读性反而下降。而且,在父模块内部,你本来就知道这个实例属于哪个模块,所以用 u_divider 就够了。

小技巧: 实例名用 u_ 开头,模块名用 _top 或功能名结尾,两者自然区分开。

4.4 信号命名前缀/后缀规则:一看就知道类型

信号命名,我习惯用前缀表示方向或类型,后缀表示功能或属性。这样,看到信号名就能猜出它大概是什么。

下面是我常用的规则:

前缀/后缀 含义 示例
clk_ 时钟信号 clk_ref, clk_pll
rst_ 复位信号 rst_n, rst_sync
en_ 使能信号 en_pll, en_div
cfg_ 配置信号 cfg_div_ratio, cfg_sel
_n 低电平有效 rst_n, en_n
_i 输入信号 clk_ref_i, cfg_i
_o 输出信号 pll_clk_o, locked_o
_b 双向信号 data_b, sda_b

举个例子,一个完整的 PLL 模块端口信号:

module pll_clk_gen_top (
    input  wire clk_ref_i,      // 参考时钟输入
    input  wire rst_n,          // 复位,低有效
    input  wire [7:0] cfg_div_ratio_i, // 分频比配置
    output wire pll_clk_o,      // PLL 时钟输出
    output wire locked_o        // 锁定指示
);

你看,每个信号名都带着前缀或后缀,一看就知道是输入还是输出,是时钟还是配置。我在项目中遇到过,有人把输入输出信号混着命名,结果写 testbench 的时候,老是搞错方向。后来我强制团队用 _i、_o 后缀,再也没出过这种问题。

注意: 前缀和后缀不要滥用。比如 clk_ref_i_o 这种,既带 _i 又带 _o,反而让人困惑。一个信号只加一个方向后缀就够了。

4.5 知识体系结构图

下面这张图,把模块命名的核心逻辑串起来了。你可以把它当作一个检查清单:

模块命名规范知识体系 顶层模块命名 格式: <项目>_<功能>_top 子模块命名 格式: <父模块>_<子功能> 实例化命名 格式: u_<短模块名> 信号命名前缀/后缀 前缀: clk_, rst_, en_ 后缀: _i, _o, _n 综合: clk_ref_i

这张图从顶层到底层,把命名规范串成了一条线。你写代码的时候,照着这个顺序检查一遍,基本不会出错。

4.6 避坑指南

最后,分享几个我踩过的坑:

  • 不要用数字开头:比如 1st_pll,Verilog 语法不支持。我见过有人这么写,编译直接报错。
  • 不要用关键字:比如 module、input、wire 这些,千万别当信号名用。我曾经在代码里看到 wire module,嗯,编译直接挂了。
  • 不要用拼音:比如 pll_shuru、pll_shuchu,看着很别扭。统一用英文,哪怕简单点也行。
  • 大小写统一:我习惯用小写加下划线,比如 pll_clk_out。不要混用,比如 PllClkOut 和 pll_clk_out 同时出现,容易混淆。
一句话总结: 命名规范不是为了好看,是为了让代码可读、可维护、可复用。你写的代码,三个月后自己看,还能一眼看懂,这就是好命名。