技术文章及通讯

将MATLAB代码转换为定点的最佳实践

哈希塔·巴拉特,汤姆·布莱恩,茱莉亚·沃尔,MathWorks


当将浮点实现转换为定点时,工程师必须确定最优的定点数据类型,既能满足嵌入式硬件的约束,又能满足系统对数值精度的要求。定点设计器™通过自动提出数据类型和定点属性,并支持将位-真定点模拟结果与浮点基线进行比较,帮助您开发定点算法并将浮点算法转换为定点。

本文概述了编写MATLAB的最佳实践®代码转换,将MATLAB代码转换为定点,并优化您的算法的效率和性能。无论您是在MATLAB中设计定点算法以准备手工编码,还是转换为定点进行代码生成,这些最佳实践都将帮助您将通用MATLAB代码转换为高效的定点实现。

准备定点转换代码

你可以采取三个步骤来确保顺利的转换过程:

  • 将核心算法与其他代码分开。
  • 为插装和加速准备代码。
  • 检查用于定点支持的函数。金宝app

分离核心算法与其他MATLAB代码

通常,算法伴随着设置输入数据的代码和创建验证结果的图的代码。由于只有算法代码需要转换为定点,因此更有效的方法是构造代码,以便测试文件创建输入,调用核心算法,并绘制结果,算法文件执行核心处理(表1)。

