EDA 工具链

VCS 常用命令与仿真流程

整理 VCS 仿真的常用命令、编译运行分离、波形生成和回归脚本组织方式。

目录最小命令推荐目录文件列表Makefile 组织Plusargs波形和日志Verdi 调试随机种子和复现常见编译选项常见问题找不到 package 或 module仿真能跑但波形为空同一个 seed 结果不一致编译很慢建议一个轻量流程阶段性总结

VCS 仿真流程通常分为编译、运行、波形查看和回归管理。这里记录通用命令形态,具体参数需要根据授权环境和项目规范调整。

对 RTL 开发来说,仿真命令不应该只是“能跑起来”的一次性输入。更好的做法是把编译选项、文件列表、宏定义、随机种子和输出目录整理成可复现的流程。这样当一个 bug 在几天后重新出现时,我们还能准确恢复当时的环境。

这篇笔记记录 VCS 使用中的常见组织方式。由于不同实验室和公司环境差异很大,下面的命令只作为通用形态,不涉及任何受限授权配置。

最小命令

vcs -full64 -sverilog -debug_access+all \
  -f filelist.f \
  -top tb_top \
  -o simv

./simv +ntb_random_seed=1

这条命令可以拆成两个阶段:

  1. 编译 elaboration:读取 RTL/testbench、展开层次、生成可执行仿真程序。
  2. 运行 simulation:执行 simv,传入 plusarg、seed 和 case 参数。

编译和运行分开后,可以在不重新编译的情况下多次运行不同 seed 或 testcase。当然,如果宏定义、文件列表或顶层参数发生变化,仍然需要重新编译。

推荐目录

sim/
  filelist.f
  Makefile
  logs/
  waves/
  work/
rtl/
tb/

仿真目录最好和 RTL 源码目录分开。VCS 生成的 csrc/simv、日志和波形都可以放在 sim/ 下,清理时不会误删源文件。

文件列表

+incdir+./rtl/include
./rtl/core.sv
./rtl/datapath.sv
./tb/tb_top.sv

文件列表应尽量保持确定顺序,尤其是在 SystemVerilog package、interface 和宏定义较多时。一个更完整的 filelist.f 可能包含:

+define+SIM
+incdir+../rtl/include
+incdir+../tb/include

../rtl/pkg/core_pkg.sv
../rtl/core.sv
../rtl/datapath.sv
../rtl/ctrl_fsm.sv

../tb/tb_pkg.sv
../tb/driver.sv
../tb/monitor.sv
../tb/scoreboard.sv
../tb/tb_top.sv

如果文件很多,可以分成多个列表,例如 rtl.ftb.fcommon.f,再由总入口包含它们。关键是不要让文件顺序依赖 shell 的不稳定展开。

Makefile 组织

我通常会用 Makefile 提供统一入口:

TOP      ?= tb_top
SEED     ?= 1
TEST     ?= smoke
BUILD    ?= build
SIMV      = $(BUILD)/simv
LOG_DIR   = logs
WAVE_DIR  = waves

VCS_OPTS += -full64
VCS_OPTS += -sverilog
VCS_OPTS += -timescale=1ns/1ps
VCS_OPTS += -debug_access+all
VCS_OPTS += -kdb

.PHONY: compile run sim clean

compile:
	mkdir -p $(BUILD) $(LOG_DIR) $(WAVE_DIR)
	vcs $(VCS_OPTS) -f filelist.f -top $(TOP) -o $(SIMV) \
		-l $(LOG_DIR)/compile.log

run:
	$(SIMV) +TEST=$(TEST) +ntb_random_seed=$(SEED) \
		-l $(LOG_DIR)/$(TEST)_$(SEED).log

sim: compile run

clean:
	rm -rf $(BUILD) csrc ucli.key *.vpd *.vcd *.fsdb

实际项目中,编译选项可能会更多。原则是:常用参数可以封装,关键变量要能从命令行覆盖。

make sim TEST=conv_basic SEED=17
make run TEST=random_stall SEED=1024

Plusargs

VCS 支持通过 plusargs 把运行时参数传入 testbench。SystemVerilog 中可以这样读取:

string test_name;
int seed;

initial begin
  if (!$value$plusargs("TEST=%s", test_name)) begin
    test_name = "smoke";
  end

  if (!$value$plusargs("SEED=%d", seed)) begin
    seed = 1;
  end

  $display("[tb] TEST=%s SEED=%0d", test_name, seed);
end

plusargs 适合选择 testcase、输入向量路径、是否打开波形、是否启用额外检查等。不要把需要重新综合或重新 elaboration 的结构参数误放到 plusargs 中。

波形和日志

目标方式
生成波形在 testbench 中加入 dump 逻辑
定位错误固定 seed 并保存日志
回归测试Makefile 或脚本统一管理 case

波形文件通常很大,不应该默认在所有回归中打开。更合理的方式是通过 plusarg 控制:

