作者:碎碎思,文章来源:OpenFPGA
本篇文章将探索一种在 FPGA 设计中经常使用的实用技术——线性插值。
线性插值通过假设两个已知数据点之间存在直线关系来估计它们之间的未知值。常见的FPGA应用包括传感器线性化、伽马校正曲线、波形生成和数学函数逼近。
要计算插值,需要两个已知点 (X0,Y0) 和 (X1,Y1)。对于给定的输入 XXX,插值 YYY 可以使用以下公式计算:
Y=Y0+(X1−X0)(X−X0) / (Y1−Y0)
其中:
(X1−X0) / (Y1−Y0)
定义了两点之间直线的斜率。然后,将该斜率乘以到 X0X_0X0 的距离,并将其加到初始值 Y0Y_0Y0 上。
虽然从数学角度来看很简单,但这种公式引入了除法运算。尽管除法器可以在FPGA中实现,但它们会消耗额外的逻辑资源,增加设计复杂性,并且通常会显著地增加延迟。
幸运的是,可以通过定义两个 X 值之间的分数位置来重新表述该方程:
frac=(X1−X0) / (X−X0)
该分数介于 0 和 1 之间:
当 frac = 0 时,输出为 Y0。
当 frac = 1 时,输出为 Y1。
中间值提供按比例插值的结果
利用该分数项,插值方程变为:
Y=Y0+frac×(Y1−Y0)
这种形式通常被称为线性插值或 lerp 函数。
当然,我们还需要确定小数部分的值。如果直接计算,还需要再做一次除法。
然而,数字逻辑为我们提供了一个优雅的解决方案。
如果选择合适的插值点间距,使得:
(X1-X0)=2^N
这样一来,除法运算就简化为简单的右移操作。这使得我们可以完全去掉除号。
这种方法在实现插值查找表时特别有用。考虑一个由 16 位输入驱动的传感器线性化表和一个包含 256 个条目的查找表。
输入数据可以分为以下几类:
[15:8] LUT 地址
[7:0] 分数位置
高八位用于选择查找表项,低八位用于定义所选项与下一个项之间的位置。
之所以可行,是因为每个查找表段跨越 256 个输入计数。因此,低八位自然代表了该段内的小数位置。
举个例子,考虑以下输入值:
0x45D0
将该值拆分后得到:
Address = 0x45 = 69
Fraction = 0×D0 = 208
Address/地址值 69 用于从查找表条目 69 中读取 Y0。下一个表条目 70 提供 Y1。
分数是:
208 / 256 = 0.8125
这意味着输入值位于 Y0 和 Y1 之间的 81.25%。
认为:
Y0 = 1000Y1 = 1400
插值结果为:
Y=1000+(1400−1000)×0.8125Y
Y = 1000 + 325
Y=1325
从FPGA实现的角度来看,这可以非常有效地映射到可用的硬件资源上:
BRAM/LUTRAM -> 存储 Y0 和 Y1
减法器 -> Y1 - Y0
DSP模块 -> (Y1 - Y0) × 分母
Shift -> 除以 256
加法器 -> 添加 Y0
无需分频器,并且该设计可以轻松实现流水线化,从而实现高时钟频率。
这种方法构成了 FPGA 应用中使用的许多插值查找表的基础,在准确性和资源利用率之间提供了良好的平衡。
利用数字系统的2的幂特性,可以仅使用简单的算术运算和单个乘数来高效地实现线性插值。
因此,线性插值仍然是 FPGA 设计中实现传感器线性化、伽马校正、波形生成和数学函数逼近的最有用技术之一。
参考
https://www.adiuvoengineering.com/post/microzed-chronicles-linear-interpolation
一个简单的线性插值示例实现上传到 GitHub 存储库,供有兴趣进一步探索该技术的人参考。

