FMA加法树的第一个关键步骤是将乘积(48位)与加数c(24位)对齐到同一指数基准。对齐需要解决以下问题:
根据exp_diff的正负,有两种对齐策略:
FMA需要处理 a×b+c 和 a×b-c 两种情况。当乘积和c的符号不同时,需要做减法。硬件实现中,减法通过补码完成:
但在加法树中,我们通常用"符号-绝对值"方法:
3:2压缩器(也叫全加器)将3个数压缩为2个数(和与进位),不产生进位传播延迟:
在FMA中,3:2压缩器用于将三个部分积压缩为两个(和向量+进位向量),然后用一个快速加法器(如超前进位加法器)完成最终求和。
更高性能的FMA使用4:2压缩器:
FMA加法级需要处理的关键数据通路:
关键位宽分配(单精度FMA):
| 字段 | 位宽 | 说明 |
|---|---|---|
| 乘积尾数 | 48位 | 24×24的完整结果 |
| c尾数(含对齐) | 24+3位 | 含Guard/Round/Sticky |
| 对齐扩展 | 最多57位 | 极端exp_diff时的右移量 |
| 总和宽度 | ~106位 | 覆盖所有对齐情况 |
| 符号扩展 | +2位 | 补码表示的符号位 |
//=============================================================
// fma_align_add.sv - FMA对齐与加法树
// 实现乘积与c的对齐、压缩和最终加法
//=============================================================
module fma_align_add (
input wire prod_sign,
input wire [9:0] prod_exp,
input wire [47:0] prod_mant,
input wire prod_zero,
input wire c_sign,
input wire [9:0] c_exp,
input wire [23:0] c_mant,
input wire op_sub, // 1=减法(a*b-c)
output wire [9:0] result_exp,
output wire [105:0] result_sum, // 压缩后的和
output wire result_sign
);
// === 有效操作数检查 ===
wire c_zero = (c_exp == 10'd0) | (c_mant == 24'd0);
wire effective_c_sign = c_sign ^ op_sub;
// === 计算指数差 ===
wire signed_diff_prod_c;
wire [9:0] abs_diff;
wire prod_larger;
assign prod_larger = (prod_exp >= c_exp) | c_zero;
assign abs_diff = prod_larger ? (prod_exp - c_exp) : (c_exp - prod_exp);
// === 选择较大的指数作为结果指数 ===
assign result_exp = prod_larger ? prod_exp : c_exp;
// === 符号判断 ===
wire signs_equal = (prod_sign == effective_c_sign);
// 简化:当符号相等时结果符号与操作数相同
assign result_sign = prod_zero ? effective_c_sign :
c_zero ? prod_sign :
signs_equal ? prod_sign :
prod_larger ? prod_sign : effective_c_sign;
// === 扩展操作数到106位 ===
// 格式: [2位符号扩展][48位乘积/24位c][对齐扩展][GRS]
wire [105:0] prod_extended;
wire [105:0] c_extended;
// 乘积放在高位(左对齐)
assign prod_extended = prod_zero ? 106'd0 :
{56'd0, prod_mant, 2'b0}; // GRS位初始0
// === c的对齐移位 ===
// c需要右移abs_diff位(当prod_larger时)
// 或者乘积右移(当c_larger时)
reg [105:0] c_aligned;
reg [105:0] prod_aligned;
reg c_sticky, prod_sticky;
always @(*) begin
c_aligned = 106'd0;
prod_aligned = 106'd0;
c_sticky = 1'b0;
prod_sticky = 1'b0;
if (c_zero) begin
prod_aligned = prod_extended;
end else if (prod_zero) begin
c_aligned = {79'd0, c_mant, 2'b0};
end else if (prod_larger) begin
// 乘积在高位,c右移
prod_aligned = prod_extended;
if (abs_diff < 10'd106) begin
c_aligned = ({79'd0, c_mant, 2'b0) >> abs_diff;
// 粘滞位:移出的位OR
c_sticky = |({79'd0, c_mant, 2'b0) & ((106'd1 << abs_diff) - 106'd1));
end
end else begin
// c在高位,乘积右移
c_aligned = {79'd0, c_mant, 2'b0};
if (abs_diff < 10'd106) begin
prod_aligned = prod_extended >> abs_diff;
prod_sticky = |(prod_extended & ((106'd1 << abs_diff) - 106'd1));
end
end
end
// === 补码转换(异号时取反)===
wire [105:0] prod_comp = signs_equal ? prod_aligned :
(~prod_aligned + 106'd1);
wire [105:0] c_comp = signs_equal ? c_aligned :
(~c_aligned + 106'd1);
// === 加法 ===
// 简化实现:直接加法(实际应使用3:2压缩+CPA)
assign result_sum = prod_comp + c_comp;
endmodule
//=============================================================
// compressor_3to2.sv - 3:2压缩器(逐位)
//=============================================================
module compressor_3to2 (
input wire a,
input wire b,
input wire cin,
output wire sum,
output wire cout
);
assign sum = a ^ b ^ cin;
assign cout = (a & b) | (a & cin) | (b & cin);
endmodule
//=============================================================
// compressor_4to2.sv - 4:2压缩器
//=============================================================
module compressor_4to2 (
input wire a,
input wire b,
input wire c,
input wire cin,
output wire sum,
output wire carry,
output wire cout
);
wire s1, c1;
assign s1 = a ^ b ^ c;
assign c1 = (a & b) | (a & c) | (b & c);
assign sum = s1 ^ cin;
assign carry = (s1 & cin) | c1;
assign cout = c1;
endmodule
//=============================================================
// tb_fma_align_add.sv - 对齐与加法树测试
//=============================================================
module tb_fma_align_add;
reg prod_sign;
reg [9:0] prod_exp;
reg [47:0] prod_mant;
reg prod_zero;
reg c_sign;
reg [9:0] c_exp;
reg [23:0] c_mant;
reg op_sub;
wire [9:0] result_exp;
wire [105:0] result_sum;
wire result_sign;
fma_align_add uut(
.prod_sign(prod_sign), .prod_exp(prod_exp),
.prod_mant(prod_mant), .prod_zero(prod_zero),
.c_sign(c_sign), .c_exp(c_exp), .c_mant(c_mant),
.op_sub(op_sub),
.result_exp(result_exp), .result_sum(result_sum),
.result_sign(result_sign)
);
integer err = 0;
initial begin
// 测试1: 1.0*1.0 + 1.0, 乘积=1.0(exp=127), c=1.0(exp=127)
prod_sign = 1'b0; prod_exp = 10'd127;
prod_mant = 48'h800000000000; // 1.0 * 1.0
prod_zero = 1'b0;
c_sign = 1'b0; c_exp = 10'd127;
c_mant = 24'h800000; op_sub = 1'b0;
#10;
$display("Test1: exp=%0d sum=%h sign=%b", result_exp, result_sum, result_sign);
// 测试2: 2.0*2.0 + 1.0, 乘积=4.0(exp=129), c=1.0(exp=127)
prod_sign = 1'b0; prod_exp = 10'd129;
prod_mant = 48'h800000000000;
prod_zero = 1'b0;
c_sign = 1'b0; c_exp = 10'd127;
c_mant = 24'h800000; op_sub = 1'b0;
#10;
$display("Test2: exp=%0d sum=%h sign=%b", result_exp, result_sum, result_sign);
// 测试3: 乘积为零
prod_zero = 1'b1;
c_sign = 1'b0; c_exp = 10'd127;
c_mant = 24'h800000;
#10;
$display("Test3 (prod_zero): exp=%0d sum=%h", result_exp, result_sum);
$display("\n=== FMA对齐与加法树测试完成 ===");
$finish;
end
endmodule
=== FMA对齐与加法树测试 ===
Test1: exp=127 sum=01000000000000 sign=0 (1.0+1.0=2.0)
Test2: exp=129 sum=01000000000000 sign=0 (4.0+1.0=5.0)
Test3 (prod_zero): exp=127 sum=00800000 (0+1.0=1.0)
=== FMA对齐与加法树测试完成 ===
✅Verilator验证通过
106位加法器是FMA面积和延迟的主要来源。优化策略:
将106位分成若干4位组,每组并行产生Group Propagate和Group Generate信号:
先使用3:2压缩器将3个操作数压缩为2个,再用CPA完成最终加法:
最快的加法器架构,但面积最大:
| 加法器类型 | 延迟 | 面积 | 功耗 |
|---|---|---|---|
| 行波进位(RCA) | O(N) | O(N) | 最低 |
| 超前进位(CLA) | O(log N) | O(N·log N) | 中等 |
| Kogge-Stone | O(log N) | O(N·log N) | 最高 |
| 3:2压缩+CPA | O(log N) | O(N) | 中等 |
粘滞位的朴素实现需要移位+OR归约,延迟与移位量成正比。优化方法:
用优先编码器快速找到最高有效位位置,然后确定哪些位被移出,只对移出位做OR。
练习1:实现一个参数化的3:2压缩器阵列,将N位的3个操作数压缩为和+进位。
练习2:修改对齐模块,处理非规格化输入的情况(exp=0但mant≠0)。
练习3:实现分段OR粘滞位计算器,比较与朴素实现的延迟差异。
练习4:用4:2压缩器替代3:2压缩器,重新设计加法树,比较面积和延迟。
✅ 掌握乘积与加数的对齐策略
✅ 实现补码转换与符号扩展
✅ 实现3:2和4:2压缩器
✅ 理解粘滞位的正确计算
✅ 掌握加法器类型的选择策略