AI写RTL代码踩坑实录:从Verilog生成到仿真综合的避坑方法

发布时间:2026/9/30 4:18:23
AI写RTL代码踩坑实录:从Verilog生成到仿真综合的避坑方法 回想这一周我几乎每天都要和AI生成的RTL代码斗智斗勇。不是因为我闲着没事而是项目里确实需要一块新的数据通路模块我想试试现在这些大模型到底能不能帮我把Verilog写出来。结果呢代码是写出来了但综合、仿真、调试加起来的时间比我手写整整多了一倍。这大概就是标题说的“第一批用AI写RTL的人快被折腾疯了”的真实写照。但也别急着唱衰。折腾归折腾用了几轮之后我反倒摸出了一些门道——AI不是不能用而是用法和你平时刷短视频看到的“一键生成代码”完全是两码事。如果你也想拿AI写RTL这篇文章里的坑你大概率会踩到我把它们全部拆开讲清楚顺便整理一套能让AI少给你挖坑的工作方法和排查思路。1. 为什么有这么多工程师开始把AI当RTL搭档1.1 从复制粘贴到代码生成我的第一次尝试事情起源于一周前新项目里需要一个数据宽度可配置的同步FIFO。这种事放在以前我直接在旧项目里挑一个功能相近的模块改改参数就能用。但那天正好刚看完大模型写代码的演示视频我心里痒痒就把键盘交给了AI。我的提示词其实只给了个大概“写一个同步FIFO宽度8位深度16带full和empty信号。”AI很快就吐出了一段很工整的Verilog代码端口声明、寄存器声明、assign语句、always块一样不缺。第一眼看过去感觉比很多初级工程师写的还规范我当时还挺兴奋。结果问题在仿真阶段暴露了。我给写逻辑和数据逻辑各写了几个方向性的测试当写指针走满一圈回到0时empty信号被拉高和满信号同时为1。理论上一个FIFO不应该既能读又能写时同时报满空但它的代码里empty的生成逻辑根本没考虑指针回卷的情况。我盯着波形看了半天又看了看AI生成的代码终于在地址计数器的处理上发现它把计数满和存储索引混在了一起。这段代码如果直接进综合功能上也不会全错但边界场景一跑就会出协作问题。我把错误信息丢回给AI它倒是很快改了可改完以后又冒出新的flag错误。来来回回改了三四轮最后还是我把那一段计数器逻辑重写了一遍才算收场。那次体验最大的感受是AI确实能生成代码但它只负责“生成”并不负责“正确”。你让它在字节层面写一个计数器和读写逻辑它可以做到但如果你期望它像资深工程师一样把边界条件和模块之间的握手关系一次想清楚那真的是想多了。1.2 AI到底能替RTL工程师做什么虽然第一次就摔了跤但我不觉得AI在RTL设计里是废物。相反经过这一周的折腾我划清了它的能力边界——哪些环节交给AI是提升效率的哪些环节交给AI纯粹是给自己找罪受。目前我觉得AI能干得不错的事情有这么几类代码骨架生成。端口列表、参数定义、模块例化结构、常用寄存器的定义这些属于纯粹的文本生成AI做得很熟练。简单模块的自动补齐。比如基础的计数器、移位寄存器、简单的状态机、多路选择器逻辑这些几乎全行业都有大量相似代码AI见过的远比我们多。testbench的脚手架。生成时钟、复位、任务task、简单的数据激励这些本来就很模板化AI写得又快又省心。语法检查和代码风格整理。把没对齐的端口头对齐把复杂的if-else改成case把明显重复的always块合并这些操作AI表现得完全可以信赖。而AI干不了也不能干的事是跨时钟域同步策略的设计它不知道你的两级同步器应该放在哪条路径上复位释放的时序逻辑它不知道你是异步复位还是同步复位更不知道复位释放信号是否已经同步到目标时钟域状态编码选择独热码、格雷码还是binary码AI默认只会用最简单的写法面积和功耗优化它从不看综合报告它的目标只有“符合你给的自然语言描述”。所以我不建议把AI当成一个全知的RTL生成器。更准确的比喻是它像一台执行力很强的打字机你告诉它每个信号何时拉高何时拉低它帮你把代码文本敲出来。它没有电路直觉但可以帮你省掉大量敲代码的时间。1.3 第一批尝鲜的人为什么会被折腾疯网上到处是“AI已经会写代码了”“AI要取代程序员了”的说法但真正拿着AI去写RTL的人几乎都经历过从惊喜到崩溃的心理过程。我觉得根源有三个。期望错位是最大的问题。大多数工程师看AI演示时看到的都是拿Python写排序、拿JavaScript调接口这类任务本质上是文本生成和数据结构处理AI确实擅长。但RTL是描述硬件电路代码的每一个if、always块、赋值语句最终都要变成一个实实在在的电路。AI在生成代码时根本没有“这个always块会产生一组寄存器那个assign语句会综合成一个多路选择器”这种概念。它只是在语言层面模仿人类的代码写法怎么可能替你保证电路正确。反馈链条太长也是个问题。写Python代码跑一下就知道结果对不对写RTL你得经过仿真、综合、时序分析几个环节才能发现错误而且有些错误是潜在的比如错误的状态编码可能会导致综合后面积大了一倍但仿真它很完美地通过了。AI不像人类工程师那样能从综合报告里吸取教训你让它“把面积缩小一点”它只能无所适从地给你改几个参数甚至越改越糟。再加上调试成本高。AI生成的代码里没有注释没有设计意图说明出了问题你得像读别人留下来的祖传代码一样去猜它当时为什么这么写。发现问题后把它丢给AI改AI改完给你一个新的错误你又得继续把新错误丢回去。几次循环下来你会发现时间全花在无效沟通上还不如自己动手。所以第一批尝鲜的人被折腾疯本质上不是AI技术本身不行而是使用方式完全错了。把它当成一个实习生而不是一个资深专家心态就会平和很多。2. 拆解AI写RTL的底层逻辑它能看懂时序吗2.1 大模型是怎么“理解”Verilog的很多人搞不清楚为什么AI生成的Verilog能过语法检查甚至会通过部分仿真却总在综合或边界测试中露出破绽。要理解这一点得先想明白大模型生成的原理。大模型本质上是在学习人类语言的概率分布。它用海量的代码语料训练出来知道什么样的代码文本在统计上更常见。你给它一句“写一个带异步复位的D触发器”它生成的代码是它在历史语料中见过的类似代码的某种“平均模式”而不是从电路原理出发推导出的设计。这就是为什么AI生成的东西往往看起来有模有样。因为真实世界里的Verilog代码长得都一样端口声明、always块、阻塞或非阻塞赋值、module和endmodule。AI把这些高频文本模式烂熟于心组合起来非常流畅。但真实电路里一个信号从一个时钟域到另一个时钟域需要同步器一个计数器溢出会影响状态跳转一个状态寄存器在IDLE状态下如果复位无效会导致系统崩溃——这些都是电路世界的“隐含规则”。这些规则不会直接显式地写在你给AI的prompt里AI也没法从prompt里推断出来它只能凭语料中的统计相关性来做猜测。我做过一个很形象的类比让AI写RTL就像让一个熟读了很多菜谱却从没摸过锅铲的人给你做一道红烧肉。他写的配料表、切菜方法甚至火候建议都有板有眼放在纸面上谁也挑不出错。但真到灶台前糖色什么时候放、火开多大、肥肉什么时候煸透这些“肌肉记忆”他根本给不了你。RTL也一样时序、复位、握手、流水线这些东西都是物理世界的约束不是文本概率能覆盖的。2.2 综合与仿真为什么总跟AI生成的代码过不去如果你和AI写RTL的代码打过几次交道可能都会遇到以下的场景仿真通过了波形看着也对但综合器一跑就是一堆warning。有些是latch有些是多种驱动有些是组合逻辑环。问题出在AI习惯于“文本上怎么像样就怎么写”而不是“电路上怎么合理就怎么写”。举个例子对于时序逻辑规范的做法是always块中统一使用非阻塞赋值并把所有要触发的信号列在敏感列表里。AI很容易生成两个或多个always块都对同一个寄存器赋值的情况。仿真时你把所有语句都按顺序执行一遍可能看起来没毛病但综合工具会认为这相当于多个驱动源直接报错或者综合出你完全意想不到的电路。再比如可综合性。Verilog里有不少语句是仿真专用的像initial、fork/join、延时控制等。AI在做testbench模板的时候很擅长生成这类语句但它有时会把同一个模块里的仿真语句和可综合语句混在一起。仿真能过综合却会把这些语句悄悄忽略最终流片后行为和仿真完全不一致。这种坑你如果不仔细看综合报告根本发现不了。还有一个特别常见的问题是latch。AI在描述组合逻辑时经常只写if分支不写else或者case的default分支缺失。在仿真中这倒不会报错可综合器就会推断出一个锁存器。如果是某个寄存器类型的变量还没赋值还会有不定态的问题。我的经验是只要AI生成的是带if-else或case的纯组合逻辑块三次里至少有一次会在综合阶段蹦出个latch warning。这不是AI笨而是因为它没有把“电路一定要完整映射不能留有读不到的分支”当成必须条件。2.3 时钟、复位、状态机AI最容易翻车的位置如果给AI生成RTL的翻车点排个名时钟、复位、状态机这三个词一定占据前三位。先说时钟。AI完全不理解时钟域划分。你给它描述一个模块它往往会假设所有信号都用同一个时钟采样不会考虑一组信号其实来自两个不同频率的时钟源。跨时钟域的同步它不知道要做即使你告诉它“这是异步FIFO”它也只是意识上知道这个词实际操作时还是会把读写指针直接放进同一个always块或者把跨时钟域的握手信号不做同步就跑到下一个模块。复位就更直接了。很多AI模型对复位逻辑的默认写法都是“异步复位高电平有效”比如always (posedge clk or posedge rst)。但在实际项目里复位极性取决于芯片设计手册和标准单元库很多库的低电平异步复位根本不能这样写。更麻烦的是异步复位同步释放这需要先对复位信号做两级同步再送到各个寄存器AI基本不会想到这一层。我试过一次AI给出的代码清清楚楚只写了两个端口clk和rst_n。功能仿真看着对但做DFT的时候复位树根本没法正常工作这就是后话了。状态机是另一个盲区。AI生成的FSM状态变量往往是一串二进制编码但没有完美的默认分支或者在case中写了default但default只赋值给状态变量忘了给一组输出赋值。结果综合出来一堆latch或者状态机进入未定义状态时直接卡死。如果你有要求它用独热码编码它就老老实实把每个状态赋一个8b00000001这样的值可后续的时序优化它完全无能为力。状态机是一个包含了状态跳转、输出逻辑、复位初值、编码选择等多个维度的设计问题AI只把最表层的那条线串了起来一旦需要做出工程取舍它就不行了。3. 实操现场我用AI写了三段RTL踩了不少坑3.1 案例一让AI生成一个异步FIFO差点被它绕晕异步FIFO是RTL设计里公认的具有一定难度的模块因为要处理读写时钟域不一致、指针跨越时钟域同步、空满判断的保守策略等。我想测试一下AI到底能不能碰这个硬骨头。我的prompt是“实现一个异步FIFO深度16宽度8写时钟100MHz读时钟75MHz提供full和empty信号以及写指针和读指针输出。”几秒钟的时间AI给我吐出一份结构还算完整的代码。端口、内存数组、二进制指针、空满标记该有的都有。我当时还开心了一下但认真看就发现逻辑上的大问题它的读写指针都用二进制计数而且没有做任何跨时钟域的格雷码转换。在异步FIFO里读写指针要分别同步到对端时钟域为了减少亚稳态传播风险通常需要用格雷码。AI把这一步彻底跳过了。再往下看它的full信号生成语句几乎是把空判断逻辑复制了一遍读指针等于写指针就报空写指针等于读指针就报满完全没考虑加上“绕回一圈”的判断。结果就是读写指针都停在同一个位置时empty和full同时为1。这在功能上完全不能用。我把问题描述给AI让它修复它确实加了一堆格雷码转换和同步器但加完后我依然发现读指针在跨时钟域后没有做两级同步就进了读判断逻辑。我再反馈一次它自己可能也被绕晕了又改成了另一种并不标准的跨时钟方案。最后我直接把异步FIFO的指针同步部分全部手写AI只保留了我修改好的内存阵列和读写数据路径。整个来回折腾下来比我直接从零开始写还慢。这个案例很典型。AI能在外部结构上模仿一个异步FIFO的样子像端口、寄存器、数组但在跨时钟域这种深层设计细节上它的表现就像没有见过这门课的学生。它不是不努力是真的不知道格雷码和两级同步器是必需的一环。3.2 案例二AI处理DFT复位插入时暴露了逻辑盲区这个坑是跟DFT相关的。如果你做的是可测试性设计复位逻辑的写法往往不是最直观的“时钟沿触发复位”那一种。工程上经常要求功能复位和扫描测试复位分开处理以便在scan_mode下还能正常地控制内部所有寄存器的复位操作。我让AI写一个带异步复位的模块它给我的是标准的异步复位代码。仿真没问题但丢到DFT工具里复位树检查直接报警。原因是复位逻辑没有考虑scan_mode信号也没有在复位路径上插入DFT可控条件。工具希望复位信号在测试模式下可以被扫入而AI的代码把复位做成纯粹的功能引脚在扫描模式下根本无法置位或清零所有寄存器。发现这个问题后我没指望AI能自己改对而是把DFT要求直接写进注释里再让它重写。它倒是添加了一个scan_mode判断但判断条件被整合到了内部逻辑而不是复位树上。综合工具最后还是认为复位路径不符合DFT规则。这次我不再和它纠缠了去EDA工具手册里查了对应规则后亲手在复位逻辑前面加了一个扫描使能和逻辑门问题才算解决。这个案例给我提了个醒RTL设计不是只有功能仿真这一道关卡DFT、综合、STA、功耗估算这些后端流程里的约束AI完全不具备背景知识。你如果想用AI写RTL就得提前把这些约束写进prompt里否则它只会按照“教科书上最标准的样子”生成代码而这些教科书代码往往缺少实际项目里的工程修饰。3.3 案例三状态机代码能跑但综合后面积翻了一番第三个案例是我自己手痒想写一个五级流水线的控制状态机。状态总数大概有12个我让AI生成FSM。AI很快给了一套二进制编码的状态寄存器case语句写得也规整。仿真通过功能正确我心里还挺得意。结果跑综合的时候我发现资源占用比预想高了很多。定位后发现状态机的组合逻辑复杂度爆炸了。因为状态数是12个二进制编码下状态转移条件需要比较6位状态寄存器而且状态转移的组合逻辑会变成一个很宽的case树。在资源紧张的高层设计中这种宽case树会占用大量查找表。我手动把状态变量改成独热码并告诉AI“以后状态机用独热码编码且每个状态要有明确的default分支”生成代码后综合资源恢复正常。这一步完全靠经验判断AI自己在生成的时候根本没有面积意识它甚至不知道我最终要跑在什么样的目标器件上。这个案例说明AI对综合后物理层面的影响完全没有感知。它会写出逻辑上正确的代码但你对面积、延迟、功耗的优化需求如果你不在prompt里明确写出来它就一点也帮不上忙。所以想在项目里少受罪就得提前把这类经验信息补给AI当弹药而不是指望它自己悟出来。3.4 案例四AI也有发光发热的时候吐槽了这么多也不是全无收获。我后来试着把AI用在一些更机械化的日常工作里效果出乎意料地好。比如维护一个旧模块时有一段三层嵌套的if-else代码运行时间长了很难读。我让AI把它翻译成case结构同时保留原有逻辑它做得干净利落。还有一次我需要给一个接口生成接口例化的模板所有信号名和位宽都已经在接口列表里了AI直接生成了完整的实例化代码我只需要替换一下例化的名称完全没出问题。最省心的一次是生成testbench的初始化任务复位释放、时钟生成、常见变量初值AI直接写了十几行语法规范还顺手补了注释。这些任务的共同特点是接口定义清楚行为模式固定与后端物理特性的耦合度低。一旦把这种工作交给AI我就可以把注意力放在真正有价值的模块架构设计上。所以我对AI的态度是不要因为它在高难度任务上的翻车就全盘否定。RTL设计里有一大堆“重复劳动”这些活AI完全接得住只是你得学会怎么把高难度任务拆成它能力范围内的子任务。4. 新一代“人机协作”工作流怎么用AI才不折腾4.1 把AI当作白板手而不是最终决策者被AI折腾了好几天之后我开始重新思考工作流的定位。在我看来AI在RTL设计里最适合的角色不是“设计者”而是“记录者”和“打字员”。一个更准确的工作方式是你先在脑子里把模块的接口定义、寄存器功能、状态跳转条件、关键路径约束全部想清楚然后把这些需求翻译成文字版的“设计文档”丢给AI让它生成Verilog代码。AI的职责只是把你的自然语言描述转换成代码文本。你不该让它替你做任何设计决策包括要不要加两级同步器、复位信号怎么接、状态编码用什么格式这些都是硬件工程师的活。这个思路有点像口头写作文和语音输入的关系。AI就是你的语音输入法它能帮你把脑子里的句子变成文字但它不会替你构思文章论点。如果你连论点都没想好就开始命令AI“写篇文章”大概率会得到一篇看似通顺但不知所云的废话。所以当你决定用AI写RTL模块时先花半小时把模块规格书写清楚反而比直接把任务扔给AI要省时间。规格书里写清端口列表、时钟频率、复位极性、模块行为、特殊约束比如需要跨时钟域同步、需要复位树可控AI生成代码的质量会明显上一个大台阶。4.2 写好RTL提示词的四步法如果你希望AI能一次生成接近可用的代码提示词的质量几乎决定了结果的上限。我自己总结出一个四步提示词模板基本可以适配绝大多数模块生成场景。第一步说明模块背景。比如“这是高速接口模块中的数据缓冲部分”“这是CPU总线桥的写数据通路”。你不用写得特别专业只要告诉AI模块在整个系统中的定位它就能大致判断功能范围。第二步给全接口信息。这一步最关键你要逐个列出模块的输入输出信号、方向、位宽、功能。例如模块名称data_buf 端口 clk输入1位主时钟 rst_n输入1位低电平异步复位 wr_en输入1位写使能 rd_en输入1位读使能 data_in输入8位 data_out输出8位 full输出1位高有效 empty输出1位高有效。第三步描述具体行为。把你希望的时序行为用文字写清楚越精确越好。比如“当wr_en为高且full不为高时在时钟上升沿采样data_in并写入存储full信号在存储中有效数据数量等于16时拉高读使能时在rd_en为高且empty为低的下一个时钟上升沿输出当前读指针指向的数据”。这些细节你写得越细AI生成的逻辑才越接近你真实需要的电路。第四步指定代码风格和约束。例如“使用可综合的Verilog代码不要在模块内部使用initial”“状态机使用独热码编码”“所有时序逻辑采用非阻塞赋值”“对复位信号做两级同步后再使用”。这些约束虽然AI不一定完全理解但它至少会按照你的格式去写能帮你少踩不少典型坑。这四步写下来可能要花十几分钟但换来的是AI生成的代码基本能进仿真。相比你拿着错误提示疯狂对话一两个小时效率高得多。4.3 验证闭环从仿真到综合的快速反馈AI生成代码后还有一个重要环节不能省快速验证。不要看到代码写得整齐就直接提交到版本库也不要让AI跟你无休止地对话修改。正确做法是建立一个自己熟悉的验证闭环。我通常会让AI生成代码后马上跑一个最小化的定向仿真覆盖关键握手路径、首尾数据、边界条件。比如FIFO就测写满、读空、绕回一圈后的满空状态状态机就测从复位释放到第一个状态、状态跳转的每条分支、非法状态恢复。这一步能在半小时内筛掉绝大多数AI的隐藏逻辑错误。仿真过了再跑一次lint检查专门排查latch推断、多驱动、阻塞赋值与非阻塞赋值混用这类可综合性问题。lint没过就直接把报错信息丢给AI修改此时它也能相对准确地定位问题。最后对关键模块做一次综合review关注面积和时序是否在预期范围内。如果综合报告出现异常大的LUT用量或者关键路径过深再回溯到RTL做优化。这套闭环跑完AI生成代码的风险基本能被控制在可控范围。记住AI最大的价值是提高你写代码的速度但验证和评审的环节永远不能交给AI一手包办。5. 常见问题速查表AI写RTL的避坑清单5.1 典型问题与排查思路这一周我几乎把自己踩过的坑都记了下来再加上和几个也在尝试AI写RTL的同行交流整理出一张常见问题速查表基本可以覆盖大部分AI生成RTL后的“翻车场景”。常见症状可能原因排查方向仿真时数据频繁错拍指针/计数器逻辑与实际握手时序不一致检查同步器、格雷码、FIFO指针产生逻辑综合后出现大量latch组合逻辑分支缺少default赋值检查case分支、if-else完整性复位行为不符合要求复位极性搞反或没有做同步释放确认工艺库复位类型补两级同步器面积/功耗指标超限状态编码不合理、存在冗余逻辑调整状态编码审查代码中的重复结构时序不收敛组合逻辑过长、缺少流水线寄存器在关键路径插入寄存器重新约束时序路径DFT测试时复位不受控复位路径未处理scan_mode控制人工插入可测性复位逻辑按DFT规则修改RTL仿真通过但综合报错代码中混入了仿真语句排查initial、fork/join、延时控制等语句AI反复修改还是出错提示词信息不足AI无法理解完整需求补全接口、行为、约束描述重新生成这张表的价值在于你可以根据症状快速判断是自己该动手检查代码还是继续把问题抛给AI修改。如果问题涉及跨时钟域、复位树、可综合性和后端优化请直接放弃AI亲自动手如果是简单的逻辑分支扩展或代码风格问题让AI改往往效果不错。5.2 我给新手的建议如果你是第一次尝试用AI写RTL我的第一建议是不要一口吃成一个胖子。先从一个小模块开始比如一个简单的8位计数器或者一个带有效信号的数据寄存器跑通完整的“生成-仿真-综合”闭环。等你对上AI哪些部分容易出错建立直觉后再让它生成相对复杂的模块。第二永远不要跳过代码评审。不管AI生成的代码看起来多完美都要拉上另一个懂硬件的人一起过一遍。很多AI代码是“逻辑正确但工程不适用”比如该打拍的地方不打拍该同步的地方不同步这种只有有经验的工程师才能一眼发现。第三学习最基本的综合和时序知识。你不需要成为后端专家但至少要能看懂综合报告里的LUT/FF占用、关键路径延迟、时序违规提示。否则你就只能被动接受AI给出的“我改好了”的结果完全无法判断改得对错。第四持续积累自己的提示词模板。每个项目、每个团队对代码风格和约束要求都不一样AI生成的代码不会天生适配你的标准。你把常见约束写进一个自己的提示词库里以后遇到同类任务直接复用效率会越来越高。说实话这一周被AI折腾得不轻但冷静下来想想AI在RTL设计里的价值不是“取代你”而是“放大你”。它把写代码的时间压缩了把重复劳动吸收了同时把真正需要设计判断的部分留给了你。你只需要盯紧那些不可妥协的硬件约束就能从一堆繁琐的机械工作中解脱出来。最后再分享一个小经验我现在让AI写RTL必在提示词里写一句“所有时序逻辑必须在时钟沿下工作不要在同一模块中使用initial语句”。这句话帮我躲过了至少两次综合事故。你也可以根据自己的历史翻车点总结出自己的“提示词护身符”。用AI这件事本质上和打仗一样工具越顺手纪律越严格你才不会在第一波冲锋时就崩溃。