OpenLava EDA 实战(四):大规模回归测试调度与自动化
OpenLava EDA 实战(四):大规模回归测试调度与自动化
摘要:Regression(回归测试)是验证团队最高频的大规模计算场景——几百上千个 testcase 一次性提交,几个小时后看结果。本文讲解如何用 OpenLava 高效组织回归测试:编译与仿真的依赖管理、作业数组批量提交、结果自动汇总、邮件报告、失败用例自动重跑、以及回归平台的整体架构设计。读完你能搭出一套一键提交、自动汇总的回归系统。
前三篇讲了集群搭建、License 调度、VCS 仿真实践。这一篇讲回归测试——EDA 集群最典型的大规模场景。
零、读前必看
0.1 适合谁
- ✅ 验证团队负责人 / CAD 工程师
- ✅ 手动跑回归太累,想自动化
- ✅ 已经在用 OpenLava,想做回归平台
0.2 你将学到
| 主题 | 重点 |
|---|---|
| 回归工作流 | 编译 → 仿真 → 汇总 → 报告 |
| 作业依赖 | 用 -w 实现 DAG 流水线 |
| 作业数组 | 几百个用例一键提交 |
| 结果汇总 | 自动统计 pass/fail,生成报告 |
| 失败重跑 | 失败用例自动带波形重跑 |
| 邮件通知 | 跑完自动发结果邮件 |
| 平台化 | 回归平台架构设计思路 |
一、回归测试整体流程
1.1 典型流程
┌────────────┐
│ 1. 编译 │ vcs compile → 生成 simv
│ (1 个作业) │
└──────┬─────┘
│ 编译完成后触发
▼
┌────────────────────────────┐
│ 2. 批量仿真 │ N 个 testcase 并行跑
│ (作业数组,100-1000 个作业) │
└──────────────┬──────────────┘
│ 所有仿真完成后触发
▼
┌────────────┐
│ 3. 汇总 │ 统计 pass/fail,生成报告
│ (1 个作业) │
└──────┬─────┘
│
▼
┌────────────┐
│ 4. 通知 │ 邮件 / 钉钉 / 企业微信
│ (1 个作业) │
└────────────┘
1.2 关键设计点
| 设计点 | 方案 |
|---|---|
| 编译 1 次,仿真 N 次 | 所有 testcase 共用同一个 simv |
| 并行度控制 | 作业数组 %N 限流,避免打爆 License |
| 依赖管理 | 用 -w done(jobid) 串起各阶段 |
| 结果汇总 | 所有仿真完成后统一汇总 |
| 失败自动重跑 | 检测失败,自动带波形重跑一次 |
| 可观测 | 实时看进度、邮件通知 |
二、目录结构
regress/
├── run_regress.sh # 一键提交脚本
├── config/
│ ├── testlist.txt # 测试用例列表
│ └── regress_config.sh # 配置参数
├── scripts/
│ ├── compile.sh # 编译脚本
│ ├── simulate.sh # 单用例仿真脚本
│ ├── collect_results.sh # 结果汇总脚本
│ ├── rerun_failures.sh # 失败重跑脚本
│ └── send_report.sh # 发邮件脚本
├── filelist.f # 设计文件列表
└── runs/ # 每次回归的结果
├── 20240115_1000/
│ ├── compile/
│ ├── sim/
│ │ ├── test_smoke/
│ │ ├── test_reset/
│ │ └── ...
│ ├── report.html
│ └── summary.txt
└── 20240116_1430/
每次回归一个独立目录,按时间戳命名,方便回溯。
三、一键提交脚本
3.1 主脚本:run_regress.sh
#!/bin/bash
# ==============================================
# OpenLava VCS Regression Runner
# Usage: ./run_regress.sh [testlist]
# ==============================================
set -e
# ===== 配置 =====
REGRESS_NAME=${1:-"daily_regression"}
TESTLIST=${2:-"config/testlist.txt"}
VCS_VERSION="O-2018.09-SP2"
QUEUE="vcs_normal"
MAX_PARALLEL=30 # 最多同时跑 30 个
SIM_CORES=1
SIM_MEM=4096 # MB
COMPILE_CORES=8
COMPILE_MEM=16384 # MB
# ===== 目录 =====
RUN_ID=$(date +%Y%m%d_%H%M%S)
RUN_DIR="$(pwd)/runs/${RUN_ID}"
mkdir -p "$RUN_DIR"/{compile,sim,report}
echo "=========================================="
echo " VCS Regression Runner"
echo " Regression Name: $REGRESS_NAME"
echo " Run ID: $RUN_ID"
echo " Test List: $TESTLIST"
echo " Max Parallel: $MAX_PARALLEL"
echo " Run Dir: $RUN_DIR"
echo "=========================================="
# ===== Step 1: 提交编译作业 =====
echo ""
echo "→ Step 1: Submitting compile job..."
COMPILE_JOB=$(bsub \
-J "${REGRESS_NAME}_compile" \
-q "$QUEUE" \
-n "$COMPILE_CORES" \
-R "rusage[mem=${COMPILE_MEM}, vcs_license=1] span[hosts=1]" \
-W 2:00 \
-o "${RUN_DIR}/compile/compile.log" \
-e "${RUN_DIR}/compile/compile.err" \
-env "RUN_DIR=$RUN_DIR VCS_VERSION=$VCS_VERSION" \
< scripts/compile.sh \
| awk -F'[<>]' '{print $2}')
echo " Compile Job ID: $COMPILE_JOB"
# ===== Step 2: 提交仿真作业数组 =====
echo ""
echo "→ Step 2: Submitting simulation job array..."
NUM_TESTS=$(wc -l < "$TESTLIST")
echo " Number of tests: $NUM_TESTS"
SIM_JOBS=$(bsub \
-J "${REGRESS_NAME}_sim[1-${NUM_TESTS}]%${MAX_PARALLEL}" \
-q "$QUEUE" \
-n "$SIM_CORES" \
-R "rusage[mem=${SIM_MEM}, vcs_license=1] span[hosts=1]" \
-W 4:00 \
-o "${RUN_DIR}/sim/%I.log" \
-e "${RUN_DIR}/sim/%I.err" \
-w "done(${COMPILE_JOB})" \
-env "RUN_DIR=$RUN_DIR TESTLIST=$TESTLIST VCS_VERSION=$VCS_VERSION REGRESS_NAME=$REGRESS_NAME" \
< scripts/simulate.sh \
| awk -F'[<>]' '{print $2}')
echo " Simulation Array Job ID: $SIM_JOBS"
# ===== Step 3: 提交汇总作业 =====
echo ""
echo "→ Step 3: Submitting collect job..."
COLLECT_JOB=$(bsub \
-J "${REGRESS_NAME}_collect" \
-q "$QUEUE" \
-n 1 \
-R "rusage[mem=2048] span[hosts=1]" \
-W 30 \
-o "${RUN_DIR}/report/collect.log" \
-e "${RUN_DIR}/report/collect.err" \
-w "done(${SIM_JOBS})" \
-env "RUN_DIR=$RUN_DIR TESTLIST=$TESTLIST REGRESS_NAME=$REGRESS_NAME" \
< scripts/collect_results.sh \
| awk -F'[<>]' '{print $2}')
echo " Collect Job ID: $COLLECT_JOB"
# ===== Step 4: 提交通知作业 =====
echo ""
echo "→ Step 4: Submitting notify job..."
NOTIFY_JOB=$(bsub \
-J "${REGRESS_NAME}_notify" \
-q "$QUEUE" \
-n 1 \
-R "rusage[mem=512] span[hosts=1]" \
-W 10 \
-o "${RUN_DIR}/report/notify.log" \
-w "done(${COLLECT_JOB})" \
-env "RUN_DIR=$RUN_DIR REGRESS_NAME=$REGRESS_NAME" \
< scripts/send_report.sh \
| awk -F'[<>]' '{print $2}')
echo " Notify Job ID: $NOTIFY_JOB"
# ===== 输出 =====
echo ""
echo "=========================================="
echo " Regression submitted successfully!"
echo " Run ID: $RUN_ID"
echo ""
echo " Job Chain:"
echo " Compile: $COMPILE_JOB"
echo " Sim Array: $SIM_JOBS ($NUM_TESTS tests, max $MAX_PARALLEL parallel)"
echo " Collect: $COLLECT_JOB"
echo " Notify: $NOTIFY_JOB"
echo ""
echo " Monitor with:"
echo " bjobs -J \"${REGRESS_NAME}_*\""
echo " cd $RUN_DIR"
echo "=========================================="
# 保存 run 信息
cat > "${RUN_DIR}/run_info.txt" << EOF
REGRESS_NAME=$REGRESS_NAME
RUN_ID=$RUN_ID
TESTLIST=$TESTLIST
NUM_TESTS=$NUM_TESTS
MAX_PARALLEL=$MAX_PARALLEL
COMPILE_JOB=$COMPILE_JOB
SIM_JOBS=$SIM_JOBS
COLLECT_JOB=$COLLECT_JOB
NOTIFY_JOB=$NOTIFY_JOB
SUBMIT_TIME=$(date)
SUBMITTED_BY=$(whoami)
EOF
3.2 配置文件
# config/regress_config.sh
VCS_VERSION="O-2018.09-SP2"
VERDI_VERSION="Verdi_O-2018.09-SP2"
QUEUE="vcs_normal"
MAX_PARALLEL=30
SIM_MEM=4096
COMPILE_MEM=16384
COMPILE_CORES=8
NOTIFY_EMAIL="verif-team@example.com"
LICENSE_FEATURE="VCS_MX"
3.3 测试用例列表
# config/testlist.txt
test_smoke
test_reset
test_intr_basic
test_intr_level
test_intr_edge
test_dma_single
test_dma_burst
test_dma_scatter_gather
test_pcie_cfg_rw
test_pcie_mem_rw
test_pcie_msi
test_error_correction
test_phy_init
test_phy_bist
# ... 更多用例
四、各阶段脚本
4.1 编译脚本
#!/bin/bash
# scripts/compile.sh
source /etc/profile.d/modules.sh
module load synopsys/scl/2019.06
module load synopsys/vcs/$VCS_VERSION
module load synopsys/verdi/$VERDI_VERSION
echo "========================================"
echo " VCS Compilation"
echo " Run ID: $RUN_ID"
echo " Job ID: $LSB_JOBID"
echo " Host: $(hostname)"
echo " Start: $(date)"
echo "========================================"
cd "$RUN_DIR/compile"
# 复制必要文件(假设项目根目录在上两级)
PROJECT_ROOT=$(dirname "$(dirname "$(dirname "$RUN_DIR")")")
cp "$PROJECT_ROOT/filelist.f" .
# 或者直接用绝对路径
# VCS 编译
vcs -full64 \
-f filelist.f \
-top tb_top \
-debug_access+pp \
-o simv \
-j$LSB_DJOB_NUMPROC \
-ntb_opts uvm-1.2 \
-sverilog \
-cm line+cond+fsm \
-l compile.log
EXIT=$?
echo "========================================"
echo " Compile exit code: $EXIT"
echo " End: $(date)"
echo "========================================"
# 编译失败,直接退出(后续作业也会因为 exit 而不跑)
if [ $EXIT -ne 0 ]; then
echo "COMPILE FAILED"
exit 1
fi
echo "COMPILE SUCCESS"
exit 0
4.2 仿真脚本
#!/bin/bash
# scripts/simulate.sh
source /etc/profile.d/modules.sh
module load synopsys/scl/2019.06
module load synopsys/vcs/$VCS_VERSION
echo "========================================"
echo " Simulation - Index: $LSB_JOBINDEX"
echo " Run ID: $RUN_ID"
echo " Job ID: $LSB_JOBID"
echo " Host: $(hostname)"
echo " Start: $(date)"
echo "========================================"
# 读取 testcase 名
TESTCASE=$(sed -n "${LSB_JOBINDEX}p" "$TESTLIST")
echo "TestCase: $TESTCASE"
if [ -z "$TESTCASE" ]; then
echo "ERROR: Test case not found for index $LSB_JOBINDEX"
exit 1
fi
# 结果目录
RESULT_DIR="$RUN_DIR/sim/$TESTCASE"
mkdir -p "$RESULT_DIR"
# 在本地 /tmp 跑(性能好)
WORK_DIR="/tmp/${REGRESS_NAME}_${LSB_JOBID}_${LSB_JOBINDEX}"
mkdir -p "$WORK_DIR"
cd "$WORK_DIR"
# 链接 simv
ln -s "$RUN_DIR/compile/simv" .
ln -s "$RUN_DIR/compile/simv.daidir" . 2>/dev/null || true
# 跑仿真
./simv \
+UVM_TESTNAME=$TESTCASE \
+UVM_VERBOSITY=LOW \
+UVM_TIMEOUT=500000000ns \
+ntb_random_seed=$LSB_JOBID \
-cm line+cond+fsm \
-cm_name ${TESTCASE} \
-cm_dir "$RUN_DIR/compile/cov.vdb" \
-l sim.log
EXIT=$?
# 判断结果
RESULT="UNKNOWN"
if grep -q "UVM_ERROR : 0" sim.log && \
grep -q "UVM_FATAL : 0" sim.log && \
[ $EXIT -eq 0 ]; then
RESULT="PASS"
else
RESULT="FAIL"
fi
echo ""
echo "Result: $RESULT"
echo "End: $(date)"
# 归档结果
cp sim.log "$RESULT_DIR/"
echo "$RESULT" > "$RESULT_DIR/result.txt"
echo "$EXIT" > "$RESULT_DIR/exit_code.txt"
# 如果失败,带波形重跑一次(在后台不阻塞)
if [ "$RESULT" = "FAIL" ]; then
echo "FAIL - will re-run with waveform in separate job"
# 提交重跑作业(依赖当前作业完成)
bsub -J "${REGRESS_NAME}_rerun_${TESTCASE}" \
-q vcs_normal \
-n 1 \
-R "rusage[mem=8192, vcs_license=1] span[hosts=1]" \
-W 4:00 \
-o "${RESULT_DIR}/rerun.log" \
-e "${RESULT_DIR}/rerun.err" \
-env "RUN_DIR=$RUN_DIR TESTCASE=$TESTCASE VCS_VERSION=$VCS_VERSION RESULT_DIR=$RESULT_DIR REGRESS_NAME=$REGRESS_NAME" \
< scripts/rerun_waveform.sh
fi
# 清理临时目录
rm -rf "$WORK_DIR"
exit 0
4.3 重跑脚本(失败带波形)
#!/bin/bash
# scripts/rerun_waveform.sh
source /etc/profile.d/modules.sh
module load synopsys/scl/2019.06
module load synopsys/vcs/$VCS_VERSION
module load synopsys/verdi/$VERDI_VERSION
echo "Re-running $TESTCASE with waveform..."
echo "Job ID: $LSB_JOBID, Host: $(hostname)"
WORK_DIR="/tmp/rerun_${LSB_JOBID}"
mkdir -p "$WORK_DIR"
cd "$WORK_DIR"
ln -s "$RUN_DIR/compile/simv" .
ln -s "$RUN_DIR/compile/simv.daidir" . 2>/dev/null || true
./simv \
+DUMP_FSDB \
+UVM_TESTNAME=$TESTCASE \
+UVM_VERBOSITY=DEBUG \
+UVM_TIMEOUT=500000000ns \
+ntb_random_seed=$(cat "$RESULT_DIR/exit_code.txt" | head -1 || echo 1) \
-l sim_debug.log
EXIT=$?
# 归档波形和调试日志
cp sim_debug.log "$RESULT_DIR/"
if [ -f wave.fsdb ]; then
mv wave.fsdb "$RESULT_DIR/"
echo "Waveform saved: $RESULT_DIR/wave.fsdb"
fi
rm -rf "$WORK_DIR"
echo "Rerun done, exit=$EXIT"
4.4 结果汇总脚本
#!/bin/bash
# scripts/collect_results.sh
echo "========================================"
echo " Collecting Results"
echo " Run ID: $RUN_ID"
echo " Start: $(date)"
echo "========================================"
SIM_DIR="$RUN_DIR/sim"
REPORT_DIR="$RUN_DIR/report"
mkdir -p "$REPORT_DIR"
PASS_COUNT=0
FAIL_COUNT=0
SKIP_COUNT=0
TOTAL=0
PASS_TESTS=""
FAIL_TESTS=""
# 遍历所有用例
while IFS= read -r testcase; do
[ -z "$testcase" ] && continue
TOTAL=$((TOTAL + 1))
RESULT_FILE="$SIM_DIR/$testcase/result.txt"
if [ -f "$RESULT_FILE" ]; then
RESULT=$(cat "$RESULT_FILE")
if [ "$RESULT" = "PASS" ]; then
PASS_COUNT=$((PASS_COUNT + 1))
PASS_TESTS="$PASS_TESTS $testcase"
else
FAIL_COUNT=$((FAIL_COUNT + 1))
FAIL_TESTS="$FAIL_TESTS $testcase"
fi
else
SKIP_COUNT=$((SKIP_COUNT + 1))
echo "SKIP (no result): $testcase"
fi
done < "$TESTLIST"
# 计算通过率
if [ $TOTAL -gt 0 ]; then
PASS_RATE=$((PASS_COUNT * 100 / TOTAL))
else
PASS_RATE=0
fi
# ===== 生成文本报告 =====
cat > "$REPORT_DIR/summary.txt" << EOF
========================================
VCS Regression Report
Regression: $REGRESS_NAME
Run ID: $RUN_ID
Generated: $(date)
========================================
Total: $TOTAL
Passed: $PASS_COUNT
Failed: $FAIL_COUNT
Skipped: $SKIP_COUNT
Pass Rate: ${PASS_RATE}%
========================================
Failed Tests:
========================================
$(for t in $FAIL_TESTS; do echo " ✗ $t"; done)
========================================
Passed Tests:
========================================
$(for t in $PASS_TESTS; do echo " ✓ $t"; done)
========================================
EOF
cat "$REPORT_DIR/summary.txt"
# ===== 生成 HTML 报告 =====
generate_html_report() {
cat > "$REPORT_DIR/report.html" << HTMLEOF
<!DOCTYPE html>
<html>
<head>
<title>Regression Report - $REGRESS_NAME</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.pass { color: green; font-weight: bold; }
.fail { color: red; font-weight: bold; }
table { border-collapse: collapse; width: 100%; }
th, td { border: 1px solid #ddd; padding: 8px; text-align: left; }
th { background-color: #4CAF50; color: white; }
tr:nth-child(even) { background-color: #f2f2f2; }
.summary-box { padding: 15px; background: #e8f5e9; border-radius: 5px; margin-bottom: 20px; }
</style>
</head>
<body>
<h1>Regression Report: $REGRESS_NAME</h1>
<p>Run ID: $RUN_ID | Generated: $(date)</p>
<div class="summary-box">
<h2>Summary</h2>
<p><strong>Total:</strong> $TOTAL</p>
<p class="pass"><strong>Passed:</strong> $PASS_COUNT</p>
<p class="fail"><strong>Failed:</strong> $FAIL_COUNT</p>
<p><strong>Pass Rate:</strong> ${PASS_RATE}%</p>
</div>
<h2>Test Results</h2>
<table>
<tr><th>Test Case</th><th>Result</th><th>Log</th></tr>
HTMLEOF
while IFS= read -r testcase; do
[ -z "$testcase" ] && continue
RESULT_FILE="$SIM_DIR/$testcase/result.txt"
if [ -f "$RESULT_FILE" ]; then
RESULT=$(cat "$RESULT_FILE")
if [ "$RESULT" = "PASS" ]; then
CLASS="pass"
else
CLASS="fail"
fi
echo "<tr><td>$testcase</td><td class=\"$CLASS\">$RESULT</td><td><a href=\"../sim/$testcase/sim.log\">log</a></td></tr>"
else
echo "<tr><td>$testcase</td><td>SKIP</td><td>-</td></tr>"
fi
done < "$TESTLIST"
cat >> "$REPORT_DIR/report.html" << HTMLEOF
</table>
</body>
</html>
HTMLEOF
}
generate_html_report
echo ""
echo "Report generated: $REPORT_DIR/report.html"
echo "Summary: $REPORT_DIR/summary.txt"
exit 0
4.5 邮件通知脚本
#!/bin/bash
# scripts/send_report.sh
echo "Sending regression report email..."
REPORT_DIR="$RUN_DIR/report"
SUMMARY=$(cat "$REPORT_DIR/summary.txt")
# 提取 pass rate
PASS_RATE=$(grep "Pass Rate" "$SUMMARY" | grep -oP '\d+%')
PASS_COUNT=$(grep "Passed" "$SUMMARY" | grep -oP '\d+')
FAIL_COUNT=$(grep "Failed" "$SUMMARY" | grep -oP '\d+')
if [ "$FAIL_COUNT" -eq 0 ]; then
SUBJECT="[PASS] Regression: $REGRESS_NAME - ${PASS_RATE} pass rate"
else
SUBJECT="[FAIL] Regression: $REGRESS_NAME - $FAIL_COUNT failed (${PASS_RATE} pass)"
fi
# 发邮件(需要配置 mailx 或 sendmail)
echo "$SUMMARY" | mailx \
-s "$SUBJECT" \
-a "$REPORT_DIR/report.html" \
verif-team@example.com
# 或者用 Python 发(更可靠)
python3 << PYEOF
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
msg = MIMEMultipart()
msg['Subject'] = "$SUBJECT"
msg['From'] = 'eda-cluster@example.com'
msg['To'] = 'verif-team@example.com'
with open('$REPORT_DIR/summary.txt') as f:
body = f.read()
msg.attach(MIMEText(body, 'plain'))
with open('$REPORT_DIR/report.html') as f:
msg.attach(MIMEText(f.read(), 'html'))
with smtplib.SMTP('smtp.example.com', 25) as s:
s.send_message(msg)
print("Email sent successfully")
PYEOF
echo "Notification sent"
五、实时监控回归进度
5.1 看进度命令
# 看整个回归的所有作业
bjobs -J "regress_name_*"
# 看仿真数组进度
bjobs -J "regress_name_sim*"
# 统计各状态数量
bjobs -J "regress_name_sim*" 2>/dev/null | awk 'NR>1 {print $3}' | sort | uniq -c
# 5 DONE
# 15 RUN
# 80 PEND
5.2 进度监控脚本
#!/bin/bash
# scripts/monitor_progress.sh
# Usage: ./monitor_progress.sh <run_dir>
RUN_DIR=$1
if [ -z "$RUN_DIR" ]; then
echo "Usage: $0 <run_dir>"
exit 1
fi
SIM_DIR="$RUN_DIR/sim"
TESTLIST="$RUN_DIR/../config/testlist.txt" # 可能需要调整
if [ ! -f "$RUN_DIR/run_info.txt" ]; then
echo "Invalid run dir: $RUN_DIR"
exit 1
fi
source "$RUN_DIR/run_info.txt"
while true; do
clear
echo "========================================"
echo " Regression Monitor: $REGRESS_NAME"
echo " Run ID: $RUN_ID"
echo " Time: $(date)"
echo "========================================"
# 统计结果
TOTAL=$NUM_TESTS
DONE=0
PASS=0
FAIL=0
for d in "$SIM_DIR"/*/; do
[ -d "$d" ] || continue
if [ -f "$d/result.txt" ]; then
DONE=$((DONE + 1))
if [ "$(cat "$d/result.txt")" = "PASS" ]; then
PASS=$((PASS + 1))
else
FAIL=$((FAIL + 1))
fi
fi
done
RUNNING=$(( $(bjobs -J "${REGRESS_NAME}_sim*" 2>/dev/null | grep -c RUN || echo 0) ))
PENDING=$(( $(bjobs -J "${REGRESS_NAME}_sim*" 2>/dev/null | grep -c PEND || echo 0) ))
echo ""
echo " Total: $TOTAL"
echo " Done: $DONE"
echo " Running: $RUNNING"
echo " Pending: $PENDING"
echo ""
echo " Pass: $PASS ✅"
echo " Fail: $FAIL ❌"
if [ $DONE -gt 0 ]; then
echo " Pass Rate: $((PASS * 100 / DONE))%"
fi
# 进度条
echo ""
PROGRESS=$((DONE * 50 / TOTAL))
printf " ["
for ((i=0; i<PROGRESS; i++)); do printf "="; done
printf ">%*s" $((50-PROGRESS)) "" | tr ' ' '.'
printf "] %d%%\n" $((DONE * 100 / TOTAL))
echo ""
echo " Press Ctrl+C to exit"
# 如果全部完成了
if [ $DONE -ge $TOTAL ]; then
echo ""
echo " 🎉 Regression complete!"
break
fi
sleep 30
done
六、进阶:回归平台化
6.1 平台架构
Web UI (Python Flask)
│
├── 提交回归(选择 testlist、配置参数)
├── 查看进度(实时刷新)
├── 查看历史(过去 30 天回归记录)
├── 对比回归(两次回归结果对比)
└── 结果分析(失败趋势、用例波动)
│
▼
OpenLava (作业调度)
│
▼
共享存储(结果归档)
6.2 核心功能
| 功能 | 说明 |
|---|---|
| 一键提交 | Web 页面选配置,一键提交回归 |
| 实时进度 | 动态更新的进度条和 pass/fail 数 |
| 结果对比 | 对比两次回归的 pass rate 变化 |
| 失败趋势 | 哪些用例经常失败 |
| 覆盖率趋势 | 覆盖率随时间变化曲线 |
| 资源监控 | License / CPU / 内存使用情况 |
| 用户管理 | 谁提交了什么,配额管理 |
6.3 数据持久化
把回归结果存到数据库(SQLite/MySQL):
-- 回归表
CREATE TABLE regressions (
id INTEGER PRIMARY KEY,
name VARCHAR(255),
run_id VARCHAR(50) UNIQUE,
submit_time DATETIME,
submitted_by VARCHAR(100),
total_tests INT,
pass_count INT,
fail_count INT,
pass_rate FLOAT,
status VARCHAR(20), -- running, completed, failed
result_dir VARCHAR(512)
);
-- 用例结果表
CREATE TABLE test_results (
id INTEGER PRIMARY KEY,
regression_id INT,
test_name VARCHAR(255),
result VARCHAR(10), -- PASS, FAIL, SKIP
runtime FLOAT,
log_path VARCHAR(512),
FOREIGN KEY (regression_id) REFERENCES regressions(id)
);
七、常见坑与最佳实践
7.1 常见坑
| 坑 | 现象 | 解决 |
|---|---|---|
| simv 路径不对 | 仿真作业找不到 simv | 用绝对路径,编译完检查文件存在 |
| License 不够 | 大批作业启动失败 | 作业数组限流 + License 资源调度 |
| 共享存储慢 | 仿真跑的慢,波形写很久 | 波形写本地 /tmp,跑完归档 |
| 节点环境不一致 | 某些节点跑失败 | 用 Module 统一环境,节点上线前全量测试 |
| 作业数组 ID 混淆 | 用名字查找不准 | 保存 run_info.txt,存所有 job id |
| 编译失败还继续跑 | 仿真作业全失败 | 用 done() 依赖,编译失败仿真不触发 |
7.2 最佳实践
- ✅ 一键提交:一个脚本搞定所有事
- ✅ 每次回归独立目录:按时间戳命名,互不干扰
- ✅ 编译和仿真分离:编一次跑 N 次
- ✅ 失败自动重跑 + 抓波形:省得手动调
- ✅ 自动汇总 + 邮件:不用手动统计
- ✅ 限流:别一下提交 1000 个全跑起来
- ✅ 实时监控:不用反复敲 bjobs
- ✅ 历史记录:存数据库,方便趋势分析
八、写在最后
回归测试是 EDA 集群最核心的应用场景。把这套流程搭起来,验证团队的效率能上一个大台阶——以前手动提交、手动统计、手动看结果,现在一键提交、自动报告、邮件通知。
核心思路就是"自动化一切能自动化的":
- 提交自动化(脚本一键搞定)
- 调度自动化(OpenLava 管资源)
- 结果自动化(自动汇总 + 发邮件)
- 调试自动化(失败自动抓波形)
做到这几点,回归就不再是痛苦的事了。
OpenLava EDA 实战系列到此结束。 四篇文章覆盖了从集群搭建、License 调度、VCS 仿真到大规模回归的完整链路。
你们的回归一次跑多少个用例?有什么独门技巧? 评论区聊聊。
OpenLava #EDA #VCS #回归测试 #Regression #UVM #自动化 #运维
声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。