原始代码
测试输入百分比X = randn(100,1);%的算法Y = 0(大小(x));Y (1) = x(1);N =2:长度(x) y(N) = y(N -1) + x(N)结束%验证结果yExpected = cumsum(x);情节(y-yExpected)标题(“错误”
修改后的代码

测试文件。

测试输入百分比X = randn(100,1);%的算法Y = cumulative_sum(x);%验证结果yExpected = cumsum(x);情节(y-yExpected)标题(“错误”

算法文件。

函数Y = cumulative_sum(x) Y = 0(大小(x));Y (1) = x(1);N =2:长度(x) y(N) = y(N -1) + x(N)结束结束

为检测和加速准备算法代码

仪表和加速有助于简化转换过程。您可以使用定点设计器来检测代码并记录所有命名变量和中间变量的最小值和最大值。该工具可以使用这些日志值来建议在定点代码中使用的数据类型。

使用定点设计器,您还可以通过创建MEX文件来加速定点算法,并根据原始版本加快验证定点实现所需的模拟。

检测和加速都依赖于代码生成技术,因此在使用它们之前,即使您不打算使用MATLAB Coder™或HDL Coder™来生成C或HDL代码,也必须为代码生成准备好算法。首先,确定MATLAB代码中不支持代码生成的函数或构造(请参阅金宝app语言支持金宝app获取支持的函数和对象的列表)金宝app。

有两种方法来自动化这个步骤:

  • 添加% # codegenpragma到包含核心算法代码的MATLAB文件的顶部。这句话说明了问题代码分析器标记未包含在支持代码生成的MATLAB语言子集中的函数和构造。金宝app
  • 使用代码生成准备工具生成一个报告,以识别对函数的调用以及不支持代码生成的数据类型的使用。金宝app

一旦为代码生成准备好了算法,就可以使用定点设计器来检测和加速它。使用buildInstrumentedMex以启用记录所有命名变量和中间变量的最小值和最大值的检测,并使用showInstrumentationResults查看包含在定点代码中使用的建议数据类型的代码生成报告。调用fiaccel将您的MATLAB算法转换为MEX文件并加速您的定点模拟。

检查算法代码中使用的函数是否支持定点金宝app

如果你确定一个函数不支持定点,你有三个选择:金宝app

  • 将函数替换为一个定点等效函数。
  • 写出你自己的等价函数。
  • 在输入端使用强制转换为doub金宝apple,在输出端使用强制转换回定点类型来隔离不受支持的函数。

然后您可以继续将代码转换为定点,并在有合适的替换时返回到不受支持的函数(表2)。金宝app

原始代码
Y = 1/exp(x);
修改后的代码
Y = 1/exp(double(x));

管理数据类型和控制位增长

在定点实现中,定点变量必须保持定点,不能被无意中转换为双精度变量。这也很重要防止钻头生长.例如,考虑下面这行代码:

Y = Y + x(n

此语句覆盖y价值是Y + x(n).当您在代码中引入定点数据类型时,y重写时可能改变数据类型,可能导致比特增长。

的数据类型y使用(,) =语法(表3)。这种语法称为下标赋值,指示MATLAB保留被覆盖变量的现有数据类型和数组大小。该声明Y (:) = Y + x(n)将右边的值转换为y的原始数据类型,并防止比特增长。

原始代码
Y = 0;N =1:长度(x) y = y + x(N);结束
修改后的代码
Y = 0;N =1:长度y(:) = y + x(N);结束

创建一个类型表,将数据类型定义与算法代码分开

将数据类型定义与算法代码分离,可以更容易地比较定点实现并将算法重新定位到不同的设备。

要应用此最佳实践,请执行以下操作:

  1. 使用铸造(x,“喜欢”,y)0 (m, n,“喜欢”,y)在第一次定义变量时将其强制转换为所需的数据类型。
  2. 创建一个数据类型定义表,从代码中使用的原始数据类型开始——典型的双精度浮点数,MATLAB中的默认数据类型(表4a)。
  3. 在转换为定点之前,请添加数据类型到类型表中查找类型不匹配等问题(表4b)。
  4. 通过运行连接到每个具有不同数据类型的表的代码并比较结果来验证连接。
原始代码
%的算法N = 128;Y = 0(大小(x));
修改后的代码
%的算法T = mytypes(“双”);N = cast(128,“喜欢”, T.n);Y = 0 (size(x),“喜欢”, T.y);%类型函数T = mytypes(dt)开关(dt)情况下“双”T.n = double([]);T.y = double([]);结束结束
原始代码
%类型函数T = mytypes(dt)开关(dt)情况下“双”T.n = double([]);T.y = double([]);结束结束
修改后的代码
函数T = mytypes(dt)开关(dt)情况下“双”T.n = double([]);T.y = double([]);情况下“单一”T.n = single([]);T.y = single([]);结束结束

在类型表中添加定点表项

一旦创建了数据类型定义表,就可以根据转换为定点的目标添加定点类型。例如,如果计划用C实现算法,则固定点类型的字长度将被限制为16的倍数。另一方面,如果您计划在HDL中实现,则单词长度不受限制。

若要为代码获取一组定点类型建议,请使用定点设计器命令buildInstrumentedMex而且showInstrumentationResults(表5)。您将需要一组测试向量来测试所有类型——Fixed-Point Designer提出的类型只与测试输入一样好。长时间的模拟运行和广泛的预期数据将产生更好的建议。从代码生成报告中的建议中选择一组初始的定点类型(图1)。

图1。由showInstrumentationResults生成的代码生成报告,其中包含过滤算法中变量的建议数据类型。

然后您可以根据需要调整建议的类型(表5和表6)。

算法代码
函数[y,z] = myfilter(b,x,z) y = 0 (size(x));N =1:长度(x) z(:) = [x(N);z (1:结束1)];Y (n) = b * z;结束结束
测试文件
%测试输入B = fir1(11,0.25);T = linspace(0,10*pi,256)';X = sin(/16)*t.^2);线性啁啾Z = 0 (size(b'));%建立buildInstrumentedMexmyfilter……-args {b,x,z} -直方图运行%[y,z] = myfilter_mex(b,x,z);%显示showInstrumentationResultsmyfilter_mex……-defaultDT numerictype(1,16) -proposeFL .使用实例

表5所示。一个过滤算法和测试脚本,用于检测和执行代码,并为变量显示建议的定点类型。

算法代码
函数[y,z] = myfilter(b,x,z,T) y = 0 (size(x),“喜欢”, T.y);N =1:长度(x) z(:) = [x(N);z (1:结束1)];Y (n) = b * z;结束结束
测试文件
%测试输入B = fir1(11,0.25);T = linspace(0,10*pi,256)';X = sin(/16)*t.^2);线性啁啾%转换输入T = mytypes(“fixed16”);B = cast(B,“喜欢”、肺结核);X = cast(X,“喜欢”, T.x);Z = 0 (size(b'),“喜欢”, T.x);运行%[y,z] = myfilter(b,x,z,T);
类型表
函数T = mytypes(dt)开关dt情况下“双”T.b = double([]);T.x = double([]);T.y = double([]);情况下“fixed16”T.b = fi([],true,16,15);T.x = fi([],true,16,15);T.y = fi([],true,16,14);结束结束

