【CNN-FPGA开源项目解析】02--floatAdd16模块

2023-09-20 19:00:03

前言

​ 上一篇文章(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

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. 浮点数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. 浮点数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;                                     

结果标准化和舍位

​ 当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

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

mantissa = fraction[9:0];

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

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

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

其他

变量宽度表

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;

特殊情况处理

  • 输入中有一个为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

always敏感列表

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

always @ (floatA or floatB) begin
   /* -------------------- */ 
end

开源项目github-URL:CNN-FPGA

更多推荐

软件测试常问面试题

1、讲一下你最熟悉的模块是怎么测试的?2、fiddler如何抓https请求?步骤:设置浏览器http代理安装证书导入证书,端口号8888手机端获取fiddler的地址,配置无线局域网代理,安装手机证书。3、jmeter如何参数化(1)用户定义的变量添加一个线程组----添加一个配置元件—用户定义的变量。填写好变量名如

【网络技术】TCP详解

1TCP是什么TCP是TransmissionControlProtocol的缩写,即传输控制协议。TCP是一种面向连接的、可靠的、基于字节流的传输协议,是互联网通信协议TCP/IP中的一个重要组成部分。2三次握手三次握手的过程可以用以下图示表示:2.1详细介绍TCP协议使用三次握手(Three-wayhandshak

使用VScode SSH公网远程连接本地服务器开发【无公网IP内网穿透】

文章目录前言1、安装OpenSSH2、vscode配置ssh3.局域网测试连接远程服务器4.公网远程连接4.1ubuntu安装cpolar内网穿透4.2创建隧道映射4.3测试公网远程连接5.配置固定TCP端口地址5.1保留一个固定TCP端口地址5.2配置固定TCP端口地址5.3测试固定公网地址远程前言远程连接服务器工具

Vue3-初识Vue3、创建Vue3工程、vue3组合式API(setup、ref函数、reactive函数)、响应式原理、计算属性、监视属性

Vue3(1)目录Vue3(1)一、Vue3简介二、创建Vue3.0工程1、使用vue-cli创建2、使用vite创建三、常用的CompositionAPI(组合式API)1、拉开序幕的setup2、ref函数3、reactive函数4、Vue3中响应式原理(1)Vue2中响应式原理(2)Vue3中的Proxy(3)V

【C++STL基础入门】list改、查操作

文章目录前言一、list查操作1.1迭代器循环1.2for_each函数二、list改操作2.1迭代器修改2.2assign函数2.3=运算符总结前言C++标准模板库(STL)是C++语言中非常重要的部分,它提供了一组通用的模板类和函数,用于处理常见的数据结构和算法问题。其中之一是list(链表),它是一种双向链表容器

TOWE一转二家用无线遥控插座,让生活变得简单

随着科技的进步,人们的生活方式正在发生改变,越来越多的智能家居产品进入我们的生活中,为我们的生活带来了极大的便利。无线遥控插座作为一种集成了无线遥控技术与插座功能的创新产品,在家庭、办公、商业场景有着广泛的应用。同为科技(TOWE)一转二家用无线遥控插座是TOWE专为万物互联时代潮流趋势下开发的场景应用产品,为用户各类

python学习之【包和内置模块】

前言接上篇文章python学习之【模块】,这篇文章接着学习python中的包。python中的包包是一种用“点式模块名”构造Python模块命名空间的方法。在包中存放着一些功能相近的模块。包的创建和导入包的创建我们可以在pytarm中创建一个package文件,当我们创建完成后会自动创建一个__init__.py文件,

【Vue+NodeJS】vue路由及NodeJS环境搭建(Windows版)

一、Vue路由1、什么是Vue路由Vue路由是Vue.js框架中用于实现单页面应用(SPA)的路由管理器。它允许您创建多个页面之间的导航,并通过URL的变化来动态加载不同的组件。Vue路由通过声明式的方式定义页面的导航规则,并提供了一些内置的导航组件和功能,如路由链接、路由视图和导航守卫。通过Vue路由,您可以定义不同

数据预处理方式合集

删除空行#delallNonevaluedata_all.dropna(axis=1,how='all',inplace=True)删除空列#delallNonevaluedata_all.dropna(axis=0,how='all',inplace=True)缺失值处理观测缺失值观测数据缺失值有一个比较好用的工具包

蓝桥杯 题库 简单 每日十题 day6

01删除字符题目描述给定一个单词,请问在单词中删除t个字母后,能得到的字典序最小的单词是什么?输入描述输入的第一行包含一个单词,由大写英文字母组成。第二行包含一个正整数t。其中,单词长度不超过100,t小于单词长度。输出描述输出一个单词,表示答案。输入输出样例示例1输入LANQIAO3输出AIAO#include<st

【物联网】常见电子元器件(电阻、电容、电感、二极管、三极管)综合,详细分析原理及其应用

电子元器件是现代电子技术的基础,它们在各个领域中发挥着重要作用。从三极管到电容器、电阻器,这些常用元器件承担着放大、开关、滤波等关键任务。它们的特性和组合方式决定了电路的性能和功能。本文将介绍常用电子元器件的工作原理和应用场景,帮助读者更好地理解和运用它们。无论是电子爱好者还是专业工程师,对于电子元器件的了解都是必不可

热文推荐