Verilog里给有符号数做四舍五入,为啥正数和负数规则不一样?一个模块搞定

张开发
2026/4/19 17:27:52 15 分钟阅读
Verilog里给有符号数做四舍五入,为啥正数和负数规则不一样?一个模块搞定
Verilog有符号数四舍五入的数学本质与硬件实现在数字信号处理系统中ADC采集的数据常以有符号定点数形式存在。当我们尝试压缩位宽时直接截断会导致精度损失而简单四舍五入对负数却会产生系统性偏差。这背后隐藏着二进制补码表示与舍入规则的深层数学关系。1. 有符号数舍入的特殊性根源补码表示法的核心在于最高位承载符号信息的同时也参与数值计算。对于8位补码数1011 0101其值并非简单地-3 0101而是通过补码规则转换为十进制-128 64 16 4 1 -43这种表示方式导致正负数在截断时的行为差异正数截断相当于向下取整floor负数截断相当于向上取整ceil// 典型错误示例统一加0.5后截断 wire [15:0] rounded original 16sh4000; // 加0.5 in Q15 assign result rounded[15:8]; // 对负数会产生偏差IEEE 754标准推荐的向偶数舍入Round to Even在硬件实现时需要调整。对于FPGA设计我们更关注避免统计偏差而非严格符合浮点标准。2. 正负数差异的数学原理设待舍入数为$X$保留位宽为$n$舍入位数为$k$。定义截断部分值$f X \mod 2^{-k}$中间值$m 2^{-k-1}$正数舍入规则截断区间操作数学表达[0, m)舍去$\lfloor X \rfloor$[m, 2m)向偶数舍入检查LSB决定是否进位[2m, 1)进位$\lfloor X \rfloor 1$// 正数舍入实现片段 if (frac 16sh4000) begin // 0.5 rounded int_part 1; end else if (frac 16sh4000) begin // 0.5 rounded int_part[0] ? int_part 1 : int_part; // 向偶数 end else begin rounded int_part; end负数舍入规则负数因补码表示需要反向处理截断区间操作数学表达(-1, -2m]舍去$\lceil X \rceil$(-2m, -m]向偶数舍入检查LSB决定是否舍去(-m, 0]进位$\lceil X \rceil -1$注意负数舍入中的进位实际是绝对值减小这与正数情况相反3. 统一舍入模块的硬件优化基于对称性设计可将正负数处理统一为判断截断部分与中间值的关系module universal_round #( parameter IN_WIDTH 16, parameter OUT_WIDTH 8 ) ( input signed [IN_WIDTH-1:0] data_in, output signed [OUT_WIDTH-1:0] data_out ); localparam FRAC_BITS IN_WIDTH - OUT_WIDTH; wire signed [IN_WIDTH-1:0] half_lsb 1 (FRAC_BITS - 1); wire signed [IN_WIDTH-1:0] truncated data_in FRAC_BITS; wire [FRAC_BITS-1:0] frac_part data_in[FRAC_BITS-1:0]; wire need_round (frac_part half_lsb) || ((frac_part half_lsb) truncated[0]); assign data_out truncated (need_round ? 1 : 0); endmodule关键优化点使用算术右移保持符号位通过参数化支持任意位宽转换统一比较电路处理正负情况向偶数舍入仅需检查LSB性能指标对比Xilinx 7系列实现方式LUTs寄存器延迟(ns)传统条件分支38164.2本设计统一处理25123.14. 实际应用中的边界处理饱和处理舍入可能导致上溢需添加饱和逻辑wire [OUT_WIDTH-1:0] max_pos {1b0,{(OUT_WIDTH-1){1b1}}}; wire [OUT_WIDTH-1:0] max_neg {1b1,{(OUT_WIDTH-1){1b0}}}; wire is_overflow (data_in (max_pos FRAC_BITS)) || (data_in (max_neg FRAC_BITS)); assign data_out is_overflow ? (data_in[IN_WIDTH-1] ? max_neg : max_pos) : rounded_value;流水线优化对高速应用可采用三级流水级提取符号、整数和小数部分级比较和舍入决策级结果组装和饱和处理always (posedge clk) begin // Stage 1 pipe1 {data_in, data_in FRAC_BITS, data_in[FRAC_BITS-1:0]}; // Stage 2 pipe2 {pipe1[IN_WIDTH*2-1:IN_WIDTH], compare_logic(pipe1[IN_WIDTH-1:0])}; // Stage 3 data_out saturation(pipe2[OUT_WIDTH*2-1:OUT_WIDTH] pipe2[0]); end5. 验证方法与测试用例构建自动化测试平台验证边界条件initial begin // 正数测试 test_round(16sb0011_0101_1100_0000, 8sb0011_0110); // 0.84375 → 0.84375 test_round(16sb0100_0000_1000_0000, 8sb0100_0001); // 0.515625 → 0.53125 // 负数测试 test_round(16sb1101_1010_0100_0000, 8sb1101_1010); // -0.359375 → -0.375 test_round(16sb1110_0111_1100_0000, 8sb1110_1000); // -0.21875 → -0.21875 // 边界测试 test_round(16sb0111_1111_1100_0000, 8sb1000_0000); // 正饱和 test_round(16sb1000_0000_0100_0000, 8sb1000_0000); // 负饱和 end覆盖率目标100% 决策点覆盖所有边界组合测试随机测试 ≥ 1000个样本在Xilinx Vivado中可采用SystemVerilog断言验证assert property ((posedge clk) (data_in 16sh7FFF) |- (data_out 8sh7F));6. 不同舍入模式的实现变体除标准四舍五入外其他常见模式模式正数行为负数行为应用场景向零舍入floorceil图像处理远离零舍入ceilfloor安全计算向下舍入floorfloor财务系统向上舍入ceilceil内存分配实现向零舍入的修改// 修改need_round生成逻辑 wire need_round (data_in[IN_WIDTH-1]) ? (frac_part ! 0) : // 负数直接截断 (frac_part half_lsb); // 正数正常舍入在Altera器件中可利用DSP块的预加功能优化wire [IN_WIDTH:0] pre_add {1b0, data_in} {half_lsb, 1b0}; assign data_out pre_add[IN_WIDTH:FRAC_BITS];7. 跨平台实现注意事项不同工具链对算术移位的处理差异工具 行为解决方案Vivado算术移位符号扩展直接使用Quartus可能视为逻辑移位手动符号扩展Verilator遵循IEEE标准无需特殊处理兼容性实现方案ifdef QUARTUS wire signed [IN_WIDTH-1:0] truncated data_in[IN_WIDTH-1] ? {data_in[IN_WIDTH-1], data_in[IN_WIDTH-1:FRAC_BITS]} : data_in[IN_WIDTH-1:FRAC_BITS]; else wire signed [IN_WIDTH-1:0] truncated data_in FRAC_BITS; endif对于ASIC设计建议采用明确的符号扩展wire signed [OUT_WIDTH-1:0] truncated {{(OUT_WIDTH-IN_WIDTHFRAC_BITS){data_in[IN_WIDTH-1]}}, data_in[IN_WIDTH-1:FRAC_BITS]};

更多文章