Verilog 组合逻辑 FP32加法器

本文详细介绍了基于IEEE754标准的单精度浮点数加法器的实现过程,包括对阶、尾数求和、规格化和舍入等步骤。代码通过组合逻辑实现,特别处理了正负溢出、NaN和无穷大的情况,使用优先编码器优化了规格化部分,确保了可综合性和效率。此外,还讨论了如何处理加法过程中可能产生的特殊情况,如输入为inf和NaN的处理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

单精度浮点数
根据 IEEE 754 标准,单精度浮点数(float)为32位,其存储格式为:
单精度浮点数格式
sign:符号位,1bit,S
exponent:指数,8 bits,原码,E = (exponent)10-12710
fraction:尾数,23 bits,原码。在浮点数中隐去了小数点前的1,因此 M = 1+(fraction)10/223
该浮点数表示的10进制数为:(-1)S×M×2E

一般情况下,浮点数加法的处理步骤为:

  1. 对阶(align)
    通过exponent将计算数对齐到相同指数,尾数只有在指数相同时才可以做加减运算。对齐方式为向高阶对齐,即将阶数低的尾数右移,这样不会损失高位数据的精度。
  2. 尾数求和
  3. 规格化(normalization)
    将计算后的数据规格化。对于产生溢出位的结果右移;对于操作数符号位不同,产生结果多个高位为0时,需要左移,直到1出现在最高位。
  4. 舍入(round)
    因为计算过程中需要扩展位数保证精度,因此在输出浮点数结果前需要截断扩展的尾数。

但浮点数表示的数据范围有限,处理时会遇到正溢出、负溢出、数值无效的特殊情况,就需要对这些情况进行特殊处理:
Special Number 处理:
当exp=8’hff 且man=23’h0时,输入为inf,表示溢出,根据sign,可以为+inf/ -inf。
当exp=8’hff 且man!=23’h0时,表示NaN。

计算过程中可产生inf的情况:

  • input为存在+inf或-inf
  • rounding前的exp=8’hfe,且rounding产生了进位

计算过程中可产生NaN的情况:

  • input为NaN
  • input两操作数分别为+inf与-inf

Round 处理
就近舍入:
即十进制下的四舍五入。保留23位尾数,当需要舍去的数大于2-24,进一;小于2-24,舍去;当等于2-24时,舍向最近的偶数。

例如:

  • 多余数字是 (1001)2,它大于 (0.5)10,故最低位进 12
  • 多余数字是 (0111)2,它小于 (0.5)10,则直接舍掉多余数字。
  • 多余数字是 (1000)2,正好是等于 (0.5)10 的特殊情况;那么此时最低位为 02 则舍掉多余位,最低位为 12 则进位 12

对于浮点数加法实现方式的学习,可参考的代码大多数是通过时序逻辑实现的,其条理清晰,也容易理解,但大多用到循环语句,并不高效,在此不再进行赘述。以下是参考了Howeng98在GitHub上的组合逻辑实现的浮点数加法器。
链接: 参考github代码
用优先编码器修改了代码的normalization部分,使其可综合(原代码用while循环,不知道是怎么跑通的=_=);
添加了sepcial处理:针对输入为inf和NaN的情况,并对inf与NaN区分输出。

/* 对于数据格式的说明:
signed  exp       man
31      30...23   22...0
特殊情况:
  exponent  fraction  value
  0         zero      0
  0         non-zero  +-2^(-126)*0.(man)
  1~254     any       +-2^(exp-127)*1.(man)
  255       zero      +-inf
  255       non-zero  NaN
*/
module fpadder (
    input       [31:0]  src1,
    input       [31:0]  src2,
    output reg  [31:0]  out
    );
    reg [66:0]  fraction_1;
    reg [66:0]  fraction_2;
    reg [66:0]  sum;
    reg [66:0]  fraction_Ans;
    reg [7:0]   exponent_1;
    reg [7:0]   exponent_2;
    reg [7:0]   exponent_Ans;
    reg [4:0]   norm_count;
    reg         norm_vld;
    reg         sign_1;
    reg         sign_2;
    reg         sign_Ans;
    reg         guard_bit;
    reg         round_bit;
    reg         sticky_bit;
    reg         inf_1;
    reg         inf_2;
    reg         nan_1;
    reg         nan_2;
