3. VCS编译与仿真流程自动化
VCS 的日常使用看似只是两条命令:vcs 编译、simv 仿真。但在真实项目中,编译选项往往多达几十个,涉及语言版本、宏定义、库路径、覆盖率、调试、时序检查、性能优化等多个维度;仿真阶段又要区分不同 test、不同 seed、不同回归标签。如果每次都手敲命令,既容易出错,也无法在回归平台中稳定复现。本节的目标是把这两条命令工程化:用封装层隔离选项细节,用脚本层组织编译与仿真,用参数化支持批量回归,再用增量编译与缓存策略把编译时间压下来。
一、命令行选项封装:把散落的开关收进配置层
VCS 选项可以按用途分成几类,理解分类是封装的前提:
- 语言与解析类:
-sverilog、-full64、-ntb_opts uvm-1.2、-timescale=1ns/1ps,决定源码如何被解析。 - 宏与包含类:
+define+SIM_TOP、+incdir+./tb、-f filelist.f,决定编译哪些文件、走哪条条件编译分支。 - 库与绑定类:
-y ./rtl +libext+.v、-v ./lib/tech.v、-lca,决定模块解析来源。 - 调试与波形类:
-debug_access+all、-kdb、-lca,影响 Verdi 可回溯能力。 - 覆盖率类:
-cm line+cond+fsm+tgl+branch、-cm_dir ./cov,决定覆盖率采集范围。 - 性能与增量类:
-j 8、-Mupdate、-Mdir ./csrc,决定并行度与增量行为。
封装的核心思路是:把选项按“项目公共 + 环境差异 + 单次运行差异”三层拆分。项目公共层放语言版本、UVM 库、覆盖率开关;环境差异层放 RTL 路径、宏定义、filelist;单次运行差异层放 test 名、seed、波形开关。这样回归平台只需替换最外层,不必关心底层细节。
一个实用的封装方式是使用 Makefile 变量加 shell 脚本。例如定义 VCS_OPTS 作为公共选项,ENV_OPTS 作为环境选项,RUN_OPTS 作为运行选项,最终拼接成完整命令。封装层还应提供“dry-run”能力,即只打印最终命令而不执行,便于排查选项拼接错误。
二、编译脚本设计:从 filelist 到可执行 simv
编译脚本的职责不是简单调用 vcs,而是完成“准备输入 → 组装命令 → 执行编译 → 检查产物”的闭环。一个典型的编译脚本包含以下环节:
- 环境检查:确认 VCS 版本、license 可用、工作目录可写。
- 生成 filelist:按 RTL、TB、UVM、VIP 分组,支持
-f嵌套引用,避免路径硬编码。 - 组装选项:按前述三层拼接,并记录到
compile.cmd文件。 - 执行编译:调用 vcs,捕获 stdout/stderr 到日志。
- 产物校验:检查
simv是否生成、csrc目录是否完整、编译日志中是否有 error。
下面是一个简化的编译脚本示例,展示选项拼接与日志记录方式:
#!/bin/bash
# compile.sh - VCS 编译封装示例
set -e
WORK_DIR=$(pwd)
LOG_DIR=$WORK_DIR/logs
mkdir -p $LOG_DIR
# 三层选项
VCS_COMMON="-full64 -sverilog -timescale=1ns/1ps -ntb_opts uvm-1.2"
VCS_ENV="+define+SIM_TOP +incdir+./tb -f ./flist/rtl.f -f ./flist/tb.f"
VCS_DBG="-debug_access+all -kdb -lca"
VCS_COV="-cm line+cond+fsm+tgl+branch -cm_dir ./cov"
# 组装
CMD="vcs $VCS_COMMON $VCS_ENV $VCS_DBG $VCS_COV -o simv -l $LOG_DIR/compile.log"
echo "[compile] $CMD" | tee $LOG_DIR/compile.cmd
eval $CMD
# 产物校验
if [ -x ./simv ]; then
echo "[compile] simv generated OK"
else
echo "[compile] ERROR: simv not found" >&2
exit 1
fi
脚本设计的关键点是可复现:每次编译都把完整命令写入 compile.cmd,回归失败时可以直接查看当时用了哪些选项。另一个关键点是失败即停,用 set -e 或显式检查返回值,避免编译失败后继续跑仿真产生误导结果。
三、仿真参数化:test、seed、波形与回归标签
仿真阶段的参数化主要围绕四个维度:test 名、seed、波形开关、回归标签。VCS 仿真通过 +plusarg 传递参数,例如 +UVM_TESTNAME=my_test、+ntb_random_seed=12345、+wave_on。脚本层应把这些参数统一成变量,避免在命令行里散落。
一个常见的做法是定义 run.sh,接受 TEST、SEED、WAVE 三个环境变量,并生成对应的仿真命令:
#!/bin/bash
# run.sh - 仿真参数化示例
TEST=${TEST:-smoke_test}
SEED=${SEED:-1}
WAVE=${WAVE:-0}
PLUSARGS="+UVM_TESTNAME=$TEST +ntb_random_seed=$SEED"
if [ "$WAVE" = "1" ]; then
PLUSARGS="$PLUSARGS +wave_on"
fi
./simv $PLUSARGS -l ./logs/run_${TEST}_${SEED}.log
参数化的价值在于批量回归:回归平台可以循环生成 TEST × SEED 组合,每个组合独立日志、独立覆盖率目录,失败用例可单独复现。需要注意的是,seed 必须显式记录,否则失败用例无法复现;波形开关应默认关闭,只在调试时打开,否则大量回归会因写波形而拖慢速度。
四、增量编译与缓存策略:把编译时间从小时压到分钟
大型 SoC 项目全量编译可能耗时数十分钟到数小时。VCS 提供了增量编译机制,核心是 -Mupdate 与 -Mdir:首次编译生成 csrc 目录,后续只重新编译发生变化的模块,未变模块复用已有对象文件。使用方式是在编译命令中加入:
vcs ... -Mupdate -Mdir ./csrc -o simv
增量编译生效的前提是编译选项保持一致。如果两次编译的宏定义、include 路径、覆盖率开关不同,VCS 会判定为不兼容而触发全量重编。因此工程上应把编译选项固化到配置文件中,增量编译只针对源码变化。
缓存策略则更进一步:把 csrc 目录、simv、覆盖率数据库按“编译配置指纹”做键值缓存。指纹可以由选项字符串加 RTL 版本号计算得到。当同一配置再次编译时,直接复用缓存,跳过编译。常见做法包括:
- 本地缓存:在
~/.vcs_cache下按指纹存放 csrc,编译前先查缓存。 - 共享缓存:在 NFS 或制品库中存放常用配置的编译产物,团队共享。
- 清理策略:按时间或容量淘汰旧缓存,避免磁盘膨胀。
--clean 选项,强制清理 csrc 后重新编译,用于排除缓存污染导致的诡异问题。五、整体流程与关键判断
把上述四部分串起来,VCS 自动化流程可以概括为:配置层提供选项,编译脚本组装并执行 vcs,仿真脚本参数化运行 simv,增量与缓存层负责加速。下图展示了这一流程中选项、脚本、产物与缓存之间的数据流关系,以及增量编译的命中判断路径。
图中左侧是配置层与编译脚本,中间是编译产物与增量判断,右侧是仿真脚本与缓存策略,底部是仿真运行后的日志、覆盖率与复现路径。实际工程中,这套结构可以进一步接入 CI:每次提交触发编译脚本,回归平台按 test 列表调用仿真脚本,覆盖率与日志统一归档。判断一次自动化是否成功,关键看三点:编译命令是否可复现、失败用例是否可单独复现、增量编译是否真正命中缓存。只要这三点成立,VCS 流程就从“手工操作”变成了“可回归、可缓存、可追溯”的工程能力。