ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

GPT-4o写Verilog为何不如C++?OPL4GPT论文精读与RTL生成实战

GPT-4o写Verilog为何不如C++?OPL4GPT论文精读与RTL生成实战

读这篇ASP-DAC 2025上的OPL4GPT论文,起因其实很简单,我最近手上有个小项目要用AI辅助写点RTL代码,结果发现同一个GPT-4o,让它写C++稳得一批,一让它写Verilog就开始“胡言乱语”。正好看到这篇论文的标题,直白得就像在说“我研究了这件事”,于是我就沉下心去精读了一遍,还顺手把论文里的实验思路搬到本地复现了一轮。这篇文章就聊聊我读到的核心内容、实验设计逻辑,以及我自己在实操中验证过的几个结论。

这篇论文最大的价值,是它没有泛泛而谈“大模型能不能写硬件”,而是直接做了一场控制变量实验:同一个模型、同一套任务难度、同一个评估标准,左边是C++,右边是Verilog,最后告诉你差距到底在哪。无论你是写软件的、写FPGA的、还是在读书的学生,只要你在用AI辅助写代码,这里的结论都值得看一下。它能帮你划清一条线:哪些活可以放心交给模型,哪些活必须自己上手盯。

1. 论文在解决什么问题:硬件设计的“AI补位”困境

1.1 为什么偏偏是GPT-4o、C++和Verilog

先聊一个基本问题:论文为什么不选Python,不选VHDL,偏偏选了C++和Verilog做对比?我理解作者有三个层面的考虑。

第一,C++是软件工程领域里大模型表现最成熟、训练数据最丰富的语言之一。从LeetCode到各类开源项目,互联网上C++代码的存量极其庞大,GPT-4o对这种“顺序执行、函数调用、标准库丰富”的语言已经有很好的分布拟合。拿它做对照组,相当于先验证“模型在理想状态下能打多少分”。

第二,Verilog是硬件描述语言(HDL)里最主流的一种,它和C++在范式上存在本质差异:C++描述算法在CPU上的执行步骤,Verilog描述硬件电路的结构与时序行为。这里面涉及并发、时钟、复位、时序约束、阻塞与非阻塞赋值等一堆软件工程师根本不熟悉的概念。把Verilog放在实验组,目的就是测试大模型能否把“软件代码生成”的能力平移到“硬件代码生成”。

第三,选GPT-4o而不是其他开源模型,也有现实考量。论文的实验定位不是“训练一个新模型”,而是评估“当前最强商用闭源模型的硬件代码能力基线”。这其实是一个非常务实的思路:既然芯片行业迟早要用大模型辅助设计,那就先看看最通用的工具目前能做什么。

1.2 OPL4GPT的定位与实际意义

我对OPL4GPT的理解是,论文把这项工作定位成一整套面向硬件代码生成的“提示词策略与评估框架”。它不只是简单地写一句“generate Verilog code”,而是设计了一个规范化的流程:先让模型理解模块接口,再让模型生成RTL实现,最后喂给仿真器做功能检查。这和我们通常说的“提示词工程”是一回事,但它被提升到了方法论层面。

这里我要说一句公道话:行业里一直有个误区,觉得“大模型写不好Verilog”是因为模型笨。但论文里的实验结果反复指向另一个结论——大多数失败案例出在任务描述太含糊、评估标准太粗放、反馈信息没给到位。这其实是典型的“AI补位困境”:硬件设计的门槛本身就高,需要领域知识做约束;大模型在这些约束不足的情况下,当然容易发挥失常。OPL4GPT的工作本质上是研究怎么把“约束”更有效地传给大模型,让它在硬件领域真正补上位置。

1.3 一篇论文对EDA行业的意义

有人可能会问,这种对比实验除了发论文,对实际行业有什么价值?我自己的感受是,它给EDA(电子设计自动化)工具链的未来指了一个方向。传统的Verilog开发流程是“写代码-编译-仿真-看波形-改代码”,每一步都依赖工程师的经验。如果大模型能在其中几个环节自动补位,比如自动生成testbench、自动检查接口位宽匹配、自动修复语法错误,那整个验证周期都能缩短不少。

