3. Simulink定点数据类型体系
在Simulink中做定点化,第一步不是急着把信号改成fixdt,而是先弄清楚这套类型体系里到底有哪几个“角色”、它们各自负责什么、彼此如何转换。Simulink的定点世界由三层对象构成:最底层是fixdt这一数据类型描述,中间层是numerictype这一属性容器,最上层是fi这一带值的定点数值对象。三者不是并列关系,而是“描述—属性—实例”的递进关系。理解这条主线,后面的继承规则和类型转换才有落脚点。
fixdt:数据类型的“身份证”
fixdt是Simulink用来描述定点数据类型的函数式表达,它本身不携带数值,只描述“这个类型长什么样”。常用形式有三种:
fixdt(1, 16, 12) % 有符号,16位字长,12位小数
fixdt(0, 8, 0) % 无符号,8位字长,0位小数(即整数)
fixdt(1, 16, 12, 0.5) % 带斜率的定点:真实值 = 存储值×0.5
fixdt('double') % 直接引用浮点类型
fixdt('Single') % 引用单精度
其中第一个参数是符号位(1有符号、0无符号),第二个是字长WordLength,第三个是小数位FractionLength。当第三个参数为负数时,表示该类型是“整数型定点”,即小数点在最低位右侧若干位,例如fixdt(1,16,-4)表示量化步长为16。第四个参数Slope用于非二进制缩放场景,此时真实值 = 存储整数 × Slope + Bias,常用于传感器原始码值到物理量的映射。
fixdt的关键价值在于:它可以作为参数直接写进模块的“数据类型”下拉框,也可以作为Simulink.DataType对象被复用。它描述的是类型,不是值,因此同一个fixdt可以被成千上万个信号共享。
numerictype:把类型属性装进一个对象
numerictype是MATLAB定点工具箱中的类,它把Signed、WordLength、FractionLength、Slope、Bias等属性封装成一个可传递的对象。构造方式很直观:
T = numerictype(1, 16, 12);
T.Signed % 1
T.WordLength % 16
T.FractionLength % 12
T.Range % 数值范围,约 [-8, 8)
numerictype与fixdt描述的是同一组属性,区别在于使用场景:fixdt更适合在Simulink模块参数里直接书写,numerictype更适合在MATLAB脚本、MATLAB Function块、Stateflow以及需要动态计算类型属性的场合使用。二者可以互相转换:fixdt(T)把numerictype转成fixdt字符串,numerictype(fixdt(1,16,12))则反向构造。实际工程中常见做法是:在脚本里用numerictype计算好类型,再通过fixdt(T)写回模型参数,实现“类型集中管理”。
fi:带数值的定点实例
fi是“fixed-point instance”的缩写,它把数值和numerictype绑在一起,形成一个真正的定点数对象:
x = fi(3.75, 1, 16, 12);
x.Value % 3.75
x.DataType % numerictype(1,16,12)
x.int % 15360,即3.75×2^12的存储整数
x.bin % 二进制存储表示
fi对象的核心意义在于:它让“值”和“类型”不可分割。当你对fi做算术运算时,MATLAB会按照定点规则自动推导结果类型,而不是像double那样无限精度。例如fi(3.75,1,16,12) * fi(2.5,1,16,12)的结果字长会扩展,小数位也会变化,这正是定点运算“类型会生长”的体现。在Simulink中,fi对象主要出现在MATLAB Function块、Stateflow和仿真数据记录里,用于精确观察每一步的量化结果。
继承规则:谁决定信号的类型
Simulink定点建模中最容易出问题的地方,就是“这个信号到底是什么类型”。模块的输出类型有三种来源:显式指定、内部规则继承、反向传播继承。
显式指定最直接:在模块的DataType参数里写fixdt(1,16,12)或numerictype对象,输出就固定为该类型,不随输入变化。这种方式可预测性最强,但灵活性差,一旦上游字长调整,下游可能溢出。
内部规则继承(Inherit via internal rule)是Simulink的默认行为。以Gain模块为例,当输出类型设为“Inherit via internal rule”时,Simulink会根据输入类型和增益值,按照一套固定规则计算输出字长和小数位:乘法结果字长通常为两输入字长之和,小数位为两输入小数位之和;加法结果则对齐小数点后取较宽者。这套规则保证了运算不丢失精度,但会导致字长逐级膨胀,需要后续用DataType Conversion模块或Quantizer做截断。
反向传播继承(Inherit via back propagation)用于“下游决定上游”的场景。典型例子是DataType Conversion模块的输出类型设为fixdt,而其输入端口设为“Inherit via back propagation”,此时Simulink会从输出端反推输入端应有的类型,避免中间出现不必要的类型转换。这种机制在总线、子系统边界和反馈回路中特别有用,但也容易造成“类型来源不直观”,建议配合Signal Attributes中的DataType显示进行核查。
定点与浮点、整数之间的转换
定点与浮点之间的转换,本质是“量化”与“反量化”。从double转到fixdt(1,16,12)时,真实值乘以2^12后四舍五入取整,再截断到16位有符号范围[-32768, 32767]。例如3.75×4096=15360,落在范围内,转换无损;而10.0×4096=40960,超出上限,会发生饱和(Saturate)或回绕(Wrap),具体行为由模块的Integer overflow mode决定。从定点转回double时,存储整数除以2^12即可还原,但已经损失了原始精度。
定点与整数之间的转换更需注意:fixdt(1,16,0)本身就是整数型定点,与int16的存储完全一致,区别只在于Simulink是否按定点规则处理溢出和舍入。当把fixdt(1,16,12)转成int16时,小数部分会被直接丢弃(相当于右移12位),3.75变成3,-3.75变成-4(向负无穷舍入)或-3(向零舍入),取决于舍入模式设置。因此,在需要保留小数信息的链路上,不要用整数类型做中间传递。
T_acc = numerictype(1,32,20); T_out = numerictype(1,16,12);,然后在模块参数中引用fixdt(T_acc)。这样当精度需求变化时,只需改一处,全模型同步更新,也便于用脚本批量检查各信号的实际范围是否超出类型表示范围。