initial begin
  if ($test$plusargs("DUMP_FSDB")) begin
    $fsdbDumpfile("waves/run.fsdb");
    $fsdbDumpvars(0, tb_top);
  end
end

运行时再决定是否生成波形:

make run TEST=conv_basic SEED=3 RUN_ARGS=+DUMP_FSDB

如果使用 VCD,也可以用 $dumpfile$dumpvars。具体波形格式取决于工具链和查看器配置。

日志方面,建议统一打印 case 名称、seed、配置参数和最终状态:

[case] random_stall
[seed] 1024
[cfg ] width=8 depth=64 stall=enabled
[pass] transactions=256 mismatches=0

这种日志比只打印 TEST PASS 更适合回归汇总。

Verdi 调试

如果编译时打开了必要的 debug 选项,可以用 Verdi 查看层次、源代码和波形:

verdi -ssf waves/run.fsdb -top tb_top &

常用定位路径包括:

  • 从错误日志中的时间点跳到波形。
  • 找到 scoreboard 报错的事务编号。
  • 反查 valid/ready 是否出现丢拍。
  • 检查复位释放后的寄存器初值。
  • 检查状态机是否进入非法状态。

波形调试要尽量带着具体问题进入,不要无目标地浏览所有信号。对复杂数据通路,建议在 RTL 中保留少量有意义的 debug 信号,例如当前 tile 坐标、事务计数和状态名编码。

随机种子和复现

随机验证的第一原则是失败可复现。每次运行都应该记录 seed:

./simv +TEST=random_stall +ntb_random_seed=20260624 \
  -l logs/random_stall_20260624.log

当回归失败时,先固定同一个 seed 重新运行。如果失败不稳定,要检查 testbench 是否存在未初始化变量、竞态、文件依赖或异步结束条件。

一个回归列表可以写成简单文本:

smoke 1
reset_basic 2
random_stall 17
random_stall 1024
overflow_boundary 9

然后用脚本逐行执行,收集通过和失败数量。

#!/usr/bin/env bash
set -euo pipefail

while read -r test seed; do
  [ -z "${test}" ] && continue
  echo "[run] ${test} seed=${seed}"
  make run TEST="${test}" SEED="${seed}"
done < regress.list

这不是完整验证平台,但已经比手工输入命令稳定很多。

常见编译选项

选项作用备注
-full64使用 64 位模式常见默认选择
-sverilog支持 SystemVerilog需要 SV 语法时打开
-timescale=1ns/1ps设置时间单位也可在源码中指定
-debug_access+all打开调试访问会影响编译和仿真开销
-f filelist.f指定文件列表推荐使用
-top tb_top指定顶层多 testbench 时很有用
-l compile.log保存日志便于追踪

不同版本和授权配置可能支持不同选项。遇到问题时应以本机 vcs -help 和项目规范为准。

常见问题

找不到 package 或 module

优先检查文件列表顺序。SystemVerilog package 必须先于使用它的模块编译。还要确认 include 路径是否正确。

仿真能跑但波形为空

检查 dump 逻辑是否真的执行、波形路径是否存在、仿真是否太早 $finish、是否打开了对应的 debug 选项。

同一个 seed 结果不一致

可能存在未初始化寄存器、testbench 竞态、文件读写顺序问题,或者多个并发进程对同一变量写入。需要先缩小 case,再看复位和事件调度。

编译很慢

可以尝试分离常用库、减少默认 debug 范围、避免每次修改都全量重新编译。学习项目中不必过早优化编译系统,但至少要避免无意义地清理所有中间产物。

建议

不要把一次性命令留在终端历史里。把可复现命令写入脚本,后续定位问题会节省大量时间。

一个轻量流程

对于中小规模 RTL 模块,我会从下面这套流程开始:

  1. 写 `filelist.f`,固定源码顺序。
  2. 写 Makefile,提供 `compile`、`run`、`sim`、`clean`。
  3. testbench 支持 `TEST`、`SEED`、`DUMP` 等 plusarg。
  4. 默认不生成波形,只在失败时打开。
  5. 用 `regress.list` 管理基本测试集合。
  6. 把失败日志、seed 和 commit 记录下来。

这套流程足够轻,不需要一开始就搭建复杂框架;同时也给后续 UVM、覆盖率和 CI 留出了入口。

阶段性总结

VCS 的核心不只是命令参数,而是围绕这些参数建立可复现的仿真习惯。文件列表、Makefile、plusargs、日志、波形和回归脚本组合起来,才能让 RTL 调试从“手工试错”逐步变成“证据驱动”的工程流程。

后续可以继续补充 UVM、覆盖率、断言和 CI 环境下的轻量 lint 流程。

我的数字集成电路设计工具链

从 Linux 环境、版本管理、仿真验证到综合时序检查,整理一个可复用的数字 IC 学习与研究工具链。