
搞 ZYNQ 的人遲早會碰到一個繞不開的問題PL 端的邏輯跑得飛快數(shù)據(jù)要往 DDR 里放PS 端的 CPU 又要把這些數(shù)據(jù)取出來做處理兩邊怎么把數(shù)據(jù)倒騰明白我手上這塊黑金 AX7020 用的是 Xilinx ZYNQ7020PS 側(cè)是雙核 Cortex-A9 加 DDR 控制器PL 側(cè)是等效 Artix-7 規(guī)模的可編程邏輯。標題里說的 AXI-FULL DDR 的 PL 與 PS 交互說白了就是讓 PL 里的自定義邏輯通過滿功能的 AXI 總線直接讀寫掛在 PS 上的那顆 DDR3而不是繞遠路走 BRAM 或者 FIFO 中轉(zhuǎn)。這套東西做通了PL 采集的高速數(shù)據(jù)、圖像幀、ADC 采樣流就能直接甩進大容量內(nèi)存PS 端再從容地做算法、跑協(xié)議棧、送顯示。寫這篇東西的起因是我自己調(diào)這塊板子時被 HP 接口、Cache 一致性、突發(fā)長度這幾個點反復(fù)折騰網(wǎng)上資料要么太碎要么語焉不詳所以把整套流程和踩過的坑整理出來適合剛上手 ZYNQ、想把 PS 和 PL 真正打通的人參考也適合做過 AXI-Lite 但沒碰過高速搬運的兄弟。1. 先搞懂 ZYNQ7020 里 PS 和 PL 誰管 DDR1.1 DDR 控制器長在 PS 里PL 只能借道這是整個交互邏輯的地基必須先說清楚。ZYNQ7020 這顆芯片的 DDR 控制器DDRC是硬核物理上就長在 PS 側(cè)和雙核 Cortex-A9、片上存儲器 OCM、各種外設(shè)控制器做在同一片硅里。PL 側(cè)壓根沒有獨立的 DDR 控制器你在 PL 里寫邏輯想訪問那 512MB 的 DDR3唯一的路徑就是通過 PS 暴露出來的 AXI 從機接口進到 PS 內(nèi)部的總線互聯(lián)再落到 DDR 控制器上。黑金 AX7020 板載的 DDR3 是兩片 16 位顆粒拼成 32 位總線容量按批次有 512MB 和 1GB 兩種我手上這塊是 512MB地址空間覆蓋 0x0000_0000 到 0x1FFF_FFFF。你要做的第一件事就是確認自己板子的實際容量和起始地址別想當(dāng)然。為什么 DDR 地址從 0 開始因為 Zynq 的地址映射里 DDR 就被分配在最低段OCM 在頂部的 0xFFFF_0000中間那一大片是各種外設(shè)寄存器。這個映射關(guān)系在 Vivado 的 Address Editor 里能看得一清二楚建議一開始就去看一眼腦子里有個地圖。理解了這一層你就明白為什么 PL 訪問 DDR 必須依賴 PS 的接口數(shù)據(jù)不是搬進 PL 自己的存儲而是搬進 PS 的存儲PL 只是發(fā)起方。這一點直接決定了后面所有配置都要圍繞打通 PL 到 PS DDRC 的通道來做。1.2 GP、HP、ACP 三種 AXI 通道選錯一個帶寬差十倍ZYNQ7020 的 PS 對外暴露了好幾組 AXI 接口名字容易搞混我按實際用途捋一遍。GP 口General Purpose包括 M_AXI_GP0/GP1 和 S_AXI_GP0/GP1。M_AXI_GP 是 PS 作為主機去訪問 PL 的從機比如 PS 配置 PL 里的寄存器這是最常用的。S_AXI_GP 是 PL 作為主機訪問 PS 的從機走這個口也能摸到 DDR但數(shù)據(jù)寬度只有 32 位而且要穿過 PS 的中央互聯(lián)Central Interconnect帶寬被卡得很死實測單口撐死幾百 MB/s還搶 CPU 的總線。拿它做寄存器配置沒問題做大數(shù)據(jù)搬運就是自找麻煩。HP 口High PerformanceS_AXI_HP0 到 HP3 一共四個每個數(shù)據(jù)寬度 64 位而且這幾個口是直連 DDR 控制器和 OCM 的專門為高帶寬數(shù)據(jù)搬運設(shè)計。PL 側(cè)做視頻、ADC、網(wǎng)絡(luò)包這類流量一律走 HP。四個口可以并行工作但底層的 DDR 物理帶寬是共享的這點后面算帶寬時會細說。ACP 口Accelerator Coherency Port這個口比較特殊它連到 PS 的 L2 Cache 和 SCU 上專門給需要和 CPU 保持緩存一致的加速器用。PL 通過 ACP 寫數(shù)據(jù)CPU 的 Cache 會自動更新省掉手動刷 Cache 的步驟。但 ACP 的帶寬和時序特性比較挑一般做加速器直連才用純粹的大容量搬運還是 HP 更穩(wěn)。結(jié)論很直白PL 要高速讀寫 DDR就用 HP 口。GP 留給寄存器ACP 留給需要一致性的加速器。我最初圖省事拿 GP 口跑數(shù)據(jù)結(jié)果帶寬上不去還莫名拖慢了 CPU換成 HP 之后整個世界都清爽了。1.3 為什么必須上 AXI-FULL 而不是 AXI-LiteAXI-Lite 和 AXI-FULL 的區(qū)別用一個類比就懂AXI-Lite 像是一次只能寄一個包裹的快遞每次傳輸都要走一遍完整的地址握手沒有突發(fā)AXI-FULL 則是一次可以裝一車貨支持突發(fā)Burst傳輸給一個起始地址后面連續(xù) N 拍數(shù)據(jù)自動跟著地址遞增走握手開銷被攤薄到極致。做寄存器讀寫AXI-Lite 簡單省事。但搬到幾百 KB、幾 MB 的數(shù)據(jù)流每一拍都握一次手地址通道的握手會吃掉大量時鐘周期實際帶寬可能連理論值的三分之一都到不了。AXI-FULL 的突發(fā)機制就是為了解決這個一次 Burst 最多可以連續(xù)傳 16 拍Zynq-7000 的 HP 口是 AXI3 協(xié)議突發(fā)長度上限 16地址只發(fā)一次數(shù)據(jù)通道連續(xù)流動。這里有個細節(jié)容易被忽略ZYNQ7020 的 HP 接口在協(xié)議版本上是 AXI3不是 AXI4。AXI3 和 AXI4 在突發(fā)長度上不同AXI3 最大 16 拍AXI4 最大 256 拍。很多教程張口就說AXI-FULL 支持 256 拍突發(fā)那是 UltraScale 的規(guī)格。你在 7020 上寫 AXI Master突發(fā)長度字段只有 4 位最大就是 16寫超了會出錯。不過好消息是Vivado 里的 AXI Interconnect 會自動做 AXI3/AXI4 的協(xié)議轉(zhuǎn)換如果你用現(xiàn)成的 AXI4 Master IP 連到 HP 口互聯(lián)會自動把長突發(fā)拆成 16 拍一段你只要心里有數(shù)就行。明白這三層邏輯之后整條技術(shù)路線就清晰了PL 里寫一個 AXI-FULL實際是 AXI3協(xié)議的 Master通過 S_AXI_HP 接口連到 PS 的 DDR 控制器PS 端軟件負責(zé)地址規(guī)劃和 Cache 維護。2. Vivado 工程搭建把 HP 通道打開2.1 ZYNQ7 PS 配置里幾個必須動的地方新建 Vivado 工程器件選 xc7z020clg400-2AX7020 用的就是這顆速度等級 -2。思路是 Block Design 里放一個 ZYNQ7 Processing System先跑一次 Run Block Automation讓工具把 DDR 和 FIXED_IO 引出來。接下來雙擊 ZYNQ7 PS 進配置幾個頁面必須挨個過DDR Configuration選 DDR3Memory Part 選和板載顆粒匹配的型號。AX7020 常用的是 MT41K256M16 或?qū)?yīng)批次顆粒選不對會導(dǎo)致初始化失敗、讀寫全錯。如果你不確定型號寧可選 generic 手動填參數(shù)也別瞎選。DDR 時鐘頻率這里板子一般跑 DDR3-1066時鐘 533MHz保守起見先別超頻跑通再說。Clock Configuration把 FCLK_CLK0 使能頻率先設(shè) 100MHz這是給 PL 邏輯用的參考時鐘。后面 HP 接口的 ACLK 也接它。想跑更高帶寬可以調(diào)到 150MHz 甚至 200MHz但前提是時序能收斂。PS-PL Configuration → AXI HP Interface這是重點把 S_AXI_HP0 勾上Data Width 保持 64 位。勾上之后 Block Design 里的 PS 模塊就會多出一個 S_AXI_HP0 接口。如果你打算做多路并行搬運可以再開 HP1、HP2但一開始別貪多先把一路調(diào)通。Interrupts如果后面要 PL 中斷通知 PS在 PS-PL Configuration 里把 PL-PS Interrupt Ports 勾上 IRQ_F2P。輪詢能解決的場景可以暫時不勾。配置完點 Validate Design沒報錯再往下走。2.2 HP 接口與時鐘的連接方式HP 接口的 ACLK 時鐘怎么接是新手最容易懵的點。ZYNQ 的 HP 端口內(nèi)部有異步橋接理論上可以用 PL 側(cè)的任意時鐘驅(qū)動它的 ACLK但為了性能最好讓 HP 的時鐘域和 PL 邏輯的時鐘域一致避免跨時鐘域帶來的額外延遲。我的做法是把 ZYNQ7 PS 輸出的 FCLK_CLK0100MHz直接接到 AXI Interconnect 的 ACLK 上同時這個時鐘也作為整個 PL 邏輯的主時鐘。如果你有獨立的 PL 時鐘源也可以接自己的只要保證 AXI 主從雙方時鐘正確連接。Vivado 會在 Validate 時報時鐘未連接的錯看到 ACLK 相關(guān)的紅色提示八成就是時鐘沒接上。關(guān)于 FIFO 和寄存器切片AXI Interconnect 內(nèi)部會自動插入寄存器切片來改善時序一般不用手動加。但如果你的頻率調(diào)到 150MHz 以上時序不收斂可以手動在 HP 接口前面加一級 AXI Register Slice把長組合路徑打斷時序會好很多。2.3 地址分配與 AXI Interconnect 的取舍Block Design 里PL 的 AXI Master 和 PS 的 S_AXI_HP0 之間要放一個 AXI Interconnect新版 Vivado 里叫 AXI SmartConnect 也行。SmartConnect 對多主機多從機的場景更友好資源占用也優(yōu)化過但它默認要求所有接口都是 AXI4連到 HP 的 AXI3 口時它會自動轉(zhuǎn)換用起來沒問題。傳統(tǒng) AXI Interconnect 更老實配置參數(shù)多但對 AXI3 支持更直接。我個人的習(xí)慣是單主機單從機就用 Interconnect多主機混用就上 SmartConnect。接好之后點 Address Editor你會看到 PS 的 DDR 段自動分配給了這個 HP 接口地址就是 0x0000_0000 起范圍 512MB 或 1GB。這里必須核對一遍確保 PL 側(cè)能訪問的 DDR 地址范圍覆蓋了你打算用的區(qū)域。有時候工具只分配了一小段你得手動改 Range 或者把整個 DDR 段分過去。分配好地址生成 Output ProductsCreate HDL Wrapper然后 Add Constraints 把引腳綁好DDR 和固定 IO 一般 PS 配置里已經(jīng)處理好不用手動綁。綜合、實現(xiàn)、生成比特流第一次可以跳過實現(xiàn)直接跑綜合看有沒有語法錯誤。3. PL 側(cè) AXI-FULL Master 的寫法3.1 五個通道的握手流程梳理要自己寫 AXI Master先把五個獨立通道的關(guān)系理順。AXI 是全雙工、通道獨立的協(xié)議寫操作和讀操作各走各的寫操作三個通道AW地址寫用來發(fā)寫地址和控制信息W數(shù)據(jù)寫用來發(fā)實際數(shù)據(jù)、字節(jié)選通 WSTRB 和末拍標志 WLASTB寫響應(yīng)是從機回給主機的告訴你這次寫成功還是失敗BRESP。讀操作兩個通道AR地址讀發(fā)讀地址R數(shù)據(jù)讀從機回數(shù)據(jù)配合 RLAST 標志末拍。每個通道都用 VALID/READY 一對信號做握手規(guī)則很簡單VALID 由發(fā)送方拉高表示數(shù)據(jù)有效READY 由接收方拉高表示我準備好了兩者同時為高的那個時鐘上升沿數(shù)據(jù)被正式接收。發(fā)送方拉高 VALID 后必須等接收方 READY 才能撤不能中途反悔。寫操作的通道依賴關(guān)系要說清楚AXI3 允許 AW 和 W 通道的先后順序相對靈活但我在實現(xiàn)時采用的是先發(fā)完 AW 再發(fā) W的保守順序邏輯簡單不易錯。B 通道的響應(yīng)必須等到 AW 和 W 都完成之后才會來。讀操作沒有響應(yīng)通道收到 RLAST 就代表這一筆讀完成了。3.2 寫通道狀態(tài)機實現(xiàn)下面是我實際用的寫狀態(tài)機骨架Verilog 寫的簡化了信號名但邏輯完整。這個 Master 做的事是收到觸發(fā)信號后往指定基地址連續(xù)寫 N 拍數(shù)據(jù)每拍 64 位。localparam S_IDLE 3d0, S_AW 3d1, S_W 3d2, S_B 3d3, S_DONE 3d4; reg [2:0] wstate; reg [3:0] wcnt; // 突發(fā)計數(shù)AXI3 最大 16 reg [63:0] wdata_reg; always (posedge aclk or negedge aresetn) begin if (!aresetn) begin wstate S_IDLE; s_axi_awvalid 1b0; s_axi_wvalid 1b0; s_axi_wlast 1b0; wcnt 4d0; end else begin case (wstate) S_IDLE: begin if (start_write) begin s_axi_awaddr base_addr; s_axi_awlen 4d15; // 16 拍突發(fā)AXI3 用 4 位值拍數(shù)-1 s_axi_awsize 3d3; // 每拍 8 字節(jié) s_axi_awburst 2b01; // INCR 遞增模式 s_axi_awvalid 1b1; wstate S_AW; end end S_AW: begin if (s_axi_awvalid s_axi_awready) begin s_axi_awvalid 1b0; s_axi_wvalid 1b1; wcnt 4d0; wstate S_W; end end S_W: begin if (s_axi_wvalid s_axi_wready) begin if (wcnt 4d15) begin s_axi_wlast 1b1; // 最后一拍 end if (s_axi_wlast s_axi_wready) begin s_axi_wvalid 1b0; s_axi_wlast 1b0; wstate S_B; end else begin wcnt wcnt 1b1; end // 數(shù)據(jù)源在這里更新實際項目從 FIFO 或采集邏輯取 wdata_reg wdata_reg 64h1; end end S_B: begin s_axi_bready 1b1; if (s_axi_bvalid) begin s_axi_bready 1b0; wstate S_DONE; end end S_DONE: wstate S_IDLE; endcase end end這段邏輯里有幾個點值得強調(diào)。AWLEN 字段寫的是拍數(shù)減一要傳 16 拍就寫 15這個減一很容易忘。AWSIZE 決定每拍多少字節(jié)3d3 代表 8 字節(jié)對應(yīng) 64 位數(shù)據(jù)寬度如果 AWADDR 沒按 8 字節(jié)對齊傳輸會報錯或者行為異常。AWBURST 用 2b01 表示遞增模式地址隨突發(fā)自動往上走這也是搬運連續(xù)數(shù)據(jù)最常用的模式。3.3 讀通道狀態(tài)機與數(shù)據(jù)拼接讀通道相對簡單只有 AR 和 R 兩個通道但數(shù)據(jù)接收階段的拼接和緩存要處理好不然高速讀的時候數(shù)據(jù)會丟。localparam R_IDLE 2d0, R_ADDR 2d1, R_DATA 2d2; always (posedge aclk or negedge aresetn) begin if (!aresetn) begin rstate R_IDLE; s_axi_arvalid 1b0; s_axi_rready 1b0; end else begin case (rstate) R_IDLE: begin if (start_read) begin s_axi_araddr base_addr; s_axi_arlen 4d15; s_axi_arsize 3d3; s_axi_arburst 2b01; s_axi_arvalid 1b1; rstate R_ADDR; end end R_ADDR: begin if (s_axi_arvalid s_axi_arready) begin s_axi_arvalid 1b0; s_axi_rready 1b1; rstate R_DATA; end end R_DATA: begin if (s_axi_rvalid s_axi_rready) begin // 實時把 rdata 送進下游 FIFO 或校驗邏輯 if (s_axi_rlast) begin s_axi_rready 1b0; rstate R_IDLE; end end end endcase end end讀數(shù)據(jù)階段的關(guān)鍵是RREADY 要盡早拉高讓從機有數(shù)據(jù)就往外送不要因為下游處理慢而卡住總線。如果下游邏輯來不及消費就在 R 通道后面加一個異步 FIFO 或者同步 FIFO 做緩沖RREADY 根據(jù) FIFO 的滿標志動態(tài)控制這樣既不丟數(shù)據(jù)也不浪費帶寬。3.4 突發(fā)長度和 4KB 邊界這兩個硬約束AXI 協(xié)議里有兩條硬性約束違反任意一條輕則數(shù)據(jù)錯重則總線掛死。第一條是突發(fā)長度上限。Zynq-7000 的 HP 口是 AXI3最多 16 拍。你如果想一次搬 1KB 數(shù)據(jù)64 位 × 128 拍就必須拆成 8 次 16 拍的突發(fā)來做。別指望一次發(fā) 128 拍AXI Interconnect 雖然會自動拆分但如果你自己寫 Master 直接懟 128 拍給 HP 口行為是未定義的。第二條更隱蔽任何一筆突發(fā)傳輸都不能跨越 4KB 地址邊界。4KB 是 AXI 從機地址譯碼的粒度一筆突發(fā)如果從 0x1000_0FF0 開始還要連傳 16 拍8 字節(jié) × 16 128 字節(jié)終點會跑到 0x1000_1070跨過了 0x1000_1000 這個 4KB 邊界這在 AXI 里是違規(guī)的很多從機會直接返回 SLVERR 或者干脆丟棄。避免的辦法是在地址計算階段就做邊界檢查每筆突發(fā)的起始地址和長度算一下如果終點跨過了 4KB 對齊線就提前把這一筆截斷下一筆從邊界處重新開始。我寫的一個小函數(shù)就是干這個的按 4KB 邊界把大塊搬運拆成一串合法的突發(fā)序列。這個檢查邏輯雖然啰嗦但能避免一大類詭異的地址錯亂問題非常值得寫。4. PS 側(cè)軟件配套地址規(guī)劃與 Cache 一致性4.1 共享內(nèi)存的地址怎么劃PS 端軟件跑在 DDR 里程序代碼、堆棧、全局變量都在占空間。PL 通過 HP 口寫 DDR 時如果地址和你程序的數(shù)據(jù)區(qū)重疊了那就是災(zāi)難性的——CPU 的變量被莫名覆蓋程序跑飛還特別難查。所以第一步是劃出一塊誰都不碰的共享區(qū)域。我的做法是在鏈接腳本 lscript.ld 里專門留一段。假設(shè)程序本身不大我用 0x1000_0000 到 0x1FFF_FFFF 這一大段給 PL 做數(shù)據(jù)緩沖區(qū)程序和數(shù)據(jù)限制在低地址區(qū)。具體操作是在 lscript.ld 里加一個自定義段_shared_start 0x10000000; _shared_end 0x1FFFFFFF;然后在 C 代碼里直接用這個絕對地址讀寫。進階一點的做法是在鏈接腳本里定義一個 section 并分配固定地址再用__attribute__((section(.shared_mem)))把緩沖區(qū)放進去。好處是編譯器知道這塊內(nèi)存的存在不會和別的變量沖突缺點是調(diào)試時得盯著 map 文件確認地址真的對上了。還有個更省心的選擇用 OCM。Zynq 的 OCM 地址在 0xFFFF_0000默認就是 non-cacheable 的PS 和 PL 都能直接訪問做小數(shù)據(jù)量的握手標志位、狀態(tài)同步特別合適省掉了所有 Cache 維護的麻煩。但 OCM 只有 256KB大塊數(shù)據(jù)還是得放 DDR。4.2 Cache 操作Flush 和 Invalidate 別搞反這塊是 PS-PL 數(shù)據(jù)交互里最容易翻車的地方我單獨拎出來講。Cortex-A9 有 L1 和 L2 CacheCPU 讀寫內(nèi)存時數(shù)據(jù)可能只在 Cache 里還沒寫回 DDR。這就導(dǎo)致兩個方向的坑PS 寫、PL 讀CPU 把數(shù)據(jù)寫進 buffer此時數(shù)據(jù)可能還躺在 Cache 里是臟的dirtyDDR 里還是舊值。PL 通過 HP 口直接讀 DDR讀到的是舊數(shù)據(jù)。解決辦法是 PL 去讀之前PS 先調(diào)Xil_DCacheFlushRange(addr, len)把 Cache 里的臟數(shù)據(jù)強制寫回 DDR。PL 寫、PS 讀PL 把新數(shù)據(jù)寫進了 DDR但 CPU 的 Cache 里可能還緩存著這塊地址的舊副本。CPU 讀的時候命中 Cache拿到的是舊數(shù)據(jù)。解決辦法是 PS 讀之前調(diào)Xil_DCacheInvalidateRange(addr, len)讓這塊地址的 Cache 行失效CPU 下次讀會重新從 DDR 取。這兩個函數(shù)用反了或者干脆忘了調(diào)癥狀都是數(shù)據(jù)對不上而且時好時壞特別迷惑人。我踩過的坑是單步調(diào)試時數(shù)據(jù)是對的一全速跑就錯原因就是斷點改變了 Cache 的時序。凡是 PS 和 PL 共享的內(nèi)存區(qū)每次跨邊界訪問前都要老老實實做 Cache 維護。如果實在嫌麻煩可以在 MMU 配置里把這塊共享內(nèi)存設(shè)成 non-cacheableDevice 或 Strongly Ordered 屬性一勞永逸代價是 CPU 訪問這塊內(nèi)存會慢一些因為每次都真的去 DDR 讀。數(shù)據(jù)量大、CPU 又不頻繁訪問的場景這個取舍很劃算。4.3 中斷還是輪詢PL 和 PS 怎么知道對方干完活了兩種方式。輪詢PS 端死循環(huán)讀某個標志位PL 寫完了就把標志位翻轉(zhuǎn)。實現(xiàn)最簡單不需要配置中斷適合調(diào)試階段。壞處是浪費 CPU而且標志位本身也要考慮 Cache 和內(nèi)存屏障問題。中斷PL 通過 IRQ_F2P 拉高中斷線PS 收到中斷進服務(wù)程序處理。效率高但配置繁瑣要在 PS 配置里使能 PL-PS 中斷在 PL 里生成中斷信號在 Vitis 里注冊中斷處理函數(shù)、使能 GIC。適合正式產(chǎn)品。我調(diào)試階段一律先用輪詢把數(shù)據(jù)通路跑通確認讀寫邏輯沒問題再換成中斷。別一上來就搞中斷中斷本身的 bug 和數(shù)據(jù)通路的 bug 混在一起排查會崩潰。順帶提一句流式數(shù)據(jù)的場景如果你的 PL 側(cè)是視頻或者傳感器采樣這種連續(xù)流通常先在 PL 里把 AXI-Stream 格式的數(shù)據(jù)轉(zhuǎn)成 AXI-FULL 寫進 DDR這就是常說的寫幀緩存然后用中斷通知 PS這一幀寫好了PS 再去 DDR 取。這樣 PL 和 PS 解耦各干各的效率最高。5. 實機聯(lián)調(diào)從波形看到帶寬5.1 先用 AXI Traffic Generator 探底自己寫的 Master 第一次上板別急著接真實數(shù)據(jù)源。先用 Xilinx 提供的 AXI Traffic Generator IP 快速探一下通路是否打通。這個 IP 可以自動產(chǎn)生指定模式的 AXI 讀寫流量你只要配好地址范圍、突發(fā)長度、讀寫比例它就能自己跑出滿負荷的流量。具體配置在 Block Design 里加一個 AXI Traffic Generator把它設(shè)成 Master 模式接口連到 AXI Interconnect地址指向 DDR 段。配置成連續(xù)寫模式數(shù)據(jù)模式選遞增或者固定 pattern。生成比特流上板后用 Vitis 跑一個簡單的程序往 DDR 某個地址先寫入已知數(shù)據(jù)然后讓 Traffic Generator 去讀看讀回來的數(shù)據(jù)對不對反過來讓 Traffic Generator 寫PS 去讀驗證。這一步能驗證的東西很多HP 接口通不通、地址映射對不對、時鐘有沒有接好、互聯(lián)配置有沒有問題。如果 Traffic Generator 都跑不通那你自己的 Master 更別想跑通先解決底層通路。5.2 ILA 抓握手信號定位卡死自己寫的 Master 上板后最常見的問題是狀態(tài)機卡在某個狀態(tài)動不了。這時候 ILA集成邏輯分析儀就是救命稻草。在 Vivado 里給 AXI 的握手信號加 ILA 核重點抓這幾個AWVALID、AWREADY、WVALID、WREADY、BVALID、ARVALID、ARREADY、RVALID、RREADY再加上你自己的狀態(tài)機狀態(tài)寄存器。抓波形的判斷邏輯很直接如果 AWVALID 拉高了但 AWREADY 一直低說明從機沒準備好接收可能是地址不合法、接口沒使能、或者時鐘域不對。如果 AW 握手成功了但 W 一直不握手檢查 WSTRB 和 WLAST 有沒有正確設(shè)置。如果卡在等 BVALID說明寫數(shù)據(jù)發(fā)出了但從機沒回響應(yīng)往往是地址越界或者觸發(fā)了 SLVERR。我第一次調(diào)試時狀態(tài)機卡在 S_W 死活不動抓波形發(fā)現(xiàn) WREADY 一直是低折騰半天才想明白是 AWLEN 配錯了——我寫了 4d15 想傳 16 拍但數(shù)據(jù)源只給了 8 拍就斷了從機等不到 WLAST 就一直不拉 READY。這個坑很典型突發(fā)長度聲明了多少拍就必須實實在在地送夠多少拍數(shù)據(jù)配合正確的 WLAST。5.3 帶寬實測數(shù)據(jù)與優(yōu)化通路跑通之后就該關(guān)心性能了。先算理論值HP 接口 64 位寬ACLK 用 100MHz單口理論帶寬是 64bit × 100MHz 6.4Gbps 800MB/s。如果 ACLK 提到 150MHz那就是 1.2GB/s。但注意PS DDR3 的物理總帶寬是有限的32 位 533MHz DDR 大概 4.26GB/s四個 HP 口和其他主設(shè)備CPU、DMA都要分這塊蛋糕。實測下來單 HP 口用 16 拍突發(fā)連續(xù)讀寫效率通常能到理論值的 60% 到 80%。影響因素排序大概是突發(fā)長度越長效率越高但受 16 拍限制、讀寫比例DDR 頻繁讀寫切換有開銷連續(xù)讀或連續(xù)寫效率更高、地址是否連續(xù)連續(xù)地址比跳躍地址效率高得多、以及 CPU 是否在搶總線。我做的優(yōu)化有幾個把連續(xù)搬運的突發(fā)盡量用滿 16 拍讀寫分開做不要交替進行批量操作時讓 PS 端 CPU 先歇著別在搬運時跑重活。調(diào)整之后同樣的數(shù)據(jù)量搬運時間縮短了差不多三分之一。想要更高帶寬就得上雙 HP 口并行但前提是 DDR 總帶寬還沒被吃滿不然加了也白加。6. 踩坑記錄與問題速查表6.1 常見問題對照表下面這張表是我和身邊朋友在 ZYNQ PS-PL DDR 交互里遇到過的典型問題按現(xiàn)象歸類方便對號入座?,F(xiàn)象可能原因排查方向PL 讀寫 DDR 返回 SLVERR地址越界、HP 接口未使能、時鐘未連接核對 Address Editor 地址范圍檢查 PS 配置里 HP 是否勾選讀回數(shù)據(jù)全是 0 或全 FF握手不完整、WSTRB 未設(shè)置、從機未響應(yīng)ILA 抓握手信號確認每拍數(shù)據(jù)都被接收數(shù)據(jù)整體錯位一兩個字節(jié)地址未按數(shù)據(jù)寬度對齊、WSTRB 與數(shù)據(jù)不匹配檢查 AWADDR 是否 8 字節(jié)對齊64 位傳輸PS 讀到舊數(shù)據(jù)Cache 未 invalidate讀前調(diào) Xil_DCacheInvalidateRangePL 讀到舊數(shù)據(jù)Cache 未 flushPL 讀前 PS 調(diào) Xil_DCacheFlushRange狀態(tài)機卡死不動AWLEN 配錯、WLAST 未在正確拍拉高核對突發(fā)拍數(shù)與 WLAST 時序移植到高頻率后時序不收斂HP 時鐘太快、組合路徑過長降頻驗證加 AXI Register Slice程序莫名跑飛PL 寫的 DDR 區(qū)域和程序區(qū)重疊嚴格劃分共享內(nèi)存地址別碰程序段仿真時 DDR 無響應(yīng)缺少 DDR 模型或未接 AXI VIP仿真用 BRAM 替代或用 Micron DDR3 模型固化到 Flash 后跑不起來FSBL 加載地址或 DDR 初始化問題確認應(yīng)用是否必須放 DDR必要時縮小放 OCM6.2 幾條血淚心得先說仿真這件事。很多人問 Vivado 里怎么仿 DDR 交互其實完整仿真整條 DDR 通路很麻煩需要 Micron 的 DDR3 Verilog 模型速度慢、配置繁瑣。我的建議是仿真階段只驗證你的 AXI Master 邏輯本身用一個簡單的 AXI Slave 模型或者 BRAM 控制器替代 DDR把握手時序、突發(fā)拆分、狀態(tài)跳轉(zhuǎn)驗證清楚就夠了真正的 DDR 時序留到上板用 ILA 看。上板抓波形比跑仿真直觀一百倍。再說固化的問題。有人問用 JTAG 固化 Flash 到底需不需要 DDR。答案是不強制Zynq 的啟動流程是 BootROM 先跑加載 FSBLFSBL 負責(zé)初始化 DDR 等外設(shè)然后加載應(yīng)用。如果你的應(yīng)用不大FSBL 和應(yīng)用都可以加載到 OCM 里跑完全不碰 DDR。但一旦應(yīng)用超過 OCM 容量256KB就必須初始化并使用 DDR 來存放應(yīng)用。所以要不要 DDR取決于你的應(yīng)用體積不是固定的規(guī)矩。PCB 層面的東西也順帶提一句。板子出廠時 DDR 走線都做好了等長和阻抗控制這部分我們改不了也不用管。但如果你自己畫板DDR 地址線和數(shù)據(jù)線的等長、分組、參考平面這些就繞不開了那又是另一門學(xué)問遠超這篇的范圍。最后是調(diào)試心態(tài)。PS-PL 交互出問題時現(xiàn)象往往很玄學(xué)——單步對、全速錯或者跑一會兒才錯。這種時候別急著懷疑玄學(xué)老老實實回到三件事上查Cache 有沒有維護、地址有沒有越界、握手有沒有漏拍。我遇到過的九成問題最后都能歸到這三類里。把 ILA 波形和 Cache 操作日志對著看問題基本無所遁形。我個人在實際項目里的體會是ZYNQ 的 PS-PL 數(shù)據(jù)交互看著復(fù)雜但骨架很清晰PL 寫個規(guī)規(guī)矩矩的 AXI MasterPS 開好 HP 口和地址兩邊用共享內(nèi)存加 Cache 維護對上眼剩下的就是帶寬和時序的調(diào)優(yōu)。真正花時間的從來不是寫代碼而是第一次把信號接對、把地址算對、把 Cache 想明白。第一次跑通之后后面加通道、提頻率、改數(shù)據(jù)格式都是水到渠成。