/ FP ADDER ///
    always@(*) begin 
        //loading
        begin
            fraction_1  = {2'd0,src1[22:0],42'd0};	// 对尾数扩展。前2bit第一位为进位位,第二位为隐藏的1,
            fraction_2  = {2'd0,src2[22:0],42'd0};	// 此处不赋值2'b01是留在special case中处理指数全0的情况。后42bit保障精度,可适当减少
            exponent_1  = src1[30:23];
            exponent_2  = src2[30:23];
            sign_1      = src1[31];
            sign_2      = src2[31]; 
            inf_1       = 1'b0;
            nan_1       = 1'b0;
            inf_2       = 1'b0;
            nan_2       = 1'b0;
        end    
        //preprocessing
        begin
            if(exponent_1 == 0) begin //when exponent is zero but fraction is non-zero,set it to 1
                exponent_1 = 1;
                fraction_1[65] = 0;    //make 0.(Frac) //参考代码开头对于特殊情况的说明
            end
            else
                fraction_1[65] = 1;            
            if(exponent_2 == 0) begin
                exponent_2 = 1;
                fraction_2[65] = 0;
            end
            else
                fraction_2[65] = 1;   //make 1.(Frac)
        end
        //special case
        begin
            if((exponent_1 == 0) && (fraction_1 == 0)) begin //if src1 is zero, then return src2
                sign_Ans     = sign_2;
                exponent_Ans = exponent_2;
                fraction_Ans = fraction_2;
            end
            else if (exponent_1 == 8'hff) begin
                if (fraction_1 == 23'h0) inf_1 = 1'b1;		// 补充原代码缺失的对inf与NaN的处理
                else nan_1 = 1'b1;
            end 
            else begin
            end
            if((exponent_2 == 0) && (fraction_2 == 0)) begin // if src2 is zero, then return src1
                sign_Ans     = sign_1;
                exponent_Ans = exponent_1;
                fraction_Ans = fraction_1;
            end  
            else if (exponent_2 == 8'hff) begin
                if (fraction_2 == 23'h0) inf_2 = 1'b1;
                else nan_2 = 1'b1;
            end 
            else begin
            end
        end    
        //align
        begin
            if(exponent_1 > exponent_2) begin
                fraction_2 = fraction_2 >> (exponent_1 - exponent_2);
                exponent_Ans = exponent_1;
            end
            else if(exponent_1 < exponent_2) begin
                fraction_1 = fraction_1 >> (exponent_2 - exponent_1);
                exponent_Ans = exponent_2;
            end
            else begin
                exponent_Ans = exponent_1;  
            end
        end
        //add significands
        begin
            if(sign_1 == sign_2) begin
                fraction_Ans = fraction_1 + fraction_2;
                sign_Ans = sign_1;
            end
            else begin
                if(fraction_1 >= fraction_2) begin
                    fraction_Ans = fraction_1 - fraction_2;
                    sign_Ans = sign_1;
                end
                else begin
                    fraction_Ans = fraction_2 - fraction_1;
                    sign_Ans = sign_2;
                end
            end
        end
        sum = fraction_Ans; //sum is for checking the addition of src1 and src2    
        //overflow
        begin
            if(fraction_Ans[66]) begin
                fraction_Ans = fraction_Ans >> 1;
                exponent_Ans = exponent_Ans + 1;
            end
        end    
        //normalization
        begin
            if(fraction_Ans == 67'b0) begin
                fraction_Ans = 67'b0;
                exponent_Ans = 8'b0;
            end
            else begin
                if(fraction_Ans[65] == 0)begin
                    PENC32({11'b0, fraction_Ans[64:42]}, norm_count, norm_vld);	// 用优先编码器计算最高位1所在bit
                    fraction_Ans = fraction_Ans << (5'd23 - norm_count);		// 左移完成规格化
                    exponent_Ans = exponent_Ans + norm_count - 5'd23;      		// 对应更新阶数
                    
			        // while((fraction_Ans[65] == 0) && (fraction_Ans[64:42] > 0))begin
			        //   fraction_Ans = fraction_Ans << 1;
			        //   exponent_Ans = exponent_Ans - 1;            
			        // end
                end    
                else if(fraction_Ans[65]) begin
                // do nothing
                end
            end
        end
        //round
        begin
            guard_bit = fraction_Ans[41]; 
            round_bit = fraction_Ans[40];
            if(fraction_Ans[39:0] > 0)
                sticky_bit = 1;  
            else
                sticky_bit = 0;                         
            if(guard_bit && (fraction_Ans[42] | round_bit | sticky_bit)) begin	// 对照进位的条件
                fraction_Ans = fraction_Ans + 67'b0000000000000000000000001000000000000000000000000000000000000000000;
            end
        end
        //convert:
        begin
            out[22:0]  = fraction_Ans[64:42];
            out[30:23] = exponent_Ans[7:0];
            out[31]    = sign_Ans; 
            //special case
            if(fraction_Ans == 0) //when fraction is 23'd0
                out = 0;
            if(nan_1 || nan_2 || (inf_1 && inf_2 && (sign_1 ^ sign_2))) begin // return NaN
                exponent_Ans = 8'hff;
                fraction_Ans = 23'h1;
            end
            else if(inf_1 || inf_2 || (exponent_Ans == 8'b11111111)) begin // return inf
                fraction_Ans = 0;
                out[22:0]  = 23'h0;
            end
            // if(exponent_Ans == 8'b11111111) //when exponent is 11111111
            //     fraction_Ans = 0;
            // out[22:0]  = fraction_Ans[64:42];
        end
    end
// PENC TASK // 用4个PENC8拼接实现32bit优先编码
    task PENC8;
        //port declaration
        input   [7:0] D;
        output  [2:0] Q;
        output        vld;
        //internal node signals declaration
        begin
            vld  = D[7]|D[6]|D[5]|D[4]|D[3]|D[2]|D[1]|D[0];
            Q[2] = D[7]|D[6]|D[5]|D[4];
            Q[1] = (D[7]|D[6]|D[5]|D[4]) ? (D[7]|D[6]) : (D[3]|D[2]);
            Q[0] = (D[7]|D[6]|D[5]|D[4]) ? (D[7]|((~D[6])&D[5])) : (D[3]|((~D[2])&D[1]));
        end
    endtask

    task PENC16;
        //port declaration
        input   [15:0] D;
        output  [3:0]  Q;
        output         vld;
        //internal node signals declaration
        reg [2:0] Q1_h, Q1_l;
        reg       vld1_h, vld1_l;
        begin
            PENC8( D[15:8],  Q1_h,   vld1_h);
            PENC8( D[7:0],   Q1_l,   vld1_l);
            Q[3]    = vld1_h;
            Q[2:0]  = vld1_h ? Q1_h : Q1_l;
            vld     = vld1_h | vld1_l;
        end
    endtask

    task PENC32;
        //port declaration
        input   [31:0] D;
        output  [4:0]  Q;
        output         vld;
        //internal node signals declaration
        reg [3:0] Q2_h, Q2_l;
        reg       vld2_h, vld2_l;
        begin
            PENC16( D[31:16],   Q2_h,   vld2_h);
            PENC16( D[15:0],    Q2_l,   vld2_l);
            Q[4]    = vld2_h;
            Q[3:0]  = vld2_h ? Q2_h : Q2_l;
            vld     = vld2_h | vld2_l;
        end
    endtask      
endmodule

这个代码应该还可以用assign的方式重写,将一些分支语句改为并行执行,然后通过mux输出,以获得更高的速度。

### Verilog 实现 32浮点数 加减法器 #### 设计概述 为了实现一个32浮点数加减法器,需要遵循 IEEE754 标准。该标准定义了浮点数的表示方式以及运算规则。具体来说,32浮点数由三部分组成:1位符号位、8位指数位和23位尾数位。 #### 主要模块结构 整个设计可以分为以下几个主要模块: - **输入处理**:解析两个操作数并提取符号位、指数位和尾数位。 - **对齐处理**:调整较小的操作数使其阶码与较大的相同。 - **相加/相减逻辑**:执行实际的加法或减法操作。 - **规格化处理**:确保结果满足IEEE754格式的要求。 - **溢出检测**:检查是否存在溢出情况。 - **输出组装**:将最终计算得到的结果重新组合成完整的32浮点数值。 #### 关键函数说明 以下是几个重要的功能单元及其作用: - `align_exponent` 函数用于使两者的指数一致; - `add_subtract_mantissa` 负责完成尾数之间的加减运算; - `normalize_result` 对结果进行必要的规范化处理; - `check_overflow_underflow` 检查是否有超出范围的情况发生; #### 完整代码示例 下面给出一段简化版本的SystemVerilog代码片段来展示基本框架[^1]: ```verilog module fp_adder ( input wire [31:0] a, b, output reg [31:0] result ); // 解析输入参数 logic sign_a, exp_a[7:0], mantissa_a[22:0]; assign {sign_a, exp_a, mantissa_a} = a; logic sign_b, exp_b[7:0], mantissa_b[22:0]; assign {sign_b, exp_b, mantissa_b} = b; // ... 中间省略若干行 ... endmodule :fp_adder ``` 这段代码展示了如何接收两个32浮点型变量作为输入,并准备对其进行进一步处理。需要注意的是,在真实的应用场景下还需要考虑更多细节,比如异常情况下的行为等。
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值