ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

ZYNQ7020 PS与PL通过AXI-FULL HP接口高速读写DDR3

ZYNQ7020 PS与PL通过AXI-FULL HP接口高速读写DDR3 搞 ZYNQ 的人迟早会碰到一个绕不开的问题PL 端的逻辑跑得飞快数据要往 DDR 里放PS 端的 CPU 又要把这些数据取出来做处理两边怎么把数据倒腾明白我手上这块黑金 AX7020 用的是 Xilinx ZYNQ7020PS 侧是双核 Cortex-A9 加 DDR 控制器PL 侧是等效 Artix-7 规模的可编程逻辑。标题里说的 AXI-FULL DDR 的 PL 与 PS 交互说白了就是让 PL 里的自定义逻辑通过满功能的 AXI 总线直接读写挂在 PS 上的那颗 DDR3而不是绕远路走 BRAM 或者 FIFO 中转。这套东西做通了PL 采集的高速数据、图像帧、ADC 采样流就能直接甩进大容量内存PS 端再从容地做算法、跑协议栈、送显示。写这篇东西的起因是我自己调这块板子时被 HP 接口、Cache 一致性、突发长度这几个点反复折腾网上资料要么太碎要么语焉不详所以把整套流程和踩过的坑整理出来适合刚上手 ZYNQ、想把 PS 和 PL 真正打通的人参考也适合做过 AXI-Lite 但没碰过高速搬运的兄弟。1. 先搞懂 ZYNQ7020 里 PS 和 PL 谁管 DDR1.1 DDR 控制器长在 PS 里PL 只能借道这是整个交互逻辑的地基必须先说清楚。ZYNQ7020 这颗芯片的 DDR 控制器DDRC是硬核物理上就长在 PS 侧和双核 Cortex-A9、片上存储器 OCM、各种外设控制器做在同一片硅里。PL 侧压根没有独立的 DDR 控制器你在 PL 里写逻辑想访问那 512MB 的 DDR3唯一的路径就是通过 PS 暴露出来的 AXI 从机接口进到 PS 内部的总线互联再落到 DDR 控制器上。黑金 AX7020 板载的 DDR3 是两片 16 位颗粒拼成 32 位总线容量按批次有 512MB 和 1GB 两种我手上这块是 512MB地址空间覆盖 0x0000_0000 到 0x1FFF_FFFF。你要做的第一件事就是确认自己板子的实际容量和起始地址别想当然。为什么 DDR 地址从 0 开始因为 Zynq 的地址映射里 DDR 就被分配在最低段OCM 在顶部的 0xFFFF_0000中间那一大片是各种外设寄存器。这个映射关系在 Vivado 的 Address Editor 里能看得一清二楚建议一开始就去看一眼脑子里有个地图。理解了这一层你就明白为什么 PL 访问 DDR 必须依赖 PS 的接口数据不是搬进 PL 自己的存储而是搬进 PS 的存储PL 只是发起方。这一点直接决定了后面所有配置都要围绕打通 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但数据宽度只有 32 位而且要穿过 PS 的中央互联Central Interconnect带宽被卡得很死实测单口撑死几百 MB/s还抢 CPU 的总线。拿它做寄存器配置没问题做大数据搬运就是自找麻烦。HP 口High PerformanceS_AXI_HP0 到 HP3 一共四个每个数据宽度 64 位而且这几个口是直连 DDR 控制器和 OCM 的专门为高带宽数据搬运设计。PL 侧做视频、ADC、网络包这类流量一律走 HP。四个口可以并行工作但底层的 DDR 物理带宽是共享的这点后面算带宽时会细说。ACP 口Accelerator Coherency Port这个口比较特殊它连到 PS 的 L2 Cache 和 SCU 上专门给需要和 CPU 保持缓存一致的加速器用。PL 通过 ACP 写数据CPU 的 Cache 会自动更新省掉手动刷 Cache 的步骤。但 ACP 的带宽和时序特性比较挑一般做加速器直连才用纯粹的大容量搬运还是 HP 更稳。结论很直白PL 要高速读写 DDR就用 HP 口。GP 留给寄存器ACP 留给需要一致性的加速器。我最初图省事拿 GP 口跑数据结果带宽上不去还莫名拖慢了 CPU换成 HP 之后整个世界都清爽了。1.3 为什么必须上 AXI-FULL 而不是 AXI-LiteAXI-Lite 和 AXI-FULL 的区别用一个类比就懂AXI-Lite 像是一次只能寄一个包裹的快递每次传输都要走一遍完整的地址握手没有突发AXI-FULL 则是一次可以装一车货支持突发Burst传输给一个起始地址后面连续 N 拍数据自动跟着地址递增走握手开销被摊薄到极致。做寄存器读写AXI-Lite 简单省事。但搬到几百 KB、几 MB 的数据流每一拍都握一次手地址通道的握手会吃掉大量时钟周期实际带宽可能连理论值的三分之一都到不了。AXI-FULL 的突发机制就是为了解决这个一次 Burst 最多可以连续传 16 拍Zynq-7000 的 HP 口是 AXI3 协议突发长度上限 16地址只发一次数据通道连续流动。这里有个细节容易被忽略ZYNQ7020 的 HP 接口在协议版本上是 AXI3不是 AXI4。AXI3 和 AXI4 在突发长度上不同AXI3 最大 16 拍AXI4 最大 256 拍。很多教程张口就说AXI-FULL 支持 256 拍突发那是 UltraScale 的规格。你在 7020 上写 AXI Master突发长度字段只有 4 位最大就是 16写超了会出错。不过好消息是Vivado 里的 AXI Interconnect 会自动做 AXI3/AXI4 的协议转换如果你用现成的 AXI4 Master IP 连到 HP 口互联会自动把长突发拆成 16 拍一段你只要心里有数就行。明白这三层逻辑之后整条技术路线就清晰了PL 里写一个 AXI-FULL实际是 AXI3协议的 Master通过 S_AXI_HP 接口连到 PS 的 DDR 控制器PS 端软件负责地址规划和 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 或对应批次颗粒选不对会导致初始化失败、读写全错。如果你不确定型号宁可选 generic 手动填参数也别瞎选。DDR 时钟频率这里板子一般跑 DDR3-1066时钟 533MHz保守起见先别超频跑通再说。Clock Configuration把 FCLK_CLK0 使能频率先设 100MHz这是给 PL 逻辑用的参考时钟。后面 HP 接口的 ACLK 也接它。想跑更高带宽可以调到 150MHz 甚至 200MHz但前提是时序能收敛。PS-PL Configuration → AXI HP Interface这是重点把 S_AXI_HP0 勾上Data Width 保持 64 位。勾上之后 Block Design 里的 PS 模块就会多出一个 S_AXI_HP0 接口。如果你打算做多路并行搬运可以再开 HP1、HP2但一开始别贪多先把一路调通。Interrupts如果后面要 PL 中断通知 PS在 PS-PL Configuration 里把 PL-PS Interrupt Ports 勾上 IRQ_F2P。轮询能解决的场景可以暂时不勾。配置完点 Validate Design没报错再往下走。2.2 HP 接口与时钟的连接方式HP 接口的 ACLK 时钟怎么接是新手最容易懵的点。ZYNQ 的 HP 端口内部有异步桥接理论上可以用 PL 侧的任意时钟驱动它的 ACLK但为了性能最好让 HP 的时钟域和 PL 逻辑的时钟域一致避免跨时钟域带来的额外延迟。我的做法是把 ZYNQ7 PS 输出的 FCLK_CLK0100MHz直接接到 AXI Interconnect 的 ACLK 上同时这个时钟也作为整个 PL 逻辑的主时钟。如果你有独立的 PL 时钟源也可以接自己的只要保证 AXI 主从双方时钟正确连接。Vivado 会在 Validate 时报时钟未连接的错看到 ACLK 相关的红色提示八成就是时钟没接上。关于 FIFO 和寄存器切片AXI Interconnect 内部会自动插入寄存器切片来改善时序一般不用手动加。但如果你的频率调到 150MHz 以上时序不收敛可以手动在 HP 接口前面加一级 AXI Register Slice把长组合路径打断时序会好很多。2.3 地址分配与 AXI Interconnect 的取舍Block Design 里PL 的 AXI Master 和 PS 的 S_AXI_HP0 之间要放一个 AXI Interconnect新版 Vivado 里叫 AXI SmartConnect 也行。SmartConnect 对多主机多从机的场景更友好资源占用也优化过但它默认要求所有接口都是 AXI4连到 HP 的 AXI3 口时它会自动转换用起来没问题。传统 AXI Interconnect 更老实配置参数多但对 AXI3 支持更直接。我个人的习惯是单主机单从机就用 Interconnect多主机混用就上 SmartConnect。接好之后点 Address Editor你会看到 PS 的 DDR 段自动分配给了这个 HP 接口地址就是 0x0000_0000 起范围 512MB 或 1GB。这里必须核对一遍确保 PL 侧能访问的 DDR 地址范围覆盖了你打算用的区域。有时候工具只分配了一小段你得手动改 Range 或者把整个 DDR 段分过去。分配好地址生成 Output ProductsCreate HDL Wrapper然后 Add Constraints 把引脚绑好DDR 和固定 IO 一般 PS 配置里已经处理好不用手动绑。综合、实现、生成比特流第一次可以跳过实现直接跑综合看有没有语法错误。3. PL 侧 AXI-FULL Master 的写法3.1 五个通道的握手流程梳理要自己写 AXI Master先把五个独立通道的关系理顺。AXI 是全双工、通道独立的协议写操作和读操作各走各的写操作三个通道AW地址写用来发写地址和控制信息W数据写用来发实际数据、字节选通 WSTRB 和末拍标志 WLASTB写响应是从机回给主机的告诉你这次写成功还是失败BRESP。读操作两个通道AR地址读发读地址R数据读从机回数据配合 RLAST 标志末拍。每个通道都用 VALID/READY 一对信号做握手规则很简单VALID 由发送方拉高表示数据有效READY 由接收方拉高表示我准备好了两者同时为高的那个时钟上升沿数据被正式接收。发送方拉高 VALID 后必须等接收方 READY 才能撤不能中途反悔。写操作的通道依赖关系要说清楚AXI3 允许 AW 和 W 通道的先后顺序相对灵活但我在实现时采用的是先发完 AW 再发 W的保守顺序逻辑简单不易错。B 通道的响应必须等到 AW 和 W 都完成之后才会来。读操作没有响应通道收到 RLAST 就代表这一笔读完成了。3.2 写通道状态机实现下面是我实际用的写状态机骨架Verilog 写的简化了信号名但逻辑完整。这个 Master 做的事是收到触发信号后往指定基地址连续写 N 拍数据每拍 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; // 突发计数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 拍突发AXI3 用 4 位值拍数-1 s_axi_awsize 3d3; // 每拍 8 字节 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 // 数据源在这里更新实际项目从 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这段逻辑里有几个点值得强调。AWLEN 字段写的是拍数减一要传 16 拍就写 15这个减一很容易忘。AWSIZE 决定每拍多少字节3d3 代表 8 字节对应 64 位数据宽度如果 AWADDR 没按 8 字节对齐传输会报错或者行为异常。AWBURST 用 2b01 表示递增模式地址随突发自动往上走这也是搬运连续数据最常用的模式。3.3 读通道状态机与数据拼接读通道相对简单只有 AR 和 R 两个通道但数据接收阶段的拼接和缓存要处理好不然高速读的时候数据会丢。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读数据阶段的关键是RREADY 要尽早拉高让从机有数据就往外送不要因为下游处理慢而卡住总线。如果下游逻辑来不及消费就在 R 通道后面加一个异步 FIFO 或者同步 FIFO 做缓冲RREADY 根据 FIFO 的满标志动态控制这样既不丢数据也不浪费带宽。3.4 突发长度和 4KB 边界这两个硬约束AXI 协议里有两条硬性约束违反任意一条轻则数据错重则总线挂死。第一条是突发长度上限。Zynq-7000 的 HP 口是 AXI3最多 16 拍。你如果想一次搬 1KB 数据64 位 × 128 拍就必须拆成 8 次 16 拍的突发来做。别指望一次发 128 拍AXI Interconnect 虽然会自动拆分但如果你自己写 Master 直接怼 128 拍给 HP 口行为是未定义的。第二条更隐蔽任何一笔突发传输都不能跨越 4KB 地址边界。4KB 是 AXI 从机地址译码的粒度一笔突发如果从 0x1000_0FF0 开始还要连传 16 拍8 字节 × 16 128 字节终点会跑到 0x1000_1070跨过了 0x1000_1000 这个 4KB 边界这在 AXI 里是违规的很多从机会直接返回 SLVERR 或者干脆丢弃。避免的办法是在地址计算阶段就做边界检查每笔突发的起始地址和长度算一下如果终点跨过了 4KB 对齐线就提前把这一笔截断下一笔从边界处重新开始。我写的一个小函数就是干这个的按 4KB 边界把大块搬运拆成一串合法的突发序列。这个检查逻辑虽然啰嗦但能避免一大类诡异的地址错乱问题非常值得写。4. PS 侧软件配套地址规划与 Cache 一致性4.1 共享内存的地址怎么划PS 端软件跑在 DDR 里程序代码、堆栈、全局变量都在占空间。PL 通过 HP 口写 DDR 时如果地址和你程序的数据区重叠了那就是灾难性的——CPU 的变量被莫名覆盖程序跑飞还特别难查。所以第一步是划出一块谁都不碰的共享区域。我的做法是在链接脚本 lscript.ld 里专门留一段。假设程序本身不大我用 0x1000_0000 到 0x1FFF_FFFF 这一大段给 PL 做数据缓冲区程序和数据限制在低地址区。具体操作是在 lscript.ld 里加一个自定义段_shared_start 0x10000000; _shared_end 0x1FFFFFFF;然后在 C 代码里直接用这个绝对地址读写。进阶一点的做法是在链接脚本里定义一个 section 并分配固定地址再用__attribute__((section(.shared_mem)))把缓冲区放进去。好处是编译器知道这块内存的存在不会和别的变量冲突缺点是调试时得盯着 map 文件确认地址真的对上了。还有个更省心的选择用 OCM。Zynq 的 OCM 地址在 0xFFFF_0000默认就是 non-cacheable 的PS 和 PL 都能直接访问做小数据量的握手标志位、状态同步特别合适省掉了所有 Cache 维护的麻烦。但 OCM 只有 256KB大块数据还是得放 DDR。4.2 Cache 操作Flush 和 Invalidate 别搞反这块是 PS-PL 数据交互里最容易翻车的地方我单独拎出来讲。Cortex-A9 有 L1 和 L2 CacheCPU 读写内存时数据可能只在 Cache 里还没写回 DDR。这就导致两个方向的坑PS 写、PL 读CPU 把数据写进 buffer此时数据可能还躺在 Cache 里是脏的dirtyDDR 里还是旧值。PL 通过 HP 口直接读 DDR读到的是旧数据。解决办法是 PL 去读之前PS 先调Xil_DCacheFlushRange(addr, len)把 Cache 里的脏数据强制写回 DDR。PL 写、PS 读PL 把新数据写进了 DDR但 CPU 的 Cache 里可能还缓存着这块地址的旧副本。CPU 读的时候命中 Cache拿到的是旧数据。解决办法是 PS 读之前调Xil_DCacheInvalidateRange(addr, len)让这块地址的 Cache 行失效CPU 下次读会重新从 DDR 取。这两个函数用反了或者干脆忘了调症状都是数据对不上而且时好时坏特别迷惑人。我踩过的坑是单步调试时数据是对的一全速跑就错原因就是断点改变了 Cache 的时序。凡是 PS 和 PL 共享的内存区每次跨边界访问前都要老老实实做 Cache 维护。如果实在嫌麻烦可以在 MMU 配置里把这块共享内存设成 non-cacheableDevice 或 Strongly Ordered 属性一劳永逸代价是 CPU 访问这块内存会慢一些因为每次都真的去 DDR 读。数据量大、CPU 又不频繁访问的场景这个取舍很划算。4.3 中断还是轮询PL 和 PS 怎么知道对方干完活了两种方式。轮询PS 端死循环读某个标志位PL 写完了就把标志位翻转。实现最简单不需要配置中断适合调试阶段。坏处是浪费 CPU而且标志位本身也要考虑 Cache 和内存屏障问题。中断PL 通过 IRQ_F2P 拉高中断线PS 收到中断进服务程序处理。效率高但配置繁琐要在 PS 配置里使能 PL-PS 中断在 PL 里生成中断信号在 Vitis 里注册中断处理函数、使能 GIC。适合正式产品。我调试阶段一律先用轮询把数据通路跑通确认读写逻辑没问题再换成中断。别一上来就搞中断中断本身的 bug 和数据通路的 bug 混在一起排查会崩溃。顺带提一句流式数据的场景如果你的 PL 侧是视频或者传感器采样这种连续流通常先在 PL 里把 AXI-Stream 格式的数据转成 AXI-FULL 写进 DDR这就是常说的写帧缓存然后用中断通知 PS这一帧写好了PS 再去 DDR 取。这样 PL 和 PS 解耦各干各的效率最高。5. 实机联调从波形看到带宽5.1 先用 AXI Traffic Generator 探底自己写的 Master 第一次上板别急着接真实数据源。先用 Xilinx 提供的 AXI Traffic Generator IP 快速探一下通路是否打通。这个 IP 可以自动产生指定模式的 AXI 读写流量你只要配好地址范围、突发长度、读写比例它就能自己跑出满负荷的流量。具体配置在 Block Design 里加一个 AXI Traffic Generator把它设成 Master 模式接口连到 AXI Interconnect地址指向 DDR 段。配置成连续写模式数据模式选递增或者固定 pattern。生成比特流上板后用 Vitis 跑一个简单的程序往 DDR 某个地址先写入已知数据然后让 Traffic Generator 去读看读回来的数据对不对反过来让 Traffic Generator 写PS 去读验证。这一步能验证的东西很多HP 接口通不通、地址映射对不对、时钟有没有接好、互联配置有没有问题。如果 Traffic Generator 都跑不通那你自己的 Master 更别想跑通先解决底层通路。5.2 ILA 抓握手信号定位卡死自己写的 Master 上板后最常见的问题是状态机卡在某个状态动不了。这时候 ILA集成逻辑分析仪就是救命稻草。在 Vivado 里给 AXI 的握手信号加 ILA 核重点抓这几个AWVALID、AWREADY、WVALID、WREADY、BVALID、ARVALID、ARREADY、RVALID、RREADY再加上你自己的状态机状态寄存器。抓波形的判断逻辑很直接如果 AWVALID 拉高了但 AWREADY 一直低说明从机没准备好接收可能是地址不合法、接口没使能、或者时钟域不对。如果 AW 握手成功了但 W 一直不握手检查 WSTRB 和 WLAST 有没有正确设置。如果卡在等 BVALID说明写数据发出了但从机没回响应往往是地址越界或者触发了 SLVERR。我第一次调试时状态机卡在 S_W 死活不动抓波形发现 WREADY 一直是低折腾半天才想明白是 AWLEN 配错了——我写了 4d15 想传 16 拍但数据源只给了 8 拍就断了从机等不到 WLAST 就一直不拉 READY。这个坑很典型突发长度声明了多少拍就必须实实在在地送够多少拍数据配合正确的 WLAST。5.3 带宽实测数据与优化通路跑通之后就该关心性能了。先算理论值HP 接口 64 位宽ACLK 用 100MHz单口理论带宽是 64bit × 100MHz 6.4Gbps 800MB/s。如果 ACLK 提到 150MHz那就是 1.2GB/s。但注意PS DDR3 的物理总带宽是有限的32 位 533MHz DDR 大概 4.26GB/s四个 HP 口和其他主设备CPU、DMA都要分这块蛋糕。实测下来单 HP 口用 16 拍突发连续读写效率通常能到理论值的 60% 到 80%。影响因素排序大概是突发长度越长效率越高但受 16 拍限制、读写比例DDR 频繁读写切换有开销连续读或连续写效率更高、地址是否连续连续地址比跳跃地址效率高得多、以及 CPU 是否在抢总线。我做的优化有几个把连续搬运的突发尽量用满 16 拍读写分开做不要交替进行批量操作时让 PS 端 CPU 先歇着别在搬运时跑重活。调整之后同样的数据量搬运时间缩短了差不多三分之一。想要更高带宽就得上双 HP 口并行但前提是 DDR 总带宽还没被吃满不然加了也白加。6. 踩坑记录与问题速查表6.1 常见问题对照表下面这张表是我和身边朋友在 ZYNQ PS-PL DDR 交互里遇到过的典型问题按现象归类方便对号入座。现象可能原因排查方向PL 读写 DDR 返回 SLVERR地址越界、HP 接口未使能、时钟未连接核对 Address Editor 地址范围检查 PS 配置里 HP 是否勾选读回数据全是 0 或全 FF握手不完整、WSTRB 未设置、从机未响应ILA 抓握手信号确认每拍数据都被接收数据整体错位一两个字节地址未按数据宽度对齐、WSTRB 与数据不匹配检查 AWADDR 是否 8 字节对齐64 位传输PS 读到旧数据Cache 未 invalidate读前调 Xil_DCacheInvalidateRangePL 读到旧数据Cache 未 flushPL 读前 PS 调 Xil_DCacheFlushRange状态机卡死不动AWLEN 配错、WLAST 未在正确拍拉高核对突发拍数与 WLAST 时序移植到高频率后时序不收敛HP 时钟太快、组合路径过长降频验证加 AXI Register Slice程序莫名跑飞PL 写的 DDR 区域和程序区重叠严格划分共享内存地址别碰程序段仿真时 DDR 无响应缺少 DDR 模型或未接 AXI VIP仿真用 BRAM 替代或用 Micron DDR3 模型固化到 Flash 后跑不起来FSBL 加载地址或 DDR 初始化问题确认应用是否必须放 DDR必要时缩小放 OCM6.2 几条血泪心得先说仿真这件事。很多人问 Vivado 里怎么仿 DDR 交互其实完整仿真整条 DDR 通路很麻烦需要 Micron 的 DDR3 Verilog 模型速度慢、配置繁琐。我的建议是仿真阶段只验证你的 AXI Master 逻辑本身用一个简单的 AXI Slave 模型或者 BRAM 控制器替代 DDR把握手时序、突发拆分、状态跳转验证清楚就够了真正的 DDR 时序留到上板用 ILA 看。上板抓波形比跑仿真直观一百倍。再说固化的问题。有人问用 JTAG 固化 Flash 到底需不需要 DDR。答案是不强制Zynq 的启动流程是 BootROM 先跑加载 FSBLFSBL 负责初始化 DDR 等外设然后加载应用。如果你的应用不大FSBL 和应用都可以加载到 OCM 里跑完全不碰 DDR。但一旦应用超过 OCM 容量256KB就必须初始化并使用 DDR 来存放应用。所以要不要 DDR取决于你的应用体积不是固定的规矩。PCB 层面的东西也顺带提一句。板子出厂时 DDR 走线都做好了等长和阻抗控制这部分我们改不了也不用管。但如果你自己画板DDR 地址线和数据线的等长、分组、参考平面这些就绕不开了那又是另一门学问远超这篇的范围。最后是调试心态。PS-PL 交互出问题时现象往往很玄学——单步对、全速错或者跑一会儿才错。这种时候别急着怀疑玄学老老实实回到三件事上查Cache 有没有维护、地址有没有越界、握手有没有漏拍。我遇到过的九成问题最后都能归到这三类里。把 ILA 波形和 Cache 操作日志对着看问题基本无所遁形。我个人在实际项目里的体会是ZYNQ 的 PS-PL 数据交互看着复杂但骨架很清晰PL 写个规规矩矩的 AXI MasterPS 开好 HP 口和地址两边用共享内存加 Cache 维护对上眼剩下的就是带宽和时序的调优。真正花时间的从来不是写代码而是第一次把信号接对、把地址算对、把 Cache 想明白。第一次跑通之后后面加通道、提频率、改数据格式都是水到渠成。
返回列表