| name | verilog-code |
| description | 编写和审查可综合的 Verilog RTL 代码。当用户要求编写、设计、实现、修复或审查任何 Verilog/RTL 模块、 硬件模块或数字逻辑时自动触发。触发词包括:写Verilog、写RTL、实现模块、设计电路、review代码、 审查Verilog、FSM、状态机、FIFO、pipeline、流水线、arbiter、仲裁器、counter、计数器、 shift register、移位寄存器、handshake、握手协议、AXI、APB、SPI、I2C、UART、 clock divider、分频器、debounce、去抖、edge detector、边沿检测、synchronizer、同步器、 dual-port RAM、双端口RAM。即使用户只说"写一个模块"或"实现这个功能"而没有显式提到"Verilog", 也应使用此 skill。涵盖新代码生成和已有 Verilog 代码的 review/debug。
|
| user-invocable | true |
Verilog RTL 编码指南
1. 模式检测
根据用户意图判断工作模式:
WRITE 模式(用户要求创建/实现/设计模块):
- 遵循下方所有编码规则
- 如果要实现的模块匹配已知设计模式(FSM、FIFO、pipeline 等),先读取
references/design-patterns.md 中对应章节作为起点
REVIEW 模式(用户要求检查/审查/debug 已有代码):
- 读取
references/review-checklist.md,按照结构化流程逐项审查
- 如果发现的问题匹配已知错误类型,参考
references/common-mistakes.md
2. 硬件思维 — Verilog 不是软件
这是写出正确 Verilog 的根基。在写每一行代码前,用以下思维替代软件的顺序执行模型:
并行执行:每个 always 块是一个独立的硬件电路,所有 always 块同时运行。它们不是"按顺序执行的函数",而是"同时工作的独立电路"。
非阻塞赋值 <=(时钟块):在 always @(posedge clk) 中,所有 RHS 在时钟沿同时采样,然后所有 LHS 同时更新。这意味着:
always @(posedge clk) begin
a <= b; // a 拿到的是 b 的旧值
b <= a; // b 拿到的是 a 的旧值 → 实现了交换
end
这不是"先执行第一行再执行第二行",而是一个寄存器组在时钟沿同时锁存。
阻塞赋值 =(组合块):在 always @(*) 中使用,描述组合逻辑的连线关系。虽然有执行顺序,但综合结果是同一个门级电路。
绝对禁止混用:同一个 always 块内不得同时出现 <= 和 =。时钟块用 <=,组合块用 =。
wire 是导线,reg 是存储或组合输出:reg 不一定是寄存器——在组合 always 块中的 reg 综合后是纯组合逻辑。关键在于它被 always 块驱动。
3. 可综合性规则
RTL 代码中禁止使用以下仿真专用语法:
| 禁止的语法 | 原因 | 替代方案 |
|---|
initial 块 | 无硬件对应物 | 使用复位信号初始化 |
#10 等延时 | 不可综合 | 用时钟周期控制时序 |
$display, $monitor, $finish | 系统任务 | 仅在 TB 中使用 |
fork/join | 无硬件对应物 | 用并行 always 块 |
force/release/deassign | 不可综合 | 用正常赋值逻辑 |
必须遵守的规则:
- 完整条件:所有
if 必须有 else,所有 case 必须有 default。不完整的条件会推断出 latch,这几乎从来不是你想要的
- 敏感列表:组合逻辑用
always @(*),不要手动列出信号。时序逻辑用 always @(posedge clk or negedge rst_n)(异步复位)或 always @(posedge clk)(同步复位)
- 复位:每个触发器都必须有复位值。使用 active-low 异步复位 (
rst_n) 作为项目惯例
- 单驱动:同一信号只能在一个
always 块或一条 assign 中驱动,不可多驱动
- 无内部三态:内部信号不要用三态,用 mux 代替
- 整数除法:非 2 的幂的除法和取模通常不可综合,用移位代替
4. 模块结构模板
所有模块遵循以下统一结构,与项目现有风格保持一致:
module module_name #(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4
) (
input wire clk,
input wire rst_n,
// ---- Input ports ------------------------------------------------
input wire [DATA_WIDTH-1:0] data_in,
input wire valid_in,
// ---- Output ports -----------------------------------------------
output reg [DATA_WIDTH-1:0] data_out,
output reg valid_out
);
// =========================================================================
// Local parameters
// =========================================================================
localparam DEPTH = 1 << ADDR_WIDTH;
// =========================================================================
// Internal signals
// =========================================================================
reg [DATA_WIDTH-1:0] data_reg;
wire some_condition;
// =========================================================================
// Combinational logic
// =========================================================================
assign some_condition = /* ... */;
// =========================================================================
// Sequential logic
// =========================================================================
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
data_reg <= {DATA_WIDTH{1'b0}};
data_out <= {DATA_WIDTH{1'b0}};
valid_out <= 1'b0;
end else begin
// 功能逻辑
end
end
endmodule
结构约定:
clk 和 rst_n 始终是前两个端口
- 端口用
// ---- 注释行 ---- 分组
- 模块内部按区域划分:Local parameters → Internal signals → Combinational → Sequential
- 区域之间用
// === 注释分隔(匹配项目现有风格)
- 参数名
UPPER_SNAKE_CASE,信号名 snake_case,模块名 snake_case
- Active-low 信号加
_n 后缀(如 rst_n, cs_n)
- 时钟信号加
clk 前缀(如 clk, clk_div2)
5. 关键编码规则
5.1 计数器边界
计数器 wrap-around 比较 MAX_VAL - 1,不是 MAX_VAL。这是最常见的 off-by-one 错误:
// 正确:0 到 N-1 共 N 个计数
if (count == MAX_COUNT - 1) begin
count <= {WIDTH{1'b0}};
end else begin
count <= count + 1'b1;
end
5.2 位宽匹配
显式指定所有字面量的位宽,不要使用无宽度字面量:
// 错误
count <= count + 1; // 1 是 32 位整数
data <= 0; // 0 是 32 位整数
// 正确
count <= count + 1'b1;
data <= {DATA_WIDTH{1'b0}};
使用 $clog2() 从深度推导地址宽度:
localparam ADDR_WIDTH = $clog2(DEPTH);
5.3 复位方法论
异步 active-low 复位,复位分支始终放在 if-else 的第一个分支:
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
// 所有寄存器的复位值 — 不要漏掉任何一个
state <= IDLE;
count <= {WIDTH{1'b0}};
data_out <= {DATA_WIDTH{1'b0}};
valid_out <= 1'b0;
end else begin
// 正常功能逻辑
end
end
5.4 FSM 编码
使用 localparam 定义状态,不要用 `define。始终有 default 分支回到安全状态:
localparam [1:0] S_IDLE = 2'd0,
S_LOAD = 2'd1,
S_PROC = 2'd2,
S_DONE = 2'd3;
reg [1:0] state, state_next;
组合逻辑中的 next-state 信号用 _next 后缀,寄存器版本不加后缀。
5.5 流水线规则
- 所有数据信号必须经过相同级数的流水线寄存器
valid 信号必须与数据同步流水
- 添加反压(backpressure)时,stall 信号必须同时冻结数据和 valid
5.6 Valid/Ready 握手协议
valid 不能依赖 ready(发送端不看接收端是否准备好就可以发起请求)
ready 可以依赖 valid
valid 一旦拉高,在 ready 为低期间不得撤回
valid 为高期间数据必须保持稳定
5.7 内存/数组声明
// 正确:使用 [0:DEPTH-1] 显式声明范围
reg [DATA_WIDTH-1:0] mem [0:DEPTH-1];
// 读写模式
always @(posedge clk) begin
if (wr_en)
mem[wr_addr] <= wr_data;
rd_data <= mem[rd_addr]; // 读优先,1 周期延迟
end
6. 输出自检清单
生成 Verilog 代码后,在呈现给用户之前,逐条对照以下清单验证:
7. 参考文件
以下参考文件在需要时读取,不要每次都全部加载:
-
references/design-patterns.md — 当实现的模块匹配以下任何模式时读取:
FSM、FIFO、pipeline、handshake/skid buffer、arbiter、counter、shift register、
edge detector、debouncer、clock divider、dual-port RAM、synchronizer
-
references/common-mistakes.md — 当调试综合错误、仿真不匹配、或需要理解某个
常见 Verilog 陷阱时读取
-
references/review-checklist.md — 在 REVIEW 模式下读取,按照结构化流程审查代码
8. Icarus Verilog 兼容性
RTL 代码使用 Verilog-2001/2005 语法,确保与 Icarus Verilog (iverilog -g2012) 兼容:
- RTL 中使用
reg/wire,不要用 SystemVerilog 的 logic
- 不要在 RTL 中使用
always_ff/always_comb/always_latch(这些是 SV 语法,仅 TB 中使用)
$clog2() 在 -g2012 模式下支持
generate 块正常支持
- TB 文件是 SystemVerilog (
.sv),由 tb-creator skill 处理,本 skill 不管 TB
9. 项目工作流提示
生成 RTL 文件后,提醒用户:
- RTL 文件放在
RTL/ 目录下
- 运行 VS Code 的 "compile and run" 任务来编译和仿真
- 如果有新的 RTL 文件,
gen_filelist.py 会自动扫描 RTL/ 目录