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,顯示如下:

此時重新運行檢測,時序約束已經被滿足

 

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