FPGA 定点数缩位:截断、舍入与饱和,避免溢出回绕

FPGA 定点数缩位:截断、舍入与饱和,避免溢出回绕

乘加结果接到较窄的输出端口时,截掉几位看起来很直接。真正需要先确定的是:丢弃小数位时怎样取近似值,整数范围不足时怎样处理溢出。这两件事分别影响量化误差和大幅度信号行为,不能用一句"做个截断"代替。本篇先建立整数参考模型,用穷举把负数、中点和进位边界检查清楚,再用于后续 RTL 对照。

1. 把格式写完整

只写 Qm.n 容易产生歧义:不同资料对 m 是否包含符号位的约定可能不同。这里统一用三项定义:总位宽 W、小数位数 F、有符号补码。存储整数为 R,真实值为 R×2^(-F),范围是 -2^(W-1-F) 到 2(W-1-F)-2(-F)。步进为 2^(-F)。补码负端比正端多一个量化台阶。精度与范围说明

例如 W=8、F=4 表示 -8 到 7.9375。改为 W=4、F=2 后,范围变成 -2 到 1.75,步进由 1/16 增至 1/4。这里既少了小数精度,也缩小了可表示范围;仅仅右移不能解决第二个问题。

下面限定 F_in≥F_out,令 d=F_in-F_out,目标存储整数的理想值为 R/2^d。输入 R 必须是已解码的有符号整数,不是尚未解释符号的十六进制码字。

2. 负数的截断不等于向零

令 D=2^d,做欧几里得除法 R=qD+r,其中 q 向负无穷取整,0≤r<D。Python 的 divmod 对正除数正好符合此约定。

以 R=-7、d=2 为例,理想输出整数是 -1.75。直接丢弃补码低位得到 -2,即 floor;向零取整得到 -1。两者在正数上可能一致,在负数上却差一个输出 LSB。MathWorks 也明确区分补码低位截断与向零取整。舍入模式说明

因此 floor 直接取 q;向零模式仅在 R<0 且 r≠0 时取 q+1。别把"truncation"这个词当作跨语言、跨库完全一致的接口约定,应写明数学方向。

3. 最近偶数舍入怎样处理中点

最近舍入还需要规定正好半格时选哪边。本例采用 ties-to-even:距离相等时选偶数输出整数。例如 -2.5→-2、-1.5→-2、-0.5→0,正数 0.5→0、1.5→2。

基于 q、r,只需判断:2r>D 时加一;2r=D 且 q 为奇数时也加一,其余保留 q。这对负数同样成立,不需要给负数另加一个"负半格偏置"。当 d=0 时余数为零,算法自然保持输入,不应构造 2^(d-1) 这样的特殊常量。

偶数舍入避免把所有中点固定推向同一方向,但不能保证任意信号序列的平均误差都是零。量化误差还依赖数据分布;若后续发生饱和,误差也不再受半个 LSB 限制。

4. 饱和必须赶在窄化之前

输出 W 位有符号整数的边界为 L=-2(W-1)、H=2(W-1)-1。先在足够宽的域中得到舍入结果 Q,再计算 min(H,max(L,Q)),最后编码为 W 位。

取 W=4、d=2、R=30,理想目标整数是 7.5,偶数舍入产生 8。虽然 floor 结果 7 能放进去,舍入进位却越过正端。饱和输出应为 7;若先只保留四位,8 会被解释成 -8,此后再检查范围已经无法发现原来的正溢出。

负端也要独立覆盖:R=-36 对应 -9,饱和为 -8,回绕则为 7。饱和是夹到端点,回绕是模运算;两种策略都可以被明确选择,但不能混用。厂商工具同样将舍入模式与溢出动作分开配置。转换与溢出设置

整数模型利用 Python 的任意精度整数保留进位。移植到硬件时,中间位宽必须容纳舍入前后的值;不能让加法器先溢出,再期待比较器补救。

