适合读者:数字 IC 工程师、FPGA 开发者、硬件爱好者
读完收获:一套完整的 AI 辅助 RTL 开发工作流,含代码模板和实操演示


一、为什么值得一试?

在数字 IC 设计领域,工程师常自嘲是“高级打字员”——每天大量时间消耗在重复劳动上:写模块框架、补注释、设计 testbench、跑仿真、看波形、统计覆盖率、修 lint 错误。这些工作虽然必要,但机械且容易出错。如果能把这些“体力活”交给 AI,工程师就能专注于真正体现价值的地方——架构设计、性能优化、疑难 bug 定位。

Claude Code 正是这样一位“结对编程搭档”。它不是普通的网页聊天机器人,而是一个运行在终端里的 AI 编程代理。它能直接读取你的 .v 文件、理解项目结构、执行仿真命令,甚至可以看着波形帮你调试。把它集成到开发流程中,你会发现 RTL 开发不再是“写代码→仿真→改 bug”的漫长循环,而是“描述意图→AI 生成→一键验证→覆盖率达标”的高效飞轮。

本文用三个真实例子——计数器、同步 FIFO、UART 接收状态机——手把手演示这套工作流。所有代码和脚本均可在 Windows 或 Linux 上直接运行,无需商业 EDA 工具,完全使用开源工具链。


二、环境搭建(5 分钟)

2.1 安装开源工具链

在 Windows 上,用 winget 一条命令就能安装 Icarus Verilog(最常用的开源 Verilog 仿真器):

winget install Icarus.Verilog

在 Linux(Ubuntu/Debian)上同样简单:

sudo apt update
sudo apt install iverilog gtkwave
工具 用途 必要性
Icarus Verilog (iverilog + vvp) 编译 Verilog 代码并运行仿真 必需
GTKWave 查看 .vcd 波形文件 强烈推荐
Verilator(可选) 快速 Lint 检查 + 生成覆盖率 HTML 报告 进阶

2.2 推荐的项目目录结构

一个清晰的项目结构,能让 Claude Code 更准确地定位文件、理解依赖。建议采用以下布局:

rtl_project/
├── rtl/                  # 可综合 RTL 源码
│   ├── counter_bin.v
│   ├── fifo_sync.v
│   └── uart_rx_fsm.v
├── tb/                   # Testbench 文件
│   ├── tb_counter_bin.v
│   ├── tb_fifo_sync.v
│   └── tb_uart_rx_fsm.v
├── sim/                  # 仿真脚本、波形文件、覆盖率报告
│   ├── run.do            # 可选:ModelSim 脚本
│   ├── run.sh            # 仿真一键脚本
│   └── waves.vcd
├── .claude/              # Claude Code 配置目录
│   ├── commands/         # 自定义快捷命令(技能)
│   └── settings.json     # 自动化 Hooks
└── CLAUDE.md             # ★ 整个工作流的灵魂

三、核心操作一:写好 CLAUDE.md——给 AI 的“项目说明书”

Claude Code 启动时,会首先读取项目根目录下的 CLAUDE.md 文件。你可以把它理解成“给 AI 的项目约定书”——里面写清了你的编码风格、命名规则、设计原则。写好了这个文件,你就不再需要每次对话都重复交代“我习惯用小写加下划线命名”“复位低电平有效”这类细节。

一个实用的 CLAUDE.md 示例如下:

# RTL 项目约定

## 命名规范
- 模块名: 小写+下划线          例: fifo_sync, uart_rx
- 输入信号: i_ 前缀            例: i_clk, i_data, i_wr_en
- 输出信号: o_ 前缀            例: o_full, o_empty, o_data_out
- 内部寄存器: r_ 或 _reg 后缀   例: r_wr_ptr, r_empty_reg
- 内部连线: w_ 或 _wire 后缀    例: w_addr_inc
- 复位: rst_n (低电平有效)
- 时钟: clk

## 赋值规则
- 时序逻辑(always @(posedge clk)) → 使用非阻塞赋值 `<=`
- 组合逻辑(always @(*))           → 使用阻塞赋值 `=`
- 严禁在同一个 always 块中混合两种赋值

## 设计原则
- 每个模块必须有对应的 testbench,tb 文件名以 tb_ 开头
- 状态机一律采用三段式写法(状态跳转、次态逻辑、输出逻辑分离)
- 跨时钟域信号必须打两拍(或使用标准 synchronizer)
- 所有 case / if-else 必须覆盖完整,有 default 分支

## 注释风格
- 每个模块开头用 Doxygen 格式写功能描述、参数、端口说明
- 复杂逻辑旁边增加注释解释“为什么这么做”而非“做了什么”

