EDA 工具链
VCS 常用命令与仿真流程
整理 VCS 仿真的常用命令、编译运行分离、波形生成和回归脚本组织方式。
VCS 仿真流程通常分为编译、运行、波形查看和回归管理。这里记录通用命令形态,具体参数需要根据授权环境和项目规范调整。
对 RTL 开发来说,仿真命令不应该只是“能跑起来”的一次性输入。更好的做法是把编译选项、文件列表、宏定义、随机种子和输出目录整理成可复现的流程。这样当一个 bug 在几天后重新出现时,我们还能准确恢复当时的环境。
这篇笔记记录 VCS 使用中的常见组织方式。由于不同实验室和公司环境差异很大,下面的命令只作为通用形态,不涉及任何受限授权配置。
最小命令
vcs -full64 -sverilog -debug_access+all \
-f filelist.f \
-top tb_top \
-o simv
./simv +ntb_random_seed=1
这条命令可以拆成两个阶段:
- 编译 elaboration:读取 RTL/testbench、展开层次、生成可执行仿真程序。
- 运行 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.f、tb.f、common.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 模块,我会从下面这套流程开始:
- 写 `filelist.f`,固定源码顺序。
- 写 Makefile,提供 `compile`、`run`、`sim`、`clean`。
- testbench 支持 `TEST`、`SEED`、`DUMP` 等 plusarg。
- 默认不生成波形,只在失败时打开。
- 用 `regress.list` 管理基本测试集合。
- 把失败日志、seed 和 commit 记录下来。
这套流程足够轻,不需要一开始就搭建复杂框架;同时也给后续 UVM、覆盖率和 CI 留出了入口。
阶段性总结
VCS 的核心不只是命令参数,而是围绕这些参数建立可复现的仿真习惯。文件列表、Makefile、plusargs、日志、波形和回归脚本组合起来,才能让 RTL 调试从“手工试错”逐步变成“证据驱动”的工程流程。
后续可以继续补充 UVM、覆盖率、断言和 CI 环境下的轻量 lint 流程。