软件在环仿真SIL:模型代码生成前必须掌握的验证方法
2026/8/31 6:22:35 网站建设 项目流程

做控制算法的人,很多都卡在一个问题上:模型里跑得好好的,生成C代码烧到硬件上却不对。排查半天,发现问题不在算法本身,而是模型到代码之间缺了一层验证。这次我们要梳理的就是这一层验证:软件在环仿真,也就是常说的SIL(Software-in-the-Loop)。

SIL的价值一句话可以概括:在不用真实硬件的前提下,把控制器模型生成C代码并编译成可执行程序,再和被控对象模型联合仿真,用于确认“代码”和“模型”在逻辑上是否等价。它处于MIL(模型在环)和PIL(处理器在环)之间,属于自动代码生成流程里成本最低、最容易提前发现问题的一环。本文将用一个典型控制模型的SIL测试流程,演示从模型准备、仿真模式切换、代码生成到结果对比的全过程,并整理一批常见的坑和排查方法。

如果你正在做Simulink建模、VCU控制策略、车辆运动学模型,或者开始接触Simulink代码生成、MIL测试、Carsim联合仿真,这篇文章可以直接收藏。理解SIL之后,再去看PIL和HIL,你会非常清楚它们各自在解决什么问题。

1. 核心能力速览

SIL并不是一个独立工具,而是MATLAB/Simulink代码生成链路中的一种仿真模式。下面先把关键信息列出来。

能力项说明
项目定位模型生成C代码后,在主机上执行代码并与模型仿真对比
核心目的验证模型与生成代码行为是否一致,提前发现代码生成配置问题
依赖工具箱Simulink Coder必装;使用覆盖率分析时还需要Simulink Coverage;使用测试管理器批量回归时推荐Simulink Test
是否支持脚本支持,MATLAB命令行可完成模式切换、代码生成、批量仿真
是否支持批量任务支持,结合Simulink Test或sim函数循环可以批量跑用例
是否支持接口API支持,仿真模式可通过SimulationMode参数或SimulationInput对象控制
是否需要目标硬件不需要,SIL在主机上运行
是否需要C编译器必须,Windows常用MinGW-w64或MSVC,Linux下常用gcc
典型适用场景代码生成配置变更后回归、硬件未到位时的算法验证、代码覆盖率分析
与PIL区别SIL跑在主机CPU上,PIL跑在目标处理器上;PIL能暴露目标平台相关问题

有一点要提前说明:SIL仿真速度不一定比模型仿真快,因为代码要编译,仿真过程中还要在模型和生成代码之间交换数据。资源占用和耗时取决于模型规模、采样周期、编译优化选项和电脑CPU,具体数字一定要在你自己机器上实测。

2. 适用场景与使用边界

SIL适合谁?最直接的一类人是做MBD开发、嵌入式代码生成的工程师。你在Simulink里搭好了控制算法,接下来准备用Embedded Coder生成C代码,在写入目标芯片之前,先在电脑上验证一下“生成的代码是否忠实于模型”,SIL就是这个环节的标准做法。

具体能解决这些问题:

  • 模型在Normal仿真下正确,但代码生成后逻辑不一致,SIL可以发现多数这类问题。
  • 更换了编译器、代码生成选项、或者从GRT目标切换到ERT目标后,行为和之前是否一致。
  • 硬件还没到位,但需要提前跑控制算法的回归测试。
  • 需要统计代码覆盖率,又暂时接不了真实硬件。

SIL不适合什么场景?

  • 不适合验证时序相关的实时性问题,因为SIL跑在主机上,和真实目标处理器执行时序不同。
  • 不适合发现芯片底层问题,比如位宽、外设驱动、中断优先级。这类问题需要PIL或HIL。
  • 不适合做整机环境验证。被控对象模型和被控对象实物之间有差距,SIL只能验证控制器代码逻辑,验证不了传感器、执行器接口。

