
在可编程逻辑和芯片设计的圈子里IPIntellectual Property这个词听起来多少有点距离感。说白了它就是一段经过验证、可以反复复用的电路功能模块从串口、I2C到复杂的总线桥都算。这几年AI辅助开发在软件界已经卷成常态但在硬件设计领域很多人还是将信将疑总觉得RTL这种东西“AI写不了”。我自己的态度是有保留的于是挑了看门狗WDTWatchdog Timer这个经典模块做了一次从零开始的AI辅助设计。结果不算惊艳却非常务实活儿能干但前提是你得把设计意图彻底想清楚不然AI会把定时器做成FPGA Demo而不是一颗能上芯的IP。这篇不是理论课我会把整个流程拆开需求规格、提示词设计、RTL生成、验证平台、集成复盘每个环节都放我实际用过的代码和踩过的坑。就算你没有接触过数字IC设计看完也能明白AI辅助硬件设计到底省在哪、难在哪。1. 需求拆解AI设计的第一步不是写代码而是把“边界”写清楚1.1 看门狗WDT到底要干哪些活看门狗定时器Watchdog TimerWDT是绝大多数SoC和单片机里都有的基础外设它的职责非常粗暴用硬件去监测系统软件是否还活着。如果软件运行正常就会周期性喂狗也就是往WDT某个寄存器写一个特定值让计数器重置如果软件死循环、跑飞或者干脆宕机了计数器就会一路减到零WDT随即发出复位信号或者中断通知让系统拉回可控状态。听起来就是一个倒计时器对不对直接按着这个理解去让AI生成会发现提示词根本没法提取出设计特征。这个模块看着逻辑简单但它有三个容易出问题的边界。一是窗口喂狗。只做“最长时间到了就复位”是最低级的看门狗现在主流设计都会做窗口模式。喂狗太早也不行因为过早喂狗说明程序可能在重复某段短暂执行的逻辑并没有真正运行到主循环甚至可能是在死循环里碰巧经过喂狗代码。窗口模式要求喂狗必须在计数器值落到某一段区间内才能成功早喂、晚喂都触发复位。二是防误关机制。看门狗开关如果只是一位使能寄存器系统软件一旦跑飞先给你写个零把WDT关了那看门狗就彻底失去了意义。所以常见做法是想要修改控制寄存器必须先在锁寄存器里写入一串固定数值行业里叫unlock sequence或者写保护。三是复位信号的处理。WDT发的不是普通脉冲它要区分是上电复位还是看门狗复位也要保证复位电平宽度足够让系统里其他模块完成复位流程同时又要避免把复位原因寄存器冲掉。这些细节直接决定芯片中的复位管理模块怎么协同工作。我最初给的提示词只写了“16位递减计数器、定时到了输出复位信号”AI立刻给我返了个勉强能跑的模块但计数器到0只保持了一个周期就自己恢复成复位值这在真实系统里完全不够——很多外设的复位逻辑需要几十甚至上百个时钟周期才能完成状态收敛。所以这次实践给我的第一个启发是给AI写提示词本质上是在帮它做“可综合功能的边界定义”。1.2 分频、计数和喂狗序列先把参数表钉死先确定一下这次设计的目标参数。为了让验证不过于简单我把核心计数器定位32位驱动时钟假设50MHz。这里需要先用一个预分频器产生低频的计数时钟否则32位计数器在50MHz下要跑到85秒才溢满仿真和功能验证都要跟着遭殃。我选的分频系数是256这样计数时钟约为195312.5Hz计数步进是5.12微秒。老老实实把全范围跑完大概需要将近86秒日常调试如果嫌长可以留一个仿真专用的快速配置寄存器直接改计数初始值。这里注意一点分频系数、计数宽度、超时阈值都做参数化不给参数化会让后面做SoC集成的人很头疼因为不同芯片主频不同WDT的固定时间反而没通用性。喂狗序列我选了经典的双键方式总线先写0x5A再写0xA5只有连续两次写入成功才真正把计数器重装回初始值。如果第一次写错或第二次写错都要让“喂狗状态机”清零避免软件只靠反复穷举喂狗序列来碰运气。再加一层锁寄存器解锁码0xE1E5必须要先写对解锁码控制寄存器里使能位才可以被修改。我把寄存器表做成了一张会说人话的表格寄存器名偏移位宽读/写功能说明WDT_CTRL0x002R/Wbit0:使能 bit1:窗口模式使能WDT_LOAD0x0432R/W重装载值WDT启动时读取WDT_FEED0x088W依次写0x5A、0xA5完成喂狗WDT_LOCK0x0C32W写0xE1E5解锁控制寄存器WDT_INT_ST0x101R/W1C中断标志位写1清除我建议读者在做类似设计时把这张表格当作提示词的一部分直接发给AI。别小看这个动作我实际对比过不给寄存器表让AI自由发挥的版本AI会自己发明接口生成之后你再跟总线侧对齐改动成本非常高。接口一旦定死AI返工的痛苦就没有了。1.3 时序和复位策略不能丢给AI自由发挥再聊聊时序约束。我说过这个模块的复位策略在真实SoC里很微妙。我自己定的复位策略如下整个模块使用一个异步复位信号sys_reset_n上电时有效由芯片复位管理模块统一拉低。WDT模块内部所有同步逻辑都统一用这个异步复位置0在这个复位树上消除毛刺的办法是由复位管理模块统一做时钟域同步WDT本身只管接收。这里很多人会纠结既然已经有了异步复位做整体复位为什么中断、使能寄存器还要额外做同步清除为了稳妥。实际项目中跑飞现场经常是无规律的你在顶层硬件上留一个“看门狗复位清使能”的电平信号比依赖软件自己重新配置更靠谱。计数器递减逻辑反而用同步复位就够了。因为异步复位用于模块整体复位计数器递减属于正常运行路径每一拍都用时钟沿对齐如果计数路径也搞全异步复位综合工具在STA阶段反而会多出不确定路径。这个决定是我在生成RTL前端就写进提示词的要求AI在这个点上做了但它额外给我加了一句“posedge clk or negedge rst_n”的异步复位写法后来审查时被我一处处揪出来改掉了因为我要的就是计数器内部的复位始终同步。把判断都在需求阶段定好最后会发现提示词已经不像是在聊天更像一份简化的RTL spec。这其实就对了AI再怎么聪明也不会替你背“系统的功能性需求”它只是把自然语言翻译成描述语言和寄存器逻辑的工具。2. 让AI动手写RTL提示词、工具与首轮代码审查2.1 工具选择和关于大模型的几条心得工具上我尝试了GitHub Copilot、ChatGPT和Claude。结论先说写这种工业风格RTLClaude的完成度高ChatGPT胜在上下文交互Copilot适合在已有框架里补填空。但我最后还是用最笨的办法把大段提示词发给对话工具然后COPY代码回编辑器。原因很现实IDE里边的AI补全对我想保持的“从需求出发”没有帮助——它太迎合我手头的半成品代码而不是从设计规范出发。关于大模型硬伤我总结三条其一它对“可综合性”是盲的会一厢情愿地写initial块、fork join、甚至原语其二它会自作聪明添加一些多余的状态——看着结构漂亮但综合出来面积和时序就讲故事了其三它对接口的想象非常脆弱如果寄存器表里没写明位宽它按时就按32位全量处理而实际设计中往往是需要位宽匹配的。这些观点不是纸上谈兵是这次设计里实际撞过的坑。AI生成代码的过程其实很流畅但你一旦放松审查它会把整个模块变成“仿真器里正常、综合器里崩溃”的定时炸弹。2.2 我实际使用的提示词高成功率模板为了方便快速执行我通常会把设计需求组合成下面这种格式尽量一次给出所有约束。提示词本身的套路比大家想象得朴素就是把“接口、行为、约束边界”分开放像填单子一样一项项写清楚。这里我给一个我自己实际用过的精简版提示词读者可以直接套请你用Verilog设计一个32位递减计数的看门狗定时器模块。 接口要求 - 输入clkrst_nbus_wrbus_addrbus_wdata - 输出wdt_rstwdt_int以及一个仿真调试用计数器值输出cnt_dbg 行为要求 - 复位方式模块入口使用异步复位计数器内部使用同步复位 - 分频对clk做256分频后产生计数脉冲但只能作为时钟使能不能作为独立时钟 - 喂狗程序依次向FEED寄存器写0x5A和0xA5两个值连续正确才重载计数器 - 窗口模式开启窗口时计数器大于0x0FF0或小于0x0800时喂狗都触发复位 - 计数器到0时输出wdt_rst一个时钟周期高电平 禁止项 - 不允许使用initial - 不允许在always块外产生变量 - 所有时序逻辑统一使用posedge clk有意思的是我试过让AI自己梳理这类需求效果一般它会过度发挥。反而是把需求和几个边界条件直接丢给它干活速度快又准。这个对比很重要AI在硬件设计里是“承重翻译”不是“架构师”。2.3 对AI生成代码做的三处手术AI在第一次就给出了能编译的主体代码大约120行。但这120行里我在“使用”之前强制要求自己做了一次完整的review找出三个必须要改的地方。第一个问题是计数器的递减条件。AI默认写成了“只要使能就递减”完全没考虑预分频和快重装之间的竞争。我要求递减信号必须是在分频计数满之后产生的单周期脉冲AI却把分频计数器放在always块里直接拿分频输出作为独立时钟节点。这在仿真中看不出来但综合工具会报多时钟警告上板甚至会出现随机丢计数。我把它改为统一时钟沿驱动用一个分频计满脉冲作为时钟使能而不是把分频输出当独立时钟。第二个问题是喂狗序列的状态寄存器。AI用了一个单字节变量去记住当前写到第几位可我要求的是通过一个只有三态的状态机去区分“空闲、已写0x5A、成功”这样既节省资源也避免了状态跳转不完整导致的非法输入问题。第三个问题也是最隐蔽的AI没有统计“早到喂狗”。早期版本里只要写入合法序列就会无条件重载计数器于是窗口模式形同虚设。我后来在状态机里加了一段比较逻辑用上限和下限控制喂狗有效区段只要计数器的当前值不在窗口区间内喂狗序列完成也算非法。做完这三处修改整个模块的代码仍然保留着AI的骨架我自己只改了20%左右。这个过程让我明显感受到AI辅助RTL创作的真实定位它适合生成大框架和模板化代码不适合承担带有系统策略性的时刻判断。3. 用验证平台来证明AI写的IP到底能不能信3.1 测试平台结构从零搭的SystemVerilog仿真环境代码写完只是开始真正验证AI生成设计是否可靠的是仿真环境。用得最多的是SystemVerilog UVMUVM对于这种小外设反而显得笨重。我用的是非常轻量的定向testbench加断言核心思路是搭一个简单CPU模型模拟软件通过总线读写WDT寄存器观测WDT的复位和中断行为。testbench里最重要的部分是总线模型。因为WDT是外围设备我要模拟软件周期写0x5A再到写0xA5的过程。总线模型里用task封装一次写操作task automatic bus_write(input bit [1:0] addr, input bit [31:0] wdata); (posedge clk); bus_wr 1b1; bus_addr addr; bus_wdata wdata; (posedge clk); bus_wr 1b0; endtask我在主测试序列里依次调用bus_write模拟CPU正常喂狗先写0x5A到FEED寄存器再写0xA5两步做完后WDT内部会重装计数器。这里如果第二个周期被调成别的值就要能看到计数器不重装继续往下运行这是最基本的功能验证项。配合断言我用一个只读的debug信号把计数器当前值引出来在testbench里直接检查喂狗之后的计数值约等于重装载值property p_feed_reload; (posedge clk) disable iff(!rst_n) (wdt_reload_flag) | (cnt_dbg cnt_load_value); endproperty assert property(p_feed_reload);有了这种断言仿真跑完直接看通过和失败情况。这种断言的意义在于让AI生成逻辑里的隐性bug在仿真阶段现形而不是等板子烧出来之后变玄学。3.2 测试用例清单一种“窗口喂狗”的独特验证功能测试用例怎么定其实是整份验证计划里最见功力的地方。对于WDT我梳理出下面几条硬性用例。首先是“正常运行不喂狗计数器到零时产生复位”。这是最核心的用例大部分WDT都必须过。仿真把超时配置为很短的初值比如计数100个主时钟周期就溢出然后等待wdt_rst信号拉高。然后是“窗口内喂狗正常重装”和“窗口外喂狗触发复位”。窗口模式开启后我把窗口定义为重装载值0x1000允许喂狗区间是0x0800到0x0FFF之间。软件必须等计数器落到中段再喂喂太早比如计数还在0x1000附近和喂太晚比如计数值已经小于0x0800都会被判断为异常。实现这种验证最重要的地方是你要给testbench一个“读取当前计数值”的仿真调试接口这个接口在真实IP里可能不会暴露给软件但在仿真阶段是查状态的关键窗口。还有一个很容易被忽略的用例是“连续两步写正确但中间间隔过长”。为什么单独提这一条因为喂狗过程中第一步写0x5A后如果长时间不写0xA5状态机会一直停在中间态这种中间态如果被后续复位意外触发清掉就会导致喂狗逻辑状态错乱。我的处理是在状态机加了一段超时自清逻辑如果0x5A写后256个周期没等到0xA5就自动回空闲态。这个特性在第一版提示词里没有体现是验证时发现仿真状态会卡在中间态回头补进去的。3.3 覆盖率统计看看AI生成的RTL有没有留死角功能仿真通过以后我跑了一遍覆盖率分析。覆盖率分两块行覆盖率和状态机迁移覆盖率。行覆盖率是说RTL里每一行代码都被执行过没有状态机迁移覆盖率则是状态机的每个迁移边都至少走过一次。行覆盖率很快跑到了95%左右剩下5%基本都是错误分支像“解锁码错误”路径如果只在异常注入用例里才走会被算进死角。我为此单独写了一个异常注入用例专门往LOCK寄存器写错误值确保错误路径也能覆盖到。状态机迁移覆盖率就更要重视喂狗状态机一共有四个状态IDLE、FEED_FIRST、FEED_SECOND、RELOAD。我的用例覆盖了全部迁移边的90%剩下一条边是“两个阶段之间非法跳转”得故意在FEED_FIRST状态下连写两个0xA5才能踩到加上这个非法序列用例之后状态机迁移覆盖率就到了100%。这些覆盖率在AI自己写testbench的时候基本是陪跑需要人工补写用例因为AI对“覆盖率优化”这种验证域的认知非常浅——它知道概念却不知道边界在哪。这正是验证工程师的经验所在。4. 综合前后AI生成代码最容易翻车的三个环节4.1 复位风格不一致仿真里的复位和综合里的复位是两回事前面在需求阶段就说了复位策略要提前定这里单独把它作为综合问题再强调一次是因为AI默认生成的复位写法往往是“全模块统一异步复位”。这在做原型验证的FPGA上问题不大但是在ASIC综合时如果整个芯片复位树已经确定你又来了一堆用异步清0的寄存器很容易破坏复位树和时钟树的平衡。我们这版代码里计数器内部用同步复位外部控制寄存器和中断状态寄存器用异步复位两种风格并存。这在RTL仿真阶段很难发现但综合工具会帮你插满某种形式的复位逻辑时序上平白多出很多需要修的路径。这种问题本质上是设计风格的规范性。AI不会知道这颗芯片复位树的时钟域分区它只会机械地根据自然语言里出现“复位”的表述选用最常用的写法。所以提示词里必须写明哪个小模块用异步哪个部分用同步。这话说着简单实际执行起来还是得你自己心里有数。4.2 预分频电路被AI写成了独立时钟综合立刻教你做人第二个翻车点是预分频电路的处理。很多RTL新手和AI都会犯同一个毛病用计数器产生一个周期信号然后把这个信号当成时钟沿去驱动下游逻辑。这在模块仿真里逻辑功能确实对但在综合工具眼中它就是一个新的时钟节点会触发CTCClock Tree Cell相关的违规整个工程里多个不确定路径会把你折腾到崩溃。我要求AI生成代码时明确所有逻辑都在同一个clk域预分频输出只是时钟使能不是时钟本身。经过这一改综合工具就不会多报出一个时钟网了时序只收敛在单一主时钟上工程简单一大截。很多读者可能在软件领域见过“把定时器回调当成时钟”的坏味道这句话在RTL里一样准确。解决思路就是“同源时钟加使能”而不是“剪出细分时钟”这个概念建议大家在审查AI代码时单独盯一下。4.3 集成角度WDT使能位不能轻易对系统软件关闭最后说一个真正会在芯片集成阶段救命的点看门狗的使能位必须和系统复位原因、安全监控逻辑绑定在一起。AI给了我一个很直接的使能寄存器en模块内部if(en)才开始计数理论上没问题可一旦系统软件有能力直接关闭它前面所有的看门狗功能都是摆设。我在集成阶段增加了两个设计约束第一WDT_CTRL的写操作必须受LOCK寄存器保护解锁成功才能修改第二WDT一旦使能即使发生复位也要回到“使能”状态除非外部有可靠的上电复位。这个做法在消费电子芯片里非常常见叫“软锁定不可逆使能”策略。在代码里它不是复杂逻辑但对安全性的提升非常关键。AI在这一步帮不上忙是我在做集成评审时被安全专家提醒回头改了一版才锁住的。5. 常见问题速查与经验总结5.1 验证阶段遇到的高频问题清单我把这次开发过程中遇到的高频问题整理成一张表方便大家以后直接对照问题现象大概率原因处理方式计数器递减永远停不住预分频使能被当成持续高电平信号检查分频模块的“满仅一个周期”逻辑喂狗成功但计数器不重装数据比较未在正确边沿采样把FEED数据的采样放到wren有效沿窗口模式喂早不触发复位窗口比较写反或漏了早期判定在断言里先观察窗口判定信号复位输出电平过窄用组合逻辑直接输出复位改为寄存器打拍输出中断置位后无法清除没做W1C写1清除处理检查总线读回逻辑增加写1清除通路综合报多时钟域警告预分频输出驱动了逻辑改为“同源时钟时钟使能”写法这张表上的前三条我在AI生成的第一版RTL里全中。说实话不意外这几个问题在行业里也比较典型很多手工写RTL的人也会踩但区别在于手工写的人会凭着惯性规避AI不会你敲下来的每个细节它都会忠实执行包括错误。5.2 关于提示词迭代的一些经验有没有什么能让prompt效果更好的技巧我用下来比较靠谱的有几条把寄存器的地址和位宽全部表格化把复位策略明确到模块级别把“只允许一个时钟域”“不允许initial”“不允许fork”这类禁列直接写成约束条目最后要求AI在生成代码的同时说明关键路径。一旦它解释不通那地方基本上就是有问题的。这个“反问”法虽然在面向过程的代码里也一样管用但放到RTL上特别有效因为可综合代码里不允许有“赌一把”的地方。另外我习惯把提示词存版本每个版本的提示词和对应的RTL、仿真结果放同一工程目录改动一行需求就重新生成一次。时间长了你会发现提示词版本历史就是比代码注释更全面的需求变更文档比很多集成手册都好用。5.3 一点心头话回到开头那句话AI能不能设计WDT能但它设计出来的东西跟你“会不会提出正确约束”是强绑定关系。从头到尾把提示词、寄存器表、验证、综合跑完我的真实体感是AI至少把我预研时间压掉了40%让我能把精力放到窗口策略、复位树和集成安全这些真正的设计难点上。以后做其他IP这套流程大概率也能复用先固化接口和时序再让AI生成代码然后人工重点审查状态机和跨时钟路径最后用断言和覆盖率把高风险功能卡死。最后再分享一个小技巧如果项目时间充裕可以把同样的设计需求发给两个不同的大模型让它们各生成一版然后互相当reviewer。我在这个项目里就试过把其中一版的问题丢给另一版去检查它能指出很多人类容易忽略的边界情况。AI互相挑刺你坐在中间当裁判这种感觉挺奇妙的也算是2024年之后硬件设计工作流里最让我兴奋的变化之一。