一、Hyper-Optimization
Hyper-Retiming 和 Hyper-Pipelining 把数据路径加速之后,限制性能的往往就变成了控制逻辑:长的 feedback loop、状态机、复杂的组合判断。Hyper-Optimization 的思路是把这些限制 retiming 的逻辑结构改写成功能等效但工具能优化的形式,用前馈或预计算路径替代长的组合反馈路径。手册给出的预期是相比前一代高性能 FPGA 有 2x 的性能提升。
二、通用优化技术
2.1 Shannon 分解
Shannon 分解在 Hyper-Optimization 里扮演一个关键角色。它是布尔函数的一种因式分解方式。一个函数 FFF 可以写成 F=x⋅Fx+xˉ⋅FxˉF = x \cdot F_x + \bar{x} \cdot F_{\bar{x}}F=x⋅Fx+xˉ⋅Fxˉ,其中 FxF_xFx 和 FxˉF_{\bar{x}}Fxˉ 是 FFF 关于 xxx 的正、负 co-factor。对于四输入函数 F(a,b,c,x)F(a, b, c, x)F(a,b,c,x),可以分解为 x⋅F(a,b,c,1)+xˉ⋅F(a,b,c,0)x \cdot F(a, b, c, 1) + \bar{x} \cdot F(a, b, c, 0)x⋅F(a,b,c,1)+xˉ⋅F(a,b,c,0)。

在 Hyper-Optimization 中,Shannon 分解把关键信号 x 推到输入逻辑锥的最前面,让它穿过逻辑锥的路径最短。代价是面积翻倍,其他信号的路径变长。

综合工具可以利用常量驱动的输入对 co-factor 做化简,如上图所示。

可以对多个关键输入信号反复应用 Shannon 分解,每次分解面积再翻一倍。
Shannon 分解对处理 loop 特别有效。在 loop 中的逻辑上做 Shannon 分解后,逻辑被移出 loop,Compiler 就可以对它做流水线或 retiming 了。

上图是一个含有一个寄存器、四级组合逻辑和一个额外输入的 loop。在 loop 里面加寄存器会改变功能,但通过 Shannon 分解可以把组合逻辑移到 loop 外面。loop 里的寄存器输出只有 0 或 1,把喂给它的组合逻辑复制两份,一份输入接 0,一份接 1。

loop 里的寄存器从两份复制逻辑中选一份。分解后 loop 里的逻辑变少了,移出去的逻辑 Compiler 可以做 retiming 或 Hyper-Pipelining,电路性能就上去了。
Shannon 分解示例
以下代码是一个根据目标值对 internal_total 做加减的电路。
module target_loop (clk, sclr, data, target, running_total);
parameter WIDTH = 32;
input clk, sclr;
input [WIDTH-1:0] data, target;
output [WIDTH-1:0] running_total;
reg [WIDTH-1:0] internal_total;
always @(posedge clk) begin
if (sclr) internal_total <= 0;
else internal_total <= internal_total +
(((internal_total > target) ? data : data) * (target/4));
end
assign running_total = internal_total;
endmodule
Fast Forward 编译报告显示大约 302 MHz,最后一条 Fast Forward Limit 提示 critical chain 是一个 loop。

下图是上述表达式对应的电路结构,包含比较、加法和乘法操作。

这个表达式适合做 Shannon 分解。与其根据比较结果只做一个加法(加正或负的 data),不如同时算两条路径:internal_total - (data * target/4) 和 internal_total + (data * target/4),然后用 internal_total > target 的比较结果来选。改后的代码如下:
module target_loop_shannon (clk, sclr, data, target, running_total);
parameter WIDTH = 32;
input clk, sclr;
input [WIDTH-1:0] data, target;
output [WIDTH-1:0] running_total;
reg [WIDTH-1:0] internal_total;
wire [WIDTH-1:0] total_minus, total_plus;
assign total_minus = internal_total - (data * (target / 4));
assign total_plus = internal_total + (data * (target / 4));
always @(posedge clk) begin
if (sclr) internal_total <= 0;
else internal_total <= (internal_total > target) ? total_minus : total_plus;
end
assign running_total = internal_total;
endmodule
重编译后性能几乎翻倍。

