1. 从 VerilogCoder 论文说起LLM 写 RTL 为什么总在功能上翻车如果你最近在折腾用大模型生成 Verilog大概率遇到过这种场景代码语法没问题iverilog 编译一把过但一跑 testbench 就冒出一堆 mismatch。你盯着波形看半天发现某个 next-state 逻辑漏了一个转移条件或者寄存器忘了初始化。这不是模型不会写 Verilog而是它缺少两样东西——足够细的规划和像人一样回溯波形的调试能力。VerilogCoder 这篇论文英伟达AAAI25正是冲着这两个短板去的。它提出两个核心机制一是TCRGTask and Circuit Relation Graph把任务拆解和电路语义绑在一张图里让每个子任务都带着相关信号、状态转移和示例二是AST-based Waveform TracingAST-WT用 Pyverilog 抽取生成代码的抽象语法树从出错的输出信号出发沿着 RVALUE 依赖关系一层层往上追再结合 VCD 波形把哪条赋值写错了定位出来。论文在 VerilogEval-Human v2 上的数据很能说明问题GPT-4 Turbo 直接 prompt 是 60.3%套上 VerilogCoder 框架后到 94.2%Llama 3 70B 从 41.7% 提到 67.3%。消融实验里光加 AST-WT 就从 66.7% 到 78.2%光换 TCRG planner 到 74.4%两个一起上到 94.2%。这说明规划和调试是互补的不是重复堆料。这篇笔记不打算复述论文而是把它的机制拆成能跑的东西TCRG 的节点和边怎么定义、AST 回溯脚本怎么写、怎么通过 TaoToken 统一 Key 和 API 通道把一次生成—仿真—回溯—修复的闭环真正跑通。适合做芯片前端、EDA 工具链、或者想把 LLM 接进 RTL 流程的工程师。下面从环境准备开始一步步来。2. TaoToken 前置准备统一 Key 与 API 通道接入 VerilogCoder 工作流VerilogCoder 本身是个多智能体框架底层要调 LLM。论文里用的是 GPT-4 Turbo但实际落地时你可能会换模型、换供应商如果每个 agent 都单独配一套 Key 和 endpoint维护起来很痛苦。我的做法是用 TaoToken 做统一通道一个 Key 走所有模型调用Base URL 固定模型 ID 按需切换。这样 Planner、Verilog Engineer、Debug Agent 这几个角色可以共用一套配置换模型只改一个字段。先说清楚 TaoToken 在这里的角色它是一个统一的 API 接入层把不同模型的调用收敛到同一个 endpoint 和同一套鉴权方式。官网在 https://taotoken.net/?utm_sourcetaotoken_aicg_blog_endutm_mediumcsdnutm_campaignrewriteutm_content API 地址是 https://taotoken.net/api 。注意 API 地址不带 UTM 参数直接用它做 Base URL。你需要准备的东西不多一个 TaoToken 账号、一个 API Key、Python 3.10 环境、iverilog 和 Pyverilog。iverilog 用来做语法检查和功能仿真Pyverilog 用来抽 AST。这两个是 VerilogCoder 工具层的基础论文里 Syntax checker 和 Simulator 都是基于 iverilogAST-WT 基于 Pyverilog。安装命令如下Ubuntu/Debian 环境下sudo apt-get update sudo apt-get install -y iverilog gtkwave pip install pyverilog jinja2 requestsPyverilog 依赖 ply 和 jinja2pip 会自动带上。装完后验证一下iverilog -V python -c import pyverilog; print(pyverilog.__version__)如果 iverilog 版本低于 11建议升级因为老版本对 SystemVerilog 子集支持不全仿真时可能报奇怪的语法错。Pyverilog 对 Python 3.10 以上兼容较好3.8 也能跑但偶尔有 AST 解析的边界问题。接下来是 Key 的获取。登录 TaoToken 控制台在 API Keys 页面创建一个新 Key。建议按项目分 Key比如 verilogcoder-dev 一个、verilogcoder-prod 一个方便后面做用量归因。创建后复制出来存到环境变量里不要硬编码进脚本export TAOTOKEN_API_KEYsk-你的key export TAOTOKEN_BASE_URLhttps://taotoken.net/api如果你用 Claude Code 或者 Cline 这类工具做辅助开发也可以在它们的配置里填同样的 Base URL 和 Key。Claude Code 的配置在 settings.jsonCline 在 MCP 配置里Codex 在 auth.json三件套都是 Base URL Key Model ID。Model ID 按你实际用的填比如 gpt-4-turbo、claude-3-5-sonnet 之类具体以控制台模型列表为准。有一点要注意TaoToken 是统一接入通道不是让你绕过什么限制它的价值在于把多模型调用收敛成一套配置减少 agent 框架里的鉴权分支。论文里 VerilogCoder 有四个 LLM 角色如果每个角色都单独配 Key调试时切换模型会很麻烦。统一通道后你只需要在配置里改 model 字段。3. 可复制配置TCRG 节点定义与 AST 校验脚本这一节给两份能直接用的配置一份是 TCRG 的图结构定义JSON一份是 AST 波形回溯脚本Python。论文里 TCRG 有四类节点——task、signal、transition、example三类边——IMPLEMENTS、SIGNALTRANSITION、EXAMPLES。我把它落成一个 JSON schema方便程序读取和构建图。先看 TCRG 的节点和边定义。假设我们做一个 Moore 状态机规格是检测输入序列 1011重叠检测。TCRG 的 JSON 大概长这样{ task_nodes: [ { id: T1, desc: Define the module interface, signals: [clk, rst_n, din, dout] }, { id: T2, desc: Implement the combinational logic for next_state, signals: [next_state, state, din] }, { id: T3, desc: Implement the output logic for dout, signals: [dout, state] } ], signal_nodes: [ {id: S1, name: next_state, type: reg, width: 3}, {id: S2, name: state, type: reg, width: 3}, {id: S3, name: dout, type: wire, width: 1} ], transition_nodes: [ { id: TR1, from: S0, to: S1, condition: din 1, signal: next_state }, { id: TR2, from: S1, to: S2, condition: din 0, signal: next_state } ], example_nodes: [ { id: E1, signal: dout, input_seq: 1 0 1 1, expected: 1 } ], edges: [ {from: T2, to: S1, type: IMPLEMENTS}, {from: S1, to: TR1, type: SIGNALTRANSITION}, {from: S1, to: E1, type: EXAMPLES} ] }这份 JSON 的关键在于边的关系链task → signal → transition/example。当 Debug Agent 在处理 T2 这个子任务时从 T2 节点出发做 2-hop 检索就能拿到 next_state 相关的所有状态转移和示例。论文里 k 值不是写死的由 agent 在 ReAct 循环里动态决定但工程实现上你可以先固定 k2跑通后再让 agent 自适应。接下来是 AST 波形回溯脚本。论文里 AST-WT 的输入是 mismatched output signals 和 back-tracing level输出是相关信号的波形表格。下面这个脚本用 Pyverilog 抽 AST从指定输出信号出发沿 RVALUE 依赖往上追再结合 VCD 文件提取波形import sys from pyverilog.vparser.parser import parse from pyverilog.vparser.ast import ( ModuleDef, Assign, Decl, Identifier, Partselect, Lvalue, Rvalue ) def build_rvalue_map(ast): 从 AST 中提取每个信号的 RVALUE 依赖关系 rvalue_map {} for node in ast.children(): if isinstance(node, ModuleDef): for item in node.items: if isinstance(item, Assign): lhs item.left.var.name rhs_signals [] collect_identifiers(item.right, rhs_signals) rvalue_map[lhs] rhs_signals return rvalue_map def collect_identifiers(node, out): 递归收集表达式中的所有标识符 if isinstance(node, Identifier): out.append(node.name) elif hasattr(node, children): for child in node.children(): collect_identifiers(child, out) def back_trace(rvalue_map, target, level): 从 target 信号出发向上回溯 level 层 result {target: []} frontier [target] for depth in range(level): next_frontier [] for sig in frontier: drivers rvalue_map.get(sig, []) result[sig] drivers next_frontier.extend(drivers) frontier next_frontier if not frontier: break return result if __name__ __main__: verilog_file sys.argv[1] target_signal sys.argv[2] trace_level int(sys.argv[3]) ast, _ parse([verilog_file]) rvalue_map build_rvalue_map(ast) trace_result back_trace(rvalue_map, target_signal, trace_level) print( AST Back Trace Result ) for sig, drivers in trace_result.items(): print(f{sig} - {drivers})用法python ast_trace.py design.v Q 2会输出 Q 往上两层的驱动信号。论文里 Figure 4 的例子Q 依赖 q_regq_reg 依赖 r_in、q_in、L、clk这个脚本能追出同样的链路。拿到信号列表后再配合 VCD 文件提取这些信号在错误时刻的值就能给 Debug Agent 提供局部诊断上下文。VCD 提取可以用 vcdvcd 库或者直接解析 iverilog 生成的 VCD。这里不展开重点是 AST 回溯这部分它是 AST-WT 的核心。脚本跑通后你可以把它包成一个 tool注册到 agent 的 action 空间里让 Debug Agent 通过 ReAct 调用。4. 验证请求跑通一次生成与仿真闭环配置和脚本都齐了现在跑一次完整闭环。我用一个简单的序列检测器做例子规格是检测 1011重叠检测。流程分四步Planner 生成任务计划、Code Agent 写 RTL、Simulator 跑 testbench、Debug Agent 用 AST-WT 定位并修复。先写一个调用 TaoToken 的最小客户端验证通道是否通import os import requests API_KEY os.environ[TAOTOKEN_API_KEY] BASE_URL os.environ[TAOTOKEN_BASE_URL] def chat(messages, modelgpt-4-turbo): headers { Authorization: fBearer {API_KEY}, Content-Type: application/json } payload { model: model, messages: messages, temperature: 0.2 } resp requests.post(f{BASE_URL}/v1/chat/completions, headersheaders, jsonpayload, timeout120) resp.raise_for_status() return resp.json()[choices][0][message][content] if __name__ __main__: reply chat([ {role: system, content: You are a Verilog engineer.}, {role: user, content: Write a Moore FSM that detects 1011 with overlap.} ]) print(reply)跑通后你会拿到一段 Verilog。把它存成seq_detect.v再写一个 testbenchtb_seq_detect.v用 iverilog 编译仿真iverilog -o sim.out seq_detect.v tb_seq_detect.v vvp sim.out如果输出有 mismatchiverilog 会打印错误时刻和信号值。假设输出 Q 在某个时刻不对接下来调用 AST 回溯python ast_trace.py seq_detect.v Q 2输出类似 AST Back Trace Result Q - [q_reg] q_reg - [r_in, q_in, L, clk]这说明 Q 由 q_reg 驱动q_reg 又依赖 r_in、q_in、L、clk。结合波形看如果 q_reg 在复位后没有初始化那就是漏了 initial 块或复位逻辑。论文里 Figure 4 的例子正是这个问题——补上initial begin q_reg 0; end后功能通过。把 AST 回溯结果和波形表格一起喂给 Debug Agent让它生成修复补丁。修复后再跑一次仿真直到 mismatch 归零。整个过程里TaoToken 负责所有 LLM 调用你只需要维护一个 Key 和一个 Base URL。如果想验证不同模型的表现改一下 model 字段就行比如从 gpt-4-turbo 换成 claude-3-5-sonnet对比一下谁在 FSM 任务上更稳。这里有个实测经验AST 回溯的 level 不要设太大2 到 3 层通常够用。设太大返回的信号太多反而稀释了关键信息Debug Agent 容易抓错重点。论文里也是按需扩展 k-hop不是一次拉满。5. 本篇常见错排查401、local proxy failed、reading choices、OAuth跑这套流程时报错主要集中在两类API 调用和工具链。下面按真实报错逐个排查。401 Unauthorized。最常见的原因是 Key 没读到或者格式不对。检查echo $TAOTOKEN_API_KEY是否有值注意不要有多余空格或换行。如果 Key 是从控制台复制的确认没有把前后引号也复制进去。另一个原因是 Base URL 写错比如写成了https://taotoken.net/api/带尾斜杠有些客户端会拼成双斜杠导致鉴权失败。正确写法是https://taotoken.net/api不带尾斜杠。local proxy failed。这个报错通常出现在你本地配了代理但代理没启动或者端口不对。如果你在用 Cline、Claude Code 这类工具检查它们的网络配置里是否填了本地代理地址。解决方法是把代理配置清掉或者确认代理进程在跑。注意这里说的是本地开发环境的网络配置问题不是让你去搞什么特殊通道纯粹是本地端口没通。reading choices 报错。这个一般出现在解析 API 响应时resp.json()[choices]取不到值。原因可能是返回结构不是标准 OpenAI 格式或者请求被限流返回了错误信息。先打印resp.status_code和resp.text看原始返回。如果是 429说明触发限流降低并发或加 sleep。如果是 400检查 model 字段是否拼错比如把 gpt-4-turbo 写成了 gpt4-turbo。OAuth 相关报错。如果你用 Claude Code 或 Codex 这类工具它们可能默认走 OAuth 流程。在配置里改成 API Key 模式填 TaoToken 的 Key 和 Base URL。Claude Code 的 settings.json 里把apiKey字段填上Codex 的 auth.json 里把OPENAI_API_KEY换成你的 KeyOPENAI_BASE_URL换成https://taotoken.net/api。三件套——Base URL、Key、Model ID——缺一不可少填一个就会报鉴权或模型找不到。iverilog 编译报错但代码看着没问题。检查是不是用了 SystemVerilog 语法但 iverilog 版本太低。比如logic类型、always_ff这些老版本 iverilog 不认。升级到 12 以上或者把代码改成 Verilog-2001 风格。Pyverilog 解析失败。Pyverilog 对某些宏定义和 include 路径处理不好。如果设计里有include确保路径正确或者先把 include 展开再解析。另外Pyverilog 对 generate 块的支持有限复杂设计可能需要预处理。AST 回溯结果为空。检查目标信号名是否和 AST 里的标识符完全一致大小写敏感。如果信号是数组或位选比如q_reg[0]回溯时要处理 Partselect 节点上面的脚本简化了这部分实际用的时候可能需要扩展。6. 把闭环接进你的 RTL 流程从单次验证到持续使用跑通一次闭环后下一步是把它变成日常能用的流程。我的做法是分三层单次验证、批量回归、持续集成。单次验证就是上面那套适合调试具体模块。批量回归是把 VerilogEval-Human v2 或者你自己的测试集跑一遍统计 pass1。论文里 VerilogCoder 在 156 道题上做到 94.2%你可以用同样的 benchmark 对比自己的实现。跑批量时注意 token 成本论文提到 VerilogCoder 的平均 token 消耗是 baseline 的 13 倍所以批量跑之前先估算一下用量避免账单失控。持续集成是把这套流程接进 CI每次提交 RTL 后自动跑生成和验证。适合团队协作场景但要注意 agent 的调试循环可能比较耗时建议设一个最大迭代次数比如 5 轮超过就标记为需人工介入。如果你长期做编码和 Agent 开发可以考虑用 Coding Plan把模型调用和工具链统一管理。模型对话入口适合快速验证单个模型的表现接入文档里有完整的 API 说明和示例代码。API Keys 页面管理你的 Key控制台看用量和调用记录。最后说一个实用技巧TCRG 的 JSON 不要手写让 Planner Agent 生成后存下来下次遇到类似模块直接复用图结构。比如 FSM 类的任务节点和边的模式高度相似复用能省不少 token。AST 回溯脚本也可以包成命令行工具注册到 agent 的 action 空间让 Debug Agent 通过 ReAct 自动调用而不是每次手动跑。这样整个闭环就从人驱动变成agent 驱动更接近论文里的 autonomous 状态。