:從推導(dǎo)到調(diào)試)
搞FPGA的兄弟十有八九都被DDR接口的時序約束折磨過。尤其當(dāng)你第一次在Vivado里寫完代碼打開時序報告發(fā)現(xiàn)一大片紅色的setup/hold違例幾十條路徑全掛在那個DDR讀數(shù)據(jù)總線上血壓直接就上來了。我陷入這個坑的時候項目的DDR3讀接口死活跑不上400MHz問題不是出在PCB布線也不是出在IDELAY調(diào)節(jié)而是出在一開始就沒把set_input_delay這個約束寫對。你去看Xilinx官方文檔UG903它把語法說得明明白白但真正到了DDR這種源同步接口上min/max兩個值怎么算、參考時鐘選哪個沿、負(fù)延遲怎么理解文檔講得云里霧里。這篇文章我就把自己踩過的坑、驗證過的思路完整的講一遍把DDR接口下set_input_delay的前因后果、推導(dǎo)過程、實際寫法和報告解讀方式一次性說透希望能幫你少走幾周的彎路。1. DDR接口時序的本質(zhì)為什么光看數(shù)據(jù)手冊沒用1.1 源同步接口的時序模型很多做FPGA的同學(xué)第一次接觸DDR接口時習(xí)慣性地用系統(tǒng)同步System Synchronous的思維去理解時序——主時鐘由FPGA內(nèi)部邏輯直接產(chǎn)生數(shù)據(jù)由外部器件在某個固定時刻送出來只要板子布線延遲穩(wěn)定一切妥妥當(dāng)當(dāng)。但DDR接口完全是另一套玩法它是典型的源同步Source Synchronous接口。所謂源同步就是數(shù)據(jù)的伴隨時鐘DQS不是由系統(tǒng)時鐘分出來的而是由發(fā)送端不管這個發(fā)送端是DDR顆粒還是FPGA從自己的內(nèi)部時鐘域里派生出來和發(fā)送的數(shù)據(jù)一起送到接收端。接收端用這個DQS來采集對應(yīng)的數(shù)據(jù)線而不是用自己本地的全局時鐘。這也是為什么DDR讀操作時DQS是邊沿對齊于數(shù)據(jù)的——DQS從0變1的那一下數(shù)據(jù)總線上的信號正好處于穩(wěn)定區(qū)間。為什么DDR要這么設(shè)計因為頻率上來了以后系統(tǒng)同步的時序預(yù)算根本撐不住。舉個例子一個400MHz的DDR3接口UIUnit Interval才2.5ns如果用系統(tǒng)時鐘做采集那PCB走線長度差1mm引入的時延差就在6ps左右看著不大但幾百mil的線長差加上片內(nèi)時鐘樹偏差和器件內(nèi)部的PLL抖動預(yù)算一下子就爆了。而源同步接口的好處在于DQS和數(shù)據(jù)走的是同一條物理路徑PVT工藝、電壓、溫度變化對兩者的影響是共模的接收端只需要關(guān)心DQS相對于數(shù)據(jù)自己的相位關(guān)系這就把很多全局不確定性變成了局部的、可控的時序預(yù)算。1.2 setup/hold在DDR里的真實含義搞清楚了源同步模型setup和hold的含義也就跟著變了。對FPGA內(nèi)部的觸發(fā)器來說setup time是數(shù)據(jù)必須在時鐘有效沿之前多長時間保持穩(wěn)定hold time是時鐘有效沿之后數(shù)據(jù)還必須繼續(xù)穩(wěn)定多長時間——這兩個參數(shù)由FPGA的硅片工藝決定是固定的物理特性比如7系列的FF在慢工藝角下setup可能是0.18ns左右hold則可能是負(fù)值。但接口約束里的setup/hold指的其實是外部DDR顆粒或者控制器在DQS采樣沿上對數(shù)據(jù)有效窗口的要求。你讀DDR數(shù)據(jù)時DQS的上升沿和下降沿都要去采數(shù)據(jù)所以一個完整的DDR讀數(shù)據(jù)窗口定義在DQS上升沿前后和下降沿前后各有一段有效時間。外部顆粒手冊會給一個tDSDQS到數(shù)據(jù)有效的建立時間和tDHDQS到數(shù)據(jù)保持的時間——你就把它理解成顆粒自己要求的交貨條件數(shù)據(jù)必須在DQS沿到來之前的tDS時間就位并且一直保持到沿過后的tDH時刻。所以這個時候set_input_delay要干的事情就是把外部這個“交貨條件”翻譯成FPGA內(nèi)部時序引擎能看懂的輸入延遲值。FPGA里布好了觸發(fā)器和IOB時序引擎并不直接知道DQS何時翻轉(zhuǎn)它只看得見FPGA的時鐘網(wǎng)絡(luò)和輸入引腳。你必須告訴它相對于哪個時鐘的哪個沿數(shù)據(jù)線上的信號在什么時候到達(dá)。這里就有一個很多人繞不過去的彎——DDR的DQS雙向信號、數(shù)據(jù)和DQS邊沿對齊到底該怎么用set_input_delay去描述答案在于你約束的不是DQS本身而是數(shù)據(jù)相對于內(nèi)部參考時鐘的到達(dá)時刻。而這個“內(nèi)部參考時鐘”通常就是你在FPGA內(nèi)部生成的一個和DQS同頻同相或者經(jīng)過MMCM/PLL調(diào)整后滿足采樣關(guān)系的時鐘它的名字會在對應(yīng)的set_input_delay里通過-clock選項指定。2. set_input_delay的數(shù)學(xué)拆解2.1 min/max兩個值到底怎么算set_input_delay的標(biāo)準(zhǔn)語法大家都不陌生給數(shù)據(jù)路徑設(shè)置一個相對于時鐘沿的延遲范圍。命令長這樣set_input_delay -clock [get_clocks ddr_dqs_clk] -max 1.2 [get_ports ddr_dq[*]] set_input_delay -clock [get_clocks ddr_dqs_clk] -min 0.8 [get_ports ddr_dq[*]]但到了DDR接口上難的不是語法是這里的max和min到底取多少。從數(shù)據(jù)手冊讀到的tDS和tDH是DQS沿相對數(shù)據(jù)的時序要求。但FPGA時序引擎分析的是內(nèi)部時鐘就是你指定的那個時鐘到達(dá)內(nèi)部觸發(fā)器時觸發(fā)器輸入引腳上的數(shù)據(jù)是否滿足建立和保持時間。所以你要算的延遲是數(shù)據(jù)相對于那個內(nèi)部時鐘的延遲而不是相對于DQS的延遲。我們來推一遍。假設(shè)DQS的上升沿在FPGA內(nèi)部時鐘的上升沿之后t_clk_q時刻到達(dá)輸入引腳這個值取決于你選擇的相位關(guān)系比如你是讓內(nèi)部時鐘上升沿對齊DQS上升沿那t_clk_q就是0如果你是讓內(nèi)部時鐘的下降沿對齊DQS上升沿則為半個周期。數(shù)據(jù)相對于DQS的建立、保持要求是tDS和tDH。那么數(shù)據(jù)相對于內(nèi)部時鐘上升沿的延遲范圍是max最晚到達(dá)t_clk_q- tDSmin最早到達(dá)t_clk_q tDH這個推導(dǎo)的邏輯就是你把自己放在FPGA內(nèi)部觸發(fā)器的位置上想問題——數(shù)據(jù)必須晚于DQS的建立要求時刻到達(dá)對setup來說是“夠早”對hold來說是“夠晚”。實際上對DDR讀數(shù)據(jù)來說數(shù)據(jù)是圍繞著DQS沿對稱分布的所以max和min的取值通常就在DQS沿前后各一段也就是數(shù)據(jù)有效窗口的邊界。以DDR3-1600為例頻率800MHzUI1.25ns數(shù)據(jù)手冊上tDS典型值可能在0.05ns到0.15nstDH類似。如果我們把內(nèi)部采樣時鐘的上升沿對齊DQS在數(shù)據(jù)有效窗口的中央那么數(shù)據(jù)相對于這個采樣沿的建立余量等于半個UI減去tDS加tDH的這里做了簡化實際計算還需加上DQS到采樣時鐘的相位關(guān)系。打個實際算例。假設(shè)你給DDR讀數(shù)據(jù)生成的采樣時鐘其上升沿與你期望的DQS鎖存沿對齊而這個DQS鎖存沿正好在數(shù)據(jù)有效窗口中央且外部顆粒手冊給出tDS0.1ns、tDH0.1ns。那么數(shù)據(jù)相對于采樣時鐘上升沿的窗口中心有一個偏移我們想讓這個偏移落在max和min的中間留出最大裕量。按上面的公式max 0 - 0.1 -0.1nsmin 0 0.1 0.1ns??吹絾栴}沒有max比min還小這在數(shù)學(xué)上是不合理的因為max代表最晚到達(dá)min代表最早到達(dá)不可能最晚時刻比最早時刻還早。這里的實際原因是DQS的鎖存沿相對于數(shù)據(jù)有效窗口中心有相位偏置而你指定的內(nèi)部時鐘沿未必就在窗口正中央。所以實際操作中你要選擇一個內(nèi)部時鐘相位使得窗口中心對齊你的采樣沿然后max和min就會變成一正一負(fù)的對稱值。比如我們將采樣時鐘沿向前調(diào)整半個采樣窗口使得數(shù)據(jù)窗口中央正好落在時鐘沿上則數(shù)據(jù)最早到達(dá) 窗口中心 - 半個窗口寬度 0 - 0.6 -0.6ns數(shù)據(jù)最晚到達(dá) 窗口中心 半個窗口寬度 0 0.6 0.6ns負(fù)的min值表示數(shù)據(jù)在時鐘沿之前就到來了這在物理上完全說得通——因為數(shù)據(jù)其實是“圍繞”著DQS采樣的有一部分?jǐn)?shù)據(jù)位在采樣沿之前就已經(jīng)有效了。這也是很多新手卡住的地方set_input_delay的min為負(fù)完全正常不要以為是寫錯了。2.2 時鐘沿的選擇和-options的細(xì)節(jié)DDR接口一個周期內(nèi)有兩個有效沿——上升沿和下降沿都要采樣所以對DDR讀數(shù)據(jù)約束時通常需要給同一個數(shù)據(jù)總線針對不同的時鐘沿寫兩組約束。怎么處理兩種常見做法第一種建立兩個主時鐘一個是DQS的上升沿采樣時鐘create_clock -name dqs_rise -period 2.5 [get_ports ddr_dqs_p]注意DDR3的DQS是差分對時鐘周期是比特周期的一倍因為DDR在每個沿都傳數(shù)據(jù)另一個是下降沿采樣時鐘create_clock -name dqs_fall -period 2.5 -waveform {1.25 3.75}。然后分別對兩組數(shù)據(jù)用不同的時鐘來約束。第二種做法只創(chuàng)建一個時鐘利用set_input_delay的-clock_fall選項。它對數(shù)據(jù)指定相對于時鐘下降沿的延遲與不寫-clock_fall即默認(rèn)上升沿的那條約束配合就能覆蓋DDR的雙沿采樣場景。set_input_delay -clock [get_clocks dqs_clk] -max 0.6 [get_ports ddr_dq[*]] set_input_delay -clock [get_clocks dqs_clk] -min -0.6 [get_ports ddr_dq[*]] set_input_delay -clock [get_clocks dqs_clk] -clock_fall -max 0.6 [get_ports ddr_dq[*]] set_input_delay -clock [get_clocks dqs_clk] -clock_fall -min -0.6 [get_ports ddr_dq[*]]這里的dqs_clk是在FPGA內(nèi)部生成的、用于捕獲DDR數(shù)據(jù)的時鐘信號。特別強(qiáng)調(diào)一下這個時鐘的實際位置可以直接定義在DQS引腳上也可以定義在內(nèi)部邏輯生成點。如果定義在DQS引腳上那么輸入路徑的起點就是這個引腳時序引擎會計算DQS從引腳到內(nèi)部觸發(fā)器的路徑延遲如果定義在內(nèi)部生成點建議在約束中加-source_latency來補(bǔ)上DQS從引腳到這個生成點的延遲否則時序引擎會低估延遲報告里的余量會失真。-source_latency是個容易忽略的坑。我見過一個項目功能仿真全對上板之后偶發(fā)讀數(shù)據(jù)錯位查到最后就是source latency沒加工具評估的保持余量比實際大了幾百皮秒導(dǎo)致IDELAY的tap值定在了一個臨界位置。DDR跑高速時幾百ps的差異足夠讓你的眼圖閉合。2.3 System Sync和Source Sync的本質(zhì)區(qū)別為什么set_input_delay在DDR上這么容易寫錯因為很多人下意識用系統(tǒng)同步的公式來算延遲算出來總覺得哪里不對勁。系統(tǒng)同步接口里數(shù)據(jù)延遲 時鐘從FPGA到外部器件的傳播延遲 外部器件的時鐘到輸出延遲 數(shù)據(jù)線從外部器件到FPGA的傳播延遲 - 內(nèi)部時鐘到達(dá)觸發(fā)器的延遲。所有路徑都以系統(tǒng)的同一個時鐘為參考。DDR的源同步接口不一樣。FPGA和DDR顆粒共享的不是系統(tǒng)時鐘而是DQS這個局部時鐘。讀數(shù)據(jù)的時候DQS本身和DQ一樣是外部器件發(fā)出的信號二者的相對相位由顆粒內(nèi)部的DLL/PLL保證DDR3的DQS和數(shù)據(jù)是邊沿對齊的DDR4則數(shù)據(jù)位于DQS兩側(cè)具體看芯片型號。所以你在算輸入延遲時參考的不是板級系統(tǒng)時鐘到DQS的絕對延遲而是DQS到達(dá)FPGA引腳那一刻與內(nèi)部采樣時鐘沿的相對位置。換句話說DDR接口set_input_delay里的-clock不是系統(tǒng)時鐘而是你在FPGA內(nèi)部生成的DQS采樣時鐘。這也是很多教程里反復(fù)強(qiáng)調(diào)的一句話DDR讀數(shù)據(jù)的輸入延遲不是相對于時鐘周期的百分比而是相對于DQS采樣沿的有效窗口。這個認(rèn)知一轉(zhuǎn)變很多矛盾就迎刃而解了。比如你會看到別人的約束文件里set_input_delay的max和min數(shù)值都很小比如±0.6ns、±0.7ns而不是像SPI接口那樣寫1.8ns、2.2ns。原因就是DQS和數(shù)據(jù)走在一起數(shù)據(jù)窗口相對采樣沿只有幾百皮秒的余量——沒有系統(tǒng)同步那種動輒一個周期的大預(yù)算。3. Vivado里寫DDR讀接口約束的完整實操3.1 一個具體的DDR3讀接口示例用一個實際例子來串聯(lián)前面說的理論。假設(shè)FPGA外接一顆DDR3L運(yùn)行頻率400MHz即DDR3-800UI2.5ns/21.25ns。DQS是差分信號數(shù)據(jù)線8根。我們通過IDELAYEYE2原語把DQ和DQS都做延遲對齊然后利用內(nèi)部生成的dqs_clk做采集中間時鐘最后把數(shù)據(jù)同步到系統(tǒng)時鐘域。初始化階段我們需要創(chuàng)建時鐘。DQS引腳在FPGA里通常先經(jīng)過IBUFDS變成單端再進(jìn)IDELAYEYE2然后進(jìn)BUFR或BUFIO等時鐘緩沖。這里我演示一種比較典型的做法——用BUFG把處理后的DQS轉(zhuǎn)成全局時鐘最大化時鐘樹的覆蓋范圍代價是延遲大一些但對低速DDR3-800來說通常夠用如果跑DDR3-1600推薦用BUFIO來減小時鐘偏斜。假設(shè)約束文件里已經(jīng)有create_clock -name dqs_clk -period 2.500 [get_ports ddr_dqs_p]注意DDR3的DQS時鐘周期是數(shù)據(jù)速率的倒數(shù)乘以2數(shù)據(jù)速率800MT/s所以周期2.5nsUI是1.25ns。接下來寫輸入延遲。假設(shè)你通過示波器或仿真確認(rèn)了DQS上升沿到達(dá)FPGA引腳時數(shù)據(jù)有效窗口的中心比這個沿早0.1ns在個別實際工程中因為你用了IDELAY此時采樣時鐘已經(jīng)做過相位補(bǔ)償所以相對位置可以做相應(yīng)調(diào)節(jié)方便起見我們假設(shè)已經(jīng)調(diào)好使時鐘沿對準(zhǔn)窗口中心set_input_delay -clock dqs_clk -max 0.60 [get_ports {ddr_dq[*]}] set_input_delay -clock dqs_clk -min -0.60 [get_ports {ddr_dq[*]}] set_input_delay -clock dqs_clk -clock_fall -max 0.60 [get_ports {ddr_dq[*]}] set_input_delay -clock dqs_clk -clock_fall -min -0.60 [get_ports {ddr_dq[*]}]這里ddr_dq[*]的寫法在Tcl里會展開成ddr_dq[0]、ddr_dq[1]等。如果你的工程用Verilog定義了[7:0] ddr_dq那么管腳名字一般就是ddr_dq[0]這種方括號帶索引的形式在XDC里最好用get_ports {ddr_dq[*]}加上花括號避免Tcl把方括號解釋成命令替換。再補(bǔ)上DQS的-source_latency。假設(shè)從DQS引腳到內(nèi)部時鐘生成點經(jīng)過IBUFDS加IDELAYEYE2的總延遲大約是1.2ns具體數(shù)值以布線后的時序報告為準(zhǔn)這里先給一個預(yù)估值實際工程中這件事的做法是先跑一版不理想的約束看路徑報告里DQS的實際延遲再回填set_input_delay -clock dqs_clk -source_latency 1.2 [get_clocks dqs_clk]3.2 參數(shù)計算的驗證與回填有些工程師會問示例里的0.60ns怎么來的是不是拍腦袋不是的它來自DDR3芯片手冊的tDS/tDH和你的采樣策略。取一個具體的DDR3顆粒為例手冊上寫著tDSDQS to DQ setup在某個slew rate下是0.05nstDH是0.08ns數(shù)據(jù)有效窗口最窄處大約是UI減去這兩個值。窗口中心到邊界的距離如果近似認(rèn)為窗口是對稱的那么就是UI/2減去tDS和tDH的平均值即1.25/2 - (0.050.08)/2 0.625 - 0.065 0.56ns??紤]到輸入jitter和板上串?dāng)_留20%的余量就得到0.67ns左右。所以示例里的0.60ns是一個相對合理但略緊的初始值正式項目里應(yīng)該在實現(xiàn)后用report_timing_summary去檢查實際裕量再決定要不要把約束調(diào)整得更接近物理極限。這個過程我強(qiáng)烈建議反復(fù)迭代。第一版你用一個“偏緊”的約束時序報告如果通過了說明實際裕量比約束預(yù)留的還大如果setup/hold有違例查看違例路徑的報告分析是約束給得太死板還是數(shù)據(jù)窗口真沒對齊。后者往往需要調(diào)節(jié)IDELAY的tap值而不是繼續(xù)改約束值。在7系列FPGA上IDEALEYEYE2的tap延遲每個約78psVivado的實際值與原語配置有關(guān)調(diào)節(jié)IDELAY前先確認(rèn)你的約束和物理延遲匹配。很多人的經(jīng)驗是先通過調(diào)整采樣時鐘相位例如MMCM的CLKOUT_PHASE或調(diào)整BUFR的分頻腳把窗口中心對準(zhǔn)再用IDELAY細(xì)調(diào)兩條腿走路。3.3 容易寫錯的幾個細(xì)節(jié)把最容易翻車的幾個細(xì)節(jié)列一下都是我實際debug過的問題。第一個get_ports和get_pins搞混。set_input_delay約束的是FPGA的輸入端口必須用get_ports。如果你把內(nèi)部信號線名放進(jìn)去Vivado會直接報warning并且這條約束不生效。第二個差分信號的命名。DQS差分對的兩個引腳在XDC里通常叫ddr_dqs_p和ddr_dqs_n但create_clock只需要定義在P端。如果你把N端也定義了工具會報時鐘穿越差分緩沖的沖突。DDR3的DQSP和N之間是互補(bǔ)關(guān)系不需要兩個獨立的時鐘。第三個用-add_delay的必要性。當(dāng)同一個網(wǎng)表里有好幾組不同的輸入延遲約束時比如既約束了DQS上升沿又約束下降沿同時還約束了地址/控制信號中間要用-add_delay隔開。例如set_input_delay -clock dqs_clk -max 0.6 [get_ports {ddr_dq[*]}] set_input_delay -clock dqs_clk -min -0.6 [get_ports {ddr_dq[*]}] -add_delay不加-add_delay后面的set命令會覆蓋前面同一條約束同group的設(shè)置。對DDR這種多沿接口是很容易踩的坑。Vivado會報warning提醒你但warning一閃而過經(jīng)常被忽略。第四個約束的層級。set_input_delay寫在XDC里時如果被約束的網(wǎng)口在一個子模塊里非頂層Vivado會判定約束作用域不對不生效。所以DDR接口約束最好統(tǒng)一寫在頂層XDC里用頂層的端口名來指定。4. 時序報告怎么讀怎么定位DDR的setup/hold違例4.1 讀懂report_timing_summary的關(guān)鍵行寫完約束跑完綜合和實現(xiàn)打開Vivado的時序報告很多人看到一堆紅色就開始慌。別急DDR接口的時序報告關(guān)鍵在于過濾出有問題的路徑來單獨分析。report_timing_summary會按時鐘域組織報告包含setup、hold和pulse width三類檢查。對DDR讀接口最??吹氖莇qs_clk域里的input path。在Vivado的Timing Summary界面右鍵選擇“Report Timing”指定-from [get_ports {ddr_dq[*]}]和-to [get_cells ...]就能只報DDR數(shù)據(jù)線到內(nèi)部寄存器的路徑。報告里有幾個字段需要盯緊字段含義關(guān)注點Slack當(dāng)前路徑的時序裕量負(fù)數(shù)意味著違例越小越危險Source Clock Edge約束里參考的時鐘沿確認(rèn)是上升沿還是下降沿的檢查Destination Clock Edge目的寄存器時鐘沿確認(rèn)setup/hold檢查的邏輯正確Data Path Delay從端口到觸發(fā)器D端的組合邏輯延遲過大說明輸入緩沖或IDELAY配置有問題Clock Path Delay時鐘從DQS引腳到觸發(fā)器時鐘端的延遲包含了IBUFDS和BUFG等過大要考慮換BUFIO拿setup檢查舉例Vivado驗證的不等式是 數(shù)據(jù)到達(dá)時間 ≤ 數(shù)據(jù)要求時間 - setup時間數(shù)據(jù)到達(dá)時間 輸入延遲你寫的set_input_delay PCB延遲port到buffer通常為0 內(nèi)部邏輯延遲。數(shù)據(jù)要求時間 內(nèi)部采樣時鐘沿 - 目的觸發(fā)器的setup time。這個不等式如果被打破報告里會標(biāo)出哪個環(huán)節(jié)超了。4.2 從違例路徑反推問題所在我總結(jié)了一套快速定位DDR接口違例的流程。第一步看違例的路徑是setup還是hold。純setup違例slack為負(fù)路徑上數(shù)據(jù)到達(dá)時間晚于要求時間優(yōu)先懷疑數(shù)據(jù)通道延遲太大。此時檢查IDELAY的tap數(shù)是不是太大或者組合邏輯里是否插了多余的LUT。通常的做法是在靠近IO的采樣寄存器路徑上不要放任何邏輯直接由IBUF-IDELAY-寄存器。如果編譯器插入了莫名其妙的邏輯看看是不是代碼里不小心給輸入信號做了跨時鐘域處理。第二步看hold違例。都符合DDR讀數(shù)據(jù)應(yīng)用因為數(shù)據(jù)和DQS是源同步的一般hold不會太難看。如果hold違例嚴(yán)重優(yōu)先懷疑采樣時鐘相位不對——DQS采樣時鐘沒有對準(zhǔn)數(shù)據(jù)有效窗口的后沿導(dǎo)致數(shù)據(jù)在沿后太早發(fā)生變化。調(diào)IDELAY tap值讓DQ整體往后移或者讓采樣時鐘往前走都能緩解。第三步看是單根線違例還是整個總線違例。整組DQ線都違例說明DQS時鐘的整體相位不對只有某幾根違例大概率是PCB布線等長沒做好或者這幾位在IDELAY配置里沒對齊。后者可以在代碼里給每一根線一個獨立的IDELAY tap配置值通過ILA在線調(diào)試逐步調(diào)。對于DDR這種高速接口示波器和ILA是兩大法寶。邏輯分析儀看采樣后的數(shù)據(jù)翻轉(zhuǎn)點示波器看DQS和DQ物理波形。我調(diào)試時習(xí)慣抓讀寫DQS的相對位置然后對照時序報告里的clock arrival time判斷是軟件模型對還是物理測量對。5. 常見問題與排查技巧實錄5.1 典型setup違例的處理思路假設(shè)你第一次上板時序報告里setup違例slack大概是-0.3ns左右數(shù)據(jù)路徑延遲高出了要求。別急著調(diào)約束先按下面順序排查檢查IDELAYEYE2的配置。每個tap的延遲是固定的多打一個tap就是多78ps7系列典型值如果你的tap數(shù)當(dāng)時是根據(jù)仿真隨便設(shè)的很可能整體偏大或偏小。Vivado里可以直接在約束里用set_property IDELAY_VALUE來配置也可以例化時在參數(shù)里指定。兩者沖突時以后者為準(zhǔn)。檢查采樣時鐘域。如果內(nèi)部采樣時鐘是由MMCM生成的確認(rèn)MMCM的輸出相位是否準(zhǔn)確。你應(yīng)讓DQS采樣時鐘沿和數(shù)據(jù)窗口中心對齊而不是和數(shù)據(jù)窗口邊緣對齊。換句話說采樣時鐘沿需要有意識地向數(shù)據(jù)穩(wěn)定的中間放。檢查IOB寄存器的推斷。Xilinx的IOB里有現(xiàn)成的寄存器資源IDDR原語就是用IOB里的寄存器實現(xiàn)的。如果工具沒有把讀數(shù)據(jù)采樣寄存器放入IOB路徑延遲會大很多因為數(shù)據(jù)要先從IOB走通用布線資源到CLB的FF。查看綜合后的schematic確認(rèn)是否例化了IDDR或IDELAYEYE2。沒有的話在代碼里直接例化原語避免綜合器自由發(fā)揮。我遇到過一次比較詭異的情況setup違例只出現(xiàn)在特定溫度和電壓角下常溫下余量還有0.1ns跑到高溫就變負(fù)了。后來查出來那是因為PCB上DQS走線比DQ走線長了接近500mil導(dǎo)致高溫下DQS延遲增大把數(shù)據(jù)窗口擠掉了。解決方式不是改約束而是換了一版等長做得好一點的PCB。這種事情在驗證板子上很常見時序分析只能告訴你系統(tǒng)在當(dāng)前的物理條件下能不能跑不能替你把PCB的問題掩蓋掉。5.2 典型hold違例的處理思路hold違例和setup違例經(jīng)常成對出現(xiàn)處理手段卻經(jīng)常是矛盾的。hold違例表示數(shù)據(jù)在采樣沿之后失效得太早即數(shù)據(jù)有效窗口在采樣沿之前就關(guān)閉了。在DDR讀接口里最常見的原因有兩個。第一個是IDELAY的tap數(shù)給得太多導(dǎo)致數(shù)據(jù)被整體向后延遲。數(shù)據(jù)后移后hold檢查的裕量會變小因為hold關(guān)心的是采樣沿之后數(shù)據(jù)還能穩(wěn)定多久。如果你發(fā)現(xiàn)setup變好了但hold變差了多半就是這個原因。第二個是采樣時鐘與DQS之間的相位關(guān)系調(diào)得不干凈。比如MMCM的CLKOUT0_PHASE設(shè)置不當(dāng)時采樣時鐘沿會落在數(shù)據(jù)窗口的邊緣附近一邊裕量很大一邊卻很小。所以在調(diào)試過程中方法就是先掃IDELAY的所有tap值找到setup和hold都為正的中間區(qū)間再在這個區(qū)間里選擇居中的值。調(diào)試我通常用Vivado的硬件管理器通過VIO核來在線改變IDELAY的tap值。具體做法是在代碼里例化一個VIO輸出把若干位連到IDELAYEYE2的CNTVALUEIN端口通過VIO界面動態(tài)調(diào)整然后回讀眼圖監(jiān)視模塊的結(jié)果。這種方法不需要反復(fù)重新編譯一次綜合就能在板子上掃描完整個延遲鏈。如果掃描完整個IDELAY范圍都找不到一個同時滿足setup和hold的tap值那就說明問題不在IDELAY而是DQS的相位本身就不對或者說設(shè)計里的采樣時鐘相位需要跨一個較大的調(diào)整范圍。此時去看看MMCM的相位設(shè)置或者檢查DDR控制器的讀DQS訓(xùn)練邏輯是否正常工作。5.3 問題速查表現(xiàn)象可能原因排查優(yōu)先級解決方案setup違例且整組DQ都出現(xiàn)采樣時鐘相位未對齊高調(diào)整MMCM相位或BUFR/BUFIO配置setup違例但只有部分DQ出現(xiàn)PCB等長不良或IDELAY不匹配高核對PCB走線長度逐位調(diào)整IDELAYsetup小幅違例如-0.1ns輸入延遲約束偏緊中確認(rèn)約束余量是否合理適當(dāng)放寬hold違例整體出現(xiàn)IDELAY tap過大了高減小tap值重新掃描窗口hold違例偶發(fā)與溫度有關(guān)DQS和DQ路徑延遲差受溫度影響中提高窗口中心加margin時序報告中約束完全沒有生效get_ports名字錯誤或約束層級不對高檢查XDC作用域使用正確端口名時序報告顯示input delay和實際不符缺少source_latency中補(bǔ)上DQS引腳到內(nèi)部時鐘的延遲5.4 關(guān)于雙向總線約束的額外提醒DDR的DQ是雙向總線讀和寫走的是同一條物理線。上面講的都是讀方向的輸入延遲約束。寫方向則是FPGA作為發(fā)送端約束是set_output_delay由DDR顆粒的tDS和tDH倒推。兩者必須同時做對才能保證整個DDR接口的閉環(huán)。實戰(zhàn)中常見一個錯誤只做了讀方向的輸入約束寫方向放養(yǎng)了結(jié)果跑讀寫綜合測試時寫操作偶發(fā)錯誤。Vivado對未約束的路徑會默認(rèn)使用相對寬松的默認(rèn)值所以報告里大概率不報錯但這種默認(rèn)約束無法保證與DDR顆粒真正的時序需求匹配。個人經(jīng)驗是讀寫兩個方向都要用ILA抓一遍實際波形不要看報告沒紅就覺得萬無一失。6. 一點實戰(zhàn)提示IDELAY、眼圖與約束的聯(lián)調(diào)這部分想分享的是約束文件之外的功夫。很多人以為時序約束寫對了問題就解決了實際上約束只是讓工具“知道”物理世界的規(guī)則真正的DDR接口成功率取決于你的訓(xùn)練/校準(zhǔn)邏輯是否能把數(shù)據(jù)和DQS對齊到窗口正中心。在DDR3接口里讀數(shù)據(jù)訓(xùn)練的基本思路是不斷調(diào)整IDELAY的tap值找到setup/hold都滿足的區(qū)間然后取中間值。有些控制器IP自帶訓(xùn)練邏輯比如Xilinx MIG會自己完成讀DQS的訓(xùn)練。如果你是自己寫DDR控制器沒有現(xiàn)成的訓(xùn)練模塊可以用狀態(tài)機(jī)配合ILA來掃方法雖笨但可靠。掃描窗口的過程中建議你好好利用report_timing_summary里的hold和setup報告在每個tap值下記錄對應(yīng)的slack。以tap值為橫軸、slack為縱軸會看到setup裕量和hold裕量兩條線交錯的形狀一上一下中間會有一段兩者都為正的重疊區(qū)。你要選的值就是這段重疊區(qū)的中間點。如果重疊區(qū)非常窄說明DQS和DQ的相位關(guān)系本身就不理想可能需要在PCB層面改善走線等長或者在FPGA內(nèi)對DQS單獨加一段延遲。這種掃描工作在第4步里提到的VIO方案下通常一個上午就能完成。掃完后把最終tap值固化到代碼里作為默認(rèn)參數(shù)就再也不用手動調(diào)整了。我個人的體會是這個功夫值得下足——比你在約束文件里反復(fù)試數(shù)字要高效得多也能真正幫你理解接口的物理特性。DDR接口這種東西紙上談兵永遠(yuǎn)比不過親手調(diào)一次IDELAY鏈。另外加一句UltraScale和UltraScale系列的IDELAYEYE3控制方式和7系列略有不同tap延遲值也差一些但整體思路完全一致。換到高云、安路這些國產(chǎn)FPGA上原語名稱和配置方式不同不過源同步接口的分析方法和set_input_delay的推導(dǎo)思路是通用的。掌握了方法換平臺只是換命令單詞的問題。