还有一条边界必须提醒:使用MATLAB/Simulink进行模型开发,需要确认你的许可证和工具箱覆盖了所用功能。SIL本身通过Simulink Coder实现,生成代码后如果要部署到商业产品或嵌入式设备,要检查代码生成许可证的适用范围,不要在未授权环境下做商用发布。

3. 环境准备与前置条件

SIL不需要高性能显卡,也不依赖GPU,但需要一个能跑得动Simulink和编译器的CPU,以及足够内存。模型规模不大时,普通办公电脑也能跑。

检查清单如下:

  • MATLAB/Simulink版本。建议使用R2019b之后的版本,SIL配置入口在较新版本中更直观;版本越老,细节差异越大。
  • 工具箱。Simulink Coder必装,Embedded Coder在需要精细配置代码风格、目标平台时使用。如果要用代码覆盖率分析,需要Simulink Coverage。
  • C/C++编译器。Windows下常见的两个选择是MinGW-w64和Microsoft Visual C++(MSVC)。安装后要在MATLAB里运行mex -setup完成工具链识别。
  • 模型命名规范。模型文件名不要用中文、不要带空格,不要以数字开头。生成代码阶段的文件名和宏定义会直接使用模型名,特殊字符容易引发编译失败。
  • 磁盘空间。生成代码和编译中间文件会占用一定空间,常规模型预留1GB以上基本够用。
  • 路径管理。建议把模型、生成代码、测试脚本分目录管理,避免模型文件放在MATLAB安装目录或系统盘临时目录。

编译器配置是SIL最容易卡住的环境问题之一。打开MATLAB后,先在命令行窗口确认编译器状态。

mex -setup

如果系统里没有可用编译器,MATLAB会给出提示。此时先安装MinGW-w64(MATLAB支持版本以官方文档为准),也可以安装Visual Studio Build Tools,然后在mex -setup窗口里选择对应编译器。

4. 模型准备与基线仿真

在做SIL之前,先做一次完整的Normal仿真,把结果保存下来作为“模型基线”。这一步很关键:没有基线的SIL对比就是空中楼阁。

这里以一个典型的一阶低通滤波/控制对象模型为例,说明流程。假设模型名称为sil_demo.slx,模型内部结构是:输入阶跃信号,经过一个PID控制器,输出到一阶惯性环节,再用Scope或To Workspace模块保存结果。

第一步,打开模型。

open_system('sil_demo')

第二步,在Normal模式下运行仿真,并把仿真输出保存到工作区。

simOut = sim('sil_demo', 'StopTime', '10');

第三步,在模型里添加To Workspace模块,或者在调用sim时设置保存参数,把关键信号记录下来。注意区分信号保存方式和输出日志方式。如果模型里已经有Scope,可以直接用Simulink.sdi.view打开Simulation Data Inspector,查看正常仿真曲线。

基线仿真跑通之后,标记一下关键指标,比如超调量、稳定时间、稳态误差。后面的SIL结果要和这组数据做对比。

5. SIL仿真模式配置

SIL模式本身不需要写额外代码,Simulink会在你切换仿真模式后自动完成代码生成、编译和运行。但在切换之前,要先把模型配置调整好。

5.1 检查代码生成配置

打开模型的“设置”(模型窗口Ctrl+E),进入“代码生成”页签。需要关注两个地方:

  • 系统目标文件。如果只做SIL验证,通常用ert.tlc(Embedded Coder)或grt.tlc(Generic Real-Time)。使用Embedded Coder时,默认目标通常就是ert.tlc。如果是纯Simulink Coder环境,常见的默认目标是grt.tlc
  • 工具链。确保这里选择的编译器和mex -setup中配置的一致。

配置项的位置在不同版本里略有差异,但关键词是“系统目标文件”“工具链”“代码生成”。如果找不到具体菜单,直接在模型设置右上角搜索“工具链”。

5.2 使用GUI切换SIL模式

