PIL 处理器在环:SIL 过了为什么还不够

XiL 家族里最容易被质疑的一环就是 PIL:「SIL 都过了,直接上 HIL 不行吗?」——不行,而且理由很具体。SIL 过了,只证明源代码的算法逻辑在 PC 编译器下行为正确;量产控制器跑的是另一份二进制、另一套数值环境。这篇文章把 PIL 讲透:它是什么、SIL 和目标机之间隔着的四堵墙、它的核心方法 back-to-back 比对、为什么这些问题不该留给 HIL 抓,以及域控时代它正在发生的变形。

一、什么是 PIL

PIL = Processor-in-the-Loop,处理器在环。 把被测软件按目标芯片的编译器编成机器码,放到真处理器(评估板)或指令集仿真器里跑;环路的其余部分——被控对象、环境模型、测试框架——还留在 PC 上仿真。

XiL 家族各级别的区别,就是「什么被放进了环里」:

级别 环里放的东西 跑在哪
MIL 模型本身(框图) PC,纯数学
SIL 源代码编的 x86 二进制 PC,Docker / 本机
PIL 目标编译器编的目标机器码 真处理器或指令集仿真器
HIL 整台 ECU(处理器 + 内存 + IO + 接插件) 实时台架 + 真总线

一次 PIL 测试的闭环:

PC 测试框架                     目标处理器(评估板)
─────────────────               ─────────────────────
加载测试向量
        │  ① 下载目标二进制(JTAG / 网口)
        │ ──────────────────▶
        │  ② 发第 i 拍激励(输入信号值)
        │ ──────────────────▶  ③ 被测函数在目标码上执行一拍
        │  ④ 读回输出 + 内部状态
        │ ◀──────────────────
比对期望 / SIL 基线
        │  ⑤ 下一拍……

PC 是「导演」,处理器是「演员」——一切由测试框架驱动和采集。

两种形态:真评估板(JTAG 烧录,最真实,顺手测执行时间和栈水位);指令集仿真器 ISS(纯软件逐条执行目标机器码,QEMU 是免费近亲,商用如 Synopsys VDK,可进 CI、可做到周期精确)。

测什么 / 不测什么:测目标代码正确性(编译器差异、浮点 / 定点精度、字长对齐)、执行时间、栈;不测真实 IO 电气、真实传感器、总线物理层——那是 HIL 的地盘,PIL 阶段的输入输出都是「数值」,不是电压。

二、为什么存在:SIL 和目标机之间隔着四堵墙

SIL 过了,只证明源代码的算法逻辑,在 PC 编译器下,行为正确。量产 ECU 跑的是另一回事:

  1. 编译器不同。车规 MCU 用 TASKING / Green Hills / 特定交叉 GCC,代码生成和 PC 编译器是两个物种。同一份 C,换个编译器换个 -O2,未定义行为(溢出、严格别名)可能当场现形。
  2. 数值环境不同。很多经典 MCU 没有双精度 FPU,甚至全定点。SIL 里 double 算得好好的控制率,到目标上是 float 甚至 Q15 定点——量化误差、溢出,SIL 一概看不见。
  3. 资源不同。PC 上栈随便吃;MCU 上一个任务栈 2KB。SIL 永远测不出栈溢出。
  4. 时间不同。x86 上 10ms 一拍随随便便;160MHz 的 Tricore 上,控制函数一拍是 800µs 还是 8ms?SIL 对执行时间零感知。

三、back-to-back:PIL 的核心方法

back-to-back(背靠背)测试是 PIL 的核心方法:用和 SIL 完全相同的测试向量驱动,逐点比对输出(容差内)。SIL≈PIL,说明算法活着穿过了「代码生成 → 编译 → 目标处理器」这条链;不等,问题就出在链上,而不是算法上——嫌疑人是代码生成器、编译器、目标运行时,控制逻辑反而可以先排除。

这正是 XiL 分级的价值所在:每一级只引入一类新变量,失败时的嫌疑范围是有限的。

四、深入:back-to-back 差异的两个来源

两边都用 IEEE double + 同族 GCC(比如 x86 对 ARM64 开发板),back-to-back 差异≈0,漂亮得无聊。真实项目引入优化手段后分歧才出现。两个最常见的来源:定点和 SIMD intrinsic——它们是让代码在目标芯片上跑得动、跑得快的优化手段,代价是数值结果与 x86 参考实现产生分歧;back-to-back 比对,量的就是这个分歧。

(a) 定点:用整数假装小数

是什么:把数值放大固定倍数后用整数存。Q15 格式:int16_t 存「真值 × 32768」,0.5 存 16384。加减直接整数运算;乘法注意量纲——Q15×Q15 得 Q30,结果要 >>15 缩回来。

为什么存在:经典车规 MCU(AUTOSAR CP 侧)没有浮点单元(FPU)或只有孱弱的单精度 FPU——一次 float 乘法靠软件模拟,慢几十倍。整数 ALU 人人都有、快且确定。定点 = 用「程序员手工管理缩放」换「硬件不需要浮点」。

代价(x86 g++ 实测):

0.1 → Q15 存 3276 → 还原 0.0999755859,误差 2.44e-05    ← 量化误差,0.1 根本存不准
0.1 × 0.2:double 真值 0.02,Q15 算出 0.0199890137      ← 每次乘法还有一次舍入

