zl程序教程

您现在的位置是:首页 >  IT要闻

当前栏目

FPGA学习笔记(2):半精度浮点数乘法器和半精度浮点数加法器的Verilog实现

2023-04-18 16:43:47 时间

开发环境

1.Vivado 2019.2
2.仿真:Vivado Simulater

半精度浮点数介绍

IEEE754-2008包含一种“半精度”格式,只有16位宽。故它又被称之为binary16,这种类型的浮点数只适合用于存储那些对精度要求不高的数字,不适合用于进行计算。与单精度浮点数相比,它的优点是只需要一半的存储空间和带宽,但是缺点是精度较低。
半精度的格式与单精度的格式类似,最左边的一位仍是符号位,指数有5位宽且以余-16(excess-16)的形式存储,尾数有10位宽,但具有隐含1。
在这里插入图片描述
具体半精度浮点数转换方法有兴趣的读者可以参考:半精度浮点数详解
本文不再赘述

半精度浮点数乘法器实现

半精度浮点数乘法器的实现主要包括对符号位的处理,指数借位,乘法计算,超范围小数等内容,均在下文的代码注释中有较为详细的介绍
Verilog代码如下:

module floatMuilt 
(
	input wire [15:0] floatA,
	input wire [15:0] floatB,
	output reg [15:0] product
);

reg sign; // 输出的正负标志位
reg signed [5:0] exponent; // 输出数据的指数,因为有正负所以选择有符号数
reg [9:0] mantissa; // 输出数据的小数
reg [10:0] fractionA, fractionB;	//fraction = {1,mantissa} // 计算二进制数据最高位补1
reg [21:0] fraction; // 相乘结果参数


always @ (floatA or floatB) 
begin
	if (floatA == 0 || floatB == 0)  // 处理乘数有一个或者两个均为0的情况
		product = 0;				//  输出为0
	else 
	begin
		sign = floatA[15] ^ floatB[15]; // 异或门判断输出的计算正负
		exponent = floatA[14:10] + floatB[14:10] - 5'd15 + 5'd2; // 由于借位给fractionA和fractionB需要先补齐两位指数
	
		fractionA = {1'b1,floatA[9:0]}; //借位给fractionA
		fractionB = {1'b1,floatB[9:0]}; //借位给fractionB
		fraction = fractionA * fractionB; //计算二进制乘法
		// 找到第一个不为0的数字并对指数进行匹配处理
		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 //太小了输出全0(精度问题)
			product=16'b0000000000000000;
		end
		else begin
			product = {sign,exponent[4:0],mantissa}; //拼接输出数据
		end
	end
end

测试文件

module tb_floatMuilt (); /* this is automatically generated */
	reg [15:0] floatA;
	reg [15:0] floatB;
	wire [15:0] product;

	floatMuilt inst_floatMuilt 
	(	.floatA(floatA), 
		.floatB(floatB), 
		.product(product)
	);

	initial begin
		floatA = 16'b0000000000000000; //0
		floatB = 16'b0000000000000000; //0
		#40;
		floatA = 16'b0100000000000000; //2
		floatB = 16'b0011100110011010; //0.7
		#40;
		floatA = 16'b0011010110011010; //0.35
		floatB = 16'b0011100011110110; //0.62
		#40;
		floatA = 16'b0011000001111011; //0.14
		floatB = 16'b0011101010100100; //0.83
		#40;
		$stop;
	end



endmodule

在Vivado的仿真软件中可以看出
在这里插入图片描述
笔者在此处给出基于python的二进制数和半精度浮点数的转化脚本,读者可以通过此脚本自行添加验证本tb文件的测试用例子进行验证。本文采用的四个测试用例均成功输出正确的数值。

import numpy as np
import struct



def float2bin_half(F):  #F是浮点数
    return '{:016b}'.format(struct.unpack('<H', np.float16(F).tobytes())[0])

def bin_half2float(B):  #B是二进制字符串
    return np.frombuffer(struct.pack('<H',int(B,2)), dtype='<f2')[0]


if __name__ == '__main__':
    # print("%X" %float2bin_half(0.12))
    print(float2bin_half(0.14))
    print(float2bin_half(0.83))
    print(bin_half2float("0010111101110000"))

半精度浮点数加法器实现

半精度浮点数的加法器不同于乘法器,需要配平待相加的两个数据的阶数后再根据正负情况进行加减运算操作。
Verilog代码如下:

module floatAdd (
	input 	wire [15:0] floatA,
	input 	wire [15:0] floatB,
	output	reg	 [15:0] sum
);

reg sign; // 输出结果的正负标志位
reg signed [5:0] exponent; //输出数据的指数,因为有正负所以选择有符号数
reg [9:0] mantissa; // 输出数据的尾数
reg [4:0] exponentA, exponentB; //输入数据的阶数
reg [10:0] fractionA, fractionB, fraction;	// 计算暂存位
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) 		// 特殊情况A为0
	begin						
		sum = floatB;
	end 
	else if (floatB == 0)  // 特殊情况B为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
		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							
			{cout,fraction} = fractionA + fractionB;
			if (cout == 1'b1) 
			begin
				{cout,fraction} = {cout,fraction} >> 1;
				exponent = exponent + 1;
			end
			sign = floatA[15];
		end 
		else 
		begin						//两数异号
			if (floatA[15] == 1'b1) // A 为负数
			begin
				{cout,fraction} = fractionB - fractionA;	// B-A
			end 
			else 
			begin
				{cout,fraction} = fractionA - fractionB;	// A-B
			end
			sign = cout;
			if (cout == 1'b1) 
				fraction = -fraction; // 0-负数可求出此数的绝对值
			// 对franction进行阶数配平求出尾数
			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 //太小了输出全0太小了
			sum = 16'b0000000000000000;
		end
		else begin
			sum = {sign,exponent[4:0],mantissa}; // 组合数据
		end		
	end		
end

endmodule

仿真文件如下图所示:

`timescale 1ns/1ps

module tb_floatAdd (); /* this is automatically generated */
	// (*NOTE*) replace reset, clock, others

	reg [15:0] floatA;
	reg [15:0] floatB;
	wire [15:0] sum;

	floatAdd inst_floatAdd 
	(	.floatA(floatA), 
		.floatB(floatB), 
		.sum(sum)
	);


	initial begin
		floatA = 16'b0000000000000000;
		floatB = 16'b0000000000000000;
		#20;
		floatA = 16'b0011110000000000;  // 1.0
		floatB = 16'b1100010100000000;	// -5.0
		#20;
		floatA = 16'b0011010011001101;  //0.2
		floatB = 16'b0011001001100110;  //0.3
		#20;
		floatA = 16'b0101011000010000;  //97
		floatB = 16'b0011010011001101;  //0.3
		#20;
		$stop;
	end

endmodule

在Vivado Simulater中仿真波形入下图所示:
在这里插入图片描述
出现的问题:有些情况下加法器会出现精度相加不准的情况。不知道为什么会出现此类现象。希望有大佬能够给予指正。