OpenLava EDA 实战(三):VCS 仿真最佳实践

OpenLava EDA 实战(三):VCS 仿真最佳实践

摘要:VCS 是数字 IC 验证最常用的仿真器,怎么在 OpenLava 上高效地跑 VCS 仿真大有讲究。本文从实际工作流出发,讲解 VCS 编译与仿真的分离提交、并行编译加速、波形管理(fsdb + 后处理)、UVM 仿真技巧、回归测试组织、以及常见问题排查。每个场景都有可直接套用的脚本模板。

前两篇讲了 EDA 集群搭建和 License 调度。这一篇聚焦 VCS 仿真——数字验证工程师每天都要用的场景。


零、读前必看

0.1 适合谁

  • ✅ 数字验证工程师,每天和 VCS 打交道
  • ✅ 想把本地跑的 VCS 仿真搬到集群上
  • ✅ CAD 工程师,想优化 VCS 仿真流程

0.2 你需要准备

项目 要求
OpenLava 集群 已搭好,VCS 工具已接入
VCS 授权 VCS_MX License
测试用例 至少一个可跑的 UVM testbench
Verdi 可选,用于调试波形

一、VCS 仿真工作流

1.1 典型流程

编译 (VCS compile)
   │
   ├── 输入:RTL + Testbench 文件列表
   ├── 输出:simv 可执行文件
   └── 一次性(代码改了才需要重编)
          │
          ▼
仿真 (simv runtime)
   │
   ├── 输入:simv + 测试用例参数
   ├── 输出:仿真结果 + 波形
   └── 可以并发跑 N 个测试用例

关键认知编译一次,仿真多次。编译是串行的(一次 VCS 编译产出一个 simv),仿真可以并行(多个 testcase 共用同一个 simv)。

1.2 为什么要放集群上跑

本地跑 集群跑
一次跑 1-2 个仿真 一次跑几百个 regression
占着自己电脑 不影响本地工作
机器配置有限 64 核 256G 大内存机器随便用
出结果慢 并行跑,回归时间从几天变几小时
手动管理 自动调度 + 结果汇总

二、编译作业提交

2.1 基础编译脚本

#!/bin/bash
#BSUB -J vcs_compile
#BSUB -q vcs_normal
#BSUB -n 8
#BSUB -R "rusage[mem=8192, vcs_license=1] span[hosts=1]"
#BSUB -o compile_%J.log
#BSUB -e compile_%J.err
#BSUB -W 2:00

# ===== 环境加载 =====
source /etc/profile.d/modules.sh
module load synopsys/scl/2019.06
module load synopsys/vcs/O-2018.09-SP2
module load synopsys/verdi/Verdi_O-2018.09-SP2

# ===== 切换到提交目录 =====
cd $LS_SUBCWD

echo "========================================"
echo " VCS Compilation Started"
echo " Job ID:   $LSB_JOBID"
echo " Host:     $(hostname)"
echo " Cores:    $LSB_DJOB_NUMPROC"
echo " Start:    $(date)"
echo "========================================"

# ===== 编译 =====
vcs -full64 \
    -f filelist.f \
    -top tb_top \
    -debug_access+all \
    -o simv \
    -j$LSB_DJOB_NUMPROC \
    -ntb_opts uvm-1.2 \
    -sverilog \
    +v2k \
    +incdir+./rtl \
    +incdir+./tb \
    -LDFLAGS -Wl,--no-as-needed

EXIT_CODE=$?

echo "========================================"
echo " VCS Compilation Finished"
echo " Exit Code: $EXIT_CODE"
echo " End Time:  $(date)"
echo "========================================"

exit $EXIT_CODE

2.2 关键参数说明

VCS 参数 作用 集群相关
-j N 并行编译,N 是线程数 ✅ 对应 -n N 申请的核数
-full64 64 位模式 ✅ 大设计必须开
-debug_access+all 生成调试信息(Verdi/断点) 会慢一点,调试用
-o simv 输出文件名
-f filelist.f 文件列表
-ntb_opts uvm-1.2 使用 UVM 1.2
-sverilog 支持 SystemVerilog
-LDFLAGS -Wl,--no-as-needed 链接参数(某些系统需要)

2.3 核数和内存评估

设计规模 建议核数 建议内存 编译时间参考
小(< 10 万门) 4 4G < 5 分钟
中(10-100 万门) 8 16G 10-30 分钟
大(100 万+ 门) 16 32-64G 30 分钟 - 2 小时
超大(千万门级) 32 128G+ 2 小时+
**经验**:VCS 编译不是核越多越快。核数超过一定数量后加速比下降。建议从 8 核开始,根据实际情况调整。

2.4 增量编译

代码小改动时,用增量编译快很多:

# 加 -Mupdate 参数
vcs -full64 -Mupdate -f filelist.f ...

原理:保留编译中间文件,只重编改动的文件。

注意

  • 第一次编译必须全量(生成中间文件)
  • 改动大时增量可能更慢(权衡使用)
  • CI/回归建议全量编译,保证干净

三、仿真作业提交

3.1 基础仿真脚本

#!/bin/bash
#BSUB -J vcs_sim_${TESTCASE}
#BSUB -q vcs_normal
#BSUB -n 1
#BSUB -R "rusage[mem=8192, vcs_license=1] span[hosts=1]"
#BSUB -o sim_%J_${TESTCASE}.log
#BSUB -e sim_%J_${TESTCASE}.err
#BSUB -W 8:00

# ===== 环境 =====
source /etc/profile.d/modules.sh
module load synopsys/scl/2019.06
module load synopsys/vcs/O-2018.09-SP2
module load synopsys/verdi/Verdi_O-2018.09-SP2

cd $LS_SUBCWD

echo "========================================"
echo " TestCase: $TESTCASE"
echo " Job ID:   $LSB_JOBID"
echo " Host:     $(hostname)"
echo " Start:    $(date)"
echo "========================================"

# ===== 仿真 =====
./simv \
    +UVM_TESTNAME=$TESTCASE \
    +UVM_VERBOSITY=LOW \
    +UVM_MAX_QUIT_COUNT=100 \
    +UVM_TIMEOUT=1000000000ns \
    -l sim_${TESTCASE}.log \
    -ucli -do "run -all; quit"

EXIT_CODE=$?

echo "========================================"
echo " TestCase: $TESTCASE"
echo " Exit Code: $EXIT_CODE"
echo " End Time:  $(date)"
echo "========================================"

# ===== 检查仿真结果 =====
if grep -q "UVM_ERROR :    0" sim_${TESTCASE}.log && \
   grep -q "UVM_FATAL :    0" sim_${TESTCASE}.log; then
    echo "RESULT: PASS"
else
    echo "RESULT: FAIL"
fi

exit $EXIT_CODE

提交:

bsub -env "TESTCASE=test_smoke" < vcs_sim.sh

3.2 仿真内存估算

仿真类型 内存估算
小 testcase,无波形 1-2G
中等 testcase 4-8G
大 testcase + 全波形 16-64G
门级仿真 32-128G
**全波形(fsdb)对内存影响很大**。建议:
- 调试时才开全波形
- 回归测试只记关键信号或用断言
- 失败的用例自动保存波形,通过的不用存

3.3 并行跑多个用例

作业数组最方便:

#!/bin/bash
#BSUB -J "vcs_regress[1-100]%20"
#BSUB -q vcs_normal
#BSUB -n 1
#BSUB -R "rusage[mem=4096, vcs_license=1] span[hosts=1]"
#BSUB -o regress/%J_%I.log
#BSUB -e regress/%J_%I.err
#BSUB -W 4:00

source /etc/profile.d/modules.sh
module load synopsys/scl/2019.06
module load synopsys/vcs/O-2018.09-SP2

cd $LS_SUBCWD

# 测试用例列表文件(一行一个用例名)
TESTCASE=$(sed -n "${LSB_JOBINDEX}p" testlist.txt)

echo "Running: $TESTCASE (index $LSB_JOBINDEX)"

./simv +UVM_TESTNAME=$TESTCASE +UVM_VERBOSITY=LOW \
      -l regress/${TESTCASE}.log

# 结果判断
if grep -q "TEST PASSED" regress/${TESTCASE}.log; then
    echo "RESULT: PASS - $TESTCASE"
    echo "$TESTCASE PASS" >> regress/results.txt
else
    echo "RESULT: FAIL - $TESTCASE"
    echo "$TESTCASE FAIL" >> regress/results.txt
fi

提交:

mkdir -p regress
bsub < vcs_regress.sh
`%20` 表示最多同时跑 20 个,避免把 License 或集群资源全占满。

四、波形管理

4.1 波形格式选择

格式 工具 文件大小 速度
fsdb Verdi 小(压缩)
vpd DVE
vcd 通用 大(纯文本)

Verdi + fsdb 是行业标配

4.2 生成 fsdb 波形

在 Testbench 里加:

// tb_top.sv
initial begin
    if ($test$plusargs("DUMP_FSDB")) begin
        $fsdbDumpfile("wave.fsdb");
        $fsdbDumpvars(0, tb_top);
        $fsdbDumpMDA();
        $fsdbDumpSVA();
        $fsdbDumpflush();
    end
end

仿真时加参数:

./simv +DUMP_FSDB +UVM_TESTNAME=$TESTCASE

4.3 波形存哪里

