目录
时序分析实操
分析数据手册
实验工程
输入部分
输出部分
顶层部分
设计层次
综合布线
时序约束
时钟约束
输入延时约束
分析输入延时的约束如何设计
数据中间采样
最小延时约束
最大延时约束
结果分析
数据边缘采样
添加input delay约束
时序报告
解决方法
PLL IP配置参数
综合布线
改变PLL IP中的相移参数
综合布线
结果分析
往期系列博客
本实验根据数据手册分析,对时序进行约束,分为两种情况:数据中间采样和数据边缘采样,并且在边缘采样的情况下,还利用PLL进行时序的优化。
分析图中信息,分别是数据中心采样和数据边缘采样,在边缘采样的情况下,对于时钟对数据采样,时钟沿与数据到来之间的偏差最大为2ns,最小为-2ns。
时钟的频率为54Mhz,也就是周期为18.519ns。
这次仍然采用上次分析input delay的工程,代码如下
`timescale 1ns / 1ps
module iddr_ctrl(
(* clock_buffer_type = "none" *) input wire rx_clk_90,
input wire rst,
input wire [3:0] rx_dat,
input wire rx_ctrl,
output wire rx_en,
output wire [7:0] rx_data
);
wire [7:0] rxd;
wire rxdv,rxerr;
reg rxdv_r;
reg rxerr_r;
reg [7:0] rxd_r;
assign rx_en = rxdv_r;
assign rx_data =rxd_r ;
always @(posedge rx_clk_90) begin
if (rst == 1'b1) begin
rxdv_r <= 1'b0;
rxerr_r <= 1'b0;
rxd_r <= 'd0;
end
else begin
rxdv_r <= rxdv;
rxerr_r <= rxerr;
rxd_r <= rxd;
end
end
generate
genvar i;
for(i=0;i<4;i=i+1) begin
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), // "OPPOSITE_EDGE", "SAME_EDGE"
// or "SAME_EDGE_PIPELINED"
.INIT_Q1(1'b0), // Initial value of Q1: 1'b0 or 1'b1
.INIT_Q2(1'b0), // Initial value of Q2: 1'b0 or 1'b1
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) IDDR_rxd_inst (
.Q1(rxd[i]), // 1-bit output for positive edge of clock
.Q2(rxd[i+4]), // 1-bit output for negative edge of clock
.C(rx_clk_90), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D(rx_dat[i]), // 1-bit DDR data input
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
end
endgenerate
IDDR #(
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"), // "OPPOSITE_EDGE", "SAME_EDGE"
// or "SAME_EDGE_PIPELINED"
.INIT_Q1(1'b0), // Initial value of Q1: 1'b0 or 1'b1
.INIT_Q2(1'b0), // Initial value of Q2: 1'b0 or 1'b1
.SRTYPE("SYNC") // Set/Reset type: "SYNC" or "ASYNC"
) IDDR_rxctrl_inst (
.Q1(rxdv), // 1-bit output for positive edge of clock
.Q2(rxerr), // 1-bit output for negative edge of clock
.C(rx_clk_90), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D(rx_ctrl), // 1-bit DDR data input
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
endmodule
module oddr_ctrl(
input wire sclk,
input wire [7:0] tx_dat,
input wire tx_en,
input wire tx_c,//相移时钟
output wire [3:0] tx_data,
output wire tx_dv,
output wire tx_clk
);
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("ASYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_DV_inst (
.Q(tx_dv), // 1-bit DDR output
.C(sclk), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D1(tx_en), // 1-bit data input (positive edge)
.D2(tx_en), // 1-bit data input (negative edge)
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("ASYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_CLK_inst (
.Q(tx_clk), // 1-bit DDR output
.C(tx_c), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D1(1'b1), // 1-bit data input (positive edge)
.D2(1'b0), // 1-bit data input (negative edge)
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
genvar j;
generate
for(j=0;j<4;j=j+1)
begin
ODDR #(
.DDR_CLK_EDGE("SAME_EDGE"), // "OPPOSITE_EDGE" or "SAME_EDGE"
.INIT(1'b0), // Initial value of Q: 1'b0 or 1'b1
.SRTYPE("ASYNC") // Set/Reset type: "SYNC" or "ASYNC"
) ODDR_DATA_inst (
.Q(tx_data[j]), // 1-bit DDR output
.C(sclk), // 1-bit clock input
.CE(1'b1), // 1-bit clock enable input
.D1(tx_dat[j]), // 1-bit data input (positive edge)
.D2(tx_dat[j+4]), // 1-bit data input (negative edge)
.R(1'b0), // 1-bit reset
.S(1'b0) // 1-bit set
);
end
endgenerate
endmodule
module top_ioddr(
input wire rx_clk,
input wire rx_ctrl,
input wire [3:0] rx_dat,
//tx
output wire tx_clk,
output wire [3:0] tx_d,
output wire tx_dv,
input wire sdrclk,
input wire [3:0] sdrdata,
input wire sdrden,
output reg tout
);
wire rst;
wire rx_clk_90;
wire rx_en;
wire [7:0] rx_data;
reg tx_en1,tx_en2;
reg [7:0] tx_data1,tx_data2;
assign rst =0;
assign rx_clk_90 = rx_clk;
/*
clk_wiz_0 clk_gen0
(
// Clock out ports
.clk_out1(rx_clk_90), // output clk_out1
// Clock in ports
.clk_in1(rx_clk)); // input clk_in1
*/
always @(posedge rx_clk_90 or posedge rst) begin
if (rst == 1'b1) begin
tx_data1 <= 'd0;
end
else if (rx_en == 1'b1) begin
tx_data1 <= rx_data+ rx_data -1;
end
end
always @(posedge rx_clk_90 or posedge rst) begin
if (rst == 1'b1) begin
tx_data2 <= 'd0;
end
else if (tx_en1 == 1'b1) begin
tx_data2 <= tx_data1+ tx_data1 -5;
end
end
always @(posedge rx_clk_90 ) begin
tx_en1 <= rx_en;
end
always @(posedge rx_clk_90 ) begin
tx_en2 <= tx_en1;
end
iddr_ctrl inst_iddr_ctrl
(
.rx_clk_90 (rx_clk_90),
.rst (rst),
.rx_dat (rx_dat),
.rx_ctrl (rx_ctrl),
.rx_en (rx_en),
.rx_data (rx_data)
);
oddr_ctrl inst_oddr_ctrl
(
.sclk (rx_clk_90),
.tx_dat (tx_data2),
.tx_en (tx_en2),
.tx_c (rx_clk_90),
.tx_data (tx_d),
.tx_dv (tx_dv),
.tx_clk (tx_clk)
);
//sdr clock domain
reg [3:0] sdrdata_r1,sdrdata_r2;
reg sdrden_r1,sdrden_r2;
always @(posedge sdrclk ) begin
{sdrdata_r2,sdrdata_r1} <= {sdrdata_r1,sdrdata};
end
always @(posedge sdrclk ) begin
{sdrden_r2,sdrden_r1} <= {sdrden_r1,sdrden};
end
always @(posedge sdrclk) begin
if(sdrden_r2 == 1'b1) begin
tout <= (&sdrdata_r1)|(&sdrdata_r2);
end
else begin
tout <= (^sdrdata_r2);
end
end
endmodule
综合布线完成之后,点击布线的open implemented design中的edit timing constraints,对时序约束进行编辑
点击creat clock中的+号,创建的时钟约束如下,对手册中的时钟进行约束,频率为54Mhz即周期为18.519ns,时钟约束名设定为sdrclk,对应着单边采样时钟的名字,让其对应上。最后点击ok即可。
这样就创建了时钟约束
再来添加输入延时约束,同样的点击左侧栏的input delay选择栏的+号添加约束
由于是中间采样,所以采样时钟位于数据的中间,而整个数据可以看成一个时钟周期,处于中间则应该是一般的时钟周期,即9.259ns,用Visio简单画了一个示意图方便理解。除此之外,还存在最大最小的延时,分别为max=2ns、min=-2ns,因此,要分别添加最小延时约束以及最大延时约束。
最小延时约束=半周期+最小延时=9.259ns-2ns=7.259ns
选择为上升沿,设定延时值为7.259ns
object对应上,然后点击ok即可
同最小延时约束的谁当大致相同,只需将延时值设定为11.259ns,并且改为max,其余不变点击ok。
这样就可以在input delay中看到刚刚添加的输入延时约束了
这时候点击apply保存,并打开时序报告,report timing
这时候可以看到建立时间和保持时间的余量都是很充足的,表示时序约束的没问题。
随便点开一条路径,可以计算验证以下
根据前面的介绍,建立时间余量=数据所被要求到达的时间-数据实际达到的时间
代入计算:setup slack = 20.048ns - 12.183ns = 7.865ns
符合设计的结果。
由于采用数据中间采样的方式,这种采样方式一般来说建立时间和保持时间都能满足,但是采用数据边缘采样的方式是否能够满足要求。
第二种采用数据边缘采样的方式进行input delay约束,如下图所示,这次同之前的不同,这次采样时钟上升沿处于数据结束的边缘上,注意!时钟沿采到的是数据结束的时候!
只需要在刚刚的约束改下延时值就行,max = 2ns ,min = -2ns
设置完点击保存即可
report timing,建立时间余量正常
保持时间余量报错,出现红色说明出现了保持时间违例
可以看出建立时间余量非常充裕,但是保持时间余量出现了负值,这说明数据结束的时间比时钟到达的时间早,就导致了时钟没有采到数据,因此要对其进行约束。
解决的方法有两个:
显然减小时钟的延时会更简单一些,可以利用PLL实现改变时钟延时,添加一个相移为零的PLL,输入输出时钟频率都为54Mhz,PLL除了有相位偏移和改变时钟频率的作用,还有一个作用就是消除时钟到来之前的延时,也就是输入时钟到达PLL之前是有一段延时的,这段延时PLL是可以消除掉的,前面说了出现保持时间余量的违例的原因是采样时钟的延时大于数据结束的时间,而添加了PLL可以在一定程度的减小时钟延时。
首先在ip catalog中添加PLL IP,并将其例化进顶层。
具体代码例化
module top_ioddr(
input wire rx_clk,
input wire rx_ctrl,
input wire [3:0] rx_dat,
//tx
output wire tx_clk,
output wire [3:0] tx_d,
output wire tx_dv,
input wire sdrclk,
input wire [3:0] sdrdata,
input wire sdrden,
output reg tout
);
wire rst;
wire rx_clk_90;
wire rx_en;
wire [7:0] rx_data;
reg tx_en1,tx_en2;
reg [7:0] tx_data1,tx_data2;
wire sdrclk1;
assign rst =0;
assign rx_clk_90 = rx_clk;
/*
clk_wiz_0 clk_gen0
(
// Clock out ports
.clk_out1(rx_clk_90), // output clk_out1
// Clock in ports
.clk_in1(rx_clk)); // input clk_in1
*/
always @(posedge rx_clk_90 or posedge rst) begin
if (rst == 1'b1) begin
tx_data1 <= 'd0;
end
else if (rx_en == 1'b1) begin
tx_data1 <= rx_data+ rx_data -1;
end
end
always @(posedge rx_clk_90 or posedge rst) begin
if (rst == 1'b1) begin
tx_data2 <= 'd0;
end
else if (tx_en1 == 1'b1) begin
tx_data2 <= tx_data1+ tx_data1 -5;
end
end
always @(posedge rx_clk_90 ) begin
tx_en1 <= rx_en;
end
always @(posedge rx_clk_90 ) begin
tx_en2 <= tx_en1;
end
iddr_ctrl inst_iddr_ctrl
(
.rx_clk_90 (rx_clk_90),
.rst (rst),
.rx_dat (rx_dat),
.rx_ctrl (rx_ctrl),
.rx_en (rx_en),
.rx_data (rx_data)
);
oddr_ctrl inst_oddr_ctrl
(
.sclk (rx_clk_90),
.tx_dat (tx_data2),
.tx_en (tx_en2),
.tx_c (rx_clk_90),
.tx_data (tx_d),
.tx_dv (tx_dv),
.tx_clk (tx_clk)
);
clk_wiz_0 pll_inst
(
// Clock out ports
.clk_out1(sdrclk1), // output clk_out1
// Clock in ports
.clk_in1(sdrclk)); // input clk_in1
//sdr clock domain
reg [3:0] sdrdata_r1,sdrdata_r2;
reg sdrden_r1,sdrden_r2;
always @(posedge sdrclk1 ) begin
{sdrdata_r2,sdrdata_r1} <= {sdrdata_r1,sdrdata};
end
always @(posedge sdrclk1 ) begin
{sdrden_r2,sdrden_r1} <= {sdrden_r1,sdrden};
end
always @(posedge sdrclk1) begin
if(sdrden_r2 == 1'b1) begin
tout <= (&sdrdata_r1)|(&sdrdata_r2);
end
else begin
tout <= (^sdrdata_r2);
end
end
endmodule
输入时钟和输出时钟频率设定为54Mhz,与数据手册中的保持一致,相移0°,其余保持默认即可。
由于利用了PLL进行时钟的约束,因此在XDC文件中应将刚刚添加的时钟约束给删除,时钟由PLL提供。
set_property IOSTANDARD LVCMOS33 [get_ports rx_clk]
set_property PACKAGE_PIN J19 [get_ports rx_clk]
set_property PACKAGE_PIN H22 [get_ports rx_ctrl]
set_property IOSTANDARD LVCMOS33 [get_ports rx_ctrl]
set_property PACKAGE_PIN K22 [get_ports {rx_dat[0]}]
set_property PACKAGE_PIN K21 [get_ports {rx_dat[1]}]
set_property PACKAGE_PIN J22 [get_ports {rx_dat[2]}]
set_property PACKAGE_PIN J20 [get_ports {rx_dat[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rx_dat[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rx_dat[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rx_dat[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {rx_dat[0]}]
set_property PACKAGE_PIN M18 [get_ports tx_dv]
set_property IOSTANDARD LVCMOS33 [get_ports tx_dv]
set_property PACKAGE_PIN K18 [get_ports tx_clk]
set_property IOSTANDARD LVCMOS33 [get_ports tx_clk]
set_property PACKAGE_PIN M22 [get_ports {tx_d[0]}]
set_property PACKAGE_PIN L18 [get_ports {tx_d[1]}]
set_property PACKAGE_PIN L19 [get_ports {tx_d[2]}]
set_property PACKAGE_PIN L20 [get_ports {tx_d[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {tx_d[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {tx_d[2]}]
set_property IOSTANDARD LVCMOS33 [get_ports {tx_d[1]}]
set_property IOSTANDARD LVCMOS33 [get_ports {tx_d[0]}]
set_property PACKAGE_PIN W19 [get_ports sdrclk]
set_property PACKAGE_PIN Y22 [get_ports sdrden]
set_property PACKAGE_PIN V20 [get_ports {sdrdata[0]}]
set_property PACKAGE_PIN U20 [get_ports {sdrdata[1]}]
set_property PACKAGE_PIN AB22 [get_ports {sdrdata[2]}]
set_property PACKAGE_PIN AB21 [get_ports {sdrdata[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports sdrclk]
set_property IOSTANDARD LVCMOS33 [get_ports {sdrdata[*]}]
set_property IOSTANDARD LVCMOS33 [get_ports sdrden]
set_property PACKAGE_PIN Y21 [get_ports tout]
set_property IOSTANDARD LVCMOS33 [get_ports tout]
综合布线后的操作和刚刚做的数据中间采样一致。
打开编辑时序约束,并report timing
可以看到建立时间余量处于正常的状态
保持时间余量依然出现了违例,但是可以发现,相对于刚刚没有添加PLL时,此时添加了PLL后的保持时间余量已经有了改善。
这说明采样时钟的延时还是过大了,需要进一步缩小延时,利用PLL可以相移的特点,将时钟向左相移60°,一个周期是360°,相当于向左移动了1/6个时钟周期,一个时钟周期是18.519ns,相当于将时钟延时减小了3.0865ns,这样就可以增加保持时间的余量。
完成之后reload,重新report timing查看时序报告。
可以看到建立时间余量和保持时间余量均恢复正常。
随便点开一条路径进行分析,其所得保持时间余量是符合设计要求的,这样就达到了建立时间余量和保持时间余量都恢复正常的目的。
【Xilinx Vivado时序分析/约束系列1】FPGA开发时序分析/约束-寄存器间时序分析
【Xilinx Vivado时序分析/约束系列2】FPGA开发时序分析/约束-建立时间
【Xilinx Vivado时序分析/约束系列3】FPGA开发时序分析/约束-保持时间
【Xilinx Vivado时序分析/约束系列4】FPGA开发时序分析/约束-实验工程上手实操
【Xilinx Vivado时序分析/约束系列5】FPGA开发时序分析/约束-IO时序分析
【Xilinx Vivado时序分析/约束系列6】FPGA开发时序分析/约束-IO时序输入延时
【Xilinx Vivado时序分析/约束系列7】FPGA开发时序分析/约束-FPGA单沿采样数据input delay时序约束实操