FPGA 入门 36:Verilog vs VHDL——语法哲学与选型
更新时间:2026-09-12。本文是
fpga/入门层第 38 篇。本系列用的是 Verilog,基础见 Verilog 设计基础,Verilog 的现代增强见 SystemVerilog 设计子集。
本文要回答的问题
- 两种语言各自诞生在什么历史背景下,这些背景怎样塑造了它们今天的语法哲学和主力行业?
- 描述同一个寄存器、同一段组合逻辑,两种语言在模块结构、类型、赋值方式上分别怎么写?
- VHDL 的强类型和 Verilog 的宽松类型,各自会在工程里拦下什么错误、又带来什么负担?
- 九值逻辑和四值逻辑在仿真中表达「未知」的思路有什么不同,综合后这些额外取值去了哪里?
- 两种语言怎样表达「硬件天生并发」,always/process、阻塞与非阻塞、信号与变量的区别如何理解?
- 一个工程里混用两种语言可行吗,跨语言边界传参、传类型时有哪些硬性规则?
- 新手到底该先学哪一门,SystemVerilog、Chisel 等新 HDL 会让这个选择过时吗?
读完本文,你应当能把同一段逻辑在两种语言间互译,读懂任意一种语言的开源工程,并能根据团队、行业和工具链给出有依据的语言选型判断。
一、定位与生态
| Verilog / SystemVerilog | VHDL | |
|---|---|---|
| 起源 | 1984,IEEE 1364/1800 | 美国防部,IEEE 1076 |
| 风格 | 类 C、简洁灵活 | Ada 风格、严谨啰嗦 |
| 类型 | 4 值(0/1/x/z)、宽松 | 9 值 std_logic、强类型 |
| 主力领域 | 互联网/消费电子/ASIC/原型、中美业界 | 军工/航天/欧洲/教学 |
| 时序块 | always @(posedge clk) | process(clk)...rising_edge |
二、同一段逻辑对照
// Verilog
module ff(input clk, input [7:0] a, output reg [7:0] q);
always @(posedge clk) q <= a & 8'hF0;
endmodule-- VHDL: entity(端口) + architecture(实现)
architecture behav of ff is
begin
process(clk) begin
if rising_edge(clk) then
q <= a and x"F0";
end if;
end process;
end behav;三、强类型 vs 灵活的取舍
- VHDL:位宽/符号/未初始化编译期就报错(稳),但类型转换繁琐;
- Verilog:写得快,但位宽不匹配会静默截断/补零,要靠 lint/仿真/自律;
- SystemVerilog:加 logic/enum/typedef/always_ff,折中。
四、选型建议
- 新手先精一门:推荐 Verilog/SystemVerilog(资源多、接 IC/验证),能读懂 VHDL;
- 入职/团队用啥就写啥;军工/航天/欧洲项目常是 VHDL;
- 工程可混合 .v/.sv/.vhd(端口用通用类型),但建议统一。
五、并发性:两种语言怎么表达「硬件天生并行」
新手最大的思维坎,是用写软件串行程序的习惯去理解 HDL,而两种语言在这一点上的答案是一致的:它们的基本组织单元不是「按顺序执行的函数」,而是一组并发运行的硬件块。Verilog 里多个 always 块、多条 assign、多个例化模块在仿真里是同时被调度的;VHDL 里多个 process、多条并发信号赋值、多个 component 例化同样如此。谁写在文件前面、谁写在后面,对最终硬件毫无影响——这和 C 程序里语句顺序决定执行顺序是根本不同的世界观。理解这一点,比记住任何具体语法都重要。
块内部才区分「并发」与「顺序」。Verilog 的 always 块和 VHDL 的 process 内部,语句按书写顺序求值(语义上如此),但它们对信号赋值的更新都要等到本时间步结束(或下一个事件)才生效,因此块内的「顺序」并不等于软件意义上的立即生效。Verilog 用阻塞赋值(=)与非阻塞赋值(<=)区分这两种语义:时序逻辑用非阻塞,所有右值在沿上采样、左值在步末统一更新,模拟真实触发器同时翻转;组合逻辑用阻塞,立即求值。VHDL 则用「信号(signal,<=,延迟更新)」与「变量(variable,:=,立即更新)」承担同一对概念。两套机制名字不同,映射的硬件事实完全相同:信号和非阻塞赋值对应寄存器/连线在时钟沿或事件后的更新,变量和阻塞赋值对应组合求值过程中的临时量。
一个实用的记忆锚点:写时序逻辑时,两种语言都要求「右值看旧值、左值步末更新」。Verilog 靠非阻塞赋值在语法上保证,VHDL 靠时钟进程里对 signal 赋值的调度规则保证。凡是在时钟块里误用立即更新(Verilog 用 =、VHDL 在时钟进程里用变量承载状态又依赖其跨周期值),都会制造仿真与综合不一致、或看似串行为什么综合出并行寄存器的诡异问题。这不是语言的怪癖,而是它们各自对「硬件并发」这一事实的表达方式。
六、九值逻辑与四值逻辑:仿真里的「未知」哲学
两种语言最常被提起的差异是逻辑取值的数量。Verilog 的线网和寄存器是四值:0、1、x(未知)、z(高阻)。VHDL 的 std_logic 是九值,除了 0/1 外还区分:U(未初始化,对象诞生后从未被赋过值)、X(强驱动冲突导致的未知)、W(弱驱动冲突的未知)、Z(高阻)、L/H(弱上拉/弱下拉对应的弱 0/弱 1)、-(不可能情况/无关项)。初看会觉得九值过于繁琐,但每个多出的状态都对应一种真实的仿真诊断需求。
U 是 VHDL 独有的强力信号:任何寄存器或信号在复位前若从未被赋值,仿真初值就是 U 而不是 0,于是「复位逻辑没真正覆盖到这个寄存器」的 bug 会在波形里以一片 U 直接暴露。Verilog 里寄存器默认初值是 x,虽然也能看出未初始化,但无法区分「从未赋过值」和「多个驱动冲突」。VHDL 的强/弱之分(X 对 W、0 对 L)则让开漏、上拉电阻、总线竞争这类模拟味更浓的场景在逻辑仿真里有区分度。九值本质是把「这个未知是怎么来的」编码进了取值本身。
但务必记住:这些丰富的取值只存在于仿真和静态检查中,综合后全部坍缩成 0 和 1。真实硅片上没有 x,也没有 U、W;x 在仿真里表示「工具无法确定」,是给人看的诊断标记,不是硬件会传播的第三种电平。综合工具看到 x 往往可以按任意值优化,这正是为什么「仿真里全是 x 就去查未初始化和驱动冲突」,而绝不能写依赖 x 的功能逻辑(比如 if (q === 1'bx)),那在真实电路上毫无意义。九值比四值多提供的,是更早、更精确地在编译和仿真阶段把错误推到你面前,而不是更强的硬件表达能力。
七、强类型在实战中拦下的三类典型 bug
VHDL 的强类型常被吐槽啰嗦,但它的价值要在维护大型工程时才看得清,最典型的是三类错误。第一类是位宽不匹配:把 8 位向量赋给 7 位信号、把单比特和向量做运算、端口连接时两边宽度不同,VHDL 编译器直接报错,连仿真都跑不起来;Verilog 默认静默处理,按右侧截断或高位补零,程序照跑,错误可能潜伏到某次特定数据才暴露。第二类是有符号无符号混用:VHDL 里 unsigned 和 signed 是不同类型,混合运算或和 integer 比较都要显式转换,逼着你回答「这串比特到底是不是补码」;Verilog 里是否有符号取决于声明和运算上下文,$signed 用错一处就可能让比较结果静默反转(参见 定点数基础)。
第三类是枚举与状态机的非法值:VHDL 用 type state_t is (IDLE, RUN, DONE) 定义枚举,不同枚举类型之间不能随便赋值,状态信号的取值在类型系统里被限定;Verilog/SystemVerilog 也要靠 enum 和 lint 才能获得类似保护,传统 Verilog 用裸 parameter 定义状态码,本质是整数,拼错一个常量名可能只是又定义了一个宏。强类型的代价是转换代码多:unsigned/signed/std_logic_vector/integer 之间要调 ieee.numeric_std 里的转换函数,端口映射常要包一层类型转换,写起来确实比 Verilog 慢。
取舍因此很清楚:VHDL 用更多键盘敲击,换编译期更早的错误暴露;Verilog 用更短的代码,换对自律、lint 和仿真覆盖率的依赖。这也解释了行业分布:军工、航天这类生命周期几十年、bug 成本极高、代码要经过严格认证的领域偏爱 VHDL 的「编译器替你把关」;迭代快、追求开发速度、有成熟验证流程兜底的互联网和消费电子领域更接受 Verilog 的灵活。SystemVerilog 加入 logic、强类型 enum、typedef、always_comb/ff,很大程度上正是在不丢掉 Verilog 简洁的前提下,把 VHDL 这三类编译期保护补回来(详见 SystemVerilog 设计子集)。
八、高频结构逐项对照
把最常用的硬件结构并排看一遍,比孤立记语法有效得多。模块定义:Verilog 一个 module ... endmodule 同时承担接口和实现;VHDL 拆成 entity(只声明端口)和 architecture(写实现),同一个 entity 可以配多个 architecture(行为级、结构级),这在做可替换实现时是天然机制。端口方向:Verilog 用 input/output/inout,VHDL 用 in/out/inout 且必须给类型(std_logic 还是 std_logic_vector(7 downto 0))。常量参数:Verilog 用 parameter,VHDL 用 generic,例化时都可覆盖。条件与分支:Verilog 用 if/else、case,VHDL 用 if/then/elsif/end if、case/when,组合逻辑里两者都要保证分支完整以免推断锁存。
时序逻辑:Verilog always @(posedge clk or negedge rst_n),VHDL process(clk, rst_n) 内 if rst_n='0' then ... elsif rising_edge(clk) then ...。组合逻辑:现代写法分别是 Verilog 的 always @(*)(或 SV 的 always_comb)和 VHDL 的 process(敏感列表),VHDL 要求手写全敏感列表,漏一个信号就是仿真综合不一致的经典坑,SV 的 always_comb 和 VHDL-2008 的 process(all) 才解决。连线与复用:Verilog assign y = a & b,VHDL 用 architecture 体内的并发赋值 y <= a and b。例化:Verilog 按端口名连接用 .port(net),VHDL 用 port map(port => net),都强烈建议用命名连接而非位置连接。数组/存储:VHDL 有正经的 type ... array,Verilog 靠存储器建模 reg [7:0] mem [0:255]。
一个深层差异是运算符风格:VHDL 的与或非是关键字 and/or/not/xor,比较是 = /= <= >=,注意 <= 同时是赋值符,判等用单个 =;Verilog 用 C 风格符号 & | ~ ^、== != <= >=、赋值 = <=。从一种语言切到另一种时,最容易出的低级错误恰恰是这些符号和判等规则,而不是结构概念——因为结构概念(并发块、时钟沿、敏感列表、例化、参数)是一一对应的,符号习惯才是肌肉记忆层面的障碍。
九、混合语言工程的规则
Vivado、Quartus、各主流仿真器都支持在同一工程里混合 .v、.sv、.vhd,工具对每种文件分别综合/编译,在 elaborate(精化)阶段按模块名把子模块接起来。跨语言能工作的前提是边界足够「朴素」。规则一:跨语言端口只传双方都有的类型——比特、逻辑向量(std_logic/std_logic_vector 对应 wire/reg [..])、时钟复位这类标量和向量;不要把 VHDL 的 record、SV 的 struct、类对象直接抛给对方语言,那在边界上无法映射。规则二:参数映射,Verilog 例化 VHDL 实体时,parameter 对 generic,按名字传,注意 VHDL 强类型——generic 若声明为整数,传入的就必须是整数语义。
规则三:库和包的可见性,VHDL 侧用到的 ieee.std_logic_1164、numeric_std 要在该文件里 use,综合顺序上 VHDL 有编译依赖(package、entity、architecture 先于例化它的文件),工程文件列表的顺序或工具的依赖分析要正确;Verilog 没有编译顺序依赖(模块是平铺的命名空间),这是混用时最容易让纯 Verilog 背景者困惑的一点。规则四:顶层可以是任意一种语言,但选一种主语言能降低维护成本;很多团队的实际做法是历史遗留的 VHDL IP 不动,新模块用 SV 写,边界处包一层适配器。
还要避免两个工程化陷阱。其一是同名冲突:Verilog 模块名是全局命名空间,两个文件定义同名 module 会报重复定义,而 VHDL 通过 entity 名加库组织,重名容忍度不同,混用时命名规范要更严格。其二是仿真器差异:混合仿真时不同工具对 x 传播、时间精度、跨语言层次路径(dump 波形时引用信号的写法)的处理略有差别,综合通过不代表任意仿真器行为一致,这类边界问题要在项目早期就用目标工具链验证,而不是等到集成末期。结论是:混合语言是能力,不是默认推荐,能统一就统一,必须混时把跨语言模块做成窄而干净的接口、配上独立的边界测试。
再给一份可直接照做的混语言接入检查单,联调时逐条打勾能省下大量返工。第一,先确认两端模块名和实体名完全一致——VHDL 的 entity 名、architecture 名和 Verilog 例化时使用的模块名大小写、拼写要对齐,某些工具对大小写敏感。第二,把跨语言端口逐根列成三列表:信号名、方向、位宽与类型,VHDL 侧的 std_logic_vector(7 downto 0) 对应 Verilog 的 [7:0],确认高低位顺序一致,downto 和 to 方向写反会静默颠倒字节序。第三,时钟和复位只传裸 std_logic/标量线网,复位极性在两侧文档中写死,不要在边界上做反相或同步化,把 CDC 和复位处理放在同语言模块内部完成。第四,参数化数值(generic/parameter)统一用整数或布尔,不要跨语言传自定义枚举或物理量类型。第五,为边界单独写一个顶层 testbench,用一种语言驱动、两种语言各自的内部行为模型比对,先验证边界再验证功能。
实际迁移中还有几个高频小问题值得提前知道。VHDL 里没有显式声明就使用的信号会直接报错,而 Verilog 拼错信号名可能被当成隐式线网默默创建一根新线,迁移时建议在 Verilog 侧全局开启 default_nettype none,让这类拼写错误像在 VHDL 里一样在编译期暴露。VHDL 的 case 必须覆盖所有取值(或写 when others),翻成 Verilog 时若漏了 default 就会推断锁存,行为和综合结果双双偏离原意。VHDL 中 = 对 std_logic 做比较时对 X/Z 会产生 false,而 Verilog 的 == 遇到 x 产生 x、=== 才按位严格比较,仿真里对未知态的判定不能想当然照搬。把这几条和上面的检查单结合,跨语言协作就从「玄学」变成了有清单可循的常规工程活动。
十、生态差异:选择常常不是语言本身决定的
「学哪个」在真实项目里很少是纯粹的技术选择,更多是生态、行业和既有资产的选择。行业惯性极强:军工、航天、铁路、核电这类高可靠领域有大量认证标准(如 DO-254)绑定 VHDL 流程和成熟的工具/人才/审查模板,换语言的风险远大于语法收益;欧洲(尤其法国、德国的工业和电信)和大量高校教学也以 VHDL 为主。相反,美国和中国的消费电子、互联网加速、GPU/CPU 等 ASIC 前端、FPGA 原型验证几乎是 SystemVerilog 的天下,因为它和 UVM 验证方法学、商业 VIP 生态绑定。
IP 和开源生态也在塑造选择。网络上能免费拿到的 FPGA 参考设计、开源 CPU(RISC-V 核、各种加速器)、开源工具链(Yosys 对 Verilog-2005/SV 子集的支持远早、远完整于 VHDL 前端)大多是 Verilog/SV,这让自学者和初创团队自然倒向 Verilog;而很多传统厂商交付的经过认证的成熟 IP 仍是 VHDL。人才供给反过来又强化生态:团队招应届生时,某一语言背景的人多,新项目就更可能选它,形成正反馈。
理解生态差异的意义在于:不要把语言之争当成宗教。当你进入一个 VHDL 团队,正确做法不是论证 SV 更先进,而是快速用对方的语言交付——因为前面已经看到,结构概念完全互通,真正要适应的只是符号、类型声明和强类型带来的转换代码。反过来,VHDL 背景者进入 SV 环境,要补的主要是验证方法学(class、约束随机、断言、覆盖率),那是 SV 相对 VHDL 拉开最大差距的地方,而不是设计描述本身。语言是工具,行业资产和协作效率才是选型的真正权重。
十一、新一代 HDL:Chisel、SpinalHDL 与 Python 系
选型讨论绕不开一个趋势:用更高抽象层次的语言生成 RTL。Chisel(Scala 嵌入)、SpinalHDL(同样基于 Scala)、以及 Python 系的 Amaranth(原 nMigen)、MyHDL 等,让你用现代高级语言的参数化、面向对象、函数式集合来描述硬件,再由工具生成 Verilog/VHDL 交给传统综合链。它们解决的核心痛点是「Verilog/VHDL 缺少现代软件的抽象机制」:想参数化生成一个 N 路交叉开关、一组可配置流水线,手写 RTL 要用大量 generate/component 循环,而在 Chisel/Spinal 里就是高阶函数和集合操作,SpinalHDL 还在生成阶段自动做跨时钟域检查、位宽检查,把很多 bug 挡在生成之前。
但要清醒看到边界。第一,这些语言不替代对底层 RTL 和时序的理解——它们生成的还是 Verilog,时序收敛、CDC、资源权衡一个都躲不开,抽象层反而可能在你没理解生成结果时制造「看不懂的网表」。第二,综合和调试链路仍回到厂商工具,报错和波形最终落在生成的 Verilog 上,调试时仍要能读懂它。第三,团队接受度和可维护性是现实门槛:用一门小众 DSL 写核心模块,意味着只有懂该语言的人能维护,人员流动风险高,这也是为什么工业界核心产品仍以 SV/VHDL 为主,新 HDL 更多活跃在研究、敏捷开发团队和 RISC-V 生态。
对新手的建议因此明确:先把一门主流 HDL(推荐 Verilog/SV)和它对应的硬件概念学到能独立写、独立调试、独立收敛时序,再去碰生成式 HDL。把 Chisel/Spinal 当成「会写 RTL 之后的生产力放大器」,而不是「跳过硬件基础的捷径」。同理,HLS(C/C++ 高层次综合)也是把算法编译成 RTL 的路线,它和生成式 HDL 都没有改变底层硅片,只改变了人表达设计的层次。基础不牢时,任何更高的抽象都会在时序和资源问题上把你打回原形。
十二、双语互译能力:一项被低估的基本功
在两种语言间互译,是检验是否真正理解 HDL 的最好练习,也是实际工作中的高频需求——接手一份 VHDL 参考设计要在 SV 工程里重写,看懂厂商的 Verilog IP 要讲给 VHDL 同事听。翻译时不要逐字逐句,而要先画出电路,再用目标语言描述同一块电路。具体步骤:第一步,从源码里抽出时钟、复位(极性、同步/异步)和所有时序块,列出触发器及其使能;第二步,识别所有组合块及其完整敏感条件;第三步,画出状态机的状态、转移条件和输出;第四步,标注每个信号的位宽、有符号性和数组结构。电路清楚之后,用目标语言重新表达,最后用同一组测试向量做仿真比对输出。
翻译中最常见的翻车点不在「语法不会」,而在三种语义细节。其一是复位风格:VHDL 常用同步复位(时钟进程里先判时钟再判复位,或只在时钟分支内判复位),Verilog 老代码常用异步复位(敏感列表含复位),翻译时若不统一,综合出的电路时序行为就不同。其二是信号更新时机:VHDL 时钟进程里对 signal 的赋值是步末生效,别机械地翻成 Verilog 的阻塞赋值,必须对应到非阻塞;VHDL 的 variable 立即更新,则要小心它在 Verilog 里没有完全对应的「过程内临时变量」语义。其三是位宽与符号:强类型语言里被编译器强制写清的转换,翻到宽松语言后极易丢失,翻译时要把每个 to_unsigned/signed 转换显式记在电路说明里。
养成互译习惯还有个额外收益:它会让你迅速意识到,两种语言之争在能力层面基本是伪命题。能把同一段 FIFO、同一个 AXI 状态机在两门语言里都写对的人,不会再执着于哪门「更优雅」,而会按团队和场景选型。建议的练习材料就是本文二、八节的对照结构:每学一个新硬件结构(FIFO、Synchronizer、边沿检测、波特率发生器、FSM),都用两门语言各写一遍并跑同样的 testbench。这种双语训练做上十几个结构,你对「HDL 描述的是电路而非程序」的理解会显著深于只用一门语言的人。
十三、结论与给新手的最终学习路线
回到最实际的问题:该学哪门、怎么学。结论分三层。选型层:功能完全等价,没有哪种语言能做出另一种做不出的电路;按团队、行业、既有 IP 和生态选——军工航天欧洲多 VHDL,消费电子、ASIC、原型、互联网与开源生态多 Verilog/SystemVerilog。入门层:若没有既定约束,推荐先精 Verilog/SystemVerilog,因为入门系列、开源参考设计、RISC-V 生态和验证方法学资源最丰富,且学懂它再读 VHDL 很快;若学校或公司就是 VHDL,直接学 VHDL 没有任何问题,概念完全可迁移。精通层:以一门为主,达到「能读懂另一门、能双语互译、理解强类型与并发语义」即可,不必两门都追求写得快。
一条可执行的学习路线:第一阶段,用主语言吃透同步设计、阻塞/非阻塞(或 signal/variable)、状态机、FIFO、CDC 这些与语言无关的硬件概念(参考 同步设计原则),每个概念都上板验证;第二阶段,集中做 5~10 个结构的双语互译练习,把语法差异一次性趟平;第三阶段,学 SystemVerilog 设计子集,把 logic、always_ff/comb、enum、typedef、简单断言用起来,感受强类型与编译器守护(见 SystemVerilog 设计子集);第四阶段,有余力再了解 Chisel/Spinal/HLS 等更高抽象,并始终保持能读生成的 RTL。
最后给一个心态建议:不要在语言之争上投入情绪。HDL 的价值在于描述正确、可综合、可收敛、可维护的硬件,VHDL 的严谨和 Verilog 的灵活各有其工程合理性,也都在向「更安全的灵活」(SystemVerilog、VHDL-2008)和「更高的抽象」(生成式 HDL)演化。把一种语言学到能独立交付,再具备跨语言读懂和互译的能力,你就不会被任何具体工具或语言绑死——在这个快速变化的行业里,这种底层可迁移性,才是真正稳定的竞争力。面试时能就同一段 FIFO 用两种语言讲清综合结果,远比在论坛上争论谁更优雅有说服力。把精力放在电路本身,语言只是表达它的两副笔墨而已。
常见坑对照
| 坑 | 说明 | 建议 |
|---|---|---|
| Verilog 位宽不匹配 | 静默截断/补零 | 显式位宽、lint、仿真 |
| VHDL 类型混用报错 | 需显式转换 | 用 numeric_std |
| 混语言传复杂类型 | record/struct 不通 | 端口只用 bit/vector |
| 以为语言学得多难 | 概念一一对应 | 精一通二 |
相关与延伸
基础:Verilog 设计基础、module 与端口。 现代写法:SystemVerilog 设计子集;开源工具(Yosys 支持 Verilog)见 开源 FPGA 工具链。
一句话总结
Verilog 和 VHDL 功能等价、都综合出同样的电路,差别在风格:Verilog(类 C、简洁、4 值、类型宽松)在互联网/ASIC/原型和中美业界主流,VHDL(Ada 风格、9 值 std_logic、强类型严谨)在军工/航天/欧洲/教学常用。VHDL 的强类型在编译期就拦得住位宽/符号/未初始化错误但代码啰嗦、类型转换繁琐;Verilog 写得快但位宽不匹配会静默截断补零、更靠 lint 和仿真自律。新手建议先精一门(推荐 Verilog/SystemVerilog,资源多且接 IC/验证),概念一一对应(entity=module、process=always、signal≈wire/reg),再读懂另一门很快,工作按团队语言走。同一工程可混合 .v/.sv/.vhd(跨语言端口只用通用 bit/vector),趋势是向 SystemVerilog 收敛。
上一篇:38 同步设计原则(避免门控时钟/组合环) | 下一篇:40 SystemVerilog 设计子集(logic/always_ff/enum) | FPGA 系列目录