表6所示。用定点数据类型更新表4中的测试脚本和过滤算法。

使用新的定点类型运行算法,并将其输出与基线算法的输出进行比较。

优化数据类型

无论您是选择自己的定点数据类型,还是使用定点设计器建议的定点数据类型,都要寻找机会优化单词长度、分数长度、符号,甚至可能是数学模式(fimath).您可以通过使用缩放双精度值、查看变量值的直方图或在数据类型表中测试不同类型来实现这一点。

使用缩放双精度检测潜在溢出

按比例缩小的双打是浮点数和定点数的混合。定点设计器存储缩放的双精度文件,保留缩放、符号和字长信息。要使用伸缩的双精度变量,请设置数据类型覆盖(DTO)属性(表7)。

DTO设置 例子
DTO设置为本地使用numerictype
数据类型的属性
>> T.a = fi([], 1, 16, 13,“数据类型”“ScaledDouble”);>> a = cast(pi,“喜欢”, T.a) a = 3.1416 DataTypeMode: Scaled double:二进制点缩放signdness: Signed WordLength: 16 FractionLength: 13
DTO设置全局使用fipref
“DataTypeOverride”属性
> > fipref (“DataTypeOverride”“ScaledDoubles”);>> T.a = fi([], 1,16,13);>> a = cast(pi,“喜欢”, T.a) a = 3.1416 DataTypeMode: Scaled double:二进制点缩放signdness: Signed WordLength: 16 FractionLength: 13

表7所示。方法用于在本地和全局设置数据类型重写属性。

使用buildInstrumentedMex运行您的代码和showInstrumentationResults查看结果。在代码生成报告中,会溢出的值用红色突出显示(图2)。

图2。代码生成报告显示溢出时,使用缩放双精度类型(左)和直方图图标(右)。

检查变量值的分布

可以使用直方图标识值在范围内、范围外或低于精度的数据类型。单击柱状图图标以启动NumericTypeScope并查看在所选变量的模拟中观察到的值的分布(图3)。

图3。直方图显示变量值的分布,溢出条件(“超出范围”)用红色表示。

在数据类型表中测试不同的类型

您可以向类型表中添加自己的定点类型变体(表8)。

算法代码
函数[y,z] = myfilter(b,x,z,T) y = 0 (size(x),“喜欢”, T.y);N =1:长度(x) z(:) = [x(N);z (1:结束1)];Y (n) = b * z;结束结束
测试文件
函数mytest%测试输入B = fir1(11,0.25);T = linspace(0,10*pi,256)';X = sin(/16)*t.^2);线性啁啾运行%y =入口点(“双”x、b);Y8 =入口点(“fixed8”x、b);Y16 =入口点(“fixed16”x、b);%的阴谋次要情节(1,1);情节(t, x,“c”t y0,“k”);传奇(“输入”“基准输出”)标题(“基线”)次要情节(3 2 3);情节(t,日元,“k”);标题(8位定点输出)次要情节(3、2、4);情节(t, y0-double(日元)“r”);标题(“8位定点错误”次要情节(3 2 5);情节(t,造成,“k”);标题(16位定点输出)包含(“时间(s)”次要情节(3 2 6);情节(t, y0-double(造成),“r”);标题(“16位定点错误”)包含(“时间(s)”结束函数[y,z] =入口点(dt,b,x) T = mytypes(dt);B = cast(B,“喜欢”、肺结核);X = cast(X,“喜欢”, T.x);Z = 0 (size(b'),“喜欢”, T.x);[y,z] = myfilter(b,x,z,T);结束
类型表
函数T = mytypes(dt)开关dt情况下“双”T.b = double([]);T.x = double([]);T.y = double([]);情况下“fixed8”T.b = fi([],true,8,7);T.x = fi([],true,8,7);T.y = fi([],true,8,6);情况下“fixed16”T.b = fi([],true,16,15);T.x = fi([],true,16,15);T.y = fi([],true,16,14);结束结束