什么电路适合 Shannon 分解
- 能重新安排许多输入来控制最后一级选择阶段的电路。
- 逻辑锥里只有一两个信号是真正关键的,其余是静态的或优先级明显更低的信号。
- 重组逻辑时要注意选择信号的逻辑深度和选择器输入端的逻辑深度,理想情况两边相近。
Shannon 分解面积代价显著,复杂函数尤其大。后面还会介绍面积代价更小的其他优化技术。
2.2 时域复用
时域复用用多个计算线程来提高电路吞吐量,也叫 C-slow retiming 或多线程化。做法是把电路里的每个寄存器替换成 C 个串联寄存器,每份额外寄存器创建一个新计算线程。数据通过整个设计需要 C 倍的时钟周期,但 Compiler 可以用这些额外寄存器做 retiming,把 fMAX 提高 C 倍。比如,与其例化两个跑 400 MHz 的模块,不如例化一个跑 800 MHz 的模块。

修改 RTL,把每个寄存器(包括 loop 里的)替换成 C 个寄存器,每个对应一个独立的计算线程。

上例中每个寄存器替换成了两个。编译后,额外的寄存器让 retiming 有更大的灵活度。

除了替换寄存器,还需要把多路输入数据流做 MUX 进入模块,输出端做 DEMUX 出去。时域复用适用于有多条并行线程、每条线程各自受 loop 限制的设计。被优化的模块必须对延迟不敏感。
2.3 Loop 展开
Loop 展开把逻辑从 loop 里移出来变成前馈流,之后可以加流水线级进一步优化。
2.4 Loop 流水线化
Loop 到处都是,但它也是 Hyper-Retiming 的死角。Compiler 不会自动对 loop 内部的逻辑做流水线,因为往 loop 里加或减时序元件可能改变功能。但你可以手动改 loop 结构,让 Compiler 能插入流水线级而不改变功能。三步走:
- 重组 loop 和非 loop 逻辑
- 手动往 loop 里加流水线级
- 级联 loop 逻辑
下图是一个逻辑 loop 的定义。结果 Zn 是输入 Xn 和它自身延迟版本的函数。

如果函数 f(·) 满足交换律、结合律和分配律(比如加法、XOR、取最大值),以下变换在数学上等价:

Loop 流水线化演示
以下用累加器来演示完整的 loop 流水线化过程。原始代码:输入 in 乘以 x,加上 out 的上一个值乘以 y。

module orig_loop_strct (rstn, clk, in, x, y, out);
input clk, rstn, in, x, y;
output out;
reg out, in_reg;
always @(posedge clk)
if (!rstn) in_reg <= 1'b0;
else in_reg <= in;
always @(posedge clk)
if (!rstn) out <= 1'b0;
else out <= y*out + x*in_reg;
endmodule
第一步是重写逻辑,尽可能把逻辑从 loop 里移出来,形成前向逻辑块。Compiler 不会优化 loop 里的任何逻辑。原则:能提前算的决策和计算在进 loop 之前算完,能塞进 loop 前面寄存器级的尽量塞进去。

第二步,retime loop 寄存器,确保功能与原始 loop 电路相同。

第三步,重复前面优化步骤,对高亮边界内的逻辑再做处理。最终结果是四级优化:

综合工具会把多个乘法器塌缩合并,实际资源比看上去少得多。
2.5 预计算
预计算是最简单也最实用的提速技术之一。遇到关键路径上的逻辑,先确认涉及的计算能不能更早拿到信号。把计算尽量提前,不让它出现在关键路径上。
处理 loop 的时候先考虑预计算。Compiler 很难单独靠 retiming 优化 loop 里的逻辑,不能把寄存器从 loop 外移进去,也不能从 loop 里移出来。所以 loop 里的逻辑越少越好。预计算之后,loop 里的逻辑最小化,编码值提前算好放在 loop 外面,可以单独做流水线和 retiming。Loop 本身还在,但它对速度的拖累被控制住了。

代码示例,原始 loop 里有比较运算符:
// 原始
StateJam: if (RetryCnt <= MaxRetry && JamCounter == 16)
Next_state = StateBackOff;
else if (RetryCnt > MaxRetry)
Next_state = StateJamDrop;
else
Next_state = Current_state;
把 RetryCnt <= MaxRetry 和 JamCounter == 16 的预计算结果提到 loop 外面:
reg RetryCntGTMaxRetry;
reg JamCounterEqSixteen;
// 预计算在 loop 外完成
StateJam: if (!RetryCntGTMaxRetry && JamCounterEqSixteen)
Next_state = StateBackOff;
else if (RetryCntGTMaxRetry)
Next_state = StateJamDrop;
else
Next_state = Current_state;
348

被折叠的 条评论
为什么被折叠?