有了这个文件,你只需说一句“帮我写一个参数化的 SPI Master”,Claude 就会自动按你的命名风格、编码规范生成代码。甚至可以自动补上 Doxygen 注释——这是很多工程师手动做起来会忽略但确实重要的工作。


四、核心操作二:用自然语言驱动 RTL 开发

4.1 第一步:描述需求,自动生成模块

在 Claude Code 终端中输入以下自然语言描述:

请用 Verilog 写一个参数化同步 FIFO:
- 数据宽度 DATA_WIDTH(默认 8)
- 深度 FIFO_DEPTH(默认 16,必须是 2 的幂)
- 输出信号包括:满(o_full)、空(o_empty)、将满(o_almost_full)、将空(o_almost_empty)
- 读写使能分别为 i_wr_en 和 i_rd_en
- 带完整 Doxygen 风格注释
- 使用 CLAUDE.md 中约定的命名规范

Claude 会生成一个完整的 .v 文件,通常包含:

  • 模块头注释(功能、参数说明、端口描述)
  • 参数化端口声明
  • 指针寄存器和地址计算(利用 $clog2 自动计算地址位宽)
  • 写满判断(经典的双指针法:写指针等于读指针但 r_wr_ptr_msb 取反时为满)
  • 读空判断(写指针等于读指针时为读空)
  • 将满 / 将空标志(深度减一或深度加一对比)
  • 双端口 RAM 实例化或生成寄存数组

整个过程不到 30 秒,而且生成的不是样板代码,而是带参数可配置的健壮模块。

4.2 第二步:自动生成 Testbench

模块生成后,紧接着就可以要求 AI 生成对应的 testbench:

请为 FIFO 模块写一个完整的 testbench:
- 覆盖以下场景:写满 → 读空 → 同时读写 → 写满后再写被阻止 → 读空后再读被阻止
- 在关键操作时用 $display 打印状态(写指针、读指针、满/空标志)
- 生成 .vcd 波形文件,文件名使用模块名
- 加入自动断言:任何时候读数据不应为 X,满标志为 1 时写不应改变内部数据

Claude 会生成一个结构完整的 testbench,包含时钟生成、复位序列、测试任务或 fork-join 并发激励,以及一个用于汇总测试结果的结束块。

4.3 第三步:一键仿真

如果你的 .claude/commands/ 目录下配置了 sim.md 快捷命令(配置方法见后文),那么在 Claude Code 中输入 /sim,AI 就会自动完成:

  1. 根据当前正在编辑的模块,自动查找对应的 testbench 文件
  2. 调用 iverilog 进行编译,链接 RTL 和 TB
  3. 运行 vvp 执行仿真
  4. 捕获仿真输出,检查断言是否全部通过
  5. 若有失败,读取仿真错误日志,分析并尝试修复 RTL 或 TB

实际终端中你会看到类似这样的输出:

=== Simulation Report ===
Module    : fifo_sync
Result    : PASS
Time      : 525000 ps
Checks    : 32/32
Errors    : 0
Waveform  : sim/fifo_sync.vcd

五、核心操作三:代码覆盖率——不只是“跑通了”

很多工程师只做到“仿真 PASS”就认为工作结束了。但事实是:仿真通过 ≠ 代码没问题。你可能从未测试过某个边沿条件,某个状态机跳转从未发生,某条 if 分支从未进入。代码覆盖率就是一面照妖镜,它量化地告诉你:还有哪些代码没有被测试到。

5.1 覆盖率的四个维度

维度 含义 典型例子
Block(行/块覆盖) 每个 always 块、每行代码是否被执行 复位分支、某个 else 分支是否走到
Expression(条件覆盖) 条件表达式中每个子项是否取过 true 和 false (i_en && !o_full) 中的 i_en!o_full 分别是否取过 0/1
Toggle(跳变覆盖) 每个信号是否从 0→1 和 1→0 各至少一次 o_overflow 脉冲信号的高电平是否出现过
FSM(状态机覆盖) 每个状态是否进入,每条状态转移是否发生 IDLE→DATADATA→DONEDONE→IDLE

5.2 用 Verilator 生成覆盖率报告

Verilator 除了是高性能仿真器,还能生成详细的覆盖率报告。在 Claude Code 中输入 /coverage(自定义命令),AI 会执行:

verilator --coverage --coverage-line --coverage-toggle --coverage-user \
          -Wall rtl/fifo_sync.v tb/tb_fifo_sync.v --top-module tb_fifo_sync
make -j -C obj_dir -f Vtb_fifo_sync.mk Vtb_fifo_sync
./obj_dir/Vtb_fifo_sync
verilator_coverage --annotate all coverage.dat

