2. 模型与代码的映射关系
做一致性验证,第一步不是打开比对工具,而是先建立一张“翻译对照表”:Simulink 模型里的每一个元素,究竟变成了 C 代码里的什么。只有把映射关系搞清楚,才能判断“代码里少了这段逻辑”到底是生成器的问题,还是模型本身就没有这个元素。本节围绕子系统、信号、参数、状态、函数和数据类型六类元素,说明它们在生成代码中的典型落点。
一、子系统:从虚拟分组到真实函数
子系统是映射关系中最容易产生误解的一环。Simulink 中的子系统分为虚拟子系统(Virtual Subsystem)和非虚拟子系统(Atomic Subsystem)两类,它们在代码中的命运完全不同。
虚拟子系统本质上只是画布上的一个分组框,不改变模型的执行语义。生成代码时,它内部的模块会被“打平”到父层,与父层其他模块一起参与运算,代码中不会出现独立的函数边界。因此,如果你在代码里找不到某个虚拟子系统的名字,这是正常的,不能据此判定代码缺失。
非虚拟子系统则会被视为一个独立的执行单元。当它配置为函数复用(Reusable function)或原子单元时,代码生成器通常会为它生成一个独立的 C 函数,函数名一般由子系统名加上模型前缀构成。例如模型中名为 Calc_Torque 的原子子系统,在代码中可能对应 MyModel_Calc_Torque 这样的函数。此时子系统名就成了验证时的关键锚点:模型里有几个原子子系统,代码里就应当能找到对应数量的函数体。
此外,条件执行子系统(使能、触发、动作)会映射为带条件判断的代码分支,其使能端口和触发端口会变成函数入口参数或模块级状态变量。验证时要特别关注这些条件信号是否被正确传递。
二、信号:从连线到变量与赋值
信号是模型中的数据流载体,映射到代码中就是变量以及变量之间的赋值语句。这里有几个关键区分。
第一,信号线本身不产生代码,产生代码的是信号的“存储”属性。只有被标记为需要存储的信号(例如跨越了函数边界、被多处读取、或显式配置了存储类)才会在代码中生成对应的变量声明。纯组合逻辑的中间信号往往被优化掉,直接内联到表达式中。
第二,信号名与变量名的对应关系受命名规则控制。默认情况下,代码生成器会基于信号名生成合法 C 标识符,非法字符被替换,重名时自动加后缀。如果模型中对信号设置了自定义存储类(如 ExportedGlobal、ImportFromFile),信号名就会直接成为全局变量名,这是验证时最可靠的对应点。
第三,信号的维度决定变量形态。标量信号映射为单个变量,向量信号映射为数组,矩阵信号映射为二维数组。验证时要核对数组的维度和下标顺序是否与模型一致,尤其是 MATLAB 列优先与 C 行优先的差异,在矩阵运算中极易出错。
三、参数:从工作区变量到常量或可调参数
模型中的参数(Gain 的增益值、Saturation 的上下限、查表数据等)来自 MATLAB 工作区或数据字典。它们在代码中的形态取决于是否被配置为可调参数。
如果参数被内联(Inline),生成代码中会直接出现字面常量,例如增益 2.5 直接写成 2.5F,此时参数值被“烧”进代码,运行时不可改。如果参数被配置为可调(Tunable),则会生成独立的参数变量或结构体成员,通常带有 P_ 前缀或位于参数结构体中,可以在不重新编译的情况下标定。
验证参数映射时,最直接的方法是抽取代码中的常量值和参数初值,与模型工作区中的变量值逐一比对。对于查表模块,还要核对断点数组和表数据数组的长度、顺序是否一致。
四、状态:从连续/离散状态到状态变量
状态是模型“记忆”的体现,也是代码与模型最容易出现偏差的地方。连续状态(Integrator 模块)在代码中通常映射为状态变量加上积分更新逻辑;离散状态(Unit Delay、Discrete Filter)映射为状态变量加上按采样周期的更新语句。
状态变量的命名通常遵循 模型名_DSTATE 或 模型名_X 的约定,离散状态和连续状态可能分属不同的结构体。验证时要关注三点:状态变量的个数是否与模型中状态个数一致;状态的初值是否与模型配置的初始条件一致;状态的更新顺序是否与模型的执行顺序一致。第三点尤其重要,因为状态更新顺序错误会导致数值结果正确但时序错误,这类问题在单步比对中很难发现。
五、函数与数据类型:执行结构与数值精度
模型的执行结构映射为代码的函数调用结构。模型的初始化对应 _initialize 函数,每个采样步长的计算对应 _step 函数,终止对应 _terminate 函数。多速率模型会进一步拆分为多个速率任务,每个任务有独立的步进函数。验证时要确认模型的采样时间配置与代码中的任务划分一致。
数据类型则贯穿所有元素。Simulink 中的 double、single、int8、boolean、定点 fixdt 等类型,会映射为 C 中的 real_T、int8_T、boolean_T 等类型别名。定点类型还会映射为带移位和饱和处理的运算代码。类型不匹配是精度偏差的主要来源,验证时必须核对每个关键信号的类型定义。
六、把映射关系变成验证锚点
理解映射的最终目的,是为一致性验证建立可操作的锚点。可以按下面的顺序建立检查清单:
- 函数级锚点:统计模型中原子子系统的数量与名称,在代码中搜索对应的函数定义,确认数量一致、命名可追溯。
- 变量级锚点:列出所有配置了存储类的信号,在代码中定位其声明位置,核对类型、维度、初值。
- 参数级锚点:抽取模型中所有可调参数的名称与数值,与代码中的参数结构体成员逐一比对。
- 状态级锚点:核对状态变量个数、初值以及更新语句在步进函数中的位置顺序。
- 类型级锚点:对关键运算路径上的信号,核对模型数据类型与代码类型别名是否一致,定点信号还要核对定标与饱和设置。
需要特别提醒的是,映射关系不是一一对应的。一个模型元素可能对应多行代码,多行代码也可能对应一个模型元素,甚至有些模型元素在代码中完全消失(被优化或打平)。因此,验证的目标不是“找到每一个元素”,而是“确认关键元素都存在且语义一致”。把上面六类元素的映射规律记清楚,后续无论是做人工走查还是搭建自动化比对脚本,都能快速定位到该看哪里、该比什么。