# Verilog Code

> 编写和审查可综合的 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。

- Skill: `ljj05170930/verilog-code` (Agent Skill, multi-file: 4 files)
- Install (CLI): `npx skillmds@latest add ljj05170930/verilog-code`
- Raw SKILL.md: https://api.skillmd.com/api/skills/ljj05170930/verilog-code/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: ljj05170930 (https://skillmd.com/u/ljj05170930)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/ljj05170930/verilog-code

---


# 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 **同时更新**。这意味着：
```verilog
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. 模块结构模板

所有模块遵循以下统一结构，与项目现有风格保持一致：

```verilog
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 错误：

```verilog
// 正确：0 到 N-1 共 N 个计数
if (count == MAX_COUNT - 1) begin
    count <= {WIDTH{1'b0}};
end else begin
    count <= count + 1'b1;
end
```

### 5.2 位宽匹配

显式指定所有字面量的位宽，不要使用无宽度字面量：

```verilog
// 错误
count <= count + 1;     // 1 是 32 位整数
data  <= 0;             // 0 是 32 位整数

// 正确
count <= count + 1'b1;
data  <= {DATA_WIDTH{1'b0}};
```

使用 `$clog2()` 从深度推导地址宽度：
```verilog
localparam ADDR_WIDTH = $clog2(DEPTH);
```

### 5.3 复位方法论

异步 active-low 复位，复位分支始终放在 if-else 的第一个分支：

```verilog
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` 分支回到安全状态：

```verilog
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 内存/数组声明

```verilog
// 正确：使用 [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 代码后，在呈现给用户之前，逐条对照以下清单验证：

- [ ] 每个 `if` 都有 `else`，每个 `case` 都有 `default`
- [ ] 没有 `initial` 块、`#delay`、系统任务（`$display` 等）
- [ ] 复位分支中列出了所有寄存器的复位值，无遗漏
- [ ] 时钟 `always` 块只用 `<=`，组合 `always` 块只用 `=`，无混用
- [ ] 所有字面量显式指定位宽（无裸 `0`、`1` 等整数字面量）
- [ ] 组合逻辑敏感列表是 `@(*)`，时序逻辑是 `@(posedge clk or negedge rst_n)`
- [ ] 计数器边界条件正确（比较 N-1 而不是 N，注意 off-by-one）
- [ ] 无多驱动信号（同一信号在多个 always/assign 中赋值）
- [ ] `clk`, `rst_n` 是模块的前两个端口
- [ ] 参数名 `UPPER_SNAKE_CASE`，信号名 `snake_case`
- [ ] 组合 always 块中的信号在所有分支中都有赋值（防止 latch 推断）
- [ ] FSM 的 case 语句有 default 分支，且 default 回到安全状态

---

## 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/` 目录