存储位置 优点 缺点
本地 /tmp 快,不占共享存储 节点宕机就丢了
共享存储 持久化,随时查看 占 NFS 带宽,慢
本地生成 + 跑完归档 兼顾性能和持久化 需要脚本管理

推荐方案:仿真时写本地 /tmp,跑完把 fsdb 移到共享存储:

# ===== 仿真 =====
# 先在本地 /tmp 跑(速度快)
WORK_DIR=/tmp/vcs_${LSB_JOBID}
mkdir -p $WORK_DIR
cd $WORK_DIR

# 链接 simv(不要复制,占空间)
ln -s $LS_SUBCWD/simv .
ln -s $LS_SUBCWD/simv.daidir .

# 跑仿真(波形输出到本地)
./simv +DUMP_FSDB +UVM_TESTNAME=$TESTCASE \
      -l sim.log

# ===== 归档 =====
# 跑完把结果和波形移回共享存储
mkdir -p $LS_SUBCWD/results/$TESTCASE
cp sim.log $LS_SUBCWD/results/$TESTCASE/
if [ -f wave.fsdb ]; then
    mv wave.fsdb $LS_SUBCWD/results/$TESTCASE/
fi

# 清理临时目录
rm -rf $WORK_DIR

4.4 失败自动保存波形

回归测试时,默认不开波形(省资源),失败时自动重跑并抓波形:

#!/bin/bash
# 先跑一次(无波形,快)
./simv +UVM_TESTNAME=$TESTCASE +UVM_VERBOSITY=LOW -l sim.log

if [ $? -ne 0 ] || grep -q "UVM_ERROR.*[1-9]" sim.log; then
    echo "Test failed, re-running with waveform..."
    # 失败了,带波形重跑一次
    ./simv +DUMP_FSDB +UVM_TESTNAME=$TESTCASE \
          +UVM_VERBOSITY=DEBUG \
          -l sim_debug.log
fi
**策略**:
- PASS 的用例:不存波形(省空间)
- FAIL 的用例:存完整波形(方便调试)

>

这样 100 个用例即使 95% pass,也只存 5 个波形文件。

五、UVM 仿真技巧

5.1 Verbosity 控制

# 回归测试:只看错误(最快)
+UVM_VERBOSITY=LOW

# 调试用:详细日志
+UVM_VERBOSITY=DEBUG

# 单个组件设 verbosity
+uvm_set_verbosity=uvm_test_top.env.agt,DEBUG,all

5.2 超时保护

# 防止仿真卡死跑无限久
+UVM_TIMEOUT=1000000000ns,YES

# 同时用 bsub -W 设置上限(双重保护)
#BSUB -W 4:00

5.3 随机种子

# 用作业 ID 做随机种子(每个作业不同,可复现)
./simv +UVM_TESTNAME=$TESTCASE +ntb_random_seed=$LSB_JOBID
用 `$LSB_JOBID` 当种子,失败时可以用同样的种子复现。

5.4 覆盖率收集

# 编译时加覆盖率选项
vcs -full64 -cm line+cond+fsm+tgl+path ...

# 仿真时收集
./simv -cm line+cond+fsm+tgl -cm_dir cov.vdb +UVM_TESTNAME=$TESTCASE

# 合并覆盖率
urg -dir cov.vdb -format both -report cov_report
覆盖率收集会让仿真慢 2-3 倍,一般只在覆盖率回归时打开。

六、常见问题排查

6.1 编译失败:License checkout failed

Error-[VCS_COM] VCS compilation error
  License checkout for VCS_MX failed: Cannot find license file.

排查

# 1. 环境变量对不对
echo $SNPSLMD_LICENSE_FILE

# 2. License 服务器通不通
lmstat -c 27000@lic_server01

# 3. 还有没有可用 License
lmstat -f VCS_MX -c 27000@lic_server01

常见原因

  • LM_LICENSE_FILE / SNPSLMD_LICENSE_FILE 没设对
  • License 服务器网络不通
  • License 用完了(OpenLava 应该调度好,但采集有延迟)

6.2 仿真 OOM(内存不足)

Out of memory. Killed by OS.

排查

# 1. 看节点内存
lsload -l compute01

# 2. 看作业用了多少内存
bjobs -l <jobid> | grep -i mem

解决

  • 加内存(-R "rusage[mem=16384]" 改大)
  • 减少波形 dump 深度
  • 分阶段跑仿真
  • 换大内存节点

6.3 仿真速度慢

可能原因

原因 解决
开了全波形 只 dump 关键模块 / 用 $fsdbDumpvars 减少层次
开了覆盖率 非覆盖率回归关掉
debug_access+all 回归用 -debug_access+pp 或更少
存储慢(NFS) 波形写本地 SSD
设计太大 分模块仿真 / 硬件加速

6.4 仿真卡死不动

