FPGA时序约束实战

改编自8 FPGA时序约束实战篇之主时钟约束_check timing no clock

 

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

下面我们来一步一步进行时序约束。

梳理时钟树

我们首先要做的就是梳理时钟树,就是工程中用到了哪些时钟,各个时钟之间的关系又是什么样的,如果自己都没有把时钟关系理清楚,不要指望综合工具会把所有问题暴露出来。

在我们这个工程中,有两个主时钟,四个衍生时钟,如下图所示。

确定了主时钟和衍生时钟后,再看各个时钟是否有交互,即clka产生的数据是否在clkb的时钟域中被使用。

这个工程比较简单,只有两组时钟之间有交互,即:

  • clk_rx与clk_tx
  • clk_samp与clk2

其中,clk_rx和clk_tx都是从同一个MMCM输出的,两个频率虽然不同,但他们却是同步的时钟,因此他们都是从同一个时钟分频得到(可以在Clock Wizard的Port Renaming中看到VCO Freq的大小),因此它们之间需要用set_false_path来约束;而clk_samp和clk2是两个异步时钟,需要用asynchronous来约束。

完成以上两步,就可以进行具体的时钟约束操作了。

约束时钟

我们先把wave_gen工程的wave_gen_timing.xdc中的内容都删掉,即先看下在没有任何时序约束的情况下会综合出什么结果?

对工程综合并Implementation后,Open Implemented Design,会看到下图所示内容。

可以看到,时序并未收敛。可能到这里有的同学就会有疑问,我们都已经把时序约束的内容都删了,按我们第一讲中提到的“因此如果我们不加时序约束,软件是无法得知我们的时钟周期是多少,PAR后的结果是不会提示时序警告的”,这是因为在该工程中,用了一个MMCM,并在里面设置了输入信号频率,因此这个时钟软件会自动加上约束。

展开check timing工具

可以看到警告信息

添加以下约束,可以看到报错信息已经变更

create_clock -period 6.000 -name virtual_clock 
#指定 virtual_clock 时钟信号,周期为 6.000 ns。用于同步其他逻辑元件。

set_input_delay -clock [get_clocks -of_objects [get_ports clk_pin_p]] 0.000 [get_ports rxd_pin] 
#设置输入延迟。当接收到 rxd_pin 的信号时,应该考虑时钟信号 clk_pin_p 的 0.000 单位延迟。

set_input_delay -clock [get_clocks -of_objects [get_ports clk_pin_p]] -min -0.500 [get_ports rxd_pin] 
#设置最小延迟。 rxd_pin 的信号必须至少在 clk_pin_p 之前 0.500 单位到达。

set_input_delay -clock virtual_clock -max 0.0 [get_ports lb_sel_pin] 
#设置 lb_sel_pin 的最大延迟,相对于 virtual_clock。最大延迟为 0.0,意味着信号应该立即到达。

set_input_delay -clock virtual_clock -min -0.5 [get_ports lb_sel_pin] 
#设置最小延迟。这意味着 lb_sel_pin 的信号必须至少在 virtual_clock 之前 0.5 单位到达。

set_false_path -from [get_ports rst_pin] 
#指定了一个“假路径”。不需要考虑 rst_pin 信号的时序路径,它不会影响设计的正确性。

 

继续添加约束来解决outputdelay问题

set_output_delay -clock virtual_clock -max 0.0 [get_ports {txd_pin led_pins[*]}] 
#设置输出延迟。当发送到 txd_pin 和 led_pins 的信号时,应该立即发送,不需要额外的延迟。

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_ports spi_clk_pin]
#定义了 spi_clk 生成时钟,来源是 dac_spi_i0/out_ddr_flop_spi_clk_i0/ODDR_inst/C,并且被除以1(即不分频)。用于同步其他逻辑元件。

set_output_delay -clock spi_clk -max 1.000 [get_ports {spi_mosi_pin dac_cs_n_pin dac_clr_n_pin}]
#设置了输出延迟。当发送到 spi_mosi_pin、dac_cs_n_pin 和 dac_clr_n_pin 的信号时,应该在 spi_clk 之前最多延迟 1.000 单位。

set_output_delay -clock spi_clk -min -1.000 [get_ports {spi_mosi_pin dac_cs_n_pin dac_clr_n_pin}]
#设置了最小延迟。信号应该至少在 spi_clk 之前 1.000 单位到达。

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
#这行代码设置了多周期路径。它指定了从 cmd_parse_i0/send_resp_data_reg[*] 到 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

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

# For 193.75 MHz CLOCK_RATE_TX
#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}] 105
#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}] -hold 104

# For 166.667 MHz CLOCK_RATE_TX
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

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]

# To keep the synchronizer registers near each other
set_max_delay -from [get_cells clkx_nsamp_i0/meta_harden_bus_new_i0/signal_meta_reg] -to [get_cells clkx_nsamp_i0/meta_harden_bus_new_i0/signal_dst_reg] 2.000
set_max_delay -from [get_cells clkx_pre_i0/meta_harden_bus_new_i0/signal_meta_reg] -to [get_cells clkx_pre_i0/meta_harden_bus_new_i0/signal_dst_reg] 2.000
set_max_delay -from [get_cells clkx_spd_i0/meta_harden_bus_new_i0/signal_meta_reg] -to [get_cells clkx_spd_i0/meta_harden_bus_new_i0/signal_dst_reg] 2.000
set_max_delay -from [get_cells lb_ctl_i0/debouncer_i0/meta_harden_signal_in_i0/signal_meta_reg] -to [get_cells lb_ctl_i0/debouncer_i0/meta_harden_signal_in_i0/signal_dst_reg] 2.000
set_max_delay -from [get_cells samp_gen_i0/meta_harden_samp_gen_go_i0/signal_meta_reg] -to [get_cells samp_gen_i0/meta_harden_samp_gen_go_i0/signal_dst_reg] 2.000
set_max_delay -from [get_cells uart_rx_i0/meta_harden_rxd_i0/signal_meta_reg] -to [get_cells uart_rx_i0/meta_harden_rxd_i0/signal_dst_reg] 2.000
set_max_delay -from [get_cells rst_gen_i0/reset_bridge_clk_rx_i0/rst_meta_reg] -to [get_cells rst_gen_i0/reset_bridge_clk_rx_i0/rst_dst_reg] 2.000
set_max_delay -from [get_cells rst_gen_i0/reset_bridge_clk_tx_i0/rst_meta_reg] -to [get_cells rst_gen_i0/reset_bridge_clk_tx_i0/rst_dst_reg] 2.000
set_max_delay -from [get_cells rst_gen_i0/reset_bridge_clk_samp_i0/rst_meta_reg] -to [get_cells rst_gen_i0/reset_bridge_clk_samp_i0/rst_dst_reg] 2.000

此外,由于对工程加入了修改,还有一个clk_in2被遗忘了,接下来,我们在tcl命令行中输入report_clock_networks -name main,显示如下:

可以看出,Vivado会自动设别出两个主时钟,其中clk_pin_p是200MHz,这个是直接输入到了MMCM中,因此会自动约束;另一个输入时钟clk_in2没有约束,需要我们手动进行约束。

或者可以使用check_timing -override_defaults no_clock指令,这个指令我们之前的内容讲过,这里不再重复讲了。

在tcl中输入create_clock -name clk2 -period 25 [get_ports clk_in2]

注:在Vivado中,可以直接通过tcl直接运行时序约束脚本,运行后Vivado会自动把这些约束加入到xdc文件中。

再执行report_clock_networks -name main,显示如下:

此时重新运行检测,时序约束已经被满足

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章