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

## 一句话结论

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，脚本骨架：

```bash
# 单条语法
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 矩阵。