(b) SIMD intrinsic:一条指令算一排数

是什么:SIMD = Single Instruction Multiple Data,CPU 向量单元一条指令同时算 4/8/16 个数(ARM 叫 NEON,x86 叫 SSE/AVX)。intrinsic 是编译器提供的「伪函数」,一条对应一条机器指令,如 NEON 的 vaddq_f32(4 个 float 并行加)、SSE 的 _mm_add_ps。

为什么存在:AP 域控跑感知 / 信号处理,逐元素循环吞吐不够,向量化是家常便饭。

代价:浮点加法不满足结合律(实测):

(x + big) - big = 0        (x=1e-9,big=1e9:大数把小数吞掉了)
x + (big - big) = 1e-09    (换个结合顺序,结果就对了)

SIMD 归约的求和顺序与标量循环必然不同;加上 FMA(融合乘加,一次舍入 vs 两次)、x86 与 ARM 对非规格化数(flush-to-zero)处理不同——同一算法,x86 标量参考版与 ARM NEON 版逐位不一致是正常的。

(c) 差异怎么判:容差带,不是逐位相同

此时 back-to-back 才从「过场」变成「工具」:它回答的不是「两边是否逐位相同」(必然不同),而是「分歧是否待在容差带内」。报告写 max abs error / max rel error,超带才算 bug——「判定阈值随对象缩放」的思想在测试里通用,只是这里判的是数值精度而非时间。

五、为什么不能留给 HIL 抓

六、域控时代 PIL 在变形

ADAS 域控方向,目标环境和开发环境的差距在缩小:Linux + ARM64/x86 + 同族 GCC + 全程 IEEE 浮点——PIL 退化为「在目标同构环境(QEMU、容器化 arm64、开发板)里再跑一遍 SIL」。传统 PIL 的主战场是跑 AUTOSAR CP、定点、TASKING 编译器的经典 MCU(车身 / 底盘 / 动力)。

所以 PIL 是不是必选项,要看项目的目标环境:经典 MCU 项目上它是刚需;域控项目上它收缩成一个轻量环节。但原理不变——只要「开发环境的二进制」和「目标环境的二进制」不是同一份,就需要一个环节证明两者行为一致。


回看整条链:SIL 证明算法对,PIL 证明算法在目标链上还对。中间隔着编译器、数值、资源、时间四堵墙,back-to-back 就是翻墙的绳梯——同一套测试向量两边各跑一遍,逐点比对,分歧超带才算 bug。把这道工序省掉留待 HIL,省的是几千块的评估板,费的是几百万台架的机时和定位问题的搜索空间。


附:名词解释(按出场顺序)

名词 通俗解释
PIL(处理器在环) Processor-in-the-Loop:把目标编译器编的机器码放到真处理器或指令集仿真器里跑,其余环节留在 PC 上仿真
指令集仿真器(ISS) 纯软件逐条执行目标机器码的仿真器;QEMU 是免费近亲,商用如 Synopsys VDK,可做到周期精确
MIL / SIL / HIL 模型在环 / 软件在环 / 硬件在环;XiL 家族里 PIL 的兄弟级别
JTAG 芯片调试 / 烧录接口,评估板上下载目标二进制的常用通道
栈水位(stack high-water mark) 任务栈历史最大使用深度,用来评估栈溢出风险
交叉编译器 在 PC 上运行、产出目标芯片机器码的编译器;车规常用 TASKING、Green Hills、特定交叉 GCC
未定义行为(UB) C/C++ 标准不保证行为的写法(如有符号溢出、严格别名违规),换个编译器 / 优化档可能当场现形
FPU(浮点单元) 硬件浮点运算单元;经典车规 MCU 没有或只有孱弱的单精度 FPU
back-to-back(背靠背)测试 同一套测试向量驱动两个实现(如 SIL 与 PIL),逐点比对输出是否在容差带内
定点 / Q15 用整数存「真值 × 固定倍数」的数值表示;Q15 用 int16 存「真值 × 32768」
量化误差 真值落到定点 / 浮点可表示网格上时产生的舍入误差,会在积分器里累积
饱和处理(saturation) 运算结果超范围时钳到最大 / 最小可表示值,而不是回绕
SIMD Single Instruction Multiple Data:一条指令同时算一排数的 CPU 向量能力
intrinsic 编译器提供的「伪函数」,一条对应一条机器指令,如 NEON 的 vaddq_f32、SSE 的 _mm_add_ps
NEON / SSE / AVX ARM 和 x86 各自的 SIMD 指令集名称
FMA(融合乘加) 一条指令完成 a×b+c 且只舍入一次;与「先乘后加」两次舍入结果不同
非规格化数 / flush-to-zero 极接近零的浮点数;x86 与 ARM 对其处理不同(有的直接刷成零),是跨平台逐位差异的来源之一
ISO 26262 / ASIL 汽车功能安全标准及其安全完整性等级(QM/A/B/C/D,D 最严);ASIL C/D 要求目标环境验证
AUTOSAR CP / AP 汽车软件平台两大分支:Classic Platform(经典 MCU)与 Adaptive Platform(域控 SoC)

下一步:

查看全部文章