
做了十多年 FPGA写过的 Verilog 少说也有几十万行。最近被问得最多的一句话是Verilog/VHDL 是不是要被淘汰了问这个问题的人多半是在搜索列表里看到一堆“verilog 计数器”“vhdl 语言”“cache 的 verilog 实现”之后又刷到了 SpinalHDL、ChipShell 这些词瞬间觉得自己学错了方向。其实不用慌。今天这篇就想聊清楚一件事为什么在 Verilog/VHDL 之后还会不断冒出新语言这些新语言到底解决了什么问题以及作为 FPGA/数字 IC 工程师我们该怎么看待它们。这篇文章适合三类人刚入门的 FPGA 新手担心一毕业就学到一门“过时语言”工作三五年、正被老工程折腾得想换工具的老工程师以及团队里负责技术选型、想知道要不要在新项目里尝试新语言的负责人。我会以实际工程视角来写不吹不黑。先摆结论新语言一定会越来越常见但 Verilog/VHDL 依然是整个数字硬件生态的基本盘新语言并不是来“杀死”它们的而是来解放工程师的。1. Verilog/VHDL 的旧账为什么工程师开始抱怨1.1 定位问题Verilog 擅长“描述电路”不擅长“设计系统”很多人没意识到Verilog 诞生于上世纪 80 年代当时它的定位是门级仿真和晶体管建模后来才逐渐被推到 RTL 综合这条路上。VHDL 的出身也类似最初偏军工和文档化表达。换句话说这两门语言的 DNA 里天生带着一个思维定式语言是用来“描述一份已经存在的电路”的而不是用来“帮助设计师更快构建一个新系统”的。这个定位在当年完全没问题。一个设计几百个门用 Verilog 把每个 wire 和 reg 写清楚非常合适。可今天的 SoC 动辄上亿门一个 IP 里就有几十个模块和几十条总线如果还靠“一行行描述信号怎么连”效率自然跟不上。我常跟新人说Verilog 里很多让你痛苦的规则比如 wire 和 reg 有什么区别、阻塞赋值和非阻塞赋值怎么选、位宽截断为什么不报错本质上都是因为语言太贴近“电路物理细节”而离“设计意图”太远。举一个最简单的例子。在 Verilog 里写assign b a 1;加法器可能需要根据a的位宽来决定进位位怎么处理。如果a是 [7:0]b是 [7:0]综合工具默认会截断进位只会给个 warning很多人就当没看见。等芯片跑起来数据高位错了再回来查一个晚上就没了。新语言里这类位宽不匹配往往直接编译报错或者自动扩容根本不会给你留下“事后背锅”的机会。1.2 复用与参数化被 generate 逼疯的人如果说位宽是小事那参数化和复用就是老工程的硬伤。Verilog 里做参数化一般靠parameter/localparam加generate再配合ifdef这种恶心人的预处理。单个参数还行参数一多代码里全是if (DEPTH 1024)的结构看起来就像在写配置文件而不是写电路。VHDL 的generic比 Verilog 的parameter规范一些但语法太啰嗦。一个实体声明可以写几十行信号类型换来换去最后团队里没人愿意重构模块只能“复制一份再改”。我就是从那个“复制粘贴改”年代过来的一个 UART 模块因为位宽不同能复制出五六份每份里面都留着上一个项目的注释。后来想统一修一个 bug得改五六处漏一处就等线上事故。这也解释了为什么高级综合HLS没有完全解决痛点。HLS 的思路是让你用 C/C 写算法再综合成 RTL。但问题在于C/C 的抽象粒度太粗对时序、流水线、握手信号的控制都不直观。你写得越高级最后生成的 RTL 越像黑盒一旦时序收敛出问题调试成本反而更高。硬件工程师真正需要的是一种“既能写生成逻辑又能精确控制电路结构”的中间层。于是SpinalHDL 和 ChipShell 这一批“新语言”就出现了。2. 新语言新在哪从“描述电路”到“生成电路”2.1 拆解 SpinalHDL、ChipShell 与新语言的定位先纠正一个常见误解SpinalHDL 并不是一种像 Verilog 那样的“综合语言”它是一套基于 Scala 的硬件生成 DSL。你用 SpinalHDL 写出来的东西最终会被编译成 Verilog再用传统工具链去仿真、综合、布局布线。ChipShell 更严格来说也不是和 SpinalHDL 并列的完整语言而更像一种芯片工程的“Shell”理念——把模块生成、IP 整合、脚本化交付这些东西组织起来让“电路单元”变成可以像命令一样组合调用的对象。那“又一门面向硬件的新语言”到底是什么我个人更愿意把它理解成一种趋势的延续而不是某一个固定名字。从 Chisel 到 SpinalHDL再到 RustHDL、Python 系的硬件生成框架这批新语言共同的方向是不再把“写一份 RTL”作为最终交付而是把“写一个能生成 RTL 的生成器”作为核心工作。如果只把 SpinalHDL 当成“另一种 Verilog”来用你大概率会失望它的价值在于让你用编程语言的能力去生成、组合和复用电路。这个转变的实质非常像“手画施工图”和“写一套自动出图脚本”的区别。Verilog 时代你在图纸上画一栋楼新语言时代你写一个函数输入楼层数、房间数、风格参数程序自动画出一栋楼。楼还是那个楼但出图方式已经被彻底改写了。2.2 一个例子看懂“生成电路”总线对象与参数化 FIFO用 SpinalHDL 举例。你想做一个参数化 FIFO位宽和深度随意配置。传统 Verilog 里要写一大堆parameter内部还要用generate展开读写指针和存储阵列。但在 SpinalHDL 里代码大概长这样class MyFifo(dataWidth: Int, depth: Int) extends Component { val io new Bundle { val push slave(Stream(Bits(dataWidth bits))) val pop master(Stream(Bits(dataWidth bits))) } val mem Mem(Bits(dataWidth bits), depth) val pushPtr Reg(UInt(log2Up(depth) bits)) val popPtr Reg(UInt(log2Up(depth) bits)) // 其余状态机逻辑... }这里的Stream是 SpinalHDL 里封装好的一种流式握手协议自带valid、ready、payload等信号。你不需要手写每个握手位只需要说“这个端口是 push 方向的 Stream位宽多少、深度多少”剩下的连接细节由生成器处理。如果哪天想换成 AXI-Stream只需要换成对应的总线类内部逻辑几乎不用动。我第一次跑通这个例子的时候最大的感受不是“代码行数少了”而是“错误被提前拦截了”。接错信号类型编译期直接报错位宽对不上也能在生成环节发现。这种安全感在 Verilog 里从来没有过。2.3 新语言带来的工程红利类型安全、自动连接、可复用组件新语言的红利里最容易被忽略的是类型安全。什么叫类型安全在 Verilog 里一个 [7:0] 的寄存器接到 [15:0] 的端口上工具最多给个 warning很多项目整个生命周期都没人发现。但在 SpinalHDL 里信号本身是有类型的把 8 位的信号接到 16 位的端口上编译直接失败。你别小看这个特性在复杂系统集成时它省掉的时间是天文数字。第二个红利是自动连接和自动推导。总线协议里最烦的是地址线、数据线、应答信号的数量和位宽。用新语言定义一次总线对象数据位宽改了所有相关信号自动跟着变。Verilog 里常见的“把 [31:0] 拆成 [15:0] 高低两路”这种体力活在新语言里只是一行切片操作。第三个红利是组件复用。传统 Verilog 模块复用基本靠“复制整个 .v 文件再改参数”。新语言里你可以把模块封装成 Scala 类或者函数像调用软件库一样调用。想生成 8 路 UART直接在循环里创建 8 个实例就行。想做一个支持深度配置的仲裁器把仲裁算法写成函数传入端口数量和优先级策略参数即可。这种复用方式才是真正对得上“IP 化”这个词。3. 新语言跑传统工程的实操记录计数器、UART、SPI 与 FIR3.1 先拿经典模块练手计数器、UART、按键消抖理论说再多都不如动手。我的建议是不管你想用哪门新语言先别急着做工程拿几个经典模块做“翻译练习”。我当初的练手清单是计数器、UART、按键消抖、SPI Slave、旋转编码器。为什么要选这些因为它们规模小、功能明确、你非常熟悉 Verilog 实现适合用来对比语言差异。先说计数器。Verilog 里要分时钟边沿、复位电平、计数位宽稍不注意就写成锁存器。新语言里可以直接定义一个Reg(UInt(8 bits))然后用:写寄存器翻转逻辑。关键区别是在新语言里Reg明确告诉你这是时序逻辑组合逻辑用Comb或Area表达不像 Verilog 靠“看赋值方式猜你是 reg 还是 wire”。这个区别对我这种老油条太重要了因为猜错代价就是综合出一堆莫名奇妙的触发器。UART 比计数器复杂一些涉及波特率分频、发送状态机、接收采样。用 Verilog 写状态机全靠parameter定义状态一堆case状态多了代码就乱。新语言里状态机更像高级语言的枚举还能把波特率分频器封装成一个可复用组件。我实践下来同样的功能Verilog 大约 180 行SpinalHDL 工程代码 100 行左右生成出来的 Verilog 大概是 220 行。行数不是关键关键是调试时间Verilog 版本我仿真时抓了两次波形才找到状态跳转问题新语言版本第一次编译就通过了。按键消抖是另一个有意思的场景。传统消抖逻辑要“按下延时稳定判断”在 Verilog 里经常要用计数器配合状态机代码一多就容易分不清时钟域。新语言里可以把“延时计数器”和“边沿检测器”做成两个小组件思路清晰很多。尤其是当你需要同时处理 4 个按键时循环生成实例比手写 4 套逻辑省心得多。3.2 总线类模块也不难SPI Slave、I2C 读写 EEPROM如果你平时写了很多 VHDL/Virtex 系列一定对 SPI Slave 和 I2C 控制器不陌生。这类总线模块的难点在协议时序。SPI Slave 要处理时钟极性和相位CPOL/CPHA数据在哪个边沿采样I2C 要处理起始条件、停止条件、应答位。用 Verilog 写这些“陷阱”全靠手写状态机一个个磨。用新语言写核心逻辑没变但有两个优势一是总线信号用类型对象封装时钟边沿和采样配置作为参数模块非常容易被复用二是可以在生成器里嵌入协议测试代码直接跑 Scala 仿真和生成后的 Verilog 仿真做对比。模块名还是那句话“读写 EEPROM 的 I2C 控制器”。我在实践时特意用 SpinalHDL 重写了 I2C 主机接口用来驱动一块 24LC64 EEPROM。总体代码量和我原来的 Verilog 版本差不多但可读性明显更好。原来 Verilog 里那些ack信号的时序全靠注释说明新语言里直接用Bool类型和when条件判断生成的 Verilog 虽然还是状态机但可读性反而高了。3.3 算法类模块的甜点分布式算法 FIR 滤波器滤波器这类 DSP 模块用新语言写有时候会有意想不到的甜头。搜索热词里“分布式算法 FIR FPGA”经常出现说明很多人在研究基于分布式算法DA的 FIR 实现。DA 算法的核心是用查找表替代乘法器尤其适合 FPGA 的查找表结构。理论很美好但手写 Verilog 实现 DA 的查找表和移位累加逻辑非常琐碎每个查找表内容怎么算、符号位怎么扩展、累加顺序怎么安排都是体力活。我用新语言做过一个 8 阶分布式算法 FIR。做法是在生成器里定义滤波器系数数组和输入位宽然后用循环生成查找表内容。因为查找表内容是“算出来的”不是“手打出来的”所以改系数只需要改数组。之前调一次系数要重新手算一遍 LUT 表特别容易出错新语言里生成一次 LUT 只要几秒而且保证数学上正确。这就是“生成电路”在算法模块上的价值。3.4 仿真和调试流程生成 Verilog 后还要回老工具新语言最终还是要生成 Verilog/VHDL所以你依然绕不开 ModelSim、Vivado、Quartus、Verilator 这些工具。整个链路大概是写生成器代码 - 生成 Verilog - 用传统工具做仿真和综合。很多人以为用了新语言就“不用看波形了”这是错的。新语言能帮你减少低级错误但不能帮你解决协议逻辑和时序架构上的设计问题仿真和波形分析仍然是基本功。我实际跑过两套仿真方案一套是 SpinalHDL 自带的 Scala 仿真环境跑起来非常快通常几秒钟出结果适合验证生成器的功能另一套是把生成的 Verilog 丢进 Verilator 或者 ModelSim和旧模块一起做系统级验证。前者的好处是调试闭环短后者的好处是覆盖真实综合工具链。两个都不能省。一个特别注意的地方是保存映射信息。生成器里的变量名可能和生成的 Verilog 信号名对不上调试时如果只拿生成后的.v文件看波形会非常痛苦。很多新语言生成器都支持“保留源码变量名”或“生成命名映射表”这个功能务必打开。不然一个 10 万行的生成文件你看波形连信号都找不到。4. 为什么 Verilog/VHDL 仍是基本盘从搜索热词看真实需求4.1 流片交付与 IP 集成兼容性就是硬门槛我前面吹了新语言很多但必须说一个现实只要你还做数字 IC 或大规模 FPGA 工程Verilog/VHDL 一定是基本盘。原因很直接代工厂、IP 厂商、验证环境、EDA 工具链绝大多数都以 Verilog/VHDL 为底层交换格式。新语言生成的 Verilog 可以交付但新语言本身不能直接进综合工具。所以最终在各种文件交接、IP 例化、网表仿真里你看到的还是 Verilog。热词里那一大串“ddr3 读写控制实现 verilog”“i2c 读写 EEPROM 代码 verilog”“cache 的 verilog 实现”“轮询仲裁器 verilog”就是最真实的用户需求。大家搜这些不是因为不想用新语言而是因为现有 IP、参考设计、教程和面试题全是 Verilog。公司招聘的时候简历上写“熟悉 SpinalHDL”能加分但“熟练 Verilog”依然是门槛。4.2 AI Agent 写 Verilog对语言生态的双面影响最近“ai agent verilog 代码”“claude code 写 verilog 代码”这类搜索突然多起来了。我自己的体会是用 AI 辅助写 Verilog 确实能提速尤其是计数器、FIFO 控制器、状态机这些“套路化”模块AI 做得又快又像样。但从工程角度AI 生成代码存在两个问题一是正确性需要你自己把关很多代码看着逻辑对综合出来却是一堆警告二是 AI 的训练语料里Verilog/SystemVerilog 占了绝大多数新语言的语料很少你让它帮你写 SpinalHDL效果明显不如写 Verilog。这带来一个很微妙的影响AI 会延长 Verilog 的寿命。因为大家习惯了用自然语言让 AI 生成 Verilog短时间内没人愿意转向一个“AI 还不怎么会”的新语言。但从另一个角度看新语言也在借 AI 获得更多工程实践机会。等语料库丰富起来AI 生成新语言代码的质量提高那又是一轮生态升级。所以我认为新语言不会让 Verilog 消失只会让“生成 RTL”这件事越来越自动化。4.3 新语言学习路径按模块边界切入别推倒重来想学新语言最忌讳的思路是“把公司项目全部重写一遍”。硬件工程不是玩具老模块跑得好好的没必要为了用新语言而重写。我的建议是按模块边界切入从增量模块开始。比如新项目里要加一个配置寄存器接口你用 SpinalHDL 或 Chisel 写一个 AXI-Lite 寄存器堆生成 Verilog 后交给同事集成验证没问题大家心里就有底了。入门路径可以这样安排先用 2 小时看看 Scala 基础语法不用精通懂对象、函数、类和循环就行再用 3 小时照着官方文档写一个计数器和一个 UART然后用 3 小时做一个小型 FIFO 或 SPI Slave把生成 Verilog、用 ModelSim/Verilator 仿真跑通最后用剩下的时间去看总线库和组件库的用法。全部加起来 10 小时左右就能判断这门语言适不适合自己。5. 新语言路上的常见问题与排查技巧5.1 生成的 Verilog 不好排查怎么办新语言最被吐槽的一点就是生成的 Verilog 代码可读性差。SpinalHDL 生成的代码默认会有很多内部信号命名带一串前缀看多了头晕。解决办法有三个第一生成时开启“保留源码名”选项这样你在 Scala 里写的pushPtr生成的 Verilog 里大概率还能看到类似名字查波形方便很多第二给关键信号加注释性前缀比如debug_开头的信号都表示临时观察点第三别直接拿生成的.v文件做顶层调试把它当成“黑盒模块”只查接口信号。只要接口波形正确内部逻辑基本不用碰。5.2 团队成员不愿意用新语言怎么办这种情况太常见了。老工程师说“Verilog 写了十年凭什么换”新人担心“学了没人会”管理层担心“代码能不能交付”。我实践下来的策略是“试点黑盒”。找一个低风险模块比如寄存器配置接口用新语言重写生成 Verilog 后放在独立目录其他人正常例化它。交付后老工程师如果想查问题直接查生成后的 Verilog不需要学新语言。等几次验证下来大家对稳定性和维护性有了信心再逐渐扩大范围。千万别搞“一刀切”式迁移那样只会让团队分裂。5.3 后仿真/时序不过问题通常在生成器还是 RTL遇到“新语言生成的模块时序收敛不了”很多人的第一反应是“新语言不行”。其实问题往往出在生成器代码本身可能你生成的组合逻辑链太长可能复位方式不统一可能跨时钟域处理缺失。我的排查习惯是先在 SpinalHDL/Chisel 的仿真环境里跑功能测试排除协议逻辑错再看生成的 Verilog重点看关键路径的寄存器级数是不是比手写版本多最后回传统工具里看综合报告如果关键路径在某个自动生成的组合逻辑块上回去改生成器比改生成后的 Verilog 更划算。实际踩过的一个坑是 FIFO 满信号。用 SpinalHDL 生成 FIFO第一次跑综合发现频率上不去查波形发现满信号是纯组合逻辑生成路径太长。解决方法是把满信号改成寄存器输出用一拍流水换时序收敛频率直接提升了 40%。这种情况在 Verilog 里也能遇到但新语言里定位起来要更快因为你能回源码看生成逻辑而不是在一堆生成代码里大海捞针。还有一点要提醒复位方式必须统一。新语言里默认可能是同步复位而你手写的模块可能是异步复位混用之后后端检查会报一堆问题。初始化参数和复位策略应该在生成器里就定好别等生成完再改。这些细节用多了自然就记住了。我个人一开始对这套“新语言”是半信半疑的真正改完几个模块才明白问题从来不是“要不要用”而是“在哪个边界上用”。把生成逻辑长、参数化要求高的模块交给新语言把稳定成熟、需要精确控制的模块留在 Verilog两边协作反而最舒服。最后再分享一个小技巧无论你用什么新语言生成后的 Verilog 一定要纳入版本管理而且要保留生成器源码和生成工具链的版本号。否则过半年想重新生成一次连你自己都忘了当初是怎么生成出来的。这个习惯能帮你省下远比想象多的折腾时间。