第二讲:RTL代码风格基础——命名规范、缩进对齐与注释规范

各位同学好,今天我们聊聊RTL代码的“门面”——代码风格。

你可能觉得这东西虚,不就是起个名字、对齐一下嘛?我告诉你,真不是。我在项目里见过太多因为命名混乱导致的低级bug,也见过因为注释误导让接手的人骂娘的情况。代码风格,说白了就是你的专业素养。

一、命名规范:让信号自己会说话

好的命名,能让代码像读文章一样顺畅。坏的命名,嗯…就像看天书。

1. 模块命名

我个人习惯用“模块名_功能”的格式。比如:

// 好的命名
uart_rx
uart_tx
fifo_wr_ctrl
spi_master

// 不好的命名
module1
ctrl
top_top

模块名要体现它的功能。你想想看,如果别人看到你的模块叫“ctrl”,他得花多少时间去猜你到底控制什么?

2. 信号命名

这里我有一套自己的规则,用了十几年了:

信号类型 命名规则 示例
时钟 clk_前缀 clk_sys, clk_100m
复位 rst_n后缀(低有效) rst_n, rst_sys_n
使能 _en后缀 wr_en, rd_en
有效指示 _vld后缀 data_vld, addr_vld
准备好 _rdy后缀 data_rdy, fifo_rdy
我的小技巧:信号名尽量控制在20个字符以内。太长的话,波形里根本看不全,调试起来很痛苦。

3. 参数命名

参数用大写字母加下划线,这是业界共识:

parameter DATA_WIDTH = 32;
parameter FIFO_DEPTH = 1024;
parameter ADDR_BITS  = 10;

我曾经见过有人把参数写成小写,结果和信号混在一起,查了半天才发现是参数赋值错了。从那以后,我坚持参数全大写。

二、缩进与对齐:让代码有呼吸感

缩进这件事,看似简单,但做不好真的很要命。

1. 缩进规则

我建议用2个空格缩进。为什么不是4个?因为RTL代码嵌套层次多,4个空格会让代码很快跑到屏幕右边去。

// 好的缩进
always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    cnt <= 8'b0;
  end else if (en) begin
    cnt <= cnt + 1'b1;
  end else begin
    cnt <= cnt;
  end
end

// 不好的缩进
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 8'b0;
end else if (en) begin
cnt <= cnt + 1'b1;
end else begin
cnt <= cnt;
end
end

你看,第二种写法,眼睛扫过去根本分不清逻辑层次。调试的时候,这种代码就是噩梦。

2. 对齐规则

赋值语句的对齐,我习惯这样:

// 好的对齐
assign data_out = (sel) ? data_a : data_b;
assign addr_out = addr_in + offset;
assign wr_en    = wr_req & fifo_full_n;

// 不好的对齐
assign data_out = (sel) ? data_a : data_b;
assign addr_out = addr_in + offset;
assign wr_en = wr_req & fifo_full_n;

对齐后的代码,一眼就能看出哪些信号是同一类操作。嗯,这里要注意,对齐不是强制要求,但能极大提升可读性。

避坑指南:我曾经在项目中遇到过一个同事,他喜欢用Tab键缩进。结果不同编辑器Tab宽度不一样,代码合并时全是冲突。从那以后,我规定团队统一用空格缩进。

三、注释规范:写给未来的自己

注释这件事,我最有发言权。因为我自己就吃过亏。

1. 模块头注释

每个模块开头,必须有一个注释块,说明这个模块是干什么的:

//==================================================
// 模块名称:fifo_wr_ctrl
// 功能描述:FIFO写控制逻辑,处理写请求和满标志
// 输入:clk, rst_n, wr_req, fifo_full
// 输出:wr_en, wr_addr
// 作者:张三
// 日期:2024-01-15
// 修改记录:
//   v1.0 - 初始版本
//   v1.1 - 修复满标志延迟问题
//==================================================

这个注释块,说白了就是模块的“身份证”。别人拿到你的代码,第一眼就看这个。

2. 行内注释

行内注释要解释“为什么”,而不是“是什么”:

// 好的注释
cnt <= cnt + 1'b1;  // 每拍加1,用于生成读地址

// 不好的注释
cnt <= cnt + 1'b1;  // cnt加1

你想想看,代码本身已经说明了“cnt加1”,注释再说一遍就是废话。注释要告诉别人“为什么加1”。

3. 复杂逻辑注释

遇到复杂的状态机或者算法,一定要写注释说明状态转移条件:

// 状态机:FIFO写控制
// IDLE: 等待写请求
// WRITE: 执行写操作,持续1拍
// FULL: FIFO已满,等待读操作释放空间
always @(posedge clk or negedge rst_n) begin
  if (!rst_n) begin
    state <= IDLE;
  end else begin
    case (state)
      IDLE: begin
        // 有写请求且FIFO未满,进入写状态
        if (wr_req & !fifo_full) begin
          state <= WRITE;
        end
      end
      // ... 其他状态
    endcase
  end
end
核心原则:注释要解释“为什么这样做”,而不是“做了什么”。代码本身已经说明了“做了什么”。

四、知识体系总览

下面这张图,是我对RTL代码风格核心要点的总结:

RTL代码风格核心要素 命名规范 模块:模块名_功能 信号:_en, _vld, _rdy 参数:全大写+下划线 缩进与对齐 缩进:2个空格 对齐:赋值符号对齐 禁止:Tab缩进 注释规范 模块头:功能/接口/作者 行内:解释"为什么" 复杂逻辑:状态转移说明 目标:让代码像读文章一样顺畅

这张图把三大核心要素串起来了。命名规范解决“叫什么”,缩进对齐解决“怎么摆”,注释规范解决“为什么”。三者缺一不可。

我的建议:刚开始写代码时,可以先把功能跑通,然后再花10分钟整理代码风格。这10分钟,能为以后调试省下几小时。

好了,这一讲就到这里。记住,代码风格不是给别人看的,是给未来的自己看的。你三个月后回头看自己的代码,如果能一眼看懂,说明你的风格合格了。

可可精品资料库