在模型编辑器窗口顶部,找到“仿真模式”下拉框。这个下拉框通常在工具栏的仿真按钮旁边,默认显示“Normal”。下拉菜单里可以看到:

  • Normal
  • Accelerator
  • Rapid Accelerator
  • Software-in-the-Loop (SIL)
  • Processor-in-the-Loop (PIL)

选择“Software-in-the-Loop (SIL)”之后,点击运行“Run”。Simulink会弹出对话框提示“该操作将生成并编译代码”,确认后等待构建完成。首次编译通常需要几十秒到几分钟,取决于模型大小。

编译完成后,仿真会自动执行。此时不要着急看结果,先在命令行窗口观察输出,确认是否有“Build process completed successfully”之类的信息。

5.3 使用命令行切换SIL模式

GUI适合人机交互,命令行模式更适合批量验证和自动化回归。代码如下:

simIn = Simulink.SimulationInput('sil_demo'); simIn = simIn.setModelParameter('SimulationMode', 'Software-in-the-loop'); simIn = simIn.setModelParameter('StopTime', '10'); simOutSIL = sim(simIn);

执行这段代码后,MATLAB会先构建代码,再运行SIL仿真。第一次运行时间较长,第二次如果模型没有变更,部分编译缓存会被复用。

如果只想先生成代码,不立即运行仿真,可以使用:

slbuild('sil_demo')

slbuild之后,在模型目录下可以看到生成的C源文件、头文件、构建日志等。生成的子文件夹名称通常和模型名、目标类型相关。

6. SIL功能测试与效果验证

SIL跑完不代表测试完成,要确认SIL结果和Normal仿真结果一致。下面是一套完整的验证步骤。

6.1 用Simulation Data Inspector对比波形

运行完Normal和SIL两组仿真后,打开Simulation Data Inspector:

Simulink.sdi.view

在SDI界面里,选择两次运行的信号,叠加对比。判断标准不要求波形一模一样,但在同样的输入和采样配置下,控制输出曲线应当基本重合。如果曲线有明显偏移、振荡甚至发散,就要进入问题定位。

6.2 用数据计算误差

肉眼对比不够严谨,建议写一段小脚本,计算两组结果的最大误差和均方根误差。

% 假设两组运行结果中信号名称都是y yModel = simOut.get('y'); ySIL = simOutSIL.get('y'); maxErr = max(abs(yModel - ySIL)); rmsErr = sqrt(mean((yModel - ySIL).^2)); fprintf('Max error: %e\n', maxErr); fprintf('RMS error: %e\n', rmsErr);

判断标准如下:

  • 误差在数值极小范围内(如1e-6以下),可以认为模型和代码在逻辑上等价。
  • 误差在可接受范围内,但明显存在,可能来自浮点运算顺序或编译器优化差异。
  • 误差大且波形形态不同,优先检查数据初始化、采样时间设置、数据类型配置和代码生成优化选项。

6.3 等价性判断的常见视角

SIL和Normal不一致,最常见的原因并不是算法错误,而是数据类型不匹配。比如Normal仿真中默认使用double,代码生成后配置成了single;或者模型中某个信号被设定为整数类型,导致除法结果截断。此时应该先检查模型里所有信号的数据类型,再检查代码生成配置里的“默认数据类型”。

另一个常见原因是被控对象模型和控制器模型使用了不同的采样时间。SIL模式对采样时间的处理更严格,如果模型中存在连续采样和离散采样混用,Normal模式下可能隐藏问题,到了SIL模式就会暴露出来。

6.4 在SIL模式下使用覆盖率分析

如果安装了Simulink Coverage,可以分析生成代码的语句覆盖率、分支覆盖率、条件覆盖率,以及汽车电子领域经常要求的MCDC覆盖率。这里注意概念区分:模型覆盖率分析的是Simulink模型里的模块执行情况,代码覆盖率分析的是生成的C代码中各条语句的执行情况。SIL模式下做代码覆盖率,是验证测试用例充分性的重要手段。

操作方式是打开“覆盖率分析”面板,选择“代码覆盖率”,运行SIL仿真后查看覆盖率报告。覆盖率偏低意味着测试用例充分性不足,需要补充更丰富的工况。