5. 怎样选规则并定位误差

不发生饱和时,若误差定义为"输出真实值减输入真实值",floor 的误差位于负一个输出 LSB 到零之间,且不包含负一个 LSB;向零取整的误差方向随输入符号改变;最近偶数舍入的绝对误差不超过半个输出 LSB。调试时可以先按这些界限检查正常幅度样本,再单独观察越界样本,避免把削顶误认为普通量化噪声。

不要在乘法输出、加法输出和最终接口上随意重复缩位。多次量化可能与宽域运算后的一次量化不同:例如目标整数值 1.375,先舍入到半整数网格得到 1.5,再舍入到整数得到 2;直接舍入到整数则得到 1。两步都用最近偶数,结果仍然不同。哪些节点需要保留保护位,应由数据范围与误差预算决定。

如果系统规定模运算,例如某些相位累加路径,回绕可以是期望行为;如果输出代表受限幅度,饱和更容易保持端点方向。最终选择应服从算法接口,不宜仅因某种写法少几行代码就更改数值规则。软件模型保留两种溢出动作,正是为了让这项选择能被单独测试。

6. 可直接运行的整数模型

保存以下代码为 demo.py,用 Python 3 执行。模型没有浮点除法,也不借助 Python 的 round,避免参考值自身引入另一套舍入规则。oracle 使用符号与绝对值独立计算,再与商余数算法对照。

python 复制代码
"""Integer-only fixed-point narrowing model; Python 3, no dependencies."""

def rounded(raw, drop, mode):
    if drop < 0:
        raise ValueError("drop must be nonnegative")
    den = 1 << drop
    q, r = divmod(raw, den)  # floor quotient; 0 <= r < den
    if mode == "floor":
        return q
    if mode == "zero":
        return q + int(raw < 0 and r != 0)
    if mode == "even":
        return q + int(2*r > den or (2*r == den and q % 2 != 0))
    raise ValueError("unknown rounding mode")


def narrow(raw, drop, width, mode="even", overflow="sat"):
    if width < 2:
        raise ValueError("width must be at least 2")
    q = rounded(raw, drop, mode)
    lo, hi = -(1 << (width-1)), (1 << (width-1))-1
    if overflow == "sat":
        return min(hi, max(lo, q))  # clamp BEFORE packing into width bits
    if overflow == "wrap":
        return (q-lo) % (1 << width) + lo
    raise ValueError("unknown overflow mode")


def oracle_round(raw, drop, mode):
    # Independent sign/magnitude formulation, never floating point.
    den = 1 << drop
    a, rem = divmod(abs(raw), den)
    if mode == "floor":
        return -a-int(rem != 0) if raw < 0 else a
    if mode == "zero":
        return -a if raw < 0 else a
    if rem*2 > den or (rem*2 == den and a % 2):
        a += 1
    return -a if raw < 0 else a


def self_test():
    checks = 0
    for win in range(3, 9):
        for wout in range(2, win):
            lo, hi = -(1 << (wout-1)), (1 << (wout-1))-1
            for fin in range(win):
                for fout in range(fin+1):
                    drop = fin-fout
                    for raw in range(-(1 << (win-1)), 1 << (win-1)):
                        for mode in ("floor", "zero", "even"):
                            q = oracle_round(raw, drop, mode)
                            assert rounded(raw, drop, mode) == q
                            sat = lo if q < lo else hi if q > hi else q
                            # Obtain wrapping independently from low-bit pattern.
                            bits = q & ((1 << wout)-1)
                            wrap = bits-(1 << wout) if bits & (1 << (wout-1)) else bits
                            assert narrow(raw, drop, wout, mode, "sat") == sat
                            assert narrow(raw, drop, wout, mode, "wrap") == wrap
                            assert raw >> drop == rounded(raw, drop, "floor")
                            checks += 1
    assert [rounded(x, 1, "even") for x in (-5, -3, -1, 1, 3, 5)] == [-2, -2, 0, 0, 2, 2]
    assert narrow(31, 2, 4) == 7 and narrow(31, 2, 4, overflow="wrap") == -8
    assert narrow(-36, 2, 4) == -8 and narrow(-36, 2, 4, overflow="wrap") == 7
    print(f"PASS: {checks} exhaustive rounding cases; saturation and wrap both checked")
    print("PASS: positive/negative ties, input extrema, zero-drop, rounding carry")
    print("raw floor zero even sat4 wrap4 (drop=2)")
    for raw in (-36, -32, -7, -6, -2, 2, 6, 30, 31):
        row = [raw] + [rounded(raw, 2, m) for m in ("floor", "zero", "even")]
        row += [narrow(raw, 2, 4, "even", o) for o in ("sat", "wrap")]
        print(*row)