最终得到的覆盖率报告会像下面这样清晰:

╔══════════════════════════════════════╗
║   COVERAGE REPORT: uart_rx_fsm       ║
╚══════════════════════════════════════╝

  --- 1. FSM State Coverage ---
  [COVERED] IDLE state
  [COVERED] START state
  [COVERED] DATA state
  [COVERED] STOP state
  [COVERED] DONE state

  --- 2. FSM Transition Coverage ---
  [COVERED] IDLE -> START (start bit detected)
  [COVERED] START -> DATA (sample start bit mid)
  [COVERED] DATA -> DATA (each bit)
  [COVERED] DATA -> STOP (after 8 bits)
  [COVERED] STOP -> DONE
  [COVERED] DONE -> IDLE
  [MISSING] IDLE -> IDLE (glitch on rx line)  ← 需要补充测试

  --- 3. Expression Coverage ---
  [COVERED] start_bit_detected
  [COVERED] frame_err_detected
  [COVERED] parity_err_detected

  --- 4. Toggle Coverage ---
  [COVERED] o_data_valid toggle
  [COVERED] o_frame_err  toggle

  ═══════════════════════════
   TOTAL : 17 / 18  (94.4%)
  ═══════════════════════════
  Missing transitions: IDLE->IDLE (glitch case)
  Suggest adding test: inject 1-cycle glitch on rx line while IDLE

发现了未覆盖的转移后,你可以告诉 Claude:“请帮我补充一个测试用例,覆盖 IDLE 状态下 rx 线毛刺的情况。”AI 会更新 testbench,然后重新跑覆盖率——直到所有关键项都变成 [COVERED]

5.3 自包含的覆盖率 Testbench 写法(模板)

如果你不使用 Verilator,也可以手动在 testbench 中加入覆盖率计数器,这对于快速评估少量模块非常实用:

// 在 testbench 中声明覆盖率标志
reg cov_load_entered;     // Block: load 分支是否进入
reg cov_en_true;          // Expression: i_en == 1 是否出现过
reg cov_en_false;         // Expression: i_en == 0 是否出现过
reg cov_overflow_toggle;  // Toggle: o_overflow 是否从 0→1

