当前位置: 首页 > news >正文

ModelSim入门实战(四): 参数化测试、自动比对与报告生成

本文衔接第三篇:我们已经能够通过批处理一键仿真并熟练使用波形调试。
但当需要测试多组不同序列、或验证设计是否在各种输入下都正确时,每次都修改 testbench 再重新编译,依然很繁琐。

本篇将彻底解决这个问题,实现:

  • ✅ 通过命令行参数传递测试数据(无需修改代码)
  • ✅ 自动比对输出与预期,实时打印[PASS]/[FAIL]
  • ✅ 仿真结束后自动生成测试报告(文本文件)
  • ✅ 编写功能完善的.bat批处理文件,支持灵活的参数传入和 GUI/Console 模式切换

我们仍以8位序列检测器为例,但方法适用于任何设计。


1 参数化测试:多种参数传递方式

1.0 Testbench 端:固定使用$value$plusargs

无论采用哪种命令行或脚本传递方式,testbench 中必须包含如下代码,用于接收外部参数:

reg [7:0] target_seq = 8'b10110001; // 默认序列 integer test_num = 0; // 默认测试编号 initial begin // 从仿真命令中读取 +TARGET=xxxxxxxx 和 +TEST_NUM=数字 if ($value$plusargs("TARGET=%b", target_seq)) $display("[INFO] Parameter overridden: TARGET = %b", target_seq); if ($value$plusargs("TEST_NUM=%d", test_num)) $display("[INFO] Parameter overridden: TEST_NUM = %0d", test_num); end

以上代码只需写入一次,后续所有参数传递方法均无需修改 testbench。