7. 自动化批量SIL测试与脚本化回归

SIL一旦能跑通,下一步就是把它接入自动化测试流程。实际项目中,一个控制器模型有几十个测试用例,不可能每次都手动切换仿真模式。

7.1 用Simulink Test批量执行

Simulink Test可以创建测试用例集,每个测试用例绑定对应的输入信号、参数和仿真模式。在测试管理器里新建Test Case,把仿真模式设置为SIL,然后一键批量运行。

优势很明显:Simulink Test会把每次都把结果和验收标准比较,输出Pass/Fail状态,并生成测试报告。遇到SIL代码变更后回归,直接选择全部用例运行即可。

7.2 用脚本循环实现参数扫描

如果没有Simulink Test,可以用脚本循环完成批量参数扫描。需要注意:SIL模式首次建立模型时编译代码,如果循环中切换了模型参数,有些参数修改会触发重新生成代码,耗时较长。

% 批量测试不同Kp参数下的SIL表现 KpList = [1.0, 1.5, 2.0, 2.5]; results = cell(length(KpList), 1); for i = 1:length(KpList) simIn = Simulink.SimulationInput('sil_demo'); simIn = simIn.setModelParameter('SimulationMode', 'Software-in-the-loop'); simIn = simIn.setVariable('Kp', KpList(i)); results{i} = sim(simIn); end

循环执行时要注意,每次sim调用之间留出足够的间隔,及时清理不再使用的输出数据,避免内存持续增长。

7.3 更面向工程的自动化模板

若是产线项目,建议补充日志和失败保存逻辑。

% 批量运行SIL并记录结果 testCases = {... struct('caseId', 'TC001', 'Kp', 1.0, 'Ki', 0.1), ... struct('caseId', 'TC002', 'Kp', 2.0, 'Ki', 0.2)}; logFile = 'sil_run_log.txt'; for idx = 1:length(testCases) tc = testCases{idx}; try simIn = Simulink.SimulationInput('sil_demo'); simIn = simIn.setModelParameter('SimulationMode', 'Software-in-the-loop'); simIn = simIn.setVariable('Kp', tc.Kp); simIn = simIn.setVariable('Ki', tc.Ki); out = sim(simIn); fprintf(logFile, '%s PASS\n', tc.caseId); catch ME fprintf(logFile, '%s FAIL: %s\n', tc.caseId, ME.message); end end

这种脚本化的方式同样适用于MIL、PIL模式。把仿真模式做成变量,一套脚本就能跑多种验证形式。

8. 资源占用与性能观察方法

SIL模式的资源消耗点主要在编译阶段和仿真执行阶段,和显卡无关,重点看CPU、内存和磁盘I/O。

  • 编译阶段:CPU占用会明显升高,风扇可能开始转,编译过程通常持续几十秒到几分钟。模型越大、代码生成选项越复杂,耗时越长。
  • 执行阶段:SIL仿真速度比Normal慢,因为模型每次时间步都要和生成代码模块交互数据。如果遇到SIL仿真长时间卡住不动,优先检查是不是模型内部有高频采样率或长时间仿真配置。比如StopTime设成10000秒,而采样周期是0.0001秒,仿真步数达到1亿步,这中间等待是合理的。
  • 内存占用:Simulink本身、生成的代码和SDI保存的数据都会占内存。跑大批量用例时,内存增长会很快。

降低资源占用的一些方法:

  • 仿真时间先缩短到能覆盖动态响应的最小范围。
  • 把不关心的信号在信号日志里关掉,减少SDI存储量。
  • 若批量跑SIL,每次仿真结束后调用clear或让输出对象及时失引用,减少内存堆积。
  • 在模型配置里开启代码生成缓存,避免重复编译。频繁变更代码生成选项或清理工作区可能导致缓存失效。
  • 如果模型规模非常大,可以把控制器模型和被控对象模型拆开,单独对控制器模型跑SIL。