表8所示。测试脚本,用于检查在类型表中为筛选函数使用不同定点数据类型的效果。

比较不同迭代的结果,以验证每次更改后算法的准确性(图4)。

图4。表8中的测试脚本图显示了转换为8位和16位定点类型后的输出和错误。

优化你的算法

有三种常用的方法来优化算法以提高性能并生成更高效的C代码。您可以:

  • 使用fimath属性来提高所生成代码的效率
  • 用更有效的定点实现取代内置函数
  • 重新执行除法操作

使用fimath提高生成代码效率的属性

当你使用默认时fimath设置时,生成额外的代码来实现饱和溢出、最接近舍入和全精度算术(表9a)。

MATLAB代码
正在编译的代码:函数Y = adder(a,b) Y = a + b;结束使用默认fimath设置定义的类型:T.a = fi([],1,16,0);T.b = fi([],1,16,0);A = cast(0,“喜欢”, T.a);B = cast(0,“喜欢”、肺结核);
生成的C代码
整数加法器(短a,短b){整数y;int钱数;int i1;int i2;int i3;I0 = a;I1 = b;如果((i0 & 65536) != 0) {i2 = i0 | -65536;}其他的{i2 = i0 & 65535;}如果((i1 & 65536) != 0) {i3 = i1 | -65536;}其他的{i3 = i1 & 65535;} i0 = i2 + i3;如果((i0 & 65536) != 0) {y = i0 | -65536;}其他的{y = i0 & 65535;}返回y;}

表9。原始的MATLAB代码和C代码生成的默认fimath设置。

要使生成的代码更高效,请选择与处理器类型匹配的定点数学设置。选择fimath属性的算术、舍入和溢出来定义对您的fi对象(表9b)。

MATLAB代码
正在编译的代码:函数Y = adder(a,b) Y = a + b;结束使用与处理器类型匹配的fimath设置定义的类型:F = fimath(…“RoundingMethod”“地板”,……“OverflowAction”“包装”,……“ProductMode”“KeepLSB”,……“ProductWordLength”32岁的……“SumMode”“KeepLSB”,……“SumWordLength”、32);a = fi([],1,16,0,F);b = fi([],1,16,0,F);A = cast(0,“喜欢”, T.a);B = cast(0,“喜欢”、肺结核);
生成的C代码
整数加法器(短a,短b) {返回A + b;}

表9 b。使用匹配处理器类型的fimath设置生成的原始MATLAB代码和C代码。

用定点实现替换内置函数

可以替换一些MATLAB函数,实现更高效的定点实现。例如,可以将内置函数替换为查找表实现或CORDIC实现它只需要迭代的shift-add操作。

重新实施分部行动

除法操作通常不完全由硬件支持,并可能导致处理缓慢。金宝app当你的算法需要除法运算时,考虑用一个更快的替代方法替换它。如果分母是2的幂,则使用位移位;例如,使用bitsra (x, 3)而不是x / 8.如果分母不变,则乘以倒数;例如,使用x * 0.2而不是x / 5

下一个步骤

在使用fixed-point Designer应用这些最佳实践将浮点代码转换为定点代码之后,请花点时间使用一组真实的测试输入彻底测试定点实现,并将比特真实的模拟结果与浮点基准进行比较。

发布于2014 - 92226v00