参数传递的上层实现分为两大类:

  • 第一类:通过run.do脚本接收参数(在 ModelSim 命令行执行do run.do <参数1> <参数2>
  • 第二类:通过 Windows 批处理文件传递参数(双击.bat文件,或带参数运行)

1.1 第一类:在run.do脚本中接收参数(适合手动命令行操作)

方式1:直接写入固定参数

编辑run.do,在vsim命令后直接写死参数:

vsim -voptargs="+acc" work.seq_detect_8bit_tb +TARGET=10110001 +TEST_NUM=1

不够灵活(每次需改脚本)。

方式2:使用脚本内变量
set TARGET 10110001 set TEST_NUM 1 vsim -voptargs="+acc" work.seq_detect_8bit_tb +TARGET=$TARGET +TEST_NUM=$TEST_NUM

修改时只需改set行,比方式1略灵活。

方式3:让run.do接收位置参数(最灵活,推荐用于命令行交互)

修改run.do如下:

set TARGET $1 set TEST_NUM $2 vsim -voptargs="+acc" work.seq_detect_8bit_tb +TARGET=$TARGET +TEST_NUM=$TEST_NUM

然后在 ModelSim 的 Transcript 窗口中执行(无需编辑文件):

do run.do 10110001 1

优点:一条命令即可更换测试条件,无需修改任何文件。

1.2 第二类:通过 Windows 批处理传递参数(适合双击运行 / 集成 CI/CD)

方式4:批处理中直接拼接vsim命令(完全独立,不依赖 DO 文件)
@echo off set TARGET=%1 set TEST_NUM=%2 if "%TARGET%"=="" set TARGET=10110001 if "%TEST_NUM%"=="" set TEST_NUM=1 vsim -c -do "vlib work; vlog seq_detect_8bit.v; vlog seq_detect_8bit_tb.v; vsim -voptargs=\"+acc\" work.seq_detect_8bit_tb +TARGET=%TARGET% +TEST_NUM=%TEST_NUM%; run 2000 ns; quit -f"
方式5:批处理调用现有的run.do脚本(复用已有命令)
@echo off set TARGET=%1 set TEST_NUM=%2 if "%TARGET%"=="" set TARGET=10110001 if "%TEST_NUM%"=="" set TEST_NUM=1 vsim -c -do "run.do %TARGET% %TEST_NUM%; quit -f"

使用说明

  • 直接双击run_auto.bat:使用默认参数TARGET=10110001, TEST_NUM=1
  • 在命令行中运行:run_auto.bat 11001010 3→ 使用自定义参数
方式6:通过环境变量传递(适合 CI/CD 自动化)

编写run_env.do脚本,用$env(...)读取环境变量:

set TARGET $env(MY_TARGET) set TEST_NUM $env(MY_TEST_NUM) vsim -voptargs="+acc" work.seq_detect_8bit_tb +TARGET=$TARGET +TEST_NUM=$TEST_NUM

批处理中设置环境变量后调用:

set MY_TARGET=10110001 set MY_TEST_NUM=1 vsim -c -do run_env.do

小结:六种参数传递方式对比

方式编号方法描述灵活性推荐场景
1DO脚本直接写死固定参数
2DO脚本内变量★★同一脚本内快速改值
3DO脚本接收位置参数★★★★★手动交互,频繁换参数
4批处理直接拼接命令★★★★★双击运行,参数可传入
5批处理调用DO脚本★★★★★复用已有DO脚本
6环境变量传递★★★★CI/CD 自动化

推荐组合

  • 个人日常调试→ 方式3(do run.do 10110001 1
  • 交付给他人双击使用→ 方式4或5(.bat带参运行)
  • 自动化回归测试→ 方式6(环境变量)

2 自动比对输出并打印结果

在 testbench 中,我们不再只是“观察波形”,而是让仿真器自动检查dout是否正确。

2.1 镜像状态机实现自动比对

在 testbench 中编写一个与 DUT 完全相同的状态逻辑(或只用组合逻辑计算预期输出),每个时钟沿比对:

// 期望状态机(与 DUT 逻辑一致) reg [3:0] exp_state; always @(posedge clk or negedge rst_n) begin if (!rst_n) exp_state <= 4'd0; else begin case (exp_state) 4'd0: exp_state <= din ? 4'd1 : 4'd0; 4'd1: exp_state <= din ? 4'd1 : 4'd2; 4'd2: exp_state <= din ? 4'd3 : 4'd0; 4'd3: exp_state <= din ? 4'd4 : 4'd2; 4'd4: exp_state <= din ? 4'd1 : 4'd5; 4'd5: exp_state <= din ? 4'd3 : 4'd6; 4'd6: exp_state <= din ? 4'd1 : 4'd7; 4'd7: exp_state <= din ? 4'd8 : 4'd0; 4'd8: exp_state <= din ? 4'd1 : 4'd2; default: exp_state <= 4'd0; endcase end end wire exp_dout = (exp_state == 4'd8); // 每个时钟沿比对 always @(posedge clk) begin if (rst_n) begin if (dout !== exp_dout) begin $display("[FAIL] at time %0t: dout=%b, exp_dout=%b", $time, dout, exp_dout); fail_cnt = fail_cnt + 1; end else begin $display("[PASS] at time %0t: dout=%b", $time, dout); pass_cnt = pass_cnt + 1; end end end

这种方法无需手动编写期望值列表,完全自动比对。


3 生成测试报告

使用$fopen$fdisplay$fclose系统函数将仿真结果写入文本文件。

3.1 报告生成代码(集成在 testbench 中)

integer report_file; integer pass_cnt, fail_cnt; initial begin // 打开报告文件 report_file = $fopen("test_report.txt", "w"); if (report_file) begin $fdisplay(report_file, "========================================"); $fdisplay(report_file, "Sequence Detector Test Report"); $fdisplay(report_file, "Target Sequence: %b", target_seq); $fdisplay(report_file, "Test Number: %0d", test_num); $fdisplay(report_file, "Start Time: %0t", $time); $fdisplay(report_file, "========================================"); end // ... 激励序列(复位、发送测试数据等)... // 仿真结束时写入统计并关闭文件 #100; if (report_file) begin $fdisplay(report_file, "----------------------------------------"); $fdisplay(report_file, "Total Comparisons : %0d", pass_cnt + fail_cnt); $fdisplay(report_file, "Passed : %0d", pass_cnt); $fdisplay(report_file, "Failed : %0d", fail_cnt); $fdisplay(report_file, "========================================"); $fclose(report_file); end $display("Report saved to test_report.txt"); $finish; end

4 完善的run_auto.bat一键仿真脚本

以下批处理脚本支持:

  • 灵活的参数输入(纯数字、空格分隔键值对、-nogui开关)
  • 默认GUI 模式(有波形窗口),添加-nogui切换为控制台模式(无界面,自动退出)
  • 动态生成临时 DO 脚本,无需外部文件依赖
  • 自动显示测试报告
@echo off setlocal enabledelayedexpansion REM ======================================================================== REM Sequence Detector Auto Simulation Script (Robust Version) REM Supports: REM 1. run_auto.bat REM 2. run_auto.bat 10101010 5 REM 3. run_auto.bat target=10101010 testnum=5 (Must use quotes: "target=...") REM 4. run_auto.bat target 10101010 testnum 5 (No quotes needed) REM 5. run_auto.bat -nogui (Console mode, no waveform window) REM ======================================================================== REM ========== 配置区 ========== set "MODELSIM_PATH=E:\modelsim\win64" set "WORK_DIR=F:\Learn\blog\modelsim_4\modelSim_8bit_seq_fsm" set "REPORT_FILE=test_report.txt" REM ============================ REM ========== 默认值 ========== set TARGET=10110001 set TEST_NUM=1 set VSIM_MODE= set GOT_TARGET=0 set GOT_TESTNUM=0 REM ========== 遍历所有参数 ========== :loop if "%~1"=="" goto :end_loop set "arg=%~1" REM 1. 检查是否为 -nogui if /i "%arg%"=="-nogui" ( set VSIM_MODE=-c shift goto :loop ) REM 2. 检查是否为键值对模式 (Key Value),例如: target 10110001 if /i "%arg%"=="target" ( set TARGET=%~2 set GOT_TARGET=1 shift & shift goto :loop ) if /i "%arg%"=="testnum" ( set TEST_NUM=%~2 set GOT_TESTNUM=1 shift & shift goto :loop ) if /i "%arg%"=="test_num" ( set TEST_NUM=%~2 set GOT_TESTNUM=1 shift & shift goto :loop ) REM 3. 检查是否包含等号 (仅当用户用了引号时生效,如 "target=123") echo "%arg%" | find "=" >nul if not errorlevel 1 ( for /f "tokens=1,* delims==" %%a in ("%arg%") do ( set "key=%%a" set "value=%%b" if /i "!key!"=="target" ( set TARGET=!value! set GOT_TARGET=1 ) if /i "!key!"=="testnum" ( set TEST_NUM=!value! set GOT_TESTNUM=1 ) if /i "!key!"=="test_num" ( set TEST_NUM=!value! set GOT_TESTNUM=1 ) ) shift goto :loop ) REM 4. 纯数字处理(位置参数) echo %arg% | findstr /r "^[0-9][0-9]*$" >nul if not errorlevel 1 ( if %GOT_TARGET% equ 0 ( set TARGET=%arg% set GOT_TARGET=1 ) else if %GOT_TESTNUM% equ 0 ( set TEST_NUM=%arg% set GOT_TESTNUM=1 ) shift goto :loop ) REM 5. 无法识别的参数 echo [WARNING] Unknown parameter: %arg% shift goto :loop :end_loop echo ======================================== echo Sequence Detector Auto Simulation echo TARGET = %TARGET% echo TEST_NUM = %TEST_NUM% if "%VSIM_MODE%"=="-c" (echo MODE = Console) else (echo MODE = GUI) echo ======================================== echo. cd /d "%WORK_DIR%" 2>nul if errorlevel 1 ( echo [ERROR] Directory "%WORK_DIR%" not found! pause exit /b 1 ) if exist "%REPORT_FILE%" del "%REPORT_FILE%" set "TEMP_DO=temp_sim.do" ( echo quit -sim echo vlib work echo vlog seq_detect_8bit.v echo vlog seq_detect_8bit_tb.v echo vsim -voptargs="+acc" work.seq_detect_8bit_tb +TARGET=%TARGET% +TEST_NUM=%TEST_NUM% REM 仅在 GUI 模式下添加波形命令(Console 模式不打开波形窗口) if not "%VSIM_MODE%"=="-c" ( echo view wave echo add wave -position end sim:/seq_detect_8bit_tb/* echo wave zoom full ) echo run 2000 ns ) > "%TEMP_DO%" echo Starting ModelSim simulation... if "%VSIM_MODE%"=="-c" ( "%MODELSIM_PATH%\vsim" -c -do "do %TEMP_DO%; quit -f" > sim.log 2>&1 ) else ( "%MODELSIM_PATH%\vsim" -do "do %TEMP_DO%" ) del "%TEMP_DO%" 2>nul echo. if exist "%REPORT_FILE%" ( echo -------- Test Report ---------- type "%REPORT_FILE%" echo ------------------------------- ) else ( echo Simulation finished. ) echo. pause exit /b 0

📖 使用说明

支持极其灵活的输入格式,详见脚本头注释。示例:

run_auto.bat # 默认参数,GUI模式 run_auto.bat "target=11110000" "testnum=3" # 等号模式

5 完整的 Testbench(整合参数读取、自动比对、报告生成)

`timescale 1ns/1ps module seq_detect_8bit_tb(); // ---------------------------- 信号定义 ---------------------------- reg clk; reg rst_n; reg din; wire dout; // ---------------------------- 可配置参数(支持命令行覆盖)------------------------ reg [7:0] target_seq = 8'b10110001; // 默认目标序列 integer test_num = 0; // 测试用例编号 // ---------------------------- DUT 实例化 ---------------------------- seq_detect_8bit dut ( .clk (clk), .rst_n (rst_n), .din (din), .dout (dout) ); // ---------------------------- 时钟生成 (20ns 周期) ---------------------------- initial clk = 0; always #10 clk = ~clk; // ---------------------------- 任务:发送一个字节(MSB 先)------------------------ task send_byte(input [7:0] data); integer i; begin for (i = 7; i >= 0; i = i - 1) begin @(posedge clk) din = data[i]; #1; // 避免与时钟边沿竞争 end end endtask // ---------------------------- 任务:发送一位 ---------------------------- task send_bit(input value); begin @(posedge clk) din = value; #1; end endtask // ---------------------------- 镜像状态机(与 DUT 完全一致)-------------------- reg [3:0] exp_state; always @(posedge clk or negedge rst_n) begin if (!rst_n) exp_state <= 4'd0; else begin case (exp_state) 4'd0: exp_state <= din ? 4'd1 : 4'd0; 4'd1: exp_state <= din ? 4'd1 : 4'd2; 4'd2: exp_state <= din ? 4'd3 : 4'd0; 4'd3: exp_state <= din ? 4'd4 : 4'd2; 4'd4: exp_state <= din ? 4'd1 : 4'd5; 4'd5: exp_state <= din ? 4'd3 : 4'd6; 4'd6: exp_state <= din ? 4'd1 : 4'd7; 4'd7: exp_state <= din ? 4'd8 : 4'd0; 4'd8: exp_state <= din ? 4'd1 : 4'd2; default: exp_state <= 4'd0; endcase end end wire exp_dout = (exp_state == 4'd8); // ---------------------------- 自动比对与统计 ---------------------------- integer pass_cnt, fail_cnt, report_file; initial begin pass_cnt = 0; fail_cnt = 0; // 读取命令行参数(覆盖默认值) if ($value$plusargs("TARGET=%b", target_seq)) $display("[INFO] Parameter overridden: TARGET = %b", target_seq); if ($value$plusargs("TEST_NUM=%d", test_num)) $display("[INFO] Parameter overridden: TEST_NUM = %0d", test_num); // 打开报告文件 report_file = $fopen("test_report.txt", "w"); if (report_file) begin $fdisplay(report_file, "========================================"); $fdisplay(report_file, " Sequence Detector Test Report"); $fdisplay(report_file, " Target Sequence : %b", target_seq); $fdisplay(report_file, " Test Number : %0d", test_num); $fdisplay(report_file, " Start Time : %0t", $time); $fdisplay(report_file, "========================================"); end else begin $display("[ERROR] Failed to open report file."); end end // 每个时钟沿比对(复位期间不检查) always @(posedge clk) begin if (rst_n) begin if (dout !== exp_dout) begin $display("[FAIL] time=%0t: dout=%b, exp_dout=%b", $time, dout, exp_dout); if (report_file) $fdisplay(report_file, "[FAIL] time=%0t", $time); fail_cnt = fail_cnt + 1; end else begin $display("[PASS] time=%0t: dout=%b", $time, dout); pass_cnt = pass_cnt + 1; end end end // ---------------------------- 激励产生 ---------------------------- initial begin $display("=================================================="); $display("8-bit Sequence Detector Test (Target: %b)", target_seq); $display("=================================================="); rst_n = 0; din = 0; #30; rst_n = 1; #20; // Test1: 正确序列 $display("\n[Test1] Send correct sequence: %b", target_seq); send_byte(target_seq); #40; // Test2: 错误序列(逐位取反) $display("[Test2] Send wrong sequence: %b", ~target_seq); send_byte(~target_seq); #40; // Test3: 连续两个正确序列 $display("[Test3] Two correct sequences back-to-back"); send_byte(target_seq); send_byte(target_seq); #40; // Test4: 重叠检测 - 跨字节出现目标序列 $display("[Test4] Overlap test: target across byte boundary"); send_bit(1); send_bit(1); send_bit(0); send_byte(target_seq); #40; // Test5: 后缀重叠测试 $display("[Test5] Sequence with potential internal overlap"); send_byte(target_seq); send_bit(1); #40; $display("\n=== Simulation finished ==="); #100; // 让剩余比对完成 // ---------------------------- 写入统计并关闭文件------------------------ if (report_file) begin $fdisplay(report_file, "----------------------------------------"); $fdisplay(report_file, " Total Comparisons : %0d", pass_cnt + fail_cnt); $fdisplay(report_file, " Passed : %0d", pass_cnt); $fdisplay(report_file, " Failed : %0d", fail_cnt); $fdisplay(report_file, "========================================"); $fclose(report_file); end $display("\nReport saved to test_report.txt"); $display("PASS = %0d, FAIL = %0d", pass_cnt, fail_cnt); $stop; end endmodule

6 操作步骤

  1. 准备文件
    将上述seq_detect_8bit_tb.v(含参数读取、自动比对、报告生成)与设计文件seq_detect_8bit.v放在同一目录。

  2. 配置run_auto.bat
    修改MODELSIM_PATHWORK_DIR为实际路径。

  3. 运行仿真

    • 双击run_auto.bat使用默认参数,启动 GUI 模式。
    • 或命令行执行run_auto.bat "target=11110000" "testnum=3" -nogui进行控制台测试。
  4. 查看结果

    • 控制台实时输出[PASS]/[FAIL]信息。

    • 仿真结束后,打开test_report.txt查看详细报告。

    • 若为 GUI 模式,可结合波形进一步分析错误点。


7 常见问题与解决方案

问题原因解决
vsim不是内部命令未设置 ModelSim 路径在 bat 中使用完整路径,或添加到系统 PATH
报告文件未生成工作目录权限或$fopen失败检查文件夹是否可写,使用绝对路径
比对结果始终失败镜像状态机逻辑与 DUT 不一致仔细检查状态转移表,确保完全相同
$value$plusargs不生效参数拼写错误或未正确传递检查命令行中的+TARGET=格式,注意大小写
波形不显示状态名未使用虚拟类型参考第三篇第2.8节添加virtual type

8 总结

本篇实现了仿真流程的最终自动化

  • ✅ 参数传递:无需修改代码即可改变测试条件(提供6种灵活方式)
  • ✅ 自动比对:实时检查输出正确性,避免人工检查波形
  • ✅ 生成报告:方便回归测试和结果归档
  • ✅ 完善的批处理脚本:支持灵活传参、GUI/Console 模式切换

📢关于文章:原创实战分享,转载需注明出处。如果觉得有用,请点赞收藏支持~
🔔 点击关注,第一时间收到后续教程推送。

📚参考文档:ModelSim SE User’s Manual

http://www.jsqmd.com/news/810630/

相关文章:

  • 【Qlib框架】因子定义层及整体框架(alpha因子库+数据预处理+模型预测+IC分析+回测backtestdaily+风险分析+交易质量分析)
  • Bilibili视频下载终极指南:如何三步搞定大会员4K视频本地化
  • ChatGPT开发者实战指南:从API集成到应用部署的完整资源导航
  • JT808协议实战:用JTXQ模拟终端复现车道偏离报警全流程(附日志分析)
  • 2026哪个机构主任护师通过率高?过来人实测对比,避坑必看 - 医考机构品牌测评专家
  • 从开发者反馈看Taotoken标准OpenAI协议兼容性的实际表现
  • 088、Python网络服务开发:HTTP服务器
  • 2026年靠谱线下红娘机构推荐:合规婚恋服务机构选型与场景适配深度分析 - 产业观察网
  • 【题解】CF1603D Artistic Partition
  • 从零到一:51单片机蓝牙遥控车实战指南(附避坑要点)
  • 前端超能力:让浏览器听你指挥
  • 2026年5月盘点:3家主任护师备考机构通过率排名,第1名断层领先 - 医考机构品牌测评专家
  • SOS-dp
  • FPGA新手避坑指南:Quartus II 13.1下NCO IP核的完整配置与授权实战
  • 2026江苏主任护师考试培训机构实力排名:3家培训机构全方位评测 - 医考机构品牌测评专家
  • 每日算法快闪赛
  • 百达翡丽鹦鹉螺出手前必看:广州五家门店实测谁更实在 - 奢侈品回收测评
  • 使用Helm Chart在Kubernetes部署高可用authentik身份认证中心
  • 089、Python玩转硬件:用pyserial搞定串口通信那些坑
  • 对比直接使用官方 API 观察到的 Taotoken 路由容错效果
  • 2026年丽水黄金回收哪家靠谱?五店深度测评与打分 - 生活测评君
  • GPU时代的有用浪费:从效率至上到算力换一切的工程范式转变
  • Java 21 面试常见问题汇总
  • 在持续集成环境中集成 Taotoken CLI 实现自动化配置与密钥轮换
  • 从0到1,构建你的第一个AI Agent:核心原理与实战指南
  • Cursor Pro破解终极指南:开源工具cursor-free-vip实现AI编程助手永久免费使用
  • 储能项目一操作记录
  • Zed编辑器深色光标主题:提升编码体验的视觉工程实践
  • 5.参考论文的文献引用没有标数字,要不要标数字?
  • 茉莉花插件:如何用Jasminum解决中文文献管理的三大痛点