【CNN-FPGA开源项目解析】01--floatMult16模块

news/2024/7/10 22:22:53 标签: fpga开发, cnn, 开源

文章目录

    • (基础)半精度浮点数的表示和乘运算
      • 16位半精度浮点数
      • 浮点数的乘运算
    • floatMult16完整代码
    • floatMult16代码逐步解析
      • 符号位sign判断
      • 指数exponent计算
      • 尾数fraction计算
      • 尾数fraction的标准化和舍位
      • 整合为最后的16位浮点数结果[sign,exponent,fraction]
    • 其他
      • 变量宽度表
      • always敏感列表
      • 特殊情况处理

(基础)半精度浮点数的表示和乘运算

16位半精度浮点数

在这里插入图片描述

浮点数的乘运算

根本原理式:
X = X S ⋅ 2 X E Y = Y S ⋅ 2 Y E X ⋅ Y = ( X S ∗ Y S ) ⋅ 2 X E + Y E X = X_{S} · 2^{X_{E}} \\ Y = Y_{S} · 2^{Y_{E}} \\ X·Y = (X_{S}*Y_{S}) · 2^{X_{E} + Y_{E}} X=XS2XEY=YS2YEXY=(XSYS)2XE+YE
基本流程:

  • 判符号。

  • 算尾数:结果的尾数是输入两数的尾数之积。(未标准化)

  • 算指数:结果的指数是输入两数的指数之和。(未标准化)

  • 标准化和舍位:

    ① 结果化为二级制(1.xx)的形式,取出尾数。

    ② 舍去低位,保留高位。


floatMult16完整代码

完整代码:

`timescale 100 ns / 10 ps

module floatMult16 (floatA,floatB,product);

input [15:0] floatA, floatB;
output reg [15:0] product;

reg sign;
reg signed [5:0] exponent; //6th bit is the sign
reg [9:0] mantissa;
reg [10:0] fractionA, fractionB;	//fraction = {1,mantissa}
reg [21:0] fraction;


always @ (floatA or floatB) begin
	if (floatA == 0 || floatB == 0) begin
		product = 0;
	end else begin
		sign = floatA[15] ^ floatB[15];
		exponent = floatA[14:10] + floatB[14:10] - 5'd15 + 5'd2;
	
		fractionA = {1'b1,floatA[9:0]};
		fractionB = {1'b1,floatB[9:0]};
		fraction = fractionA * fractionB;
		
		if (fraction[21] == 1'b1) begin
			fraction = fraction << 1;
			exponent = exponent - 1; 
		end else if (fraction[20] == 1'b1) begin
			fraction = fraction << 2;
			exponent = exponent - 2;
		end else if (fraction[19] == 1'b1) begin
			fraction = fraction << 3;
			exponent = exponent - 3;
		end else if (fraction[18] == 1'b1) begin
			fraction = fraction << 4;
			exponent = exponent - 4;
		end else if (fraction[17] == 1'b1) begin
			fraction = fraction << 5;
			exponent = exponent - 5;
		end else if (fraction[16] == 1'b1) begin
			fraction = fraction << 6;
			exponent = exponent - 6;
		end else if (fraction[15] == 1'b1) begin
			fraction = fraction << 7;
			exponent = exponent - 7;
		end else if (fraction[14] == 1'b1) begin
			fraction = fraction << 8;
			exponent = exponent - 8;
		end else if (fraction[13] == 1'b1) begin
			fraction = fraction << 9;
			exponent = exponent - 9;
		end else if (fraction[12] == 1'b0) begin
			fraction = fraction << 10;
			exponent = exponent - 10;
		end 
	
		mantissa = fraction[21:12];
		if(exponent[5]==1'b1) begin //exponent is negative
			product=16'b0000000000000000;
		end
		else begin
			product = {sign,exponent[4:0],mantissa};
		end
	end
end

endmodule

floatMult16代码逐步解析

符号位sign判断

​ 正正得正,负负得正,正负得负。用异或运算即可。

sign = floatA[15] ^ floatB[15];

指数exponent计算

​ floatA与floatB的指数相加,初步得到了乘法结果的指数(尚未标准化)。

  • 后面进行标准化时,指数要随着"尾数小数点"的移动而变化。
exponent = floatA[14:10] + floatB[14:10] - 5'd15 + 5'd2;

尾数fraction计算

​ floatA与floatB的尾数相乘,初步得到了乘法结果的尾数(尚未标准化)。

  • 输入的两个浮点数是上一级操作完成的。fraction为省略整数1的小数部分,因此运算时需要先把这个整数1还回去。
  • 此步得到的运算结果尚未经历标准化。
  • 两个10bit的二进制相乘结果先暂存为20bit,避免在乘法过程中造成精度损失。后续再进行移位和舍位。
//输入的两个浮点数A,B都是标准的。把整数1先借回去,以便参与运算。
fractionA = {1'b1,floatA[9:0]};
fractionB = {1'b1,floatB[9:0]};
//尾数相乘
fraction = fractionA * fractionB;

尾数fraction的标准化和舍位

​ 通过小数点的移动,将运算结果变为二进制(1.xx)的形式。尾数即取小数点后(xx)的部分。同时,在这个过程中"指数"要同步发生变化。

  • 尾数小数点"左移n位",二进制右移:指数+n。
  • 尾数小数点"右移n位",二进制左移:指数-n。

代码的思路是:

  • 从乘法结果尾数的高位开始,寻找到最高位的1。通过左移,将"这一位"变为最高位。

  • 半精度浮点数尾数位只取5位。舍去低位。

标准化:

		if (fraction[21] == 1'b1) begin
			fraction = fraction << 1;
			exponent = exponent - 1; 
		end else if (fraction[20] == 1'b1) begin
			fraction = fraction << 2;
			exponent = exponent - 2;
		end else if (fraction[19] == 1'b1) begin
			fraction = fraction << 3;
			exponent = exponent - 3;
		end else if (fraction[18] == 1'b1) begin
			fraction = fraction << 4;
			exponent = exponent - 4;
		end else if (fraction[17] == 1'b1) begin
			fraction = fraction << 5;
			exponent = exponent - 5;
		end else if (fraction[16] == 1'b1) begin
			fraction = fraction << 6;
			exponent = exponent - 6;
		end else if (fraction[15] == 1'b1) begin
			fraction = fraction << 7;
			exponent = exponent - 7;
		end else if (fraction[14] == 1'b1) begin
			fraction = fraction << 8;
			exponent = exponent - 8;
		end else if (fraction[13] == 1'b1) begin
			fraction = fraction << 9;
			exponent = exponent - 9;
		end else if (fraction[12] == 1'b0) begin
			fraction = fraction << 10;
			exponent = exponent - 10;
		end 

舍位:

		mantissa = fraction[21:12];

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

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

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

其他

变量宽度表

input [15:0] floatA, floatB;
output reg [15:0] product;

reg sign;
reg signed [5:0] exponent; 
reg [9:0] mantissa;
reg [10:0] fractionA, fractionB;
reg [21:0] fraction;

always敏感列表

本模块为纯组合逻辑,非时序。因此只要有数据输入,便触发模块功能,开始运算。

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

特殊情况处理

  • 输入两个数都是0时,输出也为0。
if (floatA == 0 || floatB == 0) begin
	product = 0;
end
  • 不允许指数为负数。在半精度浮点数中,指数本来只有5位。但是代码中的变量设置为6位,其中多出的最高一位是符号位。通过这一位,我们来判断指数的正负。

(这一点从上面操作fraction只进行左移也可以看出来。)

if(exponent[5]==1'b1) begin 
	product=16'b0000000000000000;
end

学习文章:二进制浮点数以及二进制浮点数算术运算
开源项目github-URL:CNN-FPGA


http://www.niftyadmin.cn/n/5039602.html

相关文章

环境搭建和编译-kernel

文章目录 一、下载Kernel二、下载完代码后配置环境变量三、编译内核四、编译uboot五、编译中的错误 这里主要记录下kernel的代码下载和编译 一、下载Kernel git clone https://gerrit.googlesource.com/git-repo (谷歌官方源) git clone https://mirrors.tuna.tsinghua.edu.…

【Redis】使用rpm包安装redis

背景说明 公司环境处于内网&#xff0c;某同事需要安装redis&#xff0c;如果使用通过源码编译安装redis&#xff0c;很多编译工具如gcc就需要先安装&#xff0c;但处于内网安装起来不太方便&#xff0c;当然也不是不可以。我们此处就选用通过redis的rpm包进行安装。 rpm包查…

Nginx 默认的location index设置网站的默认首页

/斜杠代表location定位的路径&#xff0c;路径当中最重要的字段就是root。 root默认值就是html&#xff0c;这个就是nginx安装路径下面的html文件夹作为root的路径。默认不配置就是root下面的内容&#xff0c;index指定了主页的内容。 [rootjenkins html]# echo test > te…

CentOS安装 Docker 和 docker-compose(V1和V2两个版本)

目录 一、安装 Docker1、更新docker的yum源为阿里云仓库2、安装必要的一些系统工具3、查看docker-ce版本4、安装指定版本的docker5、切换Dockek镜像下载源&#xff08;这里使用阿里云镜像&#xff09;6、启动测试docker7、Docker启动关闭操作8、卸载/更新已经安装的Docker 二、…

Linux学习之Redis使用

搭建Redis服务器 在主机redis64运行redis服务 #安装redis服务 [rootredis64 ~]# yum install -y redis # 启动redis服务并开机启动 [rootredis64 ~]# systemctl enable redis --now # 查看redis端口 [rootredis64 ~]# ss -tnlp | grep redis-server LISTEN 0 128 …

stm32学习笔记:GPIO输入

1、寄存器输入输出函数 //读取输入数据寄存器某一个端口的输入值&#xff0c;参数用来指定某一个端口&#xff0c;返回值是 uint8_t类型&#xff0c;用来代表高低电平&#xff08;读取按键的值&#xff09;uint8_t GPIO_ReadInputDataBit(GPIO_TypeDef* GPIOx, uint16_t GPIO_…

浅谈电动汽车充电桩设计与应用研究

安科瑞 华楠 摘要&#xff1a;目前&#xff0c;随着我国社会经济的快速发展&#xff0c;我国的各个领域都取得了突破性的发展&#xff0c;尤其是在电动汽车充电桩的设计方法&#xff0c;新型的电动汽车充电桩设计已经广泛的受到了人民群众的青睐与认可&#xff0c;而这种发展前…

Golang Map的使用

Go Map的使用 Go语言的map使用起来简单且高效。是常用的数据结构。 1、初始化 map[k]v&#xff0c;k是其键&#xff0c;v是其值。 //初始化key为string类型&#xff0c;val为int类型a : make(map[string]int)b : map[string]int{"fht": 1,"fyq": 2,&quo…