# 1. 看是不是真在跑
bpeek -f <jobid>
# 日志长时间没更新,可能卡死了

# 2. 进节点看 CPU 占用
ssh <host> top -p <pid>
# CPU 0% 说明卡死了

# 3. 看调用栈(如果有 gdb)
gdb -p <pid> -ex "bt" -ex "quit"

常见原因

  • 死循环 / 死锁
  • 等待外部输入
  • 文件系统 hang 住(NFS 问题)

设置超时+UVM_TIMEOUT + bsub -W 双重保护。

6.5 为什么 +UVM_VERBOSITY 不生效

检查 VCS 编译时有没有带 -ntb_opts uvm 参数,UVM 库链进去了没。

七、提交规范建议

7.1 团队推荐脚本模板

#!/bin/bash
# ==============================================
# VCS Simulation Script
# Usage: bsub -env "TESTCASE=xxx" < vcs_sim.sh
# ==============================================
#BSUB -J sim_${TESTCASE}
#BSUB -q vcs_normal
#BSUB -n 1
#BSUB -R "rusage[mem=4096, vcs_license=1] span[hosts=1]"
#BSUB -W 4:00
#BSUB -o sim_%J_${TESTCASE}.log
#BSUB -e sim_%J_${TESTCASE}.err
#BSUB -u ${USER}@example.com
#BSUB -N

# 环境
source /etc/profile.d/modules.sh
module load synopsys/vcs/O-2018.09-SP2

# 检查参数
if [ -z "$TESTCASE" ]; then
    echo "ERROR: TESTCASE not set!"
    echo "Usage: bsub -env \"TESTCASE=xxx\" < $0"
    exit 1
fi

# 工作目录
cd $LS_SUBCWD || exit 1

# 日志头
echo "[$(date)] TestCase: $TESTCASE"
echo "[$(date)] JobID:    $LSB_JOBID"
echo "[$(date)] Host:     $(hostname)"
echo "[$(date)] Seed:     ${SEED:-random}"

# 运行
./simv +UVM_TESTNAME=$TESTCASE \
      +ntb_random_seed=${SEED:-$LSB_JOBID} \
      ${DUMP_FSDB:++DUMP_FSDB} \
      +UVM_VERBOSITY=${VERBOSITY:-LOW} \
      +UVM_TIMEOUT=500000000ns \
      -l sim_${TESTCASE}_${LSB_JOBID}.log

EXIT=$?

# 结果分析
echo "[$(date)] Exit code: $EXIT"
grep -E "TEST (PASSED|FAILED)" sim_${TESTCASE}_${LSB_JOBID}.log || true
grep -E "UVM_ERROR|UVM_FATAL" sim_${TESTCASE}_${LSB_JOBID}.log | tail -5 || true

exit $EXIT

7.2 常用提交方式

# 提交单个用例
bsub -env "TESTCASE=test_smoke" < vcs_sim.sh

# 提交带波形
bsub -env "TESTCASE=test_smoke DUMP_FSDB=1" < vcs_sim.sh

# 提交 verbosity=DEBUG
bsub -env "TESTCASE=test_smoke VERBOSITY=DEBUG" < vcs_sim.sh

# 指定随机种子
bsub -env "TESTCASE=test_smoke SEED=12345" < vcs_sim.sh

八、性能优化 Checklist

  • [ ] 波形写到本地 SSD,跑完归档
  • [ ] 回归测试不开全波形,失败自动重跑抓波形
  • [ ] 用 -debug_access+pp 代替 +all(回归够用了)
  • [ ] 编译用 -j N 并行,N 取 4-8(再高收益递减)
  • [ ] 增量编译(-Mupdate)用于迭代调试
  • [ ] 覆盖率只在需要时打开
  • [ ] 设置合理的 wall time 上限(防卡死)
  • [ ] 大仿真用大内存节点
  • [ ] 用作业数组批量跑回归
  • [ ] simv 放本地 / 共享存储读取快的地方

九、写在最后

VCS 仿真放到 OpenLava 集群上跑,核心收益是并行化——以前一个人跑 100 个回归要跑好几天,现在 20 个并发几小时跑完。

关键经验就三点:

  1. 编译一次,仿真多次(别每个用例都重新编)
  2. 波形管理策略(别什么都存 fsdb,省空间又快)
  3. 作业数组批量跑(100 个用例一键提交)

下一篇我们讲大规模回归调度:怎么组织上百上千个用例的 full regression、依赖管理(编完再跑、跑完汇总)、结果自动分析和邮件报告。


你的 VCS 回归一次跑多久?有什么提速秘诀? 评论区聊聊。

OpenLava #EDA #VCS #Verdi #仿真 #UVM #数字验证 #运维

发表回复

后才能评论