9. 常见问题与排查方法

下面把SIL使用中最常遇到的问题整理成排查表。

问题现象可能原因排查方式解决方案
切换SIL后报错找不到编译器未安装C编译器或mex -setup未配置在命令行运行mex -setup安装MinGW-w64或MSVC后重新配置
SIL构建过程中提示文件路径错误模型文件名为中文、包含空格检查模型文件名改为英文和下划线命名
构建成功但仿真报错模型中包含不支持代码生成的模块检查报错模块改用支持代码生成的替代模块
SIL结果与Normal明显不一致数据类型不一致或采样时间设置问题对比信号数据类型和数据曲线统一数据类型,检查采样时间
多次运行后内存占用持续增长仿真输出对象未及时清理检查命令行窗口变量批量循环中及时清理变量
模型设置了SIL但无法选择未安装Simulink Coder检查许可证和工具箱安装Simulink Coder
覆盖率报告没有代码覆盖率数据未启用代码覆盖率或未安装Simulink Coverage检查覆盖率设置安装Simulink Coverage并开启代码覆盖率
批量SIL测试中途卡住单个用例仿真时间过长,或内存不足查看任务管理器CPU/内存占用缩短仿真时间,分批运行
生成的C代码在目标硬件上能跑但SIL失败编译器优化选项与目标工具链差异检查代码生成优化设置调整工具链和优化选项

其中“SIL结果与Normal不一致”是最需要花时间的。定位顺序建议是:先对比输出波形是全局偏差还是局部突变,再检查数据初始化、采样时间、数据类型,最后检查代码生成优化选项。不要一上来就怀疑编译器,多数问题出在模型本身。

10. 最佳实践与使用建议

从工程落地的角度,下面几条建议能在使用SIL时少走弯路。

第一,第一次跑SIL一定要控制模型规模。先用最小可运行模型跑通全流程,再把自己的控制器模型放进去。这样能区分问题是出在SIL流程没配置好,还是出在控制器模型本身。

第二,把基线数据保存下来。Normal仿真结果、SIL仿真结果、测试用例、生成代码、日志文件,建议按日期和版本分目录管理。以后模型更新了,直接回归对比。

第三,批量测试必须有日志和失败重试机制。SIL模式下如果某个用例因为一次编译问题挂了,后续用例也会受影响。加日志后能快速定位是哪一轮、哪个参数触发的失败。

第四,SIL未通过时不要急着烧硬件。很多嵌入式端问题可以在SIL阶段提前暴露,特别是数据饱和、整数溢出、死代码路径这些典型问题。

第五,涉及人脸、声音、多媒体素材或其他版权内容时,不要因为是在仿真环境里就放松授权要求。SIL只是验证逻辑,不豁免合规风险。

第六,对外发布测试报告时,注明MATLAB版本、工具箱列表、编译器版本、模型版本、仿真步长和StopTime。缺少这些环境信息,测试结果很难复现。

11. 总结与下一步

SIL是自动代码生成流程中性价比很高的一环。它不要求目标硬件,却能把模型和代码之间的大部分逻辑差异暴露出来。先跑通Normal仿真作为基线,再切换到SIL模式,对比波形,计算误差,最后通过脚本或Simulink Test把测试用例批量跑起来,这就是一套可复用的SIL验证流程。

做完了SIL,下一步通常就是PIL。SIL验证的是代码逻辑,PIL验证的是代码在真实处理器上的运行行为。理解SIL以后,你拿到PIL配置时会发现大部分概念是相通的,差别只是多了一块目标硬件和对应的工具链。

建议你在自己的模型上先做一个最小截图:随便搭一个PID闭环,记录Normal曲线,切到SIL,对比结果。整个流程跑通之后,再去碰VCU整车模型、Carsim联合仿真或者更复杂的策略模型。熟练之后,你会发现SIL其实是代码生成流程中最安全的一步——它花的时间不多,但能挡住大部分低级错误。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询