with one click
rtl-coding
RTL 编码技能 - 可综合、可验证、可维护的 RTL 编码最佳实践
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
RTL 编码技能 - 可综合、可验证、可维护的 RTL 编码最佳实践
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
物理设计全流程技能 - 从floorplan到GDSII,完整数字芯片物理设计指导
芯片架构设计技能 - 从需求定义到模块划分,完整指导芯片架构开发
EDA工具脚本开发技能 - TCL/Python/Makefile 自动化流程最佳实践
功能安全分析技能 - FMEDA 分析、安全机制设计、ISO 26262 符合
静态时序分析技能 - 时序诊断、收敛优化、签收标准
UVM 验证方法论技能 - 从验证计划到功能覆盖率,完整 UVM 验证平台开发
| name | rtl-coding |
| description | RTL 编码技能 - 可综合、可验证、可维护的 RTL 编码最佳实践 |
| description.zh | RTL 编码技能 - 可综合、可验证、可维护的 RTL 编码最佳实践 |
| origin | ICER |
| categories | front-end |
当用户需要编写 RTL 代码时,启用此技能。此技能提供可综合、可验证、可维护的 RTL 编码指导,遵循工业界最佳实践。
| 参数 | 28nm | 16nm | 7nm | 说明 |
|---|---|---|---|---|
| 逻辑级数上限 | 8-10级 | 6-8级 | 4-6级 | 超过需流水线 |
| 典型单元延迟 | 50-80ps | 30-50ps | 15-30ps | NAND2_X1 |
| 最大扇出 | 16 | 12 | 8 | 数据信号 |
| 时钟扇出 | 32 | 24 | 16 | 时钟信号 |
| 类型 | 规范 | 示例 |
|---|---|---|
| 模块名 | 小写+下划线 | axi_interconnect |
| 参数名 | 大写+下划线 | DATA_WIDTH |
| 信号名 | 小写+下划线 | data_valid |
| 寄存器 | r_ 前缀或 _reg 后缀 | r_data 或 data_reg |
| 线网 | w_ 前缀或无前缀 | w_data 或 data |
| 时钟 | clk 或 i_clk | clk_sys |
| 复位 | rst_n 或 i_rst_n | rst_sys_n |
| 检查项 | 要求 | 说明 |
|---|---|---|
| 时钟边沿 | 单一边沿 | posedge clk 或 negedge clk |
| 复位 | 同步释放 | 异步复位同步释放 |
| 锁存器 | 无 | 无不完整 if-else |
| 组合反馈 | 无 | 无组合环路 |
| 多驱动 | 无 | 无多驱动网络 |
| 未连接 | 无 | 无未连接端口 |
诊断步骤:
1. 分析时序报告
2. 统计逻辑级数
3. 检查扇出
4. 检查 RTL 结构
根本原因定位:
| 根本原因 | RTL 特征 | 解决方案 |
|---|---|---|
| 逻辑级数过多 | if-else 嵌套深 | 流水线分割 |
| 扇出过大 | 信号驱动多模块 | 复制寄存器 |
| 组合逻辑长 | 大 case 或 if-else | 拆分逻辑 |
| 时钟域交叉 | 异步信号无同步 | 添加同步器 |
RTL 修改示例:
// ❌ 问题:逻辑级数过多
always @(*) begin
case (state)
0: result = a + b + c + d + e + f; // 5级加法
...
endcase
end
// ✅ 解决:流水线分割
always @(posedge clk) begin
stage1 <= a + b + c; // 第一级
stage2 <= stage1 + d + e; // 第二级
end
always @(*) begin
result = stage2 + f; // 第三级
end
诊断步骤:
1. 定位错误波形位置
2. 分析信号驱动源
3. 检查时序关系
4. 检查复位状态
根本原因定位:
| 根本原因 | 仿真特征 | 解决方案 |
|---|---|---|
| 复位不正确 | 初始值错误 | 检查复位逻辑 |
| 时钟域问题 | 采样错误 | 添加同步器 |
| 竞争冒险 | 不稳定输出 | 修复组合逻辑 |
| 状态机问题 | 状态跳转错误 | 检查状态机 |
诊断步骤:
1. 查看综合报告
2. 检查 Warning
3. 检查推断的结构
4. 检查资源使用
根本原因定位:
| 根本原因 | 综合特征 | 解决方案 |
|---|---|---|
| 锁存器推断 | Latch Warning | 完整 if-else |
| 多驱动 | Multi-driver Warning | 检查驱动源 |
| 资源过大 | Area 超预期 | 优化结构 |
| 时钟问题 | Clock Warning | 检查时钟网络 |
诊断步骤:
1. 运行 CDC 检查工具
2. 分析跨时钟域路径
3. 检查同步器
4. 检查握手协议
根本原因定位:
| 根本原因 | 特征 | 解决方案 |
|---|---|---|
| 单比特无同步 | 亚稳态风险 | 两级同步器 |
| 多比特无握手 | 数据不一致 | 握手或 FIFO |
| 快到慢丢数据 | 数据丢失 | 脉冲同步器 |
| 复位不同步 | 复位问题 | 复位同步器 |
开始编写 RTL 代码之前,先创建模块骨架。
做什么: 创建 RTL 文件,文件名与模块名一致。
怎么做:
.sv,Verilog 用 .v✅ 好的命名:
axi_interconnect → 文件名 axi_interconnect.svddr_controller → 文件名 ddr_controller.sv❌ 不好的命名:
AXIInterconnect → 文件名 axi_interconnect.sv(不一致)axi_interconnect → 文件名 AXI.sv(不匹配)做什么: 在文件开头添加标准注释头。
模板:
//-----------------------------------------------------------------------------
// Module: module_name
// Description: 模块功能描述(一句话)
// Author: 作者名
// Date: 创建日期
// Version: 版本号
// Modification History:
// Date | Author | Description
// -----------|---------|-------------------------------------------
// 2024-01-15 | XXX | Initial version
// 2024-01-20 | XXX | Add feature X
//-----------------------------------------------------------------------------
做什么: 声明模块名、参数、端口。
模板:
module module_name #(
// 参数定义(参数化设计)
parameter DATA_WIDTH = 32,
parameter DEPTH = 16,
parameter ADDR_WIDTH = $clog2(DEPTH) // 派生参数
)(
// 端口声明
// 端口排序:时钟复位 → 配置 → 输入数据 → 输出数据
input wire i_clk, // 时钟
input wire i_rst_n, // 异步复位,低有效
// 配置接口
input wire [DATA_WIDTH-1:0] i_config,
// 数据输入
input wire [DATA_WIDTH-1:0] i_data,
input wire i_valid,
output logic o_ready,
// 数据输出
output logic [DATA_WIDTH-1:0] o_data,
output logic o_valid,
input wire i_ready
);
参数设计原则:
做什么: 确保命名符合项目规范。
标准命名约定:
| 信号类型 | 前缀 | 示例 |
|---|---|---|
| 输入端口 | i_ | i_clk, i_data |
| 输出端口 | o_ | o_valid, o_data |
| 双向端口 | io_ | io_data |
| 寄存器输出 | r_ | r_state, r_counter |
| 组合逻辑连线 | w_ | w_next_state, w_mux |
| 参数/常量 | 大写 | DATA_WIDTH, DEPTH |
| 状态机状态 | ST_ 前缀 | ST_IDLE, ST_DATA |
检查清单:
模块骨架完成后,声明内部信号。
做什么: 定义模块内部使用的常量。
// 局部参数(外部不可覆盖)
localparam ST_IDLE = 2'b00;
localparam ST_DATA = 2'b01;
localparam ST_WAIT = 2'b10;
localparam ST_DONE = 2'b11;
// 派生参数
localparam ADDR_WIDTH = $clog2(DEPTH);
localparam DATA_MASK = {DATA_WIDTH{1'b1}};
做什么: 声明需要保存状态的信号。
// 状态寄存器
logic [1:0] r_state;
logic [ADDR_WIDTH-1:0] r_wr_addr;
logic [ADDR_WIDTH-1:0] r_rd_addr;
// 数据寄存器
logic [DATA_WIDTH-1:0] r_data;
logic r_valid;
// 计数器
logic [15:0] r_counter;
寄存器命名规则:
r_ 前缀always_ff 赋值做什么: 声明组合逻辑中间信号。
// 组合逻辑连线
logic [1:0] w_next_state;
logic [DATA_WIDTH-1:0] w_mux_data;
logic w_wr_enable;
logic w_rd_enable;
logic w_full;
logic w_empty;
组合逻辑命名规则:
w_ 前缀always_comb 赋值做什么: 如果模块需要存储器,声明存储器数组。
// 存储器声明
logic [DATA_WIDTH-1:0] r_mem[DEPTH];
// 存储器读写
always_ff @(posedge i_clk) begin
if (w_wr_enable) begin
r_mem[r_wr_addr] <= i_data;
end
end
always_ff @(posedge i_clk) begin
if (w_rd_enable) begin
o_data <= r_mem[r_rd_addr];
end
end
检查清单:
做什么: 编写复位时序逻辑。
异步复位模板:
// 异步复位,同步释放(推荐)
always_ff @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_state <= ST_IDLE;
r_data <= '0;
r_valid <= 1'b0;
end else begin
r_state <= w_next_state;
r_data <= w_next_data;
r_valid <= w_next_valid;
end
end
同步复位模板:
// 同步复位
always_ff @(posedge i_clk) begin
if (!i_rst_n) begin
r_state <= ST_IDLE;
r_data <= '0;
r_valid <= 1'b0;
end else begin
r_state <= w_next_state;
r_data <= w_next_data;
r_valid <= w_next_valid;
end
end
项目级规则:
⚠️ 整个项目保持一致!要么全异步复位,要么全同步复位。
做什么: 编写状态机状态寄存器。
// 状态寄存器
always_ff @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_state <= ST_IDLE;
end else begin
r_state <= w_next_state;
end
end
做什么: 编写数据通路寄存器。
// 数据寄存器
always_ff @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_data <= '0;
r_valid <= 1'b0;
end else if (w_wr_enable) begin
r_data <= i_data;
r_valid <= 1'b1;
end else if (w_rd_enable) begin
r_valid <= 1'b0;
end
end
做什么: 编写计数器逻辑。
// 计数器
always_ff @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_counter <= '0;
end else if (w_counter_clear) begin
r_counter <= '0;
end else if (w_counter_enable) begin
r_counter <= r_counter + 1'b1;
end
end
做什么: 输出信号从寄存器出来,改善时序。
// 输出寄存器(改善输出时序)
always_ff @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
o_valid <= 1'b0;
o_data <= '0;
end else begin
o_valid <= w_valid;
o_data <= w_data;
end
end
检查清单:
做什么: 编写状态机下一状态逻辑。
// 状态转换逻辑
always_comb begin
// 默认值:保持当前状态
w_next_state = r_state;
case (r_state)
ST_IDLE: begin
if (i_valid && i_ready) begin
w_next_state = ST_DATA;
end
end
ST_DATA: begin
if (w_data_done) begin
w_next_state = ST_WAIT;
end
end
ST_WAIT: begin
if (w_wait_done) begin
w_next_state = ST_DONE;
end
end
ST_DONE: begin
w_next_state = ST_IDLE;
end
default: begin
w_next_state = ST_IDLE;
end
endcase
end
状态机编码原则:
case 语句清晰表达状态转换做什么: 编写数据处理逻辑。
// 数据通路逻辑
always_comb begin
// 默认值
w_mux_data = '0;
w_wr_enable = 1'b0;
w_rd_enable = 1'b0;
case (r_state)
ST_IDLE: begin
if (i_valid && i_ready) begin
w_wr_enable = 1'b1;
w_mux_data = i_data;
end
end
ST_DATA: begin
// 数据处理逻辑
w_mux_data = r_data + 1'b1;
end
default: begin
// 默认处理
end
endcase
end
做什么: 编写输出信号生成逻辑。
// 输出逻辑
always_comb begin
o_ready = (r_state == ST_IDLE);
w_valid = (r_state == ST_DATA) && w_data_done;
end
做什么: 确保组合逻辑不会产生锁存器。
锁存器产生原因:
case 语句没有 defaultif 语句没有 else✅ 正确:先给默认值
always_comb begin
// 先给默认值,避免锁存
w_result = '0;
case (sel)
2'b00: w_result = a;
2'b01: w_result = b;
2'b10: w_result = c;
// 不需要 default,已经有默认值
endcase
end
✅ 正确:有 default 分支
always_comb begin
case (sel)
2'b00: w_result = a;
2'b01: w_result = b;
2'b10: w_result = c;
default: w_result = '0; // 必须
endcase
end
❌ 错误:没有默认值,会产生锁存
always_comb begin
case (sel)
2'b00: w_result = a;
2'b01: w_result = b;
2'b10: w_result = c;
// sel = 2'b11 时,w_result 保持原值 → 锁存!
endcase
end
检查清单:
case 语句有 default 分支always_comb 让工具检查锁存如果模块涉及多个时钟域,必须正确处理跨时钟域信号。
做什么: 列出所有跨时钟域信号。
输出: 跨时钟域信号列表
| 信号名 | 源时钟域 | 目标时钟域 | 信号类型 |
|---|---|---|---|
| cpu_req | clk_cpu | clk_ddr | 多比特数据 |
| cpu_req_valid | clk_cpu | clk_ddr | 单比特控制 |
| ddr_ack | clk_ddr | clk_cpu | 单比特控制 |
场景: 单比特控制信号,从慢时钟域到快时钟域。
方法: 两级同步器
// 单比特信号同步(慢时钟域 → 快时钟域)
module cdc_single_bit (
input wire i_clk_fast, // 快时钟
input wire i_rst_n,
input wire i_signal_slow, // 慢时钟域信号
output logic o_signal_fast // 同步后的信号
);
(* async_reg = "true" *) logic r_sync1;
(* async_reg = "true" *) logic r_sync2;
always_ff @(posedge i_clk_fast or negedge i_rst_n) begin
if (!i_rst_n) begin
r_sync1 <= 1'b0;
r_sync2 <= 1'b0;
end else begin
r_sync1 <= i_signal_slow;
r_sync2 <= r_sync1;
end
end
assign o_signal_fast = r_sync2;
endmodule
要点:
async_reg 属性,告诉工具这是跨时钟域寄存器场景: 单比特脉冲信号,从快时钟域到慢时钟域。
方法: 脉冲展宽 + 两级同步
// 单比特脉冲同步(快时钟域 → 慢时钟域)
module cdc_pulse (
input wire i_clk_fast, // 快时钟
input wire i_clk_slow, // 慢时钟
input wire i_rst_n,
input wire i_pulse_fast, // 快时钟域脉冲
output logic o_pulse_slow // 慢时钟域脉冲
);
// 快时钟域:展宽脉冲
logic r_toggle_fast;
always_ff @(posedge i_clk_fast or negedge i_rst_n) begin
if (!i_rst_n) begin
r_toggle_fast <= 1'b0;
end else if (i_pulse_fast) begin
r_toggle_fast <= ~r_toggle_fast; // 翻转
end
end
// 慢时钟域:两级同步
(* async_reg = "true" *) logic r_sync1;
(* async_reg = "true" *) logic r_sync2;
(* async_reg = "true" *) logic r_sync3;
always_ff @(posedge i_clk_slow or negedge i_rst_n) begin
if (!i_rst_n) begin
r_sync1 <= 1'b0;
r_sync2 <= 1'b0;
r_sync3 <= 1'b0;
end else begin
r_sync1 <= r_toggle_fast;
r_sync2 <= r_sync1;
r_sync3 <= r_sync2;
end
end
// 边沿检测
assign o_pulse_slow = r_sync2 ^ r_sync3;
endmodule
场景: 多比特数据总线跨时钟域。
方法: 异步 FIFO
// 异步 FIFO(简化版,实际使用 IP 或验证过的模块)
module async_fifo #(
parameter DATA_WIDTH = 32,
parameter DEPTH = 16
)(
// 写时钟域
input wire i_wr_clk,
input wire i_wr_rst_n,
input wire [DATA_WIDTH-1:0] i_wr_data,
input wire i_wr_en,
output logic o_full,
// 读时钟域
input wire i_rd_clk,
input wire i_rd_rst_n,
output logic [DATA_WIDTH-1:0] o_rd_data,
input wire i_rd_en,
output logic o_empty
);
// 使用格雷码指针跨时钟域
// 实际实现略,使用 IP 或验证过的模块
endmodule
要点:
做什么: 使用 CDC 检查工具验证。
工具:
检查清单:
async_reg 属性做什么: 让工具自动插入门控时钟。
✅ 好:有使能信号,工具可以插入门控
always_ff @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_data <= '0;
end else if (i_enable) begin // 使能信号
r_data <= i_data;
end
// 没有 else,i_enable 为低时保持
end
❌ 不好:一直翻转,无法门控
always_ff @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_data <= '0;
end else begin
r_data <= i_data; // 每个周期都更新,无法门控
end
end
做什么: 关闭不使用通路的翻转。
✅ 好:选择通路不使用时停止翻转
// 选通 A 通路时,B 通路不翻转
always_comb begin
if (i_sel == 2'b00) begin
w_result = a_operand;
end else if (i_sel == 2'b01) begin
w_result = b_operand;
end else begin
w_result = '0;
end
end
工具会自动在 a_operand 和 b_operand 前插入门控。
做什么: 计数器使用格雷码,减少翻转。
// 二进制计数器:每次可能多位翻转
// 0111 → 1000:四位都翻转
// 格雷码计数器:每次只有一位翻转
// 0100 → 0101:只有一位翻转
格雷码计数器实现:
module gray_counter #(
parameter WIDTH = 4
)(
input wire i_clk,
input wire i_rst_n,
input wire i_enable,
output logic [WIDTH-1:0] o_gray
);
logic [WIDTH-1:0] r_binary;
// 二进制计数器
always_ff @(posedge i_clk or negedge i_rst_n) begin
if (!i_rst_n) begin
r_binary <= '0;
end else if (i_enable) begin
r_binary <= r_binary + 1'b1;
end
end
// 二进制转格雷码
assign o_gray = r_binary ^ (r_binary >> 1);
endmodule
检查清单:
做什么: 运行 Lint 工具检查代码质量。
工具:
常见 Lint 警告:
| 警告类型 | 说明 | 解决方法 |
|---|---|---|
| 锁存器推断 | 组合逻辑不完整 | 添加默认值 |
| 未使用信号 | 声明但未使用 | 删除或使用 |
| 位宽不匹配 | 赋值位宽不一致 | 明确位宽 |
| 多驱动 | 同一信号多处驱动 | 检查逻辑 |
做什么: 检查命名是否符合规范。
检查项:
i_ 开头o_ 开头r_ 开头w_ 开头ST_ 开头做什么: 检查代码风格。
检查项:
做什么: 编写基本功能测试,验证 RTL 正确性。
module tb_module_name;
// 时钟和复位
logic clk;
logic rst_n;
// DUT 端口
logic [31:0] i_data;
logic i_valid;
logic o_ready;
logic [31:0] o_data;
logic o_valid;
logic i_ready;
// 实例化 DUT
module_name u_dut (
.i_clk (clk),
.i_rst_n (rst_n),
.i_data (i_data),
.i_valid (i_valid),
.o_ready (o_ready),
.o_data (o_data),
.o_valid (o_valid),
.i_ready (i_ready)
);
// 时钟生成
initial begin
clk = 0;
forever #5 clk = ~clk; // 100MHz
end
// 复位
initial begin
rst_n = 0;
#20 rst_n = 1;
end
// 测试
initial begin
// 等待复位释放
@(posedge rst_n);
repeat(10) @(posedge clk);
// 测试用例 1:基本读写
i_data = 32'h12345678;
i_valid = 1;
i_ready = 1;
@(posedge clk);
i_valid = 0;
// 等待输出
wait(o_valid);
$display("Output data: 0x%h", o_data);
// 测试用例 2:...
// 结束
#100 $finish;
end
endmodule
做什么: 运行仿真,检查波形。
工具:
检查项:
做什么: 检查功能覆盖率和代码覆盖率。
覆盖率类型:
目标:
提交代码前,逐项检查:
可综合性检查:
initial 给寄存器赋初值#delay 延迟fork/join命名检查:
代码质量检查:
case 语句有 default跨时钟域检查:
低功耗检查:
仿真检查:
做什么: 提交代码审查请求。
审查重点:
提交信息格式:
feat: 添加 XXX 模块
- 实现 XXX 功能
- 支持 YYY 配置
- 添加 ZZZ 测试
代码完成前必须检查:
initial 赋初值#delay❌ 魔法数字:代码里直接写 8'h42,不说明含义
// ❌ 不好
if (r_state == 2'b01) ...
// ✅ 好
localparam ST_DATA = 2'b01;
if (r_state == ST_DATA) ...
❌ 重复代码:相同代码复制粘贴多处
// ❌ 不好:三个地方相同的逻辑
always_comb begin
if (sel == 0) result = a;
else if (sel == 1) result = b;
else result = c;
end
// ✅ 好:提取为函数
function automatic [DATA_WIDTH-1:0] mux3 (
input [1:0] sel,
input [DATA_WIDTH-1:0] a, b, c
);
case (sel)
2'b00: mux3 = a;
2'b01: mux3 = b;
default: mux3 = c;
endcase
endfunction
❌ 太长模块:一个模块几千行
❌ 没有注释:复杂算法完全没注释
❌ 忽略 Lint 警告:Lint 有警告不管
| 工具 | 用途 |
|---|---|
| SpyGlass | Lint 检查、CDC 检查 |
| VCS | 仿真 |
| Xcelium | 仿真 |
| Design Compiler | 综合 |
| 工具 | 用途 |
|---|---|
| Verilator | Lint 检查、仿真 |
| Icarus Verilog | 仿真 |
| Yosys | 综合 |
verification-engineer 代理开发验证平台code-reviewer 代理进行代码审查physical-design-engineer 代理帮助分析