• 【CNN-FPGA开源项目解析】卷积层02--floatAdd16模块


    前言

    ​ 上一篇文章(floatMult16模块解析)内,已经详细阐述了"半精度浮点数"的含义和乘法运算方法。同时,我们结合了开源的代码,逐步分析了"乘法模块"的具体实现细节。

    ​ 这一篇文章将继续上一篇的思路,分析半精度浮点数加法(floatAdd16)模块的构建思路和代码实现。

    浮点数加法的思路

    ​ 对于两个二进制指数的加法,我们完全可以参考平时我们运算指数的习惯去计算。举个例子:
    5 ∗ 2 3 + 7 ∗ 2 5 = ( 5 ∗ 2 − 2 ) ∗ 2 3 + 2 + 7 ∗ 2 5 = ( 5 ∗ 2 − 2 + 7 ) ∗ 2 5 = . . . 5*2^{3}+7*2^{5} = (5*2^{-2})*2^{3+2}+7*2^{5} = (5*2^{-2}+7)*2^{5}=... 523+725=(522)23+2+725=(522+7)25=...
    ​ 我们依次做了这些事情:

    • 两个指数不同时,指数化为一致;
    • 将幂指数前面的常数相加;
    • 运算得到最终结果。

    ​ 对于16bit的半精度浮点数而言,我们也完全可以这样做。只不过具体的运算有些小的出入,同时需要考虑进位和舍位等细节问题。

    • 指数化为一致:

      (小的往大的化,指数底数同步变化)

    ∣ X E − Y E ∣ = Δ E { X E 较大,化 Y = ( Y S > > Δ E ) ∗ 2 X E Y E 较大,化 X = ( X S > > Δ E ) ∗ 2 Y E |X_{E}-Y_{E}|= \Delta E \left\{\begin{matrix}X_{E} 较大,化Y=(Y_{S}>>\Delta E)*2^{X_{E}}\\Y_{E} 较大,化X=(X_{S}>>\Delta E)*2^{Y_{E}}\end{matrix}\right. XEYE=ΔE{XE较大,化Y=(YS>>ΔE)2XEYE较大,化X=(XS>>ΔE)2YE

    • 相加:

    令处理后的结果是 : { X = X S ′ ∗ 2 m a x { X E , Y E } Y = Y S ′ ∗ 2 m a x { X E , Y E } 相加 : X + Y = ( X S ′ + Y S ′ ) ∗ 2 m a x { X E , Y E } 令处理后的结果是:\left\{\begin{matrix}X=X_S^{'}*2^{max\{X_E,Y_E\}}\\Y=Y_S^{'}*2^{max\{X_E,Y_E\}} \end{matrix}\right. \\ 相加: X+Y=(X_S^{'}+Y_S^{'})*2^{max\{X_E,Y_E\}} 令处理后的结果是:{X=XS2max{XE,YE}Y=YS2max{XE,YE}相加:X+Y=(XS+YS)2max{XE,YE}

    • 处理底数相加时的进位。最后结果舍位到正确的位数。

    ​ 由于指数仅需作一致化处理即可,所以本模块需要在处理"底数"的溢出等情况下加大力度。


    floatAdd16完整代码

    `timescale 100 ns / 10 ps
    
    module floatAdd16 (floatA,floatB,sum);
    	
    input [15:0] floatA, floatB;
    output reg [15:0] sum;
    
    reg sign;
    reg signed [5:0] exponent; //fifth bit is sign
    reg [9:0] mantissa;
    reg [4:0] exponentA, exponentB;
    reg [10:0] fractionA, fractionB, fraction;	//fraction = {1,mantissa}
    reg [7:0] shiftAmount;
    reg cout;
    
    always @ (floatA or floatB) begin
    	exponentA = floatA[14:10];
    	exponentB = floatB[14:10];
    	fractionA = {1'b1,floatA[9:0]};
    	fractionB = {1'b1,floatB[9:0]}; 
    	
    	exponent = exponentA;
    
    	if (floatA == 0) begin						//special case (floatA = 0)
    		sum = floatB;
    	end else if (floatB == 0) begin					//special case (floatB = 0)
    		sum = floatA;
    	end else if (floatA[14:0] == floatB[14:0] && floatA[15]^floatB[15]==1'b1) begin
    		sum=0;
    	end else begin
    		if (exponentB > exponentA) begin
    			shiftAmount = exponentB - exponentA;
    			fractionA = fractionA >> (shiftAmount);
    			exponent = exponentB;
    		end else if (exponentA > exponentB) begin 
    			shiftAmount = exponentA - exponentB;
    			fractionB = fractionB >> (shiftAmount);
    			exponent = exponentA;
    		end
    		if (floatA[15] == floatB[15]) begin			//same sign
    			{cout,fraction} = fractionA + fractionB;
    			if (cout == 1'b1) begin
    				{cout,fraction} = {cout,fraction} >> 1;
    				exponent = exponent + 1;
    			end
    			sign = floatA[15];
    		end else begin						//different signs
    			if (floatA[15] == 1'b1) begin
    				{cout,fraction} = fractionB - fractionA;
    			end else begin
    				{cout,fraction} = fractionA - fractionB;
    			end
    			sign = cout;
    			if (cout == 1'b1) begin
    				fraction = -fraction;
    			end else begin
    			end
    			if (fraction [10] == 0) begin
    				if (fraction[9] == 1'b1) begin
    					fraction = fraction << 1;
    					exponent = exponent - 1;
    				end else if (fraction[8] == 1'b1) begin
    					fraction = fraction << 2;
    					exponent = exponent - 2;
    				end else if (fraction[7] == 1'b1) begin
    					fraction = fraction << 3;
    					exponent = exponent - 3;
    				end else if (fraction[6] == 1'b1) begin
    					fraction = fraction << 4;
    					exponent = exponent - 4;
    				end else if (fraction[5] == 1'b1) begin
    					fraction = fraction << 5;
    					exponent = exponent - 5;
    				end else if (fraction[4] == 1'b1) begin
    					fraction = fraction << 6;
    					exponent = exponent - 6;
    				end else if (fraction[3] == 1'b1) begin
    					fraction = fraction << 7;
    					exponent = exponent - 7;
    				end else if (fraction[2] == 1'b1) begin
    					fraction = fraction << 8;
    					exponent = exponent - 8;
    				end else if (fraction[1] == 1'b1) begin
    					fraction = fraction << 9;
    					exponent = exponent - 9;
    				end else if (fraction[0] == 1'b1) begin
    					fraction = fraction << 10;
    					exponent = exponent - 10;
    				end 
    			end
    		end
    		mantissa = fraction[9:0];
    		if(exponent[5]==1'b1) begin //exponent is negative
    			sum = 16'b0000000000000000;
    		end
    		else begin
    			sum = {sign,exponent[4:0],mantissa};
    		end		
    	end		
    end
    
    endmodule
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102

    floatMult16代码逐步解析

    指数化为一致

    变量解释:

    • shiftAmount : 两数的指数之差。
    • exponent:化为一致后的指数。即max{A,B}。
    if (exponentB > exponentA) begin
    	shiftAmount = exponentB - exponentA;
    	fractionA = fractionA >> (shiftAmount);
    	exponent = exponentB;
    end else if (exponentA > exponentB) begin 
    	shiftAmount = exponentA - exponentB;
    	fractionB = fractionB >> (shiftAmount);
    	exponent = exponentA;
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    底数相加,处理进位溢出

    1. 浮点数A和B符号相同时:可能出现"进位"。加法进位处理思路:
    • 用cout存储溢出的一位,初始为0。
    • 如果cout=1说明有进位,此时为了继续保持原先的位数,需要把最末一位舍弃,最高位cout纳入
    if (floatA[15] == floatB[15]) begin	
    	{cout,fraction} = fractionA + fractionB;
    	if (cout == 1'b1) begin
            {cout,fraction} = {cout,fraction} >> 1;  //把最末一位舍弃,最高位cout纳入
    		exponent = exponent + 1;
    	end
    	sign = floatA[15];
    end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    1. 浮点数A和B符号不同时:运算结果可正可负。
    • 二进制小数减大数的算法:

    B > A A − B = A + [ B ] 补码 = A + ( [ B ] 反码 + 1 ) B > A \\ A - B = A+ [B]_{补码} = A + ([B]_{反码} + 1) B>AAB=A+[B]补码=A+([B]反码+1)

    ​ 在这个过程中,若被减数较小减数较大,运算时产生进位导致cout=1,指示了这是一个负数的结果。

    else begin         
        if(floatA[15] == 1'b1) begin   //[注] 1:- ; 0:+                   
            {cout,fraction} = fractionB - fractionA;        
        end	else begin                                   
            {cout,fraction} = fractionA - fractionB;        
        end                                    
    sign = cout;                                     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    结果标准化和舍位

    ​ 当A与B符号相同时,通过"移1位"的操作即可标准化运算结果。

    ​ 但是当A与B符号不同时,可能有多重情况了:

    • 若大数减小数:10+(-2)=8,此时结果为正,cout=0,但可能不是1.xx的标准浮点形式。
    • 若小数减大数:2+(-10)=-8,此时结果为负,cout=1,也可能不是1.xx的标准浮点形式。

    ​ 对于上述两种需要标准化处理的情况,处理如下:

    • 大数减小数,cout=0:寻找到最高的非零位,移位。
    • 小数减大数,cout=1:结果取负。(因为最后cout要被舍去,底数的正负必须由自己来表征)
    			if (cout == 1'b1) begin
    				fraction = -fraction; 
    			end else begin
    			end
    			if (fraction [10] == 0) begin
    				if (fraction[9] == 1'b1) begin
    					fraction = fraction << 1;
    					exponent = exponent - 1;
    				end else if (fraction[8] == 1'b1) begin
    					fraction = fraction << 2;
    					exponent = exponent - 2;
    				end else if (fraction[7] == 1'b1) begin
    					fraction = fraction << 3;
    					exponent = exponent - 3;
    				end else if (fraction[6] == 1'b1) begin
    					fraction = fraction << 4;
    					exponent = exponent - 4;
    				end else if (fraction[5] == 1'b1) begin
    					fraction = fraction << 5;
    					exponent = exponent - 5;
    				end else if (fraction[4] == 1'b1) begin
    					fraction = fraction << 6;
    					exponent = exponent - 6;
    				end else if (fraction[3] == 1'b1) begin
    					fraction = fraction << 7;
    					exponent = exponent - 7;
    				end else if (fraction[2] == 1'b1) begin
    					fraction = fraction << 8;
    					exponent = exponent - 8;
    				end else if (fraction[1] == 1'b1) begin
    					fraction = fraction << 9;
    					exponent = exponent - 9;
    				end else if (fraction[0] == 1'b1) begin
    					fraction = fraction << 10;
    					exponent = exponent - 10;
    				end 
    			end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    舍位:舍弃运算过程中的辅助符号位cout。

    mantissa = fraction[9:0];
    
    • 1

    整合为最后的16位浮点数结果[sign,exponent,fraction]

    使用"拼接"语法,最后结果product为16位。

    product = {sign,exponent[4:0],mantissa};
    
    • 1

    其他

    变量宽度表

    input [15:0] floatA, floatB;
    output reg [15:0] sum;
    
    reg sign;
    reg signed [5:0] exponent;   //fifth bit is sign
    reg [9:0] mantissa;
    reg [4:0] exponentA, exponentB;
    reg [10:0] fractionA, fractionB, fraction;	//fraction = {1,mantissa}
    reg [7:0] shiftAmount;
    reg cout;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    特殊情况处理

    • 输入中有一个为0。
    • 输入与输出大小相同,符号相反。

    (个人认为其实这里意义虽然不大但还是有的,如果运算量庞大,上亿次的这种特殊运算能节省不少的时空)

    	if (floatA == 0) begin						
    		sum = floatB;
    	end else if (floatB == 0) begin					
    		sum = floatA;
    	end else if (floatA[14:0] == floatB[14:0] && floatA[15]^floatB[15]==1'b1) begin
    		sum=0;
    	end else begin
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    always敏感列表

    本模块非时序。一旦有输入发生改变,立刻执行模块功能。

    always @ (floatA or floatB) begin
       /* -------------------- */ 
    end
    
    • 1
    • 2
    • 3

    开源项目github-URL:CNN-FPGA

  • 相关阅读:
    七月集训(第05天) —— 双指针
    linux动静态库
    46届世界技能大赛湖北省选拔赛wp 3.0
    管理会计学复习题集
    2、Eureka的细节
    苹果“慌了”,中国客户不买账,这次要提供“折扣”可谓罕见
    聊聊ip与mac地址之间那些事
    【Linux】详解套接字编程
    Java每日一练
    Mesh快连
  • 原文地址:https://blog.csdn.net/GalaxyerKw/article/details/133094419