Xilinx FPGA 在使用Vivado软件开发时采用XDC文件作为物理和时序的约束文件。该文件规定了各种约束条件,在Vivado编译时应满足约束的各项规定,以便生成的Bit文件满足各项功能。XDC文件里的约束并不是在整个编译流程的每个环节都会使用,例如物理约束只会在Implementation阶段使用(placer and router)。注意时序约束时应合理,不应出现过约束和欠约束,过约束会使时序收敛变得困难(最终可能没有满足要求,也影响了其它时序条件的收敛)。欠约束虽然可以收敛,但会引起最终硬件运行时不满足真正的时序要求。时序约束在Synthesis和Implementation的编译流程中都会使用(这两个编译流程都是时间驱动型的算法)。
Vivado约束可以有一个或多个XDC文件组成,仅使用一个XDC文件在工程较小时的确方便,但当工程变得复杂时,所有内容都写在一个文件中,维护各种约束将变得非常困难。尤其是当工程中使用多个IP core 或一个大的复杂逻辑块是由多个团队合作开发时,众多的约束在一个文件中,将会被多个团队相互修改,维护起来极其困难。一般推荐的做法是将物理和时序约束分不同的文件存储,对于特定的模块(module)可以有自己的约束文件,但模块或IP的约束文件不会出现在约束集中,这些约束文件是和模块的源文件一起列举(list)的。下面以串行高速通信工程为例介绍XDC文件在工程中的应用。
1. 约束集及其有效性

图1
新建工程只有一个约束集(constrs_1), 如果想添加另外一个约束集,鼠标右键单击Constraints,如图2,选择Edit Constraints Sets。

图2

图3
在图3中,点击Specify Constraint set的下拉框,选中Create Constraint Set,将会创建新的约束集。命名为timing_constraint,如图4所示。

图4
点击OK,返回工程Project Navigator 窗口,如图5所示。

图5
如图5,工程创建了两个约束子集,分别为timing_constraint, constrs_1, 但只有一个子集是活跃的,即该子集中的文件在工程中被使用。可以鼠标右键选择Make Active,使被选中的子集有效。如图6所示。

图6
2. 约束文件读取的先后顺序
约束文件读取的先后顺序由约束文件的PROCESING_ORDER属性决定,PROCESING_ORDER有EARLY,NORMAL,LATE三个属性。一般情况下,Vivado先读取IP core的约束文件,后读取用户定义XDC文件,这样在读取用户XDC时可以修改IPcore的约束以满足整体工程约束的需要。
The default processing order for constraint files is(约束文件缺省的处理顺序):
1. User Constraints marked as EARLY(用户定义的被标记为EARLY的约束文件)
2. IP Constraints marked as EARLY (default)(IP core 被标记为EARLY的约束文件)
3. User Constraints marked as NORMAL(被标记为NORMAL的用户约束文件)
4. IP Constraints marked as LATE (contain clock dependencies)(被标记为LATE的IP约束文件,一般该文件包含对时钟的依赖)
5. User Constraints marked as LATE (被标记的LATE的用户约束文件)
属性相同的约束文件(一般缺省值为NORMAL)在Vivado环境下按照先后顺序读取(从上到下)。后读取的文件可以覆盖先读取文件中的约束(如果出现对同一个目标的约束)。
3. 创建约束文件
选中某个子集,点击“+” ,如图7,或鼠标右键在弹出的图7对话框中选择Add source。

图7

图8
在图9中单击Create File 创建hs_phy.xdc文件,点击Finish完成创建(此时为空文件)。

图9
4.查看及修改约束文件的属性
选中要查看的XDC文件,在Source File Properties(源文件属性)窗口中选取Properties 标签, 找到PROCESSING_ORDER, 在下拉框中修改约束文件的属性(NORMAL,EARLY或LATE),如图10所示。

图10
在General标签中可以选择该创建的文件可以被哪些编译流程(compilation flow)使用,如图11

