时序约束实战篇


本文摘抄自:
个人网站:http://www.technomania.cn/
微信公众号:Quant_times、Reading_Times

行万里路–时序约束实战篇

我们以Vivado自带的wave_gen工程为例,该工程的各个模块的功能较为明确,如下图所示。为了引入异步时钟域,我们在此程序上增加了另一个时钟–clk2,该时钟产生脉冲信号pulse,samp_gen中在pulse为高时才产生信号。
在这里插入图片描述

1. 梳理时钟树

我们首先要做的就是梳理时钟树,就是工程中用到了哪些时钟,各个时钟之间的关系又是什么样的,如果自己都没有把时钟关系理清楚,不指望综合工具会把所有问题暴露出来。
在我们这个工程中,有两个主时钟,四个衍生时钟,如下图所示。
在这里插入图片描述
确定了主时钟和衍生时钟后,再看各个时钟是否有交互,即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的箭头。
完成以上两步,就可以进行具体的时钟约束操作了。

2. 约束主时钟

在这一讲开讲之前,我们先把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]

3. 约束衍生时钟

系统中有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这两个异步时钟之间存在数据交互,因此要进行约束,如下:
在这里插入图片描述

4. 延迟约束

对于延迟约束,比较麻烦一些,因为有时还要计算PCN上的走线延迟导致的时间差。而且不加延迟约束,Vivado也只是在Timing Report中提示warning,并不会导致时序错误。
在这里插入图片描述
比如在很多的ADC设计中,输出的时钟边沿刚好是数据的中心位置,而如果我们不加时序约束,则Vivado会默认时钟和数据是对齐的。
在这里插入图片描述
对于输入管脚,首先判断捕获时钟是主时钟还是衍生时钟,如果是主时钟,直接用set_input_delay即可,如果是衍生时钟,要先创建虚拟时钟,然后再设置delay。对于输出管脚,判断有没有输出随路时钟,若有,则直接使用set_output_delay,若没有,则需要创建虚拟时钟。
在本工程中,输入输出数据管脚的捕获时钟如下图所示:
在这里插入图片描述
根据上表,我们创建的延迟约束如下,其中的具体数字在实际工程中药根据上下游器件的时序关系(在各个器件手册上可以找到)和PCB走线延迟决定。未避免有些约束有歧义,我们把前面所有的约束也加进来。
在这里插入图片描述

5. 伪路径约束

在不加伪路径的时序约束是,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. 这个路径在代码中确实存在
  2. 若把该sdc文件,设置为仅在Implementation中使用,则不会提示该warning
  3. 在Implementation完成后,无论是Timing Report还是通过tcl的report_clocks指令,都可以看到这几个时钟已经被正确约束。下图所示即为设置完上面的约束后的Timing Report。
    4.

6. 多周期路径约束

多周期路径,我们一般按照以下几个步骤来约束:
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
  1. 两个有数据交互的时钟之间存在相位差 在本工程中,没有这种应用场景,incident不需要添加此类约束
  2. 存在快时钟到慢时钟的路径 在本工程中,没有这种应用场景,incident不需要添加此类约束
  3. 存在慢时钟到快时钟的路径 在本工程中,没有这种应用场景,incident不需要添加此类约束

至此,重新Synthesis和Implementation后,可以看到,已经没有时序错误
在这里插入图片描述
到这里,教科书版的时序约束教程就基本结束了。但在我们平时的工程中,跟上面这种约束还是有差异的:

  1. 首先是虚拟时钟,这个约束在平时的工程中基本不会用到,像需要设计虚拟时钟的场景,我们也都是通过设计来保证时序收敛,设置虚拟时钟的意义不大。
  2. 第二就是output delay,在FPGA的最后一级寄存器到输出的路径上,往往都使用了IOB,也就是IO block,因此最后一级寄存器的位置是固定的,从buffer到pad的走线延时是确定的。在这种情况下,是否满足时序要求完全取决于设计,做约束只是验证一下看看时序是否收敛。所以也基本不做。但是input delay是需要的,因为这是上一级器件输出的时序关系。
  3. 第三个就是多周期路径,我们讲了那么多多周期路径的应用场景,但实际我们是根据Timing Report来进行约束的,即便那几种场景都存在,但如果Timing Report中没有提示任何时序warning,我们往往也不胡去添加约束。
  4. 第四个就是在设置了多周期后,如果还是提示Intra-Clocks Paths的setup time不过,那就要看下程序,是否写的不规范。比如
    在这里插入图片描述
    这么写的话,如果时钟频率稍微高一些,比如250MHz,就很容易导致从regB到regD的setup time不满足要求。因为begin end里面的代码都是按顺序执行的,要在4ns内完成这些复制与判断逻辑,挑战还是挺大的。因此,我们可以这样改写:
    在这里插入图片描述
    把寄存器的赋值分开,功能还是一样的,只是分到了几个always中,这样就不会导致问题了。

猜你喜欢

转载自blog.csdn.net/gemengxia/article/details/108200204