手把手教你用 Claude Code 写 Verilog:从零到 100% 覆盖率
适合读者:数字 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 就会自动完成:
- 根据当前正在编辑的模块,自动查找对应的 testbench 文件
- 调用
iverilog进行编译,链接 RTL 和 TB - 运行
vvp执行仿真 - 捕获仿真输出,检查断言是否全部通过
- 若有失败,读取仿真错误日志,分析并尝试修复 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→DATA,DATA→DONE,DONE→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。
更多推荐



所有评论(0)