最近在altera FPGA里設計一個外設的驅動模塊,模塊本身邏輯很簡單如下圖所示,但是模塊和外設之間的時序約束問題搞的很頭疼,今天先講講總結的一些Timequest下外設約束方法,特別是那毫無用戶體驗而言的Create Generated Clocks用法。
要讓外設正確接收FPGA發出的數據,需要dout和clkout滿足外設的建立保持時間,如下圖所示。
時序分析是基於源reg的Tco、目的reg的Tsu,源reg到目的reg的Tdelay路徑延遲以及到兩個reg的clk skew計算出來的,現在Timequest不知道外設接收reg和時鍾輸入端的延遲參數,無法分析,還需分若干步配置:
1.使用Create Clocks建立系統時鍾sysclk
create_clock -name {sysclk} -period 20.000 -waveform { 0.000 10.000 } [get_ports {sysclk}]
Timequset里的所有時鍾都需要手動設置,首先設置系統時鍾,后面的時鍾都要基於這個時鍾才能生成。
2.使用Create Generated Clocks建立輸出時鍾clkout
外設的時鍾源於FPGA的輸出port clkout,如果不建立時鍾,timequest只會把clkout當作一個普通的輸出引腳,時序分析器認為目的reg缺少驅動時鍾,無法分析。Timequest中將通過倍頻、分頻或者移相等生成的時鍾都歸為Generated Clocks,你可以使用Create Clocks創建試一下,不會提示創建失敗,但是在最后的時序分析里不會加入clkout的clock network delay,Timequest沒有你想象的那么智能,知道clkout是從一個分頻模塊輸出,自動加入模塊延遲分析,它不知道這些。所以還是使用Create Generated Clocks來創建吧,先填寫源時鍾sysclk,再填寫生成時鍾和源時鍾的關系,2分頻,最后指定target 生成時鍾到clkout引腳,這三個步驟看似沒問題,結果卻是創建失敗,提示找不到sysclk到clkout之間的路徑,如下所示。
Warning: No paths exist between clock target "clk_out" of clock "clkout" and its clock source. Assuming zero source clock latency.
但是sysclk到clkout明明是有路徑的啊,之間的邏輯也很簡單,就是一個二分頻關系,bdf圖如下所示。
clk_div模塊的代碼如下:
reg clk_div;
always @(posedge clk or negedge rset_n)
begin
if(!rset_n)
clk_div <= 0;
else
clk_div <= ~clk_div;
end
嘗試了很多方法都無效,最終實驗成功了一種自認為很別扭的方法,“曲線救國”分兩步走:
首先Create Generated Clocks 從引腳sysclk 到 寄存器clk_div
create_generated_clock -name {clk_div_r} -source [get_ports {sysclk}] -divide_by 2 -master_clock {sysclk} [get_registers {clk_div:inst4|clk_div}]
再Create Generated Clocks 從寄存器clk_div到輸出引腳clk_out
create_generated_clock -name {clkout} -source [get_registers {clk_div:inst4|clk_div}] -master_clock {clk_div_r} [get_ports {clk_out}]
這才把clk_out設置為和sysclk關聯的輸出時鍾。
3. 用set output delay 設置基於clkout時鍾的輸出dout延遲。
set_output_delay -add_delay -clock [get_clocks {clkout}] 5.000 [get_ports {dout}],這句話在源reg和目的reg之間搭建起一座橋梁,實現兩個作用:
1.告訴分析工具,clkout是目的reg的驅動時鍾(但是前提是clkout需要被Timequest認為是個時鍾),dout是目的reg的數據源,分析工具才能計算出路徑的clk skew(clkout的clock network delay - sysclk的clock network delay);
2.告訴了分析工具外設reg的建立時間Tsu和路徑延遲Tdelay,這兩個參數之和其實就是輸出延遲,分別源於外設數據手冊和PCB板的走線延遲。
4.設置set max delay和set min delay
如果不需要更嚴苛的約束,不設置這兩個參數也可以。經過前三步,時序分析器已經知道了源reg和目的reg的時鍾頻率,系統會默認使用時鍾頻率分析,既最大延遲為時鍾周期Tclk,最小延遲為0。這里的set max delay約束Tco和clk skew為了滿足外設的建立時間,使(Tclk + clk skew)-(Tco+Tdelay) > Tsu,set min delay 約束Tco和clk skew滿足外設的保持時間,(Tco+Tdelay) - clk skew > Th。
5.設置多周期約束
由於本例的特殊性,源時鍾是目的時鍾的兩倍,需要多時鍾約束設置,由篇幅所限,下次再細講。
最后可以在Report Timing的Registers to Outputs的setup和hold里看到時序余量分析了。
回顧約束的整個過程,最鬧心的就是第二步Create Generated Clocks了,可能一直受altera里的pll設置影響,只要設置源時鍾,填好輸出輸入時鍾關系,pll就可以用了,到了Timequest里的生成時鍾設置,我也想當然的認為,clkout是由sysclk生成變化而來,源里面只要填sysclk,關系填好就肯定沒問題了,但是結果並非這么簡單。
再回到第二步,再嘗試從sysclk直接Create Generated Clocks指定到輸出時鍾clkout,發現除了找不到sysclk和clkout之間的路徑之外還有一個警告
Warning: Node: clk_div:inst|clk_div was determined to be a clock but was found without an associated clock assignment。
警告clk_div被設置為了時鍾,但是沒有指定關聯的源時鍾即沒指定clk_div是由哪個時鍾生成的,從這個警告推理:我設置的Generated 時鍾是clkout,而clkout是由clk_div驅動的,所以Timequest也將clk_div認為是一個時鍾路徑node,但是我只告訴了Timequest clkout的源時鍾是sysclk,而沒有告訴它clk_div的源時鍾是哪個,所以創建失敗,雖然從程序顯而易見源也是sysclk,看來我高估了TimeQuest的智商,按照上述的“兩步走”方案,第一步就是告知clk_div的源時鍾是sysclk,第二步再生成基於clk_div的clkout就ok了
推測Timequest中對Creat Generated Clocks分析過程是這樣的:從最終的target Clock端口往回分析,尋找這個時鍾路徑上的net寄存器,看其是否有關聯時鍾,如果有,看是否已經指定了,如果指定了則繼續反向分析直到源時鍾,如果沒有則提示創建失敗。為了驗證這個推測,再級聯一個分頻模塊clk_div如下圖所示。
此時該如何建立基於sysclk的輸出時鍾clk_out呢,從clk_out管腳返回分析,clk_out由ints5|clk_div驅動,ints5|clk_div的關聯時鍾是inst4|clk_div,inst4|clk_div的關聯時鍾是sysclk,所以要分三步創建:
首先創建基於sysclk的inst4|clk_div。
create_generated_clock -name {clk_div_r} -source [get_ports {sysclk}] -divide_by 2 -master_clock {sysclk} [get_registers {clk_div:inst4|clk_div}]
再創建基於inst4|clk_div的inst5|clk_div。
create_generated_clock -name {clk_div_rr} -source [get_registers {clk_div:inst4|clk_div}] -divide_by 2 -master_clock {clk_div_r} [get_registers {clk_div:inst5|clk_div}]
最后創建基於inst5|clk_div的clk_out。
create_generated_clock -name {clkout} -source [get_registers {clk_div:inst5|clk_div}] -master_clock {clk_div_rr} [get_ports {clk_out}]
驗證結果,少了上述任何一步都無法得到正確的輸出時鍾,比如用Create clk 創建inst4|clk_div 取代第一步,雖然也可以生成clk_out,但最終的輸出延遲會減小。
多數工程中,使用分頻模塊輸出時鍾的做法並不多,更多的是使用pll,我又嘗試了下使用PLL產生一個外部時鍾,PLL的源為sysclk,仍然無法直接Create Generated Clocks從sysclk到clk_out,而是首先derive_pll_clocks將所有的PLL輸出都設為時鍾,再Create Generated Clocks從PLL的時鍾輸出寄存器到clk_out才可以。