第 1 章 · 共 16 章
为什么是 SystemVerilog:从 Verilog 到验证语言
理解硬件描述与软件执行的本质区别、Verilog 在验证场景下的局限、SystemVerilog 新增了什么,以及贯穿本系列始终的核心心智模型:设计代码与验证代码。
如果你写过软件,看到一段代码时,脑子里大概会自动"跑"一遍:先执行这一行,再执行下一行,条件成立就跳转,循环结束就退出。这个直觉在硬件世界里会让你摔跤——硬件描述语言(HDL,Hardware Description Language)描述的不是"先做什么、再做什么",而是一堆同时存在、同时工作的电路。一个模块里的多条语句,本质上是在并行地描述不同的逻辑,而不是一条顺序执行的指令流。
把这个转变放在心里,是学习 SystemVerilog 的第一步。
Verilog 是什么
Verilog 是最早被广泛使用的硬件描述语言之一,用来描述数字电路:模块(module)、端口、wire、reg,以及用 assign、always 等语句描述电路的结构和行为。设计工程师用它来编写会被综合工具变成真实门电路和触发器的代码。
这不是一篇 Verilog 教程,你不需要现在就精通它——后面的章节会逐步补齐需要的语法。这里只需要知道:Verilog 天生是为"描述电路"设计的。
当 Verilog 遇到验证:捉襟见肘之处
设计出一个电路只是第一步,你还需要证明它是对的——这就是验证(verification)的工作:给电路施加激励,检查它的行为是否符合预期。
问题是,Verilog 里几乎没有为验证准备的语言特性:没有真正的类和对象、没有内建的随机激励生成、复用检查逻辑也很别扭。于是验证工程师们只能各显神通——有人把驱动逻辑封装进模块里凑合用,有人直接在 initial 块里堆代码。结果是每个项目的 testbench 长得都不一样,难以复用,新人接手成本很高。
SystemVerilog 到底是什么
SystemVerilog(IEEE 1800) 是 Verilog 的超集,它在两个方向上同时扩展了 Verilog:
- 更强的设计(RTL)语法:比如后面章节会讲到的
logic类型、always_comb/always_ff等更清晰的过程块写法。 - 一整套面向验证的语言能力:类和对象、约束随机化(constrained randomization)、断言(assertion)等——这些代码不会变成任何电路,纯粹用于仿真阶段的激励生成与结果检查。
也就是说,SystemVerilog 并不是"另一门语言",而是把设计和验证这两件事,统一在了同一套语法之下。本系列后面的章节会依次覆盖这两部分:先补齐设计侧的核心语法,再逐步进入面向对象、随机化、断言这些验证侧的能力。
核心心智模型:设计代码 vs. 验证代码
这是本章最重要的一件事,也是贯穿整个系列的心智模型:
- **设计代码(RTL)**必须是可综合的(synthesizable)——它最终会变成真实的寄存器和门电路,所以只能使用工具能够映射到硬件的语法子集。
- **验证代码(testbench)**永远不会变成硬件——它是运行在仿真器里的"软件",用来产生激励、检查结果,因此可以自由使用那些在硬件里没有直接对应物的语言特性:不可综合的循环、动态分配的内存、
$display打印、文件读写等等。
同一套 SystemVerilog 语法,写出来的目的和约束却完全不同,取决于你现在戴的是"设计工程师"还是"验证工程师"的帽子。
一个最小例子
下面是一个两选一 mux 的设计代码,以及一小段用来验证它的 testbench 代码,并排放在一起:
// —— 设计代码(RTL):会被综合成真实电路 ——
module mux2 (
input logic sel,
input logic a,
input logic b,
output logic y
);
always_comb begin
y = sel ? b : a;
end
endmodule// —— 验证代码(testbench):永远不会变成任何门电路 ——
module tb_mux2;
logic sel, a, b, y;
// 例化被测设计(DUT,Design Under Test)
mux2 dut (
.sel(sel),
.a(a),
.b(b),
.y(y)
);
initial begin
a = 1'b0;
b = 1'b1;
sel = 1'b0;
#10 $display("sel=%0b -> y=%0b (期望 %0b)", sel, y, a);
sel = 1'b1;
#10 $display("sel=%0b -> y=%0b (期望 %0b)", sel, y, b);
$finish;
end
endmoduletb_mux2 里的 initial 块和 #10 延时语句,在真实芯片里没有任何对应物——它们只存在于仿真世界,用来按顺序改变输入、等待电路稳定、再打印检查结果。这正是"验证代码"的典型样子。
提前打个招呼:上面的代码里还有几处语法这一章没有细讲——
sel ? b : a这个问号写法(三目运算符)、1'b0/1'b1这种"位宽'进制值"的数字字面量、#10延时语句,下一章会一次性讲清楚;initial、always_comb这两种过程块要到第 8 章才正式展开。现在只需要看懂这段代码想表达的意思,不需要马上就能照着写出一份一模一样的代码。
这些代码是怎么跑起来的
一段 SystemVerilog 代码从文本变成仿真结果,大致要经过:
- 编译(compile):检查语法,把源码翻译成仿真器能理解的内部表示。
- 精化(elaborate):把模块例化关系展开成完整的电路/testbench 结构。
- 仿真(simulate):真正按时间推进,执行
initial/always块,产生波形和打印输出。
任何支持 SystemVerilog 的仿真器都可以完成这个流程,包括一些免费的在线环境(比如 EDA Playground)——本系列不会绑定某一款具体工具,跟着代码示例理解语言本身即可。
接下来会学什么
搞清楚"设计 vs. 验证"这个区别之后,下一章会先补齐模块声明、数字字面量、$display 这些随处可见的基础语法;再往后是 logic、reg、wire 等数据类型、数组、运算符、过程块、任务与函数,然后正式进入面向对象、随机化、接口、断言这些验证专属的语言能力,最后用一个小型 testbench 把整个系列串起来——那也是下一个 UVM 系列的起点。
关于设计代码(RTL)与验证代码(testbench)的说法,以下哪一项是正确的?
SystemVerilog 相对于 Verilog,最主要新增的是什么?