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

开发环境

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

  • 12
    点赞
  • 87
    收藏
    觉得还不错? 一键收藏
  • 14
    评论
### 回答1: 基于FPGA(可编程逻辑器件)的浮点数乘法器是用于实现浮点数乘法运算的硬件电路。该乘法器的代码可以通过硬件描述语言(如VHDL或Verilog)编写。 在设计浮点数乘法器的代码时,首先需要定义浮点数的格式和表示方法,如指数和尾数的位数以及符号位等。然后,可以通过一系列的逻辑电路和算法来实现浮点数的乘法运算。通常,浮点数的乘法运算可以分成四个步骤:对齐、乘法、规格化和舍入。 在对齐步骤中,首先需要将两个浮点数的小数点位置对齐,以便进行后续的乘法运算。然后,在乘法步骤中,使用乘法器电路对两个浮点数的尾数进行乘法运算,并得到一个乘积。接下来,在规格化步骤中,对乘积进行规格化处理,以确保结果的精度和准确性。最后,在舍入步骤中,根据浮点数的规定,对规格化后的乘积进行舍入处理,并得到最终的乘法结果。 设计基于FPGA浮点数乘法器代码需要考虑到的因素包括性能、面积和功耗等。为了获得更好的性能和效率,可以使用流水线技术将浮点数乘法的各个步骤并行处理。此外,还可以通过优化电路结构和算法来减少面积和功耗的消耗。 综上所述,基于FPGA浮点数乘法器是通过硬件描述语言编写的代码,实现浮点数的乘法运算。通过一系列的逻辑电路和算法,将输入的浮点数进行对齐、乘法、规格化和舍入等处理步骤,最终得到乘法的结果。该乘法器的代码需要考虑性能、面积和功耗等因素,并通过优化电路结构和算法来提高效率。 ### 回答2: 基于FPGA(可编程逻辑门阵列)的浮点数乘法器代码主要用于实现浮点数的乘法运算。以下是一个简单的浮点数乘法器FPGA代码示例: ```verilog module floating_point_multiplier( input wire [31:0] a_mantissa, input wire [31:0] b_mantissa, input wire [7:0] a_exponent, input wire [7:0] b_exponent, output wire [31:0] result_mantissa, output wire [7:0] result_exponent ); reg [63:0] multiplied_value; always @(a_mantissa or b_mantissa or a_exponent or b_exponent) begin multiplied_value = $signed(a_mantissa) * $signed(b_mantissa); result_mantissa = multiplied_value[63:32]; // 取高32位作为结果的尾数 result_exponent = a_exponent + b_exponent; // 指数相加 end endmodule ``` 这个代码模块接受两个32位的浮点数尾数 `a_mantissa` 和 `b_mantissa` ,以及两个8位的浮点数指数 `a_exponent` 和 `b_exponent` 作为输入。输出为32位的结果尾数 `result_mantissa` 和8位的结果指数 `result_exponent`。 在 `always` 块中,使用 `$signed()` 函数将输入的无符号数转换为有符号数,然后执行浮点数乘法并将结果存储在 `multiplied_value` 寄存器中。通过取高32位可以得到结果的尾数,并将 `a_exponent` 和 `b_exponent` 直接相加得到结果的指数。 这个简单的浮点数乘法器模块可以在FPGA上使用,用于实现浮点数的乘法运算。请注意,这只是一个基本的示例,实际的浮点数乘法器需要考虑更多的细节和优化。 ### 回答3: 基于FPGA浮点数乘法器是一种用于执行浮点数乘法运算的电路。它使用可编程逻辑门阵列(FPGA)的资源来实现高性能和高效能的浮点数乘法运算。 在实现浮点数乘法器时,关键的组件是乘法器加法器乘法器负责执行两个浮点数的乘法运算,而加法器则负责执行浮点数的结果的规范化和舍入。 FPGA浮点数乘法器的代码设计需要考虑以下几个方面: 1. 数据表示:浮点数一般采用IEEE 754标准进行表示,代码需要实现按照该标准表示的浮点数,包括正负号位、指数位和尾数位。 2. 乘法运算:代码需要实现浮点数的乘法运算。可以使用 Booth编码算法进行乘法运算,该算法能够减少计算过程中的位移操作。 3. 加法运算:乘法运算后得到的结果可能需要进行加法运算,来进行结果的规范化和舍入。代码需要实现浮点数的加法运算,包括考虑溢出、舍入和舍入模式等因素。 4. 浮点数格式转换:在乘法运算和加法运算中,需要进行浮点数的格式转换,如将浮点数转换为对应的二进制表示,或者将二进制表示转换为浮点数表示。 实现FPGA浮点数乘法器的代码可以使用硬件描述语言(HDL)编写,如VHDL或Verilog。通过使用FPGA开发工具,可以将代码综合成对应的电路,并下载到FPGA芯片中运行。 总之,基于FPGA浮点数乘法器代码设计需要考虑数据表示、乘法运算、加法运算和浮点数格式转换等关键因素,以实现高效、高性能的浮点数乘法运算。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值