vlsiNote EN 中文
Author verified by published 2026-10-22 07:43 UTC updated 2026-10-22 07:43 UTC 9 views

Verilator 语法支持边界实测:Verilog/SystemVerilog 哪些能跑、哪些会静默失真

Other languages 中文 (original)English
Verilator 5.052 的语法覆盖比“只能跑可综合子集”的印象完整得多:延时(含 real 变量延时)、real 运算、pullup/nmos 开漏、三态 inout、SVA、covergroup 都实现了;不支持的集中在对门级精度有意义的少数语法上——tran…

一句话结论

Verilator 5.052 的语法覆盖比“只能跑可综合子集”的印象完整得多:延时(含 real 变量延时)、real 运算、pullup/nmos 开漏、三态 inout、SVA、covergroup 都实现了;不支持的集中在对门级精度有意义的少数语法上——tran 双向开关、specify 门延时、驱动强度仲裁、Verdi 专有系统任务。危险的不是报错那类,而是“接受但静默失真”那类。

判定依据:同一份源码分别给 Verilator 5.052 和 VCS 2023.12,逐条 A/B 对照;语法支持矩阵用 39 个独立小用例(每条语法单独一个文件)分别做 lint、build、run 得到,不是查文档抄来的。

环境与方法

项 值
模拟器 Verilator 5.052(2026-09-05),gcc 12.5.0
平台 CentOS 7.9 / glibc 2.17,无 root
对照 VCS 2023.12-SP1
方法 每条语法一个独立文件 → verilator --lint-only --timing -Wno-fatal → 可疑者再 --binary --timing build + run
统计 39 个用例:lint 38 PASS / 1 FAIL;FAIL 的那条(tran)build 也失败

跑通一份真实 design 要改什么(简写)

对象:7 系列 FPGA 上的 1080p 时序控制器,29 个模块,100 MHz 输入、490 MHz LVDS 串行输出、I2C 从机,testbench 跑 43 ms 仿真时间。

必须改的四处:

# 改动 原因
1 $fsdbDumpfile/$fsdbDumpvars/$fsdbSuppress → $dumpfile/$dumpvars 没有 Verdi PLI,$fsdb* 是未知系统任务
2 unisims + secureip 换成行为级 fast 模型 secureip 是加密源码,Verilator 读不了;unisims 的 SERDES 又依赖它
3 testbench 内显式写 glbl glbl(); 有原语用 XMR glbl.GSR,且实例名必须正好是 glbl
4 波形格式换 VCD 或 FST 见“波形相关的坑”

结果:TB 抓帧写出的两张 BMP 与 VCS 逐字节相同(md5 一致),结束时刻到 1 ps 一致;编译 4.6 s(-j32)对 VCS 23 s,仿真 52 s 对 51 s。

支持矩阵(实测)

完全支持,且与 VCS 行为一致

类别 构造
时序/组合块 always_ff / always_comb / always_latch / always @(*)
复合类型 packed struct、unpacked struct、packed union、typedef enum + '( ) 强转
包与接口 package + import、interface + modport
类 class、new、virtual function(隐式 static 生命期会告 IMPLICITSTATIC)
容器 动态数组 int a[]、队列 int q[$]、关联数组 int tbl[string]
字符串 string、$sformatf
赋值模式 '{default:1'b1} 打包模式、'{0,1,2,3} 数组模式
生成 generate for + genvar
断言 立即断言 assert(...) else $error、并发断言 assert property(真正执行,见下)
覆盖率 covergroup/coverpoint + --coverage(verilator_coverage 会报 covergroup bin 命中率)
进程控制 fork/join、join_any、join_none、wait fork、disable fork、disable {具名fork}
过程连续赋值 force / release
文件与随机 $readmemh/$readmemb、$fopen/$fwrite/$fclose、$urandom、randcase
延时 过程块 #(0.250ns)、连续赋值 assign #(real变量)
三态与门级 pullup/pulldown、nmos/pmos、and/nand/or/nor/xor/buf/not、tri inout
其它 casez/casex、unique case、有符号算术、数组端口、let、inside、XMR(u_sub.sig 读写)、DPI-C import

两个容易踩反的结论,都是实测推翻的旧印象:

  • disable {具名fork} 是支持的。fork: W ... join_any; disable W; 实测只留下胜方分支的打印;disable fork 同样生效。
  • SVA 不只是“能解析”,违例时真的会报 %Error: Assertion failed ... %Error: Verilog $stop 并中止仿真——CI 里这条要当失败处理。

不支持(硬报错)

构造 报错
tran / tranif0 / tranif1 双向开关 %Error-UNSUPPORTED: Verilog 1995 gate primitive: tranif1,build 直接失败
加密的厂商模型(secureip 一类) 源码不可读,无法解析
$fsdb* 等 Verdi 专有系统任务 无对应 PLI,未知系统任务
门级/SDF 反标、$setup/$hold timing check 无等价实现

接受但静默失真(最需要警惕)