但论文也冷静地指出,硬件代码生成远比软件代码生成复杂。C++出错,编译器报错后,模型可以立刻根据错误信息局部修改;Verilog出错,很多时候语法是过的,仿真波形却不对,定位问题的成本高得多。所以这个领域未来很长一段时间内,大概率不是“大模型替代工程师”,而是“大模型成为工程师的辅助工具”。这一点,我觉得论文的定位非常清醒。

2. 实验是怎么设计的:一次对照测试的“切口”

2.1 任务与语料:从LeetCode风格到RTL风格

论文的实验设计是我最感兴趣的部分。它没有用现成的C++题库或公开的Verilog测试集,而是自己构建了一组“同源任务”。什么叫同源任务?就是用同一个功能描述,分别要求GPT-4o用C++和Verilog实现。

举个例子,同样是实现一个“计数器”,C++任务可能是:

  • 写一个函数,输入计数上限,输出从0到上限的循环序列;
  • 写一个类,内部维护计数器状态,暴露increment和reset接口。

Verilog任务则是:

  • 写一个带异步复位的8位计数器模块,时钟上升沿触发;
  • 写一个带使能端的四位BCD计数器,仿真验证进位。

这种设计的好处是非常直观地暴露模型的“语言偏见”:同一个逻辑功能,模型在软件描述下能轻松完成,换到硬件描述下就频频出错。论文里还覆盖了更多的典型任务,比如全加器、状态机、FIFO读写控制、UART发送逻辑、I2C接口时序等等,基本把RTL设计里最常见的小模块都扫了一遍。

还有一点值得注意,论文在生成提示词时特意规范了“输出格式”。对于C++,要求给出完整可编译的单文件代码;对于Verilog,要求给出完整的模块定义和端口声明,但不需要testbench。之所以这么设置,是为了避免大模型用“附加内容”掩盖核心逻辑的缺失。

2.2 评估的“双重标准”:C++看编译运行,Verilog要跑仿真波形

论文在评估环节做了一个非常关键的设计,也是我认为最值得深究的一点:C++和Verilog采用了难度完全不对等的评估标准。

C++这边,模型生成的代码经过g++编译,如果能通过编译且跑出预期输出,就算“正确”。这也是软件领域评估大模型代码能力的常用做法,本质上靠的是“真实反馈”驱动的多轮修正:编译报错是明确的,单元测试输出是明确的。

Verilog这边就麻烦得多。论文使用了Icarus Verilog这类开源仿真工具作为验证环境,但功能正确性不能只看语法,还需要生成测试激励、跑仿真、对比波形。这里每一步都容易出问题:

  • 语法通过,但模块端口位宽和testbench不匹配;
  • testbench不过,但模块内部逻辑有严重的latency偏差;
  • 波形能跑,但输出比预期晚了几个时钟周期。

论文在评估设计里明确区分了“编译通过率”和“功能正确率”两个指标。编译通过率高,说明模型已经掌握了Verilog的基本语法结构;但功能正确率明显下降,说明模型对时序逻辑、状态迁移这类硬件语义的理解还远远不够。我读到这里时非常有共鸣,因为我平时拿GPT-4o写RTL代码,最常遇到的就是这种“语法全对、仿真乱掉”的尴尬。

2.3 关于“提示词”的变量控制

论文在实验设置里还专门控制了提示词质量这个变量。它把提示词分成三种:简单描述式(“write a verilog module for a counter”)、结构化描述式(明确端口、时钟、复位、功能)、和OPL4GPT优化后的描述式(在结构化基础上加入设计约束和检查要求)。评估结果显示,提示词从简单变到结构化后,Verilog代码的编译通过率和功能正确率都有明显提升,而C++组的提升幅度相对有限。

