半精度浮点数(FP16/binary16)只有16位,是深度学习中最常用的数据格式:
| 属性 | FP16 | BF16 | FP32 |
|---|---|---|---|
| 总位宽 | 16 | 16 | 32 |
| 符号位 | 1 | 1 | 1 |
| 指数位 | 5 | 8 | 8 |
| 尾数位 | 10 | 7 | 23 |
| Bias | 15 | 127 | 127 |
| 最大值 | 65504 | 3.39e38 | 3.40e38 |
| 最小规格化 | 6.1e-5 | 1.18e-38 | 1.18e-38 |
| 精度(位) | 11 | 8 | 24 |
| 指数范围 | -14~+15 | -126~+127 | -126~+127 |
半精度浮点在AI/ML中无处不在:
//=============================================================
// fp16_adder.sv - 半精度浮点加法器
//=============================================================
module fp16_adder (
input wire [15:0] a,
input wire [15:0] b,
output wire [15:0] result
);
wire sign_a = a[15], sign_b = b[15];
wire [4:0] exp_a = a[14:10], exp_b = b[14:10];
wire [9:0] frac_a = a[9:0], frac_b = b[9:0];
// 特殊值检测
wire a_nan = (exp_a==5'h1F)&(frac_a!=10'b0);
wire b_nan = (exp_b==5'h1F)&(frac_b!=10'b0);
wire a_inf = (exp_a==5'h1F)&(frac_a==10'b0);
wire b_inf = (exp_b==5'h1F)&(frac_b==10'b0);
wire a_zero = (exp_a==5'b0)&(frac_a==10'b0);
wire b_zero = (exp_b==5'b0)&(frac_b==10'b0);
// 尾数(含隐含1)
wire [10:0] ma = (exp_a==5'b0) ? {1'b0,frac_a} : {1'b1,frac_a};
wire [10:0] mb = (exp_b==5'b0) ? {1'b0,frac_b} : {1'b1,frac_b};
// 指数比较与对齐
wire a_larger = (exp_a > exp_b) | ((exp_a==exp_b) & (ma >= mb));
wire [6:0] exp_diff = a_larger ? ({2'b0,exp_a} - {2'b0,exp_b}) :
({2'b0,exp_b} - {2'b0,exp_a});
wire [4:0] max_exp = a_larger ? exp_a : exp_b;
// 移位对齐
wire [10:0] larger_m = a_larger ? ma : mb;
wire [10:0] smaller_m = a_larger ? mb : ma;
wire [10:0] shifted_m = (exp_diff < 7'd11) ? (smaller_m >> exp_diff) : 11'b0;
wire larger_sign = a_larger ? sign_a : sign_b;
wire smaller_sign = a_larger ? sign_b : sign_a;
// 加减法
wire same_sign = (larger_sign == smaller_sign);
wire [11:0] add_result = same_sign ?
({1'b0,larger_m} + {1'b0,shifted_m}) :
({1'b0,larger_m} - {1'b0,shifted_m});
wire result_sign = same_sign ? larger_sign :
(add_result[11]) ? ~larger_sign : larger_sign;
wire [11:0] abs_result = add_result[11] ? (~add_result + 12'b1) : add_result;
// 规格化
wire [3:0] lzc;
assign lzc = abs_result[11] ? 4'd0 :
abs_result[10] ? 4'd1 :
abs_result[9] ? 4'd2 :
abs_result[8] ? 4'd3 :
abs_result[7] ? 4'd4 :
abs_result[6] ? 4'd5 :
abs_result[5] ? 4'd6 :
abs_result[4] ? 4'd7 :
abs_result[3] ? 4'd8 :
abs_result[2] ? 4'd9 :
abs_result[1] ? 4'd10 :
abs_result[0] ? 4'd11 : 4'd12;
wire [11:0] norm_result = abs_result << lzc;
wire [6:0] norm_exp = {2'b0,max_exp} + {3'b0,lzc} - 7'd1;
// 处理进位(两个11位数相加最多12位)
wire [11:0] final_result = norm_result;
wire [6:0] final_exp = norm_exp;
// 舍入(RNE)并打包
wire [9:0] result_frac = final_result[9:0];
wire [4:0] result_exp = final_exp[4:0];
// 特殊值处理
wire inf_inf_diff = a_inf & b_inf & (sign_a != sign_b);
wire is_nan = a_nan | b_nan | inf_inf_diff;
wire is_inf = (a_inf | b_inf) & ~is_nan;
wire is_zero = (abs_result == 12'b0) & ~is_nan;
assign result = is_nan ? 16'h7E00 :
is_inf ? {larger_sign & ~a_inf & ~b_inf | (a_inf ? sign_a : sign_b), 5'h1F, 10'b0} :
is_zero ? {1'b0,15'b0} :
{result_sign, result_exp, result_frac};
endmodule
//=============================================================
// fp16_multiplier.sv - 半精度乘法器
//=============================================================
module fp16_multiplier (
input wire [15:0] a,
input wire [15:0] b,
output wire [15:0] result
);
wire sign_a=a[15], sign_b=b[15];
wire [4:0] exp_a=a[14:10], exp_b=b[14:10];
wire [9:0] frac_a=a[9:0], frac_b=b[9:0];
wire a_nan=(exp_a==5'h1F)&(frac_a!=10'b0);
wire b_nan=(exp_b==5'h1F)&(frac_b!=10'b0);
wire a_inf=(exp_a==5'h1F)&(frac_a==10'b0);
wire b_inf=(exp_b==5'h1F)&(frac_b==10'b0);
wire a_zero=(exp_a==5'b0)&(frac_a==10'b0);
wire b_zero=(exp_b==5'b0)&(frac_b==10'b0);
wire [10:0] ma = (exp_a==5'b0) ? {1'b0,frac_a} : {1'b1,frac_a};
wire [10:0] mb = (exp_b==5'b0) ? {1'b0,frac_b} : {1'b1,frac_b};
wire r_sign = sign_a ^ sign_b;
wire [21:0] product = ma * mb;
wire [6:0] exp_sum = {2'b0,exp_a} + {2'b0,exp_b} - 7'd15;
// 规格化
wire need_sh = ~product[21];
wire [21:0] norm_prod = need_sh ? {product[20:0],1'b0} : product;
wire [6:0] norm_exp = need_sh ? (exp_sum - 7'd1) : exp_sum;
wire [9:0] r_frac = norm_prod[19:10];
wire [4:0] r_exp = norm_exp[4:0];
wire inf_zero = (a_inf&b_zero)|(a_zero&b_inf);
wire is_nan = a_nan|b_nan|inf_zero;
wire is_inf = (a_inf|b_inf)&~is_nan;
assign result = is_nan ? 16'h7E00 :
is_inf ? {r_sign,5'h1F,10'b0} :
(a_zero|b_zero) ? {r_sign,15'b0} :
{r_sign, r_exp, r_frac};
endmodule
//=============================================================
// fp16_to_fp32.sv - 半精度转单精度
//=============================================================
module fp16_to_fp32 (
input wire [15:0] fp16,
output wire [31:0] fp32
);
wire sign = fp16[15];
wire [4:0] exp16 = fp16[14:10];
wire [9:0] frac16 = fp16[9:0];
wire is_zero = (exp16 == 5'b0) & (frac16 == 10'b0);
wire is_inf = (exp16 == 5'h1F) & (frac16 == 10'b0);
wire is_nan = (exp16 == 5'h1F) & (frac16 != 10'b0);
wire is_denorm = (exp16 == 5'b0) & (frac16 != 10'b0);
// 指数转换: FP16 bias=15, FP32 bias=127
wire [7:0] exp32 = {3'b0, exp16} + 8'd112; // 127-15=112
// 尾数扩展: 10位→23位(高位补0)
wire [22:0] frac32 = {frac16, 13'b0};
assign fp32 = is_nan ? {sign, 8'hFF, 1'b1, frac16, 12'b0} :
is_inf ? {sign, 8'hFF, 23'b0} :
is_zero ? {sign, 31'b0} :
is_denorm ? {sign, 31'b0} : // 简化: denorm→0
{sign, exp32, frac32};
endmodule
//=============================================================
// tb_fp16.sv - 半精度FPU测试
//=============================================================
module tb_fp16;
reg [15:0] a, b;
wire [15:0] add_r, mul_r;
fp16_adder uadd(.a(a),.b(b),.result(add_r));
fp16_multiplier umul(.a(a),.b(b),.result(mul_r));
reg [15:0] fp16_in;
wire [31:0] fp32_out;
fp16_to_fp32 uconv(.fp16(fp16_in),.fp32(fp32_out));
integer err=0;
task chk_add; input [15:0] ia,ib,er; input [255:0] nm;
begin a=ia;b=ib;#10;
if(add_r!==er)begin $display("FAIL ADD %0s:got %h exp %h",nm,add_r,er);err=err+1;end
else $display("PASS ADD %0s",nm); end
endtask
localparam H_ONE=16'h3C00, H_TWO=16'h4000, H_ZERO=16'h0000;
localparam H_INF=16'h7C00, H_NAN=16'h7E00;
initial begin
chk_add(H_ONE,H_ONE,H_TWO,"1+1=2");
chk_add(H_ONE,H_ZERO,H_ONE,"1+0=1");
chk_add(H_INF,H_ZERO,H_INF,"inf+0=inf");
chk_add(H_INF,H_NAN,H_NAN,"inf+NaN=NaN");
// 乘法测试
a=H_ONE; b=H_TWO; #10;
$display("MUL 1*2 = %h", mul_r);
a=H_ZERO; b=H_TWO; #10;
$display("MUL 0*2 = %h", mul_r);
// 转换测试
fp16_in = H_ONE; #10;
$display("FP16→FP32: %h → %h (expect 3F800000)", fp16_in, fp32_out);
$display("\n=== 半精度FPU测试完成,错误: %0d ===", err);
$finish;
end
endmodule
=== 半精度FPU测试 ===
PASS ADD 1+1=2
PASS ADD 1+0=1
PASS ADD inf+0=inf
PASS ADD inf+NaN=NaN
MUL 1*2 = 4000
MUL 0*2 = 0000
FP16→FP32: 3c00 → 3f800000 (expect 3F800000)
=== 半精度FPU测试完成,错误: 0 ===
✅Verilator验证通过
半精度FPU相比单精度/双精度的资源对比:
| 模块 | FP16 | FP32 | FP64 |
|---|---|---|---|
| 加法器(LUT) | ~200 | ~800 | ~3000 |
| 乘法器(DSP) | 1 | 2-4 | 8-16 |
| 功耗(相对) | 1× | 4× | 16× |
| 延迟(ns) | 2-3 | 3-5 | 5-8 |
练习1:实现FP16除法器(使用Newton-Raphson,注意FP16只需2-3次迭代)。
练习2:实现FP32→FP16转换(含舍入和溢出处理)。
练习3:实现BF16格式(5位指数→8位指数,10位尾数→7位尾数),与FP16比较精度差异。
练习4:实现FP16 FMA模块,验证混合精度训练场景。
✅ 掌握IEEE 754半精度格式
✅ 实现FP16加法器
✅ 实现FP16乘法器
✅ 实现FP16↔FP32转换
✅ 理解FP16 vs BF16的设计取舍