构造 Verilator VCS 说明
驱动强度:pullup 对 assign (weak0,weak1) s=0 0 1 不报警,任何两个驱动按值仲裁,强度不参与
specify ... endspecify 路径延时 y 在 2000 ps 翻转 y 在 3000 ps 翻转 只有 %Warning-SPECIFYIGN,延时被丢弃
$dumpoff / $dumpon 被解析但不生效 正常 dump 会一路写到仿真结束
--no-timing 或 +notimingchecks 每条 #延时 变成 %Warning-STMTDLY: Ignoring delay,所有边沿塌到 t=0 — 配上 -Wno-fatal 就是静默改变行为

关于 --timing:5.x 默认就开着,不加也是同一个结果;要防的是把它关掉(或 +notimingchecks 这种“接受并忽略”的选项)。建议脚本里显式写 --timing,让意图留在命令行上。

波形相关的坑

  1. $dumpvars 必须配 --trace(VCD)或 --trace-fst(FST)。少了这个编译期开关,$dump* 是静默空操作:跑完没有文件,也没有任何提示。
  2. $dumpoff/$dumpon 无效(见上表),所以“只 dump 一段窗口”做不到;能调的只有 $dumpvars(层级, 作用域) 的层级。
  3. 体积量级差三个数量级:同一个 43 ms、两条 490 MHz 总线的 design,只 dump TB 与 DUT 顶层两层——VCD 12.4 GB,FST 123 MB。没有 Verdi 的 suppress 等价物,压不下去就只能改层级或换 FST。
  4. 抓波形本身是主要成本:同一配置下无波形 52 s,FST 152 s(约 3 倍),VCD 约 98 s;而 VCS 那边开 FSDB 只多 0 s。要快就别默认开波形。
  5. Verdi 读不了 FST,GTKWave 原生支持;FST 也要 lz4 头文件才能编(fst2vcd 可转 VCD 给别的工具)。
  6. $time 返回“整数个时间单位”:1 ns/1 ps 的设计里 %0t 打印会显示 42922750.000 ns,看着像比 VCS 差 355 ps,其实 VCD 里的最后时间戳是 42922749645 ps,两侧一致。判断时间轴要看波形时间戳,不要看 $time 的打印。

写 TB / RTL 时的规矩(checklist)

  1. 注释不要以 // Verilator 开头。Verilator 会把 // verilator ... 当 pragma,报 %Error-BADVLTPRAGMA: Unknown verilator comment。用 // NOTE:、// VLNOTE: 之类前缀。
  2. 原语先确认有没有行为级模型。只有加密 secureip 版本的 IP,Verilator 直接没戏;能拿到 fast 行为模型的,功能仿真完全够用。
  3. XMR 到 global(如 glbl.GSR)必须有一个合适的实例名:testbench 里写 glbl glbl();(实例名与模块同名),Verilator 靠向上搜索解析。
  4. -Wno-fatal 必须配警告过滤。把 UNSUPPORTED / SPECIFYIGN / STMTDLY / WIDTHEXPAND / MULTIDRIVEN 这些 code 单独 grep 出来逐条判断;一口气 -Wno-fatal 当“零警告”用,等于把静默失真放过去。
  5. 断言失败会 $stop 中止仿真,回归脚本的退出码/日志匹配要按失败处理,不能当“跑完了”。
  6. 不要依赖驱动强度和门延时。开漏 + 上拉这种“strong0 压过 pull1”的常见拓扑结果正确,但“pull1 压过 weak0”这类就错,而且没有警告。
  7. 先 --lint-only --timing 再 build:lint 阶段把语法问题一次清干净,比每次 4 s 编译去试快得多。
  8. dump 层级按需给:$dumpvars(1, tb) 加 $dumpvars(1, dut) 通常就够定位,全层次 dump 的体积和耗时都不划算。

什么时候不该用 Verilator

场景 原因
门级 / 后仿、SDF 反标 无 specify/门延时、无 timing check
依赖驱动强度的模型 强度不参与仲裁,静默错
只有加密模型可用的 IP 源码读不了
流程绑定 Verdi($fsdb*、KDB) 无 PLI;波形只能 FST/VCD
期望仿真速度大幅提升 本例 52 s 对 51 s:编译快 5 倍,仿真没加速(时钟沿数量主导,不是事件调度开销)

复现方法

每个语法一条用例、一个文件,先 lint 后 build,脚本骨架:

# 单条语法
verilator --lint-only --timing -Wno-fatal --top-module m case.sv
# 可疑的(断言/覆盖率/进程控制)再真跑
verilator --binary --timing -Wno-fatal --coverage --top-module m -o V case.sv && ./obj_dir/V
# 覆盖率汇总(会列出 covergroup 命中率)
verilator_coverage coverage.dat

要看“和商用模拟器是否一致”,就把同一个文件交给两个模拟器各跑一遍,比较打印出的时刻与数值(本文所有“一致/不一致”都来自这种做法)。

局限

本文数据来自 Verilator 5.052 单一版本、39 个语法用例和一份 29 模块的真实 design,不代表更早/更晚版本的行为差异;program、clocking 块、DPI-C 只验证了“能解析/能编译”,运行时语义没有逐条核对。升级 Verilator 后建议重跑一遍这套 lint 矩阵。

VerilatorVerilogSystemVeriloglint仿真

Credibility

How far would you trust this note? 0-10.

How this note earned its label

Author verified bill2026 2026-10-22 07:43 UTC

Comments (0)

No comments yet.
Markdown source RSS Share card: light · dark The author checked this note line by line.