这个结果其实很好解释:C++的IDE和编辑器生态太成熟了,模型从海量样本里已经隐式学到了“函数怎么写”,所以即使用含糊的提示词,它也能猜一个大概率可用的实现。但Verilog在开源世界的优质样本少得多,如果提示词不给足上下文,模型就只能靠拼凑常见模式来应对,自然容易翻车。所以OPL4GPT的工作重点之一,就是设计一套能充分传递设计意图的提示词模板。

3. 核心结论精读:GPT-4o的“能力差”到底在哪

3.1 编译通过率的反差与原因

论文给出了一个让我印象深刻的对比结论:GPT-4o在C++子任务上的编译通过率普遍高于Verilog子任务。这个结论不是意外,但它背后的原因值得展开说一说。

C++能获得高通过率,最关键的因素是训练语料的质量与数量。GitHub上有海量可编译的C++代码,Stack Overflow上有海量问题修复讨论,模型实际上是在一个“高密度正确答案”的环境里训练出来的。更妙的是,C++编译器给出的错误信息非常结构化,模型在预训练和指令微调阶段已经大量接触过这种“报错-修复”的配对数据,所以它对编译错误很敏感,能快速自我修正。

Verilog的处境则尴尬得多。首先,开源的高质量Verilog项目数量远远少于C++,很多商业IP是闭源的,模型能学到的常见样本基本来自教材、开源小模块和论坛问答,这部分数据的深度和广度都有限。其次,Verilog的编译器错误信息质量参差不齐,Icarus Verilog的报错有时候非常晦涩,模型即使看到了也很难基于它完成精准修复。最后,Verilog的“语义正确”依赖仿真验证,而仿真结果不是现成的训练语料,模型对“仿真失败后如何改代码”几乎没有直接的先验知识。

所以,编译通过率上的反差,本质上反映的是模型在两类语言上的“内隐知识密度”差异。

3.2 功能正确性的“致命点”:时序与状态

如果说编译通过率只是“热身”,那功能正确率才是论文的主战场。论文通过波形比对发现,GPT-4o在Verilog任务上犯的错误非常集中,我总结为三大类。

第一类是“时钟与复位”的使用错误。模型经常生成类似于always @(posedge clk or negedge rst_n)的代码,这本身没问题,但它往往会在注释里写着“异步复位”,实现里却漏掉if (!rst_n)的分支,导致复位逻辑完全不生效。更常见的错误是在组合逻辑里用了always @(*),却在里面给寄存器赋值,这在可综合设计里是不允许的。

第二类是阻塞赋值与非阻塞赋值的混用。这个错误在C++里根本没有对应物。如果模型在always块里把=和<=混在一起,仿真器不会报错,但生成的实际硬件会出现预期之外的竞态。论文的评估结果显示,GPT-4o对这类硬件语义的敏感度并不高,它更容易“照猫画虎”,而不是真正理解“非阻塞赋值用于时序逻辑”这条铁律。

第三类是状态机的“隐式状态”问题。让模型写一个简单的序列检测器,它经常能写出正确的跳转表,但写不出结构的always块划分(一段式、两段式、三段式)。这会导致综合出来的电路时序不理想,或者功能在特殊输入下跑错。可以说,这三种错误模式,每一个都是硬件工程师上手时最容易踩的坑,模型一个没落下全踩了一遍。论文把这个结果呈现在实验数据里,其实就是在告诉我们:大模型目前的“硬件直觉”还停留在语法层面,远未达到逻辑语义层面。

3.3 修复能力:有报错信息加持的Verilog也在起步

论文里还设计了一组“自我修复”实验,也就是把编译或仿真的错误信息回传给模型,让模型自己修代码。C++组的表现非常亮眼,模型在拿到编译器报错后,往往一两轮就能把所有问题修干净,甚至能自动补充遗漏的边界条件。Verilog组的修复效果则要打折扣,尤其是仿真波形不匹配这类“非结构化反馈”,模型经常陷入“改一版、波形更乱、再改一版”的死循环。

