1 浮点运算单元FPU
超标量CPU中的浮点运算单元是专门处理浮点数运算的关键组件。浮点运算单元的设计涉及多个复杂的子模块和技术,以保证高效、准确地执行浮点数的加减法、乘法、除法、平方根等操作。
1)浮点数术语
浮点数通常采用IEEE 754标准表示,包括三部分:符号位、指数位和尾数位。
半精度浮点数:16位,FP16,包括1位符号位,5位指数位,10位尾数位。
单精度浮点数:32位,FP32,包括1位符号位,8位指数位,23位尾数位。
双精度浮点数:64位,FP64,包括1位符号位,11位指数位,52位尾数位。
FP8 是一种新的浮点数格式,主要用于机器学习加速器和一些低精度计算场景,两种变种:
E4M3(Exponent 4 bits, Mantissa 3 bits):包括1位符号位,4位指数位,3位尾数位。
E5M2(Exponent 5 bits, Mantissa 2 bits):包括1位符号位,5位指数位,2位尾数位。
BF16 (16位浮点数,Brain Floating Point, BP16),是一种为机器学习优化的16位浮点数格式,保留了单精度浮点数的指数范围,但减少了尾数的位数:包括1位符号位,8位指数位,7位尾数位。BF16 具有与单精度浮点数(FP32)相同的指数范围,因此可以表示同样大的数值范围,但精度较低,这使得 BF16 特别适合于训练深度神经网络,因为它可以减少内存带宽和存储需求,同时保持足够的数值范围。
2)浮点运算单元组成
加法器和减法器,乘法器,除法器,平方根计算单元,归约和规范化单元,异常处理单元
3)浮点加法和减法单元
浮点加减法需要处理对齐、加减运算和归约等步骤:
1)对齐:比较指数(对齐操作数,使其指数相同)、移位尾数(根据指数差调整尾数)。
2)加法/减法:计算尾数(执行加法或减法运算)
3)规范化结果:归约结果(规范化结果,使最高有效位位于合适位置)、舍入结果(根据IEEE 754标准,对结果进行舍入操作:向偶数舍入、向零舍入、向正无穷舍入、向负无穷舍入)。
逻辑实现
module FPAdder (
input [31:0] a, b, // 两个单精度浮点数
output [31:0] result
);
// 提取符号位、指数位和尾数位
wire sign_a = a[31];
wire [7:0] exp_a = a[30:23];
wire [23:0] frac_a = {1'b1, a[22:0]};
wire sign_b = b[31];
wire [7:0] exp_b = b[30:23];
wire [23:0] frac_b = {1'b1, b[22:0]};
// 对齐指数和尾数
wire [7:0] exp_diff = exp_a - exp_b;
wire [23:0] aligned_frac_b = frac_b >> exp_diff;
// 执行加法
wire [24:0] frac_sum = frac_a + aligned_frac_b;
// 规范化结果
wire [7:0] result_exp = exp_a + 1;
wire [23:0] result_frac = frac_sum[24] ? frac_sum[23:0] : frac_sum[22:0];
assign result = {sign_a, result_exp, result_frac[22:0]};
endmodule
4)浮点乘法单元
浮点乘法包括指数相加和尾数相乘两个主要步骤:
1)指数相加:结果指数是两个操作数指数之和,减去一个偏置值。
2)尾数相乘:乘法操作需要高效的部分积生成和压缩机制。
3)规范化结果。
逻辑实现
module FPMultiplier (
input [31:0] a, b, // 两个单精度浮点数
output [31:0] result
);
// 提取符号位、指数位和尾数位
wire sign_a = a[31];
wire [7:0] exp_a = a[30:23];
wire [23:0] frac_a = {1'b1, a[22:0]};
wire sign_b = b[31];
wire [7:0] exp_b = b[30:23];
wire [23:0] frac_b = {1'b1, b[22:0]};
// 计算符号
wire result_sign = sign_a ^ sign_b;
// 计算指数
wire [8:0] result_exp = exp_a + exp_b - 8'd127;
// 尾数相乘
wire [47:0] frac_mult = frac_a * frac_b;
// 规范化结果
wire [23:0] result_frac = frac_mult[47] ? frac_mult[46:24] : frac_mult[45:23];
wire [7:0] final_exp = frac_mult[47] ? result_exp + 1 : result_exp;
assign result = {result_sign, final_exp[7:0], result_frac[22:0]};
endmodule
5)浮点除法和平方根单元
浮点除法和平方根计算通常比加减法和乘法更复杂,可以采用多种算法实现,如牛顿-拉弗森法、非恢复性除法等。
浮点除法主要步骤如下:
符号计算:结果的符号是被除数和除数符号的异或。
指数相减:结果的指数是被除数指数减去除数指数再加上偏置值。
尾数除法:计算尾数的商。
规范化结果。
逻辑实现
module FPDivider(
input [31:0] a, b, // 两个单精度浮点数
output [31:0] result
);
// 提取符号位、指数位和尾数位
wire sign_a = a[31];
wire [7:0] exp_a = a[30:23];
wire [23:0] frac_a = {1'b1, a[22:0]}; // 隐含1
wire sign_b = b[31];
wire [7:0] exp_b = b[30:23];
wire [23:0] frac_b = {1'b1, b[22:0]}; // 隐含1
// 计算符号
wire result_sign = sign_a ^ sign_b;
// 计算指数
wire [8:0] result_exp = exp_a - exp_b + 8'd127;
// 尾数相除
wire [47:0] frac_div = (frac_a << 23) / frac_b;
// 规范化结果
wire [23:0] result_frac = frac_div[46] ? frac_div[46:24] : frac_div[45:23];
wire [7:0] final_exp = frac_div[46] ? result_exp + 1 : result_exp;
assign result = {result_sign, final_exp[7:0], result_frac[22:0]};
Endmodule
浮点开方主要步骤如下:
符号计算:平方根运算结果的符号为正。
指数计算:结果的指数是操作数指数的一半再加上偏置值。
尾数计算:计算尾数的平方根。
规范化结果。
逻辑实现
module FPSqrt(
input [31:0] a,
output [31:0] result
);
wire [7:0] exp_a;
wire [23:0] frac_a;
wire [7:0] result_exp;
wire [23:0] frac_result;
wire [23:0] norm_frac_result;
wire [7:0] norm_exp_result;
// 各个阶段模块化实现
FPSqrt_Preprocess preprocess (.a(a), .exp_a(exp_a), .frac_a(frac_a));
FPSqrt_Exponent exponent (.exp_a(exp_a), .result_exp(result_exp));
FPSqrt_Mantissa mantissa (.frac_a(frac_a), .frac_result(frac_result));
FPSqrt_Normalize normalize (.frac_result(frac_result), .result_exp(result_exp),
.norm_frac_result(norm_frac_result), .norm_exp_result(norm_exp_result));
FPSqrt_Round round (.norm_frac_result(norm_frac_result), .norm_exp_result(norm_exp_result),
.final_result(result));
endmodule
6)归约和规范化单元
归约和规范化用于确保结果符合标准的浮点数格式,包括对结果的尾数进行移位处理,以使结果尾数的最高有效位为1,并调整指数值。
7)异常处理单元
异常处理包括对各种浮点运算异常的检测和处理,例如:
溢出:结果超出可表示的范围。
下溢:结果小于可表示的最小值。
除零:除法操作中分母为零。
无效操作:如0/0或sqrt(-1)。
2 单指令多数据SIMD
SIMD(Single Instruction, Multiple Data)是一种并行计算架构,通过扩展处理器的指令集来操作多个数据元素,这些数据元素通常被存储在一个大的寄存器中,例如处理器可以使用一条指令同时对四个 32 位浮点数或八个 16 位整数进行运算。SIMD设计在现代超标量CPU中被广泛应用,特别是在多媒体处理、科学计算和其他需要处理大量数据的应用中。
1)常见的 SIMD 指令集
Intel MMX、SSE、AVX 和 AVX-512
ARM NEON
IBM AltiVec
AMD 3DNow!
2)SIMD 寄存器
SIMD 指令通常使用宽寄存器来存储多个数据元素。
MMX 寄存器:64 位宽,通常处理 8 个 8 位数据或 4 个 16 位数据。
SSE 寄存器:128 位宽,通常处理 4 个 32 位浮点数或 16 个 8 位整数。
AVX 寄存器:256 位宽,通常处理 8 个 32 位浮点数或 32 个 8 位整数。
AVX-512 寄存器:512 位宽,处理的数据量更大。
3)SIMD操作数
SIMD 指令的操作数通常表示为向量类型的数据。例如在 Intel 的 AVX 指令集中,ymm 寄存器表示 256 位寄存器,其中可以存储 8 个 32 位浮点数。
4)SIMD 指令操作
算术运算:加法、减法、乘法、除法等
逻辑运算:与、或、非、异或等
移位运算:左移、右移、算术右移等
比较运算:大于、小于、等于等
数据搬移:加载、存储、混合、打包和解包数据
5)SIMD执行步骤
指令获取和解码:处理器从内存中获取 SIMD 指令,并将其解码为内部操作码和操作数。
数据加载:将操作数从内存加载到 SIMD 寄存器中。
并行计算:在多个数据元素上并行执行指令。
结果存储:将计算结果从 SIMD 寄存器存储回内存或其他寄存器中。
SIMD 加法器示例(并行处理四个 32 位整数)
module SIMDAdder (
input [127:0] A, // 四个 32 位整数输入 A
input [127:0] B, // 四个 32 位整数输入 B
output [127:0] Sum // 四个 32 位整数输出 Sum
);
assign Sum[31:0] = A[31:0] + B[31:0]; // 第一个整数加法
assign Sum[63:32] = A[63:32] + B[63:32]; // 第二个整数加法
assign Sum[95:64] = A[95:64] + B[95:64]; // 第三个整数加法
assign Sum[127:96] = A[127:96] + B[127:96]; // 第四个整数加法
endmodule
6)SIMD 优化技术
数据对齐:确保数据在内存中的对齐,以便于 SIMD 加载和存储操作。
数据预取:提前加载数据以减少等待时间,提高指令执行效率。
向量化:将标量代码转换为向量代码,使其能够利用 SIMD 指令集。
循环展开:减少循环控制开销,通过展开循环体使更多操作在同一条指令中执行。
指令调度:优化指令顺序,以最大限度地减少依赖关系和等待时间。
7)SIMD 的应用
多媒体处理:图像处理、音频处理、视频编码和解码。
科学计算:矩阵运算、向量运算、快速傅里叶变换(FFT)。
数据分析:大数据处理、机器学习、神经网络。
加密和解密:数据加密标准(AES)、安全哈希算法(SHA)。