图11
图11默认创建的约束文件可以被Synthesis和Implementation两个流程使用,但是如果确定该文件仅仅是物理约束,没有时序约束的内容,Sythesis的选项也可以去掉,只保留Implementation选项。当然两者都选也没有问题,物理约束相关内容综合会自动忽略。
5.查看IP核的约束文件
由于系统使用了PLL, 因此在生成IP核时也相应的生成了IP核的XDC文件, 在Source窗口选中Libraries标签,展开Block Sources, 找到pll1.xdc, 如图12,双击打开pll1.xdc文件。

图12
pll1.xdc文件的内容如下:
create_clock -period 20.000 [get_ports clk_in1]
set_input_jitter [get_clocks -of_objects [get_ports clk_in1]] 0.2
set_property PHASESHIFT_MODE WAVEFORM [get_cells -hierarchical *adv*]
该文件设置了约束内容,创建了时钟,设置了jitter, 设置了相位模式PHASESHIFT_MODE和波形(WAVEFORM)。其中相位和波形是从逻辑单元中导出的的。*adv* 表示在pll1的路径及子目录中查找含有adv字符的逻辑单元(或模块)。

图13
打开图13的文件pll1_clk_wiz, 可以看到文件中有MMCME2_ADV的例化名 mmcm_adv_inst包含 adv, 因此上面的各种属性如PHASESHIFT_MODE,WAVEFORM都是从mmcm_adv_inst的CELL导出。
`timescale 1ps/1ps
module pll1_clk_wiz
(// Clock in ports
// Clock out ports
output clk_out1,
output clk_out2,
// Status and control signals
input reset,
output locked,
input clk_in1
);
// Input buffering
//————————————
wire clk_in1_pll1;
wire clk_in2_pll1;
IBUF clkin1_ibufg
(.O (clk_in1_pll1),
.I (clk_in1));
// Clocking PRIMITIVE
//————————————
// Instantiation of the MMCM PRIMITIVE
// * Unused inputs are tied off
// * Unused outputs are labeled unused
wire clk_out1_pll1;
wire clk_out2_pll1;
wire clk_out3_pll1;
wire clk_out4_pll1;
wire clk_out5_pll1;
wire clk_out6_pll1;
wire clk_out7_pll1;
wire [15:0] do_unused;
wire drdy_unused;
wire psdone_unused;
wire locked_int;
wire clkfbout_pll1;
wire clkfbout_buf_pll1;
wire clkfboutb_unused;
wire clkout0b_unused;
wire clkout1b_unused;
wire clkout2_unused;
wire clkout2b_unused;
wire clkout3_unused;
wire clkout3b_unused;
wire clkout4_unused;
wire clkout5_unused;
wire clkout6_unused;
wire clkfbstopped_unused;
wire clkinstopped_unused;
wire reset_high;
MMCME2_ADV
#(.BANDWIDTH (“OPTIMIZED”),
.CLKOUT4_CASCADE (“FALSE”),
.COMPENSATION (“ZHOLD”),
.STARTUP_WAIT (“FALSE”),
.DIVCLK_DIVIDE (1),
.CLKFBOUT_MULT_F (16.000),
.CLKFBOUT_PHASE (0.000),
.CLKFBOUT_USE_FINE_PS (“FALSE”),
.CLKOUT0_DIVIDE_F (8.000),
.CLKOUT0_PHASE (0.000),
.CLKOUT0_DUTY_CYCLE (0.500),
.CLKOUT0_USE_FINE_PS (“FALSE”),
.CLKOUT1_DIVIDE (2),
.CLKOUT1_PHASE (180.000),
.CLKOUT1_DUTY_CYCLE (0.500),
.CLKOUT1_USE_FINE_PS (“FALSE”),
.CLKIN1_PERIOD (20.000))
mmcm_adv_inst
// Output clocks
(
.CLKFBOUT (clkfbout_pll1),
.CLKFBOUTB (clkfboutb_unused),
.CLKOUT0 (clk_out1_pll1),
.CLKOUT0B (clkout0b_unused),
.CLKOUT1 (clk_out2_pll1),
.CLKOUT1B (clkout1b_unused),
.CLKOUT2 (clkout2_unused),
.CLKOUT2B (clkout2b_unused),
.CLKOUT3 (clkout3_unused),
.CLKOUT3B (clkout3b_unused),
.CLKOUT4 (clkout4_unused),
.CLKOUT5 (clkout5_unused),
.CLKOUT6 (clkout6_unused),
// Input clock control
.CLKFBIN (clkfbout_buf_pll1),
.CLKIN1 (clk_in1_pll1),
.CLKIN2 (1’b0),
// Tied to always select the primary input clock
.CLKINSEL (1’b1),
// Ports for dynamic reconfiguration
.DADDR (7’h0),
.DCLK (1’b0),
.DEN (1’b0),
.DI (16’h0),
.DO (do_unused),
.DRDY (drdy_unused),
.DWE (1’b0),
// Ports for dynamic phase shift
.PSCLK (1’b0),
.PSEN (1’b0),
.PSINCDEC (1’b0),
.PSDONE (psdone_unused),
// Other control and status signals
.LOCKED (locked_int),
.CLKINSTOPPED (clkinstopped_unused),
.CLKFBSTOPPED (clkfbstopped_unused),
.PWRDWN (1’b0),
.RST (reset_high));
assign reset_high = reset;
assign locked = locked_int;
// Clock Monitor clock assigning
//————————————–
// Output buffering
//———————————–
BUFG clkf_buf
(.O (clkfbout_buf_pll1),
.I (clkfbout_pll1));
BUFG clkout1_buf
(.O (clk_out1),
.I (clk_out1_pll1));
BUFG clkout2_buf
(.O (clk_out2),
.I (clk_out2_pll1));
endmodule
其它还有DPRAM等IP也有自己的XDC文件,可以打开阅读,这里不在详述。当IP核pll1加载到Vivado开发环境后, pll1.xdc将一起被加载的Vivado系统中,因此如果没有用户XDC修改该选项,该约束将在整个工程内有效。
修改顶层文件添加测试时钟(test_clk)与test 端口,程序如下:
module hs_txrx
#(
parameter IDLE=10'b1010_1010_10
)
(
input inclk,
//=========================test clock and register
input test_clk,
output reg [3:0] test=0,
//=============================test clock and register
input pll_rst,
input [10:0] tx_addr,
input [15:0] tx_data,
input tx_en,
output [15:0]tx_q,
output tx_p,
output tx_n,
input rx_p,
input rx_n,
output rx_data_vld,
output rx_data_rdy,
output [7:0] rx_data,
output clk_100M,
output pll_locked
);
wire clk_400M;
wire rst=!pll_locked;
//=================================test
always@(posedge test_clk)
test<=test+1;
//=================================test
trans_data_link
#(
.IDLE(IDLE)
)
trans_data_link_inst
(
.rst (rst),
.clk_100M (clk_100M),
.tx_addr_a (tx_addr),
.tx_data_a (tx_data),
.tx_en_a (tx_en),
.tx_q_a (tx_q),
.tx_out (tx)
);
recv_data_link #
(
.SYNC_START1(10'b0011111010), //K28.5 with dipin=0;
.SYNC_START2(10'b1100000101), //K28.5 with dispin=1
.SYNC_END1 (10'b0011111001), //K28.5 with dipin=0;
.SYNC_END2 (10'b1100000110) //K28.5 with dispin=1
)
recv_data_link_inst
(
.rst (rst),
.clk_100M (clk_100M),
.clk_400M (clk_400M),
.rx (rx),
.link_rx_data_rdy (rx_data_rdy),
.link_rx_data (rx_data),
.link_rx_data_vld (rx_data_vld)
);
pll1 pll1_inst
(
.reset ( pll_rst ), //1'b0;
.clk_in1 ( inclk ),
.clk_out1 ( clk_100M),
.clk_out2 ( clk_400M ),
.locked ( pll_locked )
);
OBUFDS #
(
.IOSTANDARD("LVDS25"), // Specify the output I/O standard
.SLEW("FAST") // Specify the output slew rate
) LVDS_OUT
(
.O (tx_p), // Diff_p output (connect directly to top-level port)
.OB(tx_n), // Diff_n output (connect directly to top-level port)
.I(tx) // Buffer input
);
IBUFDS #(
.DIFF_TERM("TRUE"), // Differential Termination
.IBUF_LOW_PWR("FALSE"), // Low power="TRUE", Highest performance="FALSE"
.IOSTANDARD("LVDS25") // Specify the input I/O standard
) LVDS_IN (
.O(O), // Buffer output
.I(rx_p), // Diff_p buffer input (connect directly to top-level port)
.IB(rx_n) // Diff_n buffer input (connect directly to top-level port)
);
endmodule
由于时序约束被编译流程的Synthesis和Implementation使用,工程综合后可以在综合流程的constraints Wizard查看时序约束状况,如图14。双击constraints Wizard,如图15.

图14
图15鉴别缺失的时序约束并推荐和指导创建时序约束的的方法。点击Next, 如图16所示。

图15
图16 探测到test_clk为时钟,应该添加约束,修改约束后如图17所示。同时在对话框的底部Existing Create Clock Contraints列出已创建的时钟inclk,这个时钟就是pll1.xdc约束文件创建的。

图16
修改完成后在TCL Command Preview 对应的窗口中列出了对应的TCL命令,这些命令将会出现在即将生成的约束文件中。点击next, 如图18。

图17
图18说明如果使用了MMCM/PLL,clock buffers(如BUFG),以及UltraScale GTs等, 所有生成时钟约束将会自动派生,这里仅检查是否有遗漏的由用户产生的生成时钟,没有添加约束。
本工程中的inclk已经由pll1派生,test_clk没有生成时钟,因此图18没有检查到遗漏时钟。点击next,如图19.

图18
图19是对Forwarded Clocks(输出时钟)的检查与约束,Forwarded Clocks 一般是指由FPGA内部时钟连接到FPGA管脚的时钟,如PLL产生的某个时钟连接到FPGA管脚作为输出时钟,本工程中没有输出时钟因此检查后没有发现输出时钟需要约束。但在千兆网,HDMI等工程中有输出时钟,将来在这两个工程中需要注意输出时钟的约束。点击next,如图20.

图19
图20说明外部反馈时钟的检查与约束,一般是指MMCM/PLL的反馈路径经过外部(线路板)又通过另外的管脚返回的时钟。细节参看对应的视频。点击Next, 如图21.

图20
图21是关于Input Delays的设置,由于Input Delay涉及的概念比较多,后面会有一个专题讨论。本工程中由于tx_addr, tx_data, tx_en是由同步时钟生成,可以如图22的参数设置。

图21

图22
图23设置Output Delays, 同样会有一个专题讨论 Input/Output Delays, 这里可以如图23给定的参数设置。

图23
图24是组合逻辑延迟的设定,一般是输入/输出之间没有时序逻辑的时序约束,后面会有专题结合实例介绍。本工程中没有纯逻辑的输入/输出。

图24
图25是互斥时钟的设定,所谓互斥时钟,一般指在FPGA的时钟输入脚存在两个时钟(clkA, clkB ,如外部PLL由FPGA根据不同的逻辑状态进行配置,产生不同时钟频率或相位),如图26所示,

图25
图26中由于clkA,clkB 不会同时存在,在XDC文件中set_clock_groups -physically_exclusive -group clkA -group clkB, 这样可以阻止Vivado 分析这两个时钟组之间的关系。

图26
其它的选项暂时不设置(后续课程结合理论讲解),直接点击Finish。最后在工程中约束子集中生成各种约束。

图27
有一点不能令人满意的的地方是,在点击finish时并没有询问将生成的约束存在哪个文件中,直接存储到hs_phy.xdc中,而hs_phy.xdc原打算是用来存储物理约束的。发生这种原因是因为当前的有效子集(active)中只有一个约束文件。因此建议在有效子集和无效子集各包含两个文件分别为hs_timing1.xdc 和phy1.xdc以及hs_timing2.xdc 和phy2.xdc,这样就可以分开设置了。
老师,从图27可以看出,是对全部数据(不包括时钟)进行约束,那为什么不直接只约束时钟延迟一定精度就可以达到采稳定值的效果了。所以问题是约束时钟不是比约束全部数据更省事?
你的想法很好,但实际上时钟线在全局总线上,一般没有办法做延迟;而数据使用LUT,MUX进行布线,可以增加额外的LUT,MUX实现数据延迟。