这里我想分享一个自己的实测体会。我用Icarus Verilog跑论文里类似的案例,把编译错误直接贴回给GPT-4o,它能很快修正语法问题。但当我把仿真输出波形和期望波形“描述”给它(注意是不能直接传图的纯文本对话),它就开始含糊其辞了。这说明模型对“结构化错误信息”非常敏感,但对“你需要把输出信号提前一个周期”这种语义级反馈,理解能力非常有限。论文在修复实验里也指出,如果能设计一种“把波形差异转化为文本断言”的中间表示,模型的表现或许还能再上一大截,这正是OPL4GPT后续可能扩展的方向。

3.4 数据里的“语言偏见”现象

论文还有一个有意思的发现,我称之为“语言偏见”现象。它指的是:即使任务逻辑完全一致,GPT-4o在C++版本里能轻松实现的功能,在Verilog版本里却表现得像一个初学者。这从侧面说明,大模型的“推理能力”其实高度绑定在它训练时见过的语言模板上。你给它一个C++函数,它能依赖强大的“代码记忆”直接输出正确答案;你给它一个Verilog模块,它就没有那么多现成的“优秀答案”可以套用,只能靠它的“推理能力”现编。

这让我想到一个很生活化的类比:一个英语说得很流利的人,让他翻译一段法律文书可能没问题,但让他用从来没学过的德语去翻译同样的法律文书,就算他逻辑再清楚,也会卡在词汇和语法上。GPT-4o在Verilog上的表现,就像那个“没学过德语的聪明人”,它的逻辑能力还在,但语言层面的“母语级熟练度”差了太多。

4. 实操心得:把论文结论变成可落地的Prompt技巧

4.1 从“一句话需求”到“可综合描述”的细节标注

精读论文归精读,最后能不能落地才是关键。我根据论文里的实验设定,自己在本地做了一组代码生成测试,这里直接分享实际操作中用着最有效的几个技巧。

最核心的一条:不要用“write a counter in Verilog”这种一句话提示词。论文里多次提到,模型在任务描述包含明确的位宽、时钟域、复位极性和触发方式时,输出质量会显著提升。我自己实测,把提示词从“写一个计数器”改成下面这种结构化描述,生成的代码几乎可以直接用:

请用Verilog HDL编写一个8位计数器模块: - 端口定义:clk、rst_n(异步低有效复位)、ce(时钟使能)、q[7:0](计数值输出); - 复位后计数值清零; - 在clk上升沿,若ce为高则q递增; - 计到8'hff后自动回绕到0; - 代码可综合,不要使用initial语句。

从效果上看,结构化描述带来的提升是肉眼可见的,尤其在端口位宽和复位逻辑的生成上,几乎不会再出现“凭空多个信号”或“漏掉复位”的低级错误。这一点对新手尤其有用:与其抱怨模型不够聪明,不如先反思自己有没有把需求说清楚。

4.2 让GPT-4o“先写测试再写设计”的奇效

论文里有一个我觉得特别有价值的操作细节:OPL4GPT框架会在生成RTL代码之前,先让模型生成一份模块接口文档或testbench草案,然后再反向生成设计代码。一开始我不以为然,觉得这是多此一举,但实测之后发现,这招对Verilog的正面效果远大于C++。

原因也很简单:testbench本身就是一种“使用约束”。当模型试图创建testbench时,它必须把模块的端口名、位宽、复位极性、时钟周期这些细节“定死”。一旦定了,后面再生成RTL就相当于照着既定接口去填空,模型跑偏的空间大大缩小。我在测试一个UART发送模块时,先让GPT-4o写出testbench,再让它写模块本体,结果testbench和RTL的接口匹配度几乎是100%,而直接生成RTL的时候,波特率分频系数经常和时钟定义对不上。

实操上,你可以这么写:

请先为以下模块功能编写一份Verilog testbench: - 模块名:uart_tx - 输入:clk、rst_n、tx_start、data_in[7:0] - 输出:tx、tx_busy - 要求:时钟周期10ns,数据变化沿与时钟沿对齐 在testbench中要包含基本的复位时序和一组发送测试用例。 请先输出testbench,再输出与之匹配的RTL实现。

