以Quartus为例(延时数据为Stratix III器件典型延时)手动布局:
module top(input clk,din,output dout);
[cpp]view plaincopyprint?
regdin_ff,din_ff2;
always@(posedgeclk)
begin
din_ff<=din;
din_ff2<=din_ff;
end
assigndout=din_ff2; endmodule
reg din_ff,din_ff2;
always@(posedge clk)
begin
din_ff <= din;
din_ff2 <= din_ff;
end
assign dout = din_ff2;
endmodule
忽略IO时序,怎样使这个电路跑到最快?在FPGA中线延时占主导,因此我们首先需要把din_ff和din_ff2两个寄存器邻近摆放。在Assignment Editor中,新增Location规则,目标选择din_ff或din_ff2,位置选择“LAB”,再选择你希望的LAB坐标(在此例中具体坐标在哪不重要)。将这两个寄存器摆到同一个LAB内,就实现了数据路径上的最优化。 这就是所谓的“手动布局”。
一个寄存器的Tsu/Th大概是50ps,Tco大概85ps,同一个LAB的互联延时大概200ps,忽略Minimum Pulse Width约束,是否意味着这个电路的最小周期是335ps即能跑3GHz呢?注意,此时数据路径上已经没有瓶颈了。
****************........我是分割线.......*****************
::基于OCV的fmax计算::
查看Timequest报告,你会发现报出的fmax远小于期望的3GHz,为什么呢?
在Quartus自动PR时,时钟通常会被拉到全局缓冲CLKCTRL(这有时是不可避免的,如PLL输出),再拉到每个DFF。虽然全局时钟到每个DFF的平均Skew较小,但绝对延时是比较大的(ns级)。而Timequest的分析是基于OCV的(简单来说,就是所有延时都有min和max),延时的绝对值越大,min和max的差值也越大。比如,假定CLKCTRL出来的延时如下:
[cpp]view plaincopyprint?
CLKCTRL->din_ff|clk平均延时2ns(min1.5ns,max2.5ns) CLKCTRL->din_ff2|clk平均延时2ns(min1.5ns,max2.5ns)
CLKCTRL -> din_ff|clk 平均延时2ns(min 1.5ns,max 2.5ns)
CLKCTRL -> din_ff2|clk 平均延时2ns(min 1.5ns,max 2.5ns)
工具先修Hold Time,din_ff到din_ff2的最快路径为(我们假定Tco(min)+线延时(min)-Th =250ps):
[cpp]view plaincopyprint?
Data(early):CLKCTRL----1.5ns---->din_ff|clk----250ps---->din_ff2|d
Data(early):CLKCTRL ----1.5ns----> din_ff|clk ----250ps----> din_ff2|d
din_ff2的最慢时钟路径为:
[cpp]view plaincopyprint?
Clock(late):CLKCTRL----2.5ns---->din_ff2|clk
Clock(late):CLKCTRL ----2.5ns----> din_ff2|clk
(1.5ns+250ps<2.5ns)此时工具发现需要在din_ff后面插750ps(min)的Buffer才能使Hold Time无违规(FPGA中不插Delay Cell,一般是绕线)。插完Buffer后,我们来看看此时能达到的最小周期(计算Setup Time):
din_ff到din_ff2的最慢路径为(我们假定Tco(max)+线延时 (max)+Tsu = 350ps,插的Buffer(max)=950ps):
[cpp]view plaincopyprint?
Data(late):CLKCTRL----2.5ns---->din_ff|clk----350ps+950ps=1.3ns---->din_ff2|d
Data(late): CLKCTRL ----2.5ns----> din_ff|clk ----350ps+950ps=1.3ns----> din_ff2|d
din_ff2的最快时钟路径为:
[cpp]view plaincopyprint?
Clock(early):CLKCTRL----1.5ns---->din_ff2|clk
Clock(early): CLKCTRL ----1.5ns----> din_ff2|clk
数据路径相对于时钟的延时为(2.5ns+1.3ns-1.5ns=2.3ns),所以最小时钟周期2.3ns,对应fmax为435MHz,与一开始预测的3GHz差别巨大。
细心的朋友会发现,这个最小周期值非常接近于CLKCTRL延时(max-min)的两倍。
****************........我是分割线.......*****************
::手动时钟树 -- 创建共同时钟路径::
OCV的计算引擎会对共同时钟路径不计算min/max,因为物理上同一段路径的延时是固定的,这个在Quartus中称为CCPP,在Cadence中称为CPPR,在Synopsys中称为CRPR。。。
既然CLKCTRL延时巨大,那我们可以在CLKCTRL到两个寄存器这段路径上创建共同时钟路径,减小CLKCTRL延时(max-min)对时序的冲击。我们对原代码作以下修改:
[cpp]view plaincopyprint?
moduletop(inputclk,din,outputdout);
wireclk_buf/*synthesiskeep*/;
assignclk_buf=clk;
regdin_ff,din_ff2;
always@(posedgeclk_buf)
begin
din_ff<=din;
din_ff2<=din_ff;
end
assigndout=din_ff2;
endmodule
module top(input clk,din,output dout);
wire clk_buf/*synthesis keep*/;
assign clk_buf = clk;
reg din_ff,din_ff2;
always@(posedge clk_buf)
begin
din_ff <= din;
din_ff2 <= din_ff;
end
assign dout = din_ff2;
endmodule
通过Location规则把clk_buf节点放在与两个寄存器同一个LAB内,取得最大的共同时钟路径和最小的分离时钟路径。此时再计算一下Hold Time(假定clk_buf到寄存器时钟端的延时min=300ps,max=400ps):
[cpp]view plaincopyprint?
Data(early):clk_buf----300ps---->din_ff|clk----250ps---->din_ff2|d Clock(late):clk_buf----400ps---->din_ff2|clk
Data(early):clk_buf ----300ps----> din_ff|clk ----250ps----> din_ff2|d
Clock(late):clk_buf ----400ps----> din_ff2|clk
(300ps+250ps>400ps)可见Hold Time无违规,不需要插Buffer。此时计算Setup余量:
[cpp]view plaincopyprint?
Data(late):clk_buf----400ps---->din_ff|clk----350ps---->din_ff2|d Clock(early):clk_buf----300ps---->din_ff2|clk
Data(late):clk_buf ----400ps----> din_ff|clk ----350ps----> din_ff2|d
Clock(early):clk_buf ----300ps----> din_ff2|clk
数据路径相对延时为(400ps+350ps-300ps=450ps),如不考虑Minimum Pulse Width约束,则最小周期450ps,fmax为2.2GHz。这就是手动时钟树 -- 创建共同时钟路径的威力。
****************........我是分割线.......*****************
::手动时钟树 -- 手动Skew调整::
Quartus的Fitter中有很多优化Clock Skew的选项,其实选了根本没有任何效果。。。Quartus自动PR对时钟的优化就是这么弱。
我们还能进一步对时序进行优化,那就是Clock Skew手工调整,平衡(Tco+线延时+Tsu/-Th)那段250ps~350ps的数据延时。代码继续修改如下:
[cpp]view plaincopyprint?
moduletop(inputclk,din,outputdout);
wireclk_buf/*synthesiskeep*/;
wireclk_buf2/*synthesiskeep*/;
assignclk_buf=clk;
assignclk_buf2=clk_buf;
regdin_ff,din_ff2;
always@(posedgeclk_buf)
din_ff<=din;
always@(posedgeclk_buf2)
din_ff2<=din_ff;
assigndout=din_ff2;
endmodule
module top(input clk,din,output dout);
wire clk_buf/*synthesis keep*/;
wire clk_buf2/*synthesis keep*/;
assign clk_buf = clk;
assign clk_buf2 = clk_buf;
reg din_ff,din_ff2;
always@(posedge clk_buf)
din_ff <= din;
always@(posedge clk_buf2)
din_ff2 <= din_ff;
assign dout = din_ff2;
endmodule
假定clk_buf到clk_buf2的延时为250ps~300ps(如需更大Skew,在SDC中用set_net_delay -min/max对clk_buf到clk_buf2的延时进行约束即可):
[cpp]view plaincopyprint?
Data(early):clk_buf----300ps---->din_ff|clk----250ps---->din_ff2|d Clock(late):clk_buf----300ps---->clk_buf2----400ps---->din_ff2|clk
Data(early):clk_buf ----300ps----> din_ff|clk ----250ps----> din_ff2|d
Clock(late):clk_buf ----300ps----> clk_buf2 ----400ps----> din_ff2|clk
(300ps+250ps<300ps+400ps)可见Hold Time违规,工具需要插150ps(min)的Buffer。假设Buffer的延时为150ps~200ps,此时计算Setup余量
[cpp]view plaincopyprint?
Data(late):clk_buf----400ps---->din_ff|clk----350ps+200ps---->din_ff2|d Clock(early):clk_buf----250ps---->clk_buf2----300ps---->din_ff2|clk
Data(late):clk_buf ----400ps----> din_ff|clk ----350ps+200ps----> din_ff2|d
Clock(early):clk_buf ----250ps----> clk_buf2 ----300ps----> din_ff2|clk
(400ps+350ps+200ps-250ps-300ps=400ps), 如不考虑Minimum Pulse Width约束, 则最小周期缩短到400ps,fmax可达2.5GHz。
这就是手动时钟树 -- 手动Skew调整,这个手段在优化对外控制器模块时非常有用,用于平衡内部时钟输出到Pad的巨大延时。
****************........我是分割线.......*****************
在顶楼的DDR2-1066控制器实现过程中,大量应用了“手动布局、手动时钟树 -- 创建共同时钟路径、手动时钟树 -- 手动Skew调整”三项PR技术,使得用内部ALUT搭的控制器/PHY电路,最高频率得以赶超片内硬核。