// 持续采样(在时钟沿上)
always @(posedge clk) begin
    if (!rst_n) begin
        cov_load_entered <= 1'b0;
        cov_en_true      <= 1'b0;
        cov_en_false     <= 1'b0;
        cov_overflow_toggle <= 1'b0;
    end
    else begin
        if (i_load)                 cov_load_entered <= 1'b1;
        if (i_en)                   cov_en_true      <= 1'b1;
        if (!i_en)                  cov_en_false     <= 1'b1;
        if (o_overflow === 1'b1)    cov_overflow_toggle <= 1'b1;
    end
end

// 仿真结束时输出汇总
initial begin
    #500000;  // 等仿真跑完
    $display("Coverage summary:");
    $display("  load branch entered: %s", cov_load_entered ? "YES" : "NO");
    $display("  i_en true: %s",       cov_en_true      ? "YES" : "NO");
    $display("  i_en false: %s",      cov_en_false     ? "YES" : "NO");
    $display("  overflow toggled: %s",cov_overflow_toggle ? "YES" : "NO");
    $finish;
end

六、核心操作四:配置一键自动化

6.1 快捷命令(Skills)

.claude/commands/ 下创建 Markdown 文件,文件名即为命令名。例如创建 sim.md

---
description: 编译并仿真当前模块对应的 testbench
---

请按以下步骤执行:
1. 识别当前活跃的 RTL 模块(例如 rtl/counter.v)
2. 在 tb/ 目录下找到对应的 tb_counter.v
3. 运行 iverilog -o sim/tb_out tb/tb_counter.v rtl/counter.v
4. 运行 vvp sim/tb_out
5. 解析输出结果,若发现错误,尝试自动修复 RTL 或 TB

之后,你只需要在 Claude Code 中输入 /sim,AI 就会严格按照这个流程执行。类似地,你可以创建:

.claude/commands/
  sim.md     → /sim   一键仿真
  lint.md    → /lint  一键 Verilator Lint
  fsm.md     → /fsm   根据描述生成三段式状态机模板
  cov.md     → /cov   跑覆盖率并输出缺失项
  cdc.md     → /cdc   检查跨时钟域(简单版:搜索单比特跨域未打拍)

6.2 自动 Lint(Hooks)

更高级的用法是配置 Hooks——每次 Claude 生成或修改一个 .v 文件后,自动触发 Lint 检查。在 .claude/settings.json 中写入:

{
  "hooks": {
    "PostToolUse": [{
      "matcher": "Write",
      "hooks": [{
        "type": "command",
        "command": "verilator --lint-only -Wall $CLAUDE_TOOL_FILE_PATH 2>&1 | tee -a verilator.log"
      }]
    }]
  }
}

效果令人舒适:每次 Claude 写完代码,终端里会立刻弹出一行 Lint 结果。如果位宽不匹配、漏了 default 分支、推断出锁存器等问题都会被即时发现。Claude 看到这些错误后,甚至可以主动尝试修正——形成一个“生成 → 检查 → 修复”的闭环。


七、三步上手路线(建议收藏)

阶段 时间预算 要做的事 预期成果
第 1 步 Day 1 安装 iverilog + gtkwave,创建 CLAUDE.md(写清命名规范和编码约定) 能手动编译仿真一个最简单的计数器模块
第 2 步 Day 2–3 练手三个模块:计数器 → 同步 FIFO → 状态机(UART RX)。每个模块走通“生成 RTL → 生成 TB → 仿真 → 查看覆盖率”完整闭环 对 Claude 的能力边界心中有数,知道哪些自然语言描述最有效
第 3 步 Day 4+ 配置 /sim /lint /cov 快捷命令 + Hooks 自动 Lint,形成个人工作流;尝试将 1–2 个真实工作小模块用 AI 辅助完成 开发效率提升 2–3 倍,新人也能写出规整、高覆盖率的 RTL

八、常见问题(Q&A)

Q: Claude Code 会写出有 bug 的代码吗?
A: 。它并非万无一失,有时会生成位宽错误、组合逻辑环路、忘记 reset 值等问题。但它的价值在于“快速生成 + 快速验证”。仿真会失败,Lint 会报错,覆盖率会显示缺失——而这些反馈 Claude 自己都能读取并尝试修复。这才是真正的 AI 辅助闭环:不是 AI 一次写对,而是 AI 能持续迭代到正确。

Q: 适合什么规模的模块?
A: 最适合 几百到几千行 的独立模块:UART/SPI/I2C 控制器、FIFO 缓存、仲裁器、各种状态机、计数器、CRC 计算单元。对于整个 SoC 级别的集成、多时钟域复杂设计、低功耗架构,目前仍然需要人类的顶层设计和判断。AI 是帮你搬砖的,不是帮你画蓝图的。

Q: 需要联网吗?Claude Code 会泄露我的 RTL 代码吗?
A: Claude Code 需要联网调用 Claude API 才能工作(在终端中运行,底层调用云端模型)。根据 Anthropic 的隐私政策,对话内容用于推理,但不会被用于训练其他模型,也不会被其他用户看到。不过对于极端敏感的 IP 代码,建议谨慎评估。好消息是,很多公司已经允许工程师在非机密模块上使用这类工具,并显著提升了效率。
Note:根据需求,用户也可以不登陆claude code账号,使用第三方LLM key

Q: 与 GitHub Copilot 有什么区别?
A: Copilot 更像“自动补全增强版”,适合在写代码时给出下一行建议。而 Claude Code 是 代理型 AI:它能主动读文件、执行命令、查看仿真输出、修改代码、再跑仿真。它是可以独立完成多步任务的“结对程序员”,不仅仅是补全工具。


九、总结:从“手写 RTL”到“描述 RTL”

传统开发流程是一条单向的长链条:

手写 RTL → 手写 TB → 编译 → 仿真 → 人工看波形 → 手算覆盖率 → 补测试点 → 重跑

每个环节都消耗工程师的时间精力,而且大部分时间花在重复性劳动上。

引入 Claude Code 后的新流程变成了一个高速迭代环:

描述需求(一句中文) → Claude 同时生成 RTL+TB → /sim 一键仿真 
→ 覆盖率报告 → 根据 [MISSING] 项自动或手动补充测试 → 重新跑 → 100% 覆盖

整个过程的核心心法只有一句话:
把约束写在 CLAUDE.md 里,把流程封装在 Skills 里,把验证自动化在 Hooks 里。

当你的工具链完成这三个“里”之后,你会发现 RTL 开发不再是枯燥的代码打字,而是一种“对话式设计”——你只需要清晰地告诉 AI 你想要什么,它帮你实现,然后你们一起检查、改进,最终得到一个高质量、高覆盖率的硬件模块。

现在,就去安装 Icarus Verilog,创建一个 CLAUDE.md,然后对 Claude Code 说出你的第一个模块需求吧。


本文所有示例代码均可在 Windows/Linux 上运行,只需安装 Icarus Verilog + GTKWave。

更多推荐