FPGA新语言来了,Verilog/VHDL会被淘汰吗?一个老工程师的答案
2026/9/8 11:47:48 网站建设 项目流程

做了十多年 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/localparamgenerate,再配合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 里封装好的一种流式握手协议,自带validreadypayload等信号。你不需要手写每个握手位,只需要说“这个端口是 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 与 FIR

3.1 先拿经典模块练手:计数器、UART、按键消抖

理论说再多都不如动手。我的建议是,不管你想用哪门新语言,先别急着做工程,拿几个经典模块做“翻译练习”。我当初的练手清单是:计数器、UART、按键消抖、SPI Slave、旋转编码器。为什么要选这些?因为它们规模小、功能明确、你非常熟悉 Verilog 实现,适合用来对比语言差异。

先说计数器。Verilog 里要分时钟边沿、复位电平、计数位宽,稍不注意就写成锁存器。新语言里可以直接定义一个Reg(UInt(8 bits)),然后用:=写寄存器翻转逻辑。关键区别是:在新语言里,Reg明确告诉你这是时序逻辑,组合逻辑用CombArea表达,不像 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 一定要纳入版本管理,而且要保留生成器源码和生成工具链的版本号。否则过半年想重新生成一次,连你自己都忘了当初是怎么生成出来的。这个习惯,能帮你省下远比想象多的折腾时间。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询