这样生成的代码,功能正确率比我之前直接写RTL高了不少,关键是省掉了反复对齐接口的时间。

4.3 纠错要利用“语法错误”而非“自然语言”

论文的修复实验部分验证了一件事:大模型对编译器错误文本的响应速度,远快于对“我觉得代码有点问题”这种模糊自然语言的响应。我建议所有用AI辅助写Verilog的朋友,在使用Icarus Verilog或任何仿真器遇到报错时,把报错信息原样贴回给GPT-4o,不要自己转述。

举个我踩过的坑,有一次模型生成的代码里有一条reg [7:0] q;但q在always块里被赋了16位的值,Icarus Verilog直接报了个lvalue相关的错。我一开始只跟模型说“q的赋值好像有问题”,它给我改了三四次都没改对。后来我把终端里那一整段报错信息直接贴过去,它立刻定位到位宽不匹配,顺手还帮我修了另一个隐藏的位截断问题。这个差异的根源,就是论文里反复强调的“结构化反馈信号”——编译器报错是“结构化”的,模型在训练中见过太多类似样本,处理起来自然得心应手。

4.4 工具链配置:用开源工具构建复现环境

要复现论文里的实验,不需要商业EDA工具,我自己的环境就是VS Code + Icarus Verilog + vvp的标准组合。这里简单说几个配置要点,给想动手试一轮的朋友参考。

安装部分,Windows环境下去Icarus Verilog官网下载安装包就能直接装,自带iverilog和vvp两条命令。Ubuntu环境更简单,sudo apt install iverilog一条命令搞定。VS Code里装Verilog-HDL/SystemVerilog插件,能提供语法高亮和基础的代码检查。

配置完成后,写一个hello级别的测试流程:

iverilog -o tb.vvp tb.v uart_tx.v vvp tb.vvp

如果代码里有语法错误,iverilog会在终端直接输出报错;如果testbench里写了$display,仿真结果也会在终端打印出来。如果想看波形,可以在testbench里加上:

initial begin $dumpfile("wave.vcd"); $dumpvars(0, tb); end

然后用GTKWave打开wave.vcd,就能非常直观地检查信号时序是否正确。这套流程,我强烈建议所有想在LLM辅助下学习Verilog的同学试一遍。它能让你把“模型生成的代码”和“真实硬件行为”之间的差距,转化成肉眼可见的波形,而不是靠猜。

4.5 不要迷信“一键生成”,要把验证当常规动作

论文通篇看下来,没有一处暗示“大模型可以完全替代RTL工程师”。相反,它的实验数据不断在提醒读者:即便是GPT-4o这种级别的模型,生成Verilog时的功能正确率依然不稳定。所以我在自己的开发流程里,把“生成代码”和“验证代码”彻底分离。

所谓验证代码,不是简单跑一下仿真就完事,而是要像论文里那样,对输出波形和预期行为做逐项比对。比如生成一个状态机,至少要看两个东西:第一,复位后是否进入IDLE状态;第二,目标输入出现后,输出是否在预期的时钟周期跳变。如果这两条都不能100%满足,就必须手工介入,绝不因为“代码能编译”就判定它可以用了。

另外还有一个非常实用的习惯:每轮生成代码之后,我都会要求模型在注释里写出“我所理解的设计约束”。这招是论文实验带来的灵感。当你让模型把它理解的约束写出来时,很多隐藏的误解直接就暴露了——比如它可能以为异步复位是高有效,或者以为数据在下降沿采样。把这些误解解决在代码生成之前,后面验证阶段能少掉很多坑。

5. 精读之后的几点总结与延伸思考

5.1 论文没有明说,但我读出来的三个要点

第一,大模型在硬件设计领域的能力边界,很大程度上不是“能不能学会Verilog语法”,而是“能不能建立硬件时序直觉”。语法可以通过大量样本拟合,时序直觉则需要显式的反馈和结构化训练。论文里的OPL4GPT框架,本质上就是在弥补这个“反馈缺失”的短板。

