我们以Vivado自带的wave_gen工程为例,该工程的各个模块的功能较为明确,如下图所示。为了引入异步时钟域,我们在此程序上增加了另一个时钟–clk2,该时钟产生脉冲信号pulse,samp_gen中在pulse为高时才产生信号。
我们首先要做的就是梳理时钟树,就是工程中用到了哪些时钟,各个时钟之间的关系又是什么样的,如果自己都没有把时钟关系理清楚,不指望综合工具会把所有问题暴露出来。
在我们这个工程中,有两个主时钟,四个衍生时钟,如下图所示。
确定了主时钟和衍生时钟后,再看各个时钟是否有交互,即clka产生的数据是否在clkb的时钟域中被使用。
在这个工程比较简单,只有两组时钟之间有交互,即:
a. clk_rx与clk_tx
b. clk_samp与clk2
其中,clk_rx和clk_tx都是从同一个MMCM输出的,两个频率虽然不同,但他们却是同步的时钟,因此他们都是从同一个时钟分频得到,因此它们之间需要用set_false_path来约束;而clk_samp和clk2是两个异步时钟,需要用asychronous来约束。
注:图中只有clk_rx到clk_tx的箭头,不应该有从clk_tx到clk_rx的箭头。
完成以上两步,就可以进行具体的时钟约束操作了。
在这一讲开讲之前,我们先把wave_gen工程中的wave_gen_timing.xdc中的内容删掉,即先看下载没有任何时序约束的情况下会综合处什么结果?
对工程综合并Implementation后,Open Implementation Design,会看到下图所示内容。
可以看到,时序并未收敛。由于在改工程中,用了一个MMCM,并在里面设置了输入信号频率,因此这个时钟软件会自动加上约束,则不存在之前讲的“如果我们不加时序约束,软件是无法得知我们的时钟周期是多少,PAR后的结果是不会提示时序警告的”。
接下来,我们在Tcl命令行中输入report_clock_networks -name mainclock,显示如下:
输入下列语句,创建时钟
create_clock -name clk2 -period 25 [get_ports clk_in2]
系统中有4个衍生时钟,但其中有两个是MMCM输出的,不需要我们手动约束,因此我们只需要对clk_samp和spi_clk进行约束即可。约束如下:
create_generated_clock -name clk_samp -source [get_pins clk_gen_i0/clk_core_i0/clk_tx] -divide_by 32 [get_pins clk_gen_i0/BUFHCE_clk_samp_i0/o]
create_generated_clock -name spi_clk -source [get_pins dac_spi_i0/out_ddr_flop_spi_clk_i0/ODDR_inst/C] -divide_by 1 -invert [get_pins spi_clk_pin]
我们再运行report_clocks,显示如下:
我们再理论篇“create_generated_clock”一节中讲到,我们可以重新设置Vivado自动生成衍生时钟的名字,这样可以更方便我们后续的使用。按照前文所讲,只需设置name和source参数即可,其中这个source可以直接从report_clocks中得到,因此我们的约束如下:
create_generated_clock -name clk_tx -source [get_pins clk_gen_i0/clk_core_i0/inst/mmcm_adv_inst/CLKIN1] [get_pins clk_gen_i0/clk_core_i0/inst/mmcm_adv_inst/CLKOUT1]
create_generated_clock -name clk_rx -source [get_pins clk_gen_i0/clk_core_i0/inst/mmcm_adv_inst/CLKIN1] [get_pins clk_gen_i0/clk_core_i0/inst/mmcm_adv_inst/CLKOUT0]
大家可以对比一下repot_clocks的内容和约束指令,很容易就能看出他们之间的关系。
把上述的约束指令在Tcl中运行后,我们再运行一遍repot_clocks,显示如下:
在时序的分析中,我们看到,clk_samp和clk2这两个异步时钟之间存在数据交互,因此要进行约束,如下:
对于延迟约束,比较麻烦一些,因为有时还要计算PCN上的走线延迟导致的时间差。而且不加延迟约束,Vivado也只是在Timing Report中提示warning,并不会导致时序错误。
比如在很多的ADC设计中,输出的时钟边沿刚好是数据的中心位置,而如果我们不加时序约束,则Vivado会默认时钟和数据是对齐的。
对于输入管脚,首先判断捕获时钟是主时钟还是衍生时钟,如果是主时钟,直接用set_input_delay即可,如果是衍生时钟,要先创建虚拟时钟,然后再设置delay。对于输出管脚,判断有没有输出随路时钟,若有,则直接使用set_output_delay,若没有,则需要创建虚拟时钟。
在本工程中,输入输出数据管脚的捕获时钟如下图所示:
根据上表,我们创建的延迟约束如下,其中的具体数字在实际工程中药根据上下游器件的时序关系(在各个器件手册上可以找到)和PCB走线延迟决定。未避免有些约束有歧义,我们把前面所有的约束也加进来。
在不加伪路径的时序约束是,Timing Report会提示很多的erro,其中就有跨时钟域的error.
我们可以直接在上面右键,然后设置来哥哥时钟的伪路径。
这样会在xdc中自动生成约束。
我们可以手动天界这两个时钟的伪路径如下:
伪路径的设置是单向的,如果两个时钟直接存在相互的数据的传输,则还需要添加从clk_tx到clk_rx的路径,这个工程中只有从rx到tx的数据传输,因此这一条就可以了。
异步复位也需要添加伪路径,rst_pin的复位输入在本工程中就是当做异步复位使用,因此还需要添加一句:
对于clk_samp和clk2,它们之间存在数据交换,但我们在前面已经约束过asynchronous了,这里就可以不用重复约束了。
这里需要提示一点,添加上面这些约束后,综合时会提示xdc文件的warning。
但这可能是Vivado综合过程中,读取到该约束文件时,内部电路并未完全建好,就出现了没有发现clk_gen_i0/clk_core_io/inst/mmcm_adv_inst/CLKIN1等端口的情况,有如下几点证明:
多周期路径,我们一般按照以下几个步骤来约束:
1.带有使能的数据
首先来看带有使能的数据,在本工程中的Timing Report中,也提示了同一个时钟域之间的几个路径建立时间不满足要求
其实这几个路径都是带有使能的路径,使能的周期为2倍的时钟周期,本来就应该在2个时钟周期内去判断时序收敛。因此,我们添加时序约束:
set_multicycle_path 2 -setup -from [get_cells {cmd_parse_i0/send_resp_data_reg[*]} -include_replicated_objects] -to [get_cells {resp_gen_i0/to_bcd_i0/bcd_out_reg[*]}]
set_multicycle_path 1 -hold -from [get_cells {cmd_parse_i0/send_resp_data_reg[*]} -include_replicated_objects] -to [get_cells {resp_gen_i0/to_bcd_i0/bcd_out_reg[*]}]
也可以这样写
set_multicycle_path -from [get_cells {cmd_parse_i0/send_resp_data_reg[*]} -include_replicated_objects] -to [get_cells {resp_gen_i0/to_bcd_i0/bcd_out_reg[*]}] 2
set_multicycle_path -hold -from [get_cells {cmd_parse_i0/send_resp_data_reg[*]} -include_replicated_objects] -to [get_cells {resp_gen_i0/to_bcd_i0/bcd_out_reg[*]}] 1
我们也可以直接右键通过GUI的方式进行约束,效果是一样的。
在工程的uart_tx_ctl.v和uart_rx_ctl.v文件中,也存在带有使能的数据,但这些路径在未加多路径约束时并未报出时序错误或者警告。
在接收端,捕获时钟频率是200MHz,串口速率是115200,采用16倍的Oversampling,因此使能信号周期是时钟周期的200e6/115200/16=108.5倍。
在接收端,捕获时钟频率是200MHz,串口速率是115200,采用16倍的Oversampling,因此使能信号周期是时钟周期的166.667e6/115200/16=90.4倍。
因此,时序约束如下:
#串口接收端
set_multicycle_path -from [get_cells uart_rx_i0/uart_rx_ctl_i0/* -filter IS_SEQUENTIAL] -to [get_cells uart_rx_i0/uart_rx_ctl_i0/* -filter IS_SEQUENTIAL] 108
set_multicycle_path -hold -from [get_cells uart_rx_i0/uart_rx_ctl_i0/* -filter IS_SEQUENTIAL] -to [get_cells uart_rx_i0/uart_rx_ctl_i0/* -filter IS_SEQUENTIAL] 107
#串口发送端
set_multicycle_path -from [get_cells uart_tx_i0/uart_tx_ctl_i0/* -filter IS_SEQUENTIAL] -to [get_cells uart_tx_i0/uart_tx_ctl_i0/* -filter IS_SEQUENTIAL] 90
set_multicycle_path -hold -from [get_cells uart_tx_i0/uart_tx_ctl_i0/* -filter IS_SEQUENTIAL] -to [get_cells uart_tx_i0/uart_tx_ctl_i0/* -filter IS_SEQUENTIAL] 89
至此,重新Synthesis和Implementation后,可以看到,已经没有时序错误
到这里,教科书版的时序约束教程就基本结束了。但在我们平时的工程中,跟上面这种约束还是有差异的: