4. 命名规范(二):时钟与复位命名、总线命名、状态机命名
好,咱们接着聊命名。上一章讲了模块和信号的基础命名规则,这一章咱们深入一点,专门聊聊三个容易出乱子的地方:时钟与复位、总线、还有状态机。
这三个东西,说白了就是芯片的“心跳”、“血管”和“大脑”。名字起不好,后期调试能把你逼疯。我自己就吃过不少亏,今天把这些经验掰开了揉碎了讲给你听。
4.1 时钟与复位命名:别让“心跳”乱了节奏
时钟信号是芯片里最“忙”的信号,没有之一。它每时每刻都在翻转。复位信号则是芯片的“总开关”,一按下去,所有状态归零。这两个信号的名字,必须一眼就能看出它的用途和来源。
4.1.1 时钟命名:清晰标明来源与频率
我个人习惯,时钟信号的名字要包含三个信息:来源、用途、频率(如果有多时钟域)。
举个例子,一个从PLL(锁相环)出来的200MHz系统主时钟,我会这么命名:
// 推荐命名
wire clk_pll_sys_200m;
wire clk_pll_ddr_400m;
wire clk_osc_32k; // 外部晶振输入的32KHz慢速时钟
你看,clk 开头,一眼就知道是时钟。接着是来源 pll 或 osc,然后是用途 sys 或 ddr,最后是频率 200m。这样写,不管谁拿到代码,都能快速理解。
clk1、clk2 这种名字,三个月后你自己都看不懂。
我曾经在一个项目中,看到有人把时钟命名为 c1、c2、c3。结果综合时发现 c2 和 c3 其实是同一个时钟源的不同分频,但因为名字看不出关联,导致后端工程师做了两套时钟树,白白浪费了功耗和面积。嗯,从那以后,我对时钟命名就特别较真。
4.1.2 复位命名:区分同步与异步,标明有效电平
复位信号比时钟稍微复杂一点,因为它有同步复位和异步复位的区别,还有高有效和低有效的区别。
我的建议是:
- 低有效复位:用
_n或_b后缀(n代表negative,b代表bar)。 - 高有效复位:用
_p后缀(p代表positive)。 - 异步复位:名字里带上
async或arst。 - 同步复位:名字里带上
sync或srst。
// 推荐命名
wire rst_n; // 全局低有效异步复位
wire rst_pll_sync_n; // PLL锁定后的同步复位(低有效)
wire rst_domain_a_async_p; // A时钟域的高有效异步复位
arst_n,这会让读代码的人产生严重误解,甚至导致综合工具推断出错误的寄存器类型。
你想想看,如果代码里写 always @(posedge clk or negedge arst_n),但 arst_n 实际上是一个同步复位信号,那综合工具会把它当成异步复位来处理,生成不必要的复位网络。这种坑,我见过不止一次。
4.2 总线命名:统一规范,避免“鸡同鸭讲”
总线是模块之间通信的“高速公路”。如果总线命名不规范,不同模块对接时就像鸡同鸭讲,光对齐信号名就能耗掉半天时间。
我参与过一个大项目,总线协议是自己定义的。一开始大家各写各的,A模块叫 data_in,B模块叫 din,C模块叫 wdata。结果集成的时候,光改名字就改了两天,还漏改了几个,导致仿真跑不通。后来我们痛定思痛,定了一套总线命名规范。
4.2.1 通用总线信号命名模板
对于大多数自定义总线,我建议采用以下模板:
<总线前缀>_<信号类型>_<方向/属性>
举个例子,一个简单的AHB-like总线:
| 信号名 | 含义 |
|---|---|
bus_haddr |
总线地址 |
bus_hwdata |
总线写数据 |
bus_hrdata |
总线读数据 |
bus_hwrite |
总线写使能 |
bus_hsel |
总线从设备选择 |
bus_hready |
总线就绪信号 |
你看,所有信号都以 bus_ 开头,一眼就知道这是总线信号。后面的 haddr、hwdata 等,则清晰表明了具体功能。
axi_、apb_、mybus_。这样在代码里搜索时,可以快速定位到特定总线的所有信号。
4.2.2 总线接口信号分组
对于复杂的总线接口(比如AXI),信号非常多。我建议按通道分组命名,每组内部再按功能细分。
// AXI 写地址通道
wire axi_awid;
wire axi_awaddr;
wire axi_awlen;
wire axi_awsize;
wire axi_awburst;
// AXI 写数据通道
wire axi_wdata;
wire axi_wstrb;
wire axi_wlast;
wire axi_wvalid;
// AXI 写响应通道
wire axi_bid;
wire axi_bresp;
wire axi_bvalid;
这样分组后,代码的可读性会大大提高。你看到 axi_aw 开头的,就知道是写地址通道的信号;看到 axi_w 开头的,就知道是写数据通道的信号。
4.3 状态机命名:让“大脑”逻辑一目了然
状态机是数字电路设计的核心。一个设计精良的状态机,代码读起来就像在读一个故事。而命名,就是这个故事里的“角色名”。
4.3.1 状态编码命名:用枚举,别用魔法数字
我强烈建议使用 enum 或 parameter 来定义状态,而不是直接用二进制数字。
// 不推荐
parameter IDLE = 3'b000;
parameter READ = 3'b001;
parameter WRITE = 3'b010;
parameter DONE = 3'b011;
// 推荐(SystemVerilog)
typedef enum logic [2:0] {
ST_IDLE,
ST_READ,
ST_WRITE,
ST_DONE
} state_t;
state_t current_state, next_state;
你看,用枚举后,代码里到处都是 ST_READ、ST_WRITE 这样的名字,而不是 3'b001、3'b010。哪个更容易理解?一目了然。
ST_ 或 S_ 开头,后面跟描述状态的英文单词。比如 ST_IDLE、ST_FETCH、ST_DECODE、ST_EXECUTE。
4.3.2 状态机变量命名:区分当前状态和下一状态
状态机通常有两个核心变量:current_state 和 next_state。我建议统一使用这两个名字,不要用 cs、ns 这种缩写。
// 推荐
always_ff @(posedge clk or negedge rst_n) begin
if (!rst_n)
current_state <= ST_IDLE;
else
current_state <= next_state;
end
always_comb begin
next_state = current_state; // 默认保持
case (current_state)
ST_IDLE: begin
if (start)
next_state = ST_READ;
end
ST_READ: begin
if (data_valid)
next_state = ST_DONE;
end
// ...
endcase
end
我曾经见过有人用 state 和 state_next,或者 st 和 st_nxt。说实话,这些写法虽然也能用,但不如 current_state 和 next_state 来得直观。你想想看,在代码里搜索 current_state,是不是比搜索 st 要准确得多?
4.3.3 状态机输出信号命名:标明来源状态
状态机的输出信号,我建议在名字里带上状态机的名字,方便追踪来源。
// 假设有一个状态机叫 fsm_ahb_master
wire fsm_ahb_master_idle; // 状态机处于IDLE状态
wire fsm_ahb_master_busy; // 状态机处于忙状态
wire fsm_ahb_master_done; // 状态机完成操作
这样,当你在顶层模块里看到 fsm_ahb_master_done 这个信号时,你立刻就知道它来自 fsm_ahb_master 这个状态机模块。调试的时候,这种命名方式能帮你节省大量时间。
4.4 本章知识体系
说了这么多,我画了一张图,帮你把这一章的核心逻辑串起来:
好了,这一章的内容就到这里。命名规范这东西,说起来简单,做起来需要坚持。刚开始可能会觉得麻烦,但养成习惯后,你会发现代码质量提升了一大截。下一章咱们聊聊代码布局和注释规范,那也是很多工程师容易忽略的地方。
可可精品资料库