第二,提示词在硬件代码生成中的杠杆作用,远比软件代码生成要大。因为Verilog代码本身就需要更多的“物理条件约束”,这些约束如果没有在提示词里交代清楚,模型就只能靠猜。这一点对所有硬件工程师都有直接启发:与其花时间反复调试模型输出,不如在写提示词时多花几分钟,把时钟、复位、位宽、有效电平全说清楚。

第三,评估方式是制约大模型硬件代码生成的另一个关键瓶颈。C++可以通过编译器和单元测试轻松给出结构化反馈,而Verilog的验证依赖波形,波形无法直接变成模型可读的训练信号。论文在最后指出了一个我很认同的方向:未来如果有工具能把波形差异自动转成文本形式的断言,大模型在Verilog上的表现可能会有质的飞跃。

5.2 我在复现实验时踩过的几个坑

顺手聊聊我复现过程中的实战问题,给后人指个路。

一个是Icarus Verilog对SystemVerilog语法支持不完整。论文里的很多Prompt模板会生成带logic类型或always_ff关键字的SystemVerilog代码,但如果混用iverilog编译,记得加-g2012参数,否则会报一堆莫名其妙的符号错误。

另一个是仿真时间单位的问题。模型生成的testbench里经常写#10 clk = ~clk;,而模块内部可能用timescale 1ns/1ps。如果testbench没有对应的timescale指令,仿真结果的时间轴会对不上,导致波形看起来永远是“慢半拍”。这个问题看起来很小,却是我在复现论文实验时最容易被误导的地方。

还有就是reg和wire的声明。GPT-4o在生成组合逻辑时,有时候会把输出声明成reg,但实际上是在assign语句里驱动,直接编译就会报错。这种问题在C++里没有对应物,纯属于Verilog语言本身的严格性。处理方式也很简单,把报错信息贴回给模型,它一般能立刻意识到问题所在。

5.3 后续可以怎么扩展这套方法

如果你读到这里,也想顺着这篇论文的思路做点自己的实验,我给你三个方向建议。

第一,换模型跑一遍同样的对比。论文只做了GPT-4o的实验,但你完全可以拿Claude、Gemini或开源的Qwen-Coder试试。我自己的零散测试显示,不同模型在C++上的差距可能不大,但在Verilog上的表现差异非常大,有些开源模型甚至能跑到和GPT-4o相当的水平。这是一个很有意思的基准测试思路。

第二,把实验从“模块级”扩展到“系统级”。论文里的任务大多是计数器、UART、I2C这类小模块,但实际工程里的瓶颈往往在模块互连和整体时序。你可以试着让GPT-4o生成一个简单的FIFO读写控制,再让它生成一个测试状态机来驱动FIFO,看看它在“多模块协同”场景下的能力是否还会继续掉线。

第三,尝试“波形反馈闭环”。论文提到模型对结构化反馈更敏感,那我们自己在实验时能不能搭建一个自动化脚本:用iverilog跑完仿真,解析vcd文件,把信号跳变提取成文本,再喂回给模型去修改?这个流程虽简陋,但本质上就是在复现论文最后展望的“文本化波形反馈”思路。我已经在本地跑通了一个最小版本,效果比纯靠人话反馈好了不少,后续有机会专门写一篇展开讲。

说起来,这篇论文我不打算只读一遍就算了。它选的角度其实很聪明——不吹捧大模型,也不否定大模型,而是老老实实地把C++和Verilog之间的能力差距摊开给你看。恰恰是这个“老实”,让我觉得它比那些堆砌华丽指标的论文更有参考价值。你在自己的项目里如果用大模型写过Verilog代码,大概也会跟我有同感:真正的难点从来不在语法,而在约束的传递和验证的闭环。OPL4GPT把这个道理讲透了,剩下的,就是咱们这些工程师如何在日常流程里用好这个道理了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进