if __name__ == "__main__":
    self_test()

7. 实际测试结果与使用边界

本次运行穷举输入位宽 3~8 位、输出位宽 2~W_in-1 位,F_in 从 0 到 W_in-1,F_out 从 0 到 F_in;每组遍历全部输入码值及三种舍入模式。每个计数项均检查舍入结果、饱和结果和回绕结果,并覆盖 d=0、正负端点及中点。不同格式可能对应相同 d,计数保留这些格式组合。

运行输出如下,表中全部是存储整数,sat4、wrap4 使用偶数舍入;若 F_out=2,还需除以 4 才是真实值。

text 复制代码
PASS: 241200 exhaustive rounding cases; saturation and wrap both checked
PASS: positive/negative ties, input extrema, zero-drop, rounding carry
raw floor zero even sat4 wrap4 (drop=2)
-36 -9 -9 -9 -8 7
-32 -8 -8 -8 -8 -8
-7 -2 -1 -2 -2 -2
-6 -2 -1 -2 -2 -2
-2 -1 0 0 0 0
2 0 0 0 0 0
6 1 1 2 2 2
30 7 7 8 7 -8
31 7 7 8 7 -8

这 241200 组是软件模型的穷举检查,不是 RTL 仿真或板级验证,也不说明资源、频率或吞吐率。落到 RTL 后,应将同一批向量送入设计,按实际流水延迟比对输出,并额外检查复位和有效信号。验收数值接口时,把 W、F、补码符号、舍入规则、溢出动作一起写入规格,才能让算法模型和硬件实现讨论同一个结果。

相关推荐
hahaha601616 小时前
3D降噪算法YUV降噪FPGA实现
fpga开发
Hello-FPGA2 天前
基于RFSOC FPGA ZCU216 的CoaXPress 采集demo
fpga开发
9527华安2 天前
FPGA基于ADV7182解码PAL视频,Video Processing Subsystem去隔行,提供6套工程源码和技术支持
fpga开发·音视频·pal·adv7182
hahaha60162 天前
数字图像时域滤波或者3D降噪算法FPGA实现
算法·计算机视觉·fpga开发
GateWorld2 天前
Xilinx 7系列FPGA时钟网络详解
fpga开发·时钟网络·xilinx 7系列
szxinmai主板定制专家2 天前
X86 工控机 vs RK3588 异构主控:AMR 控制器 BOM、散热、长期运维对比
运维·stm32·单片机·嵌入式硬件·fpga开发·am62x
szxinmai主板定制专家3 天前
AMR 项目落地:机器人控制器硬件定制化需求复盘
arm开发·单片机·fpga开发·系统架构·机器人·am62x
FPGA小迷弟4 天前
FPGA 与卫星:从星载硬件到在轨智能的完整技术解析
fpga开发·架构·fpga·1024程序员节·无线电
NamoAmitabha1284 天前
CPU 内部结构是什么?---deepseek
fpga开发
abc light4 天前
ARM 裸机开发(6)——时钟树与定时器
arm开发·单片机·fpga开发