FPGA 进阶 13:DDR3/DDR4 MIG 使用——app_cmd 用户接口、地址映射与带宽效率
本文是
fpga/进阶层第 17 篇。片内 BRAM/URAM 只有几 MB,图像帧缓存、DMA 大缓冲、波形/数据记录都得靠外挂 DDR3/DDR4。但 DDR 的物理层(PHY)极其复杂——要做 ZQ 校准、读写训练、刷新、预充电、相位追踪,没人手写。Xilinx 的 MIG(Memory Interface Generator) 把这一切打包成 IP,只暴露一个简单的用户接口。这篇讲你真正要面对的那一侧:怎么生成 MIG、用户接口握手怎么用、AXI 怎么接、带宽为什么打不满、calibration 不过怎么查。
本文要回答的问题
- MIG 生成时要配哪些东西?管脚约束从哪来?
- app_cmd / app_addr / app_en / wdf / rd_data 这组握手怎么配合?
- 读数据为什么有延迟,怎么正确接收?
- AXI(DMA/PS)怎么连到 MIG/DDR?
- DDR 带宽为什么不是 100%,怎么提高有效吞吐?
- init_calib_complete 不拉高怎么排查?
一、MIG 在系统里的位置
DDR 颗粒/DIMM 的时序是纳秒级、要随温度电压动态延迟追踪的模拟活,FPGA 厂商用专用硬件块(I/O Bank 的 PHY、ISERDESE/ODELAY、校准逻辑)+ MIG IP 实现。对用户逻辑而言,MIG 提供两种前端:
| 前端 | 接口 | 适合 |
|---|---|---|
| Native(用户接口) | app_* 命令/数据握手 | 自定义 RTL 直接读写、学习原理 |
| AXI4 接口 | 标准 AXI4 Full slave | Zynq PS、AXI DMA 直接连,工程主流 |
大多数工程选 AXI 版 MIG:PS/DMA 经 SmartConnect 像访问内存一样读写 DDR,你完全不用碰 app_cmd。但理解 native 接口对调试(MIG example design、ILA 抓波形)很有帮助。
生成 MIG 时关键配置:器件型号与 DDR 型号(按板子,如 DDR3 1.5GB/16bit、DDR4 SODIMM)、数据位宽、时钟(sys_clk 输入频率、MIG 内部倍频)、是否使能 AXI 接口。管脚 XDC 由 MIG 根据你选的器件/板级自动产出一份 .xdc,必须加进工程——DDR 管脚在特定 byte group、含 VRP/VRN 校准脚,不能手改。
二、Native 用户接口握手
MIG 把复杂的 DRAM 操作抽象成「发一条命令 + 喂/收数据」。核心信号(ui_clk 域,MIG 输出的用户时钟):
| 信号 | 方向 | 含义 |
|---|---|---|
app_en / app_rdy | 用户→MIG / MIG→用户 | 命令有效 / MIG 可接收命令(同高=命令被接受) |
app_cmd | 用户→MIG | 3'b000=写、3'b001=读 |
app_addr | 用户→MIG | 命令地址(按地址槽、非字节地址,见 FAQ) |
app_wdf_wren / app_wdf_rdy | 用户→MIG / MIG→用户 | 写数据有效 / MIG 可收数据 |
app_wdf_data / app_wdf_end | 用户→MIG | 写数据 / 本拍是 burst 最后一拍 |
app_rd_data / app_rd_data_valid | MIG→用户 | 读数据 / 读数据有效 |
init_calib_complete | MIG→用户 | PHY 校准完成(不拉高一切免谈) |
ui_clk / ui_rst | MIG→用户 | 用户侧时钟/同步复位 |
写操作(稳妥写法:命令与数据同拍推):
// 写:app_rdy 与 app_wdf_rdy 都高时,同拍发命令+数据
wire cmd_fire = app_en && app_rdy;
wire data_fire = app_wdf_wren && app_wdf_rdy;
always @(posedge ui_clk) begin
if (ui_rst) begin
app_en <= 1'b0;
app_wdf_wren <= 1'b0;
end else begin
// 有待写数据且 MIG 两侧都能收 -> 同拍发命令和数据
if (wr_pending && app_rdy && app_wdf_rdy) begin
app_en <= 1'b1;
app_cmd <= 3'b000; // 写命令
app_addr <= wr_addr;
app_wdf_wren <= 1'b1;
app_wdf_data <= wr_data;
app_wdf_end <= 1'b1; // 满位宽,每拍即 burst 末尾
end else begin
app_en <= 1'b0;
app_wdf_wren <= 1'b0;
end
end
end读操作:先发读命令,数据几十拍后才随 app_rd_data_valid 返回——因为 DRAM 要先激活行(tRCD)、读、预充电,且 MIG 内部调度。必须用 valid 收数、不能假设命令后第几拍数据就到:
// 读命令
always @(posedge ui_clk) begin
if (rd_pending && app_rdy) begin
app_en <= 1'b1;
app_cmd <= 3'b001; // 读命令
app_addr <= rd_addr;
end else app_en <= 1'b0;
end
// 收数据:valid 拉高时才锁存,顺序对应发出去的读命令
always @(posedge ui_clk) begin
if (app_rd_data_valid) begin
rd_fifo_din <= app_rd_data; // 通常写进 FIFO 供下游取
rd_fifo_we <= 1'b1;
end else rd_fifo_we <= 1'b0;
end注意 app_en/app_rdy 是 valid/ready 关系:
app_en拉高后若app_rdy为低,命令没被接受,必须保持 app_cmd/app_addr 不变直到 app_rdy 高(和 AXI 一样的「valid 不撤」规则)。多笔读命令可以连续发(out-of-order 返回由 MIG 保证顺序),读数据按命令顺序回来。
三、AXI 版 MIG:工程主流接法
实际 Zynq/纯 FPGA 工程几乎都用 AXI 前端的 MIG,你完全不写 app 逻辑:
PS M_AXI_HP ─┐
AXI DMA ─┼─► SmartConnect ─► MIG(AXI slave) ─► DDR3/DDR4
自定义master┘- MIG 配成 AXI4 slave(数据位宽如 64/128 bit),挂到 SmartConnect;
- 在 Block Design 的 Address Editor 给 MIG 分配一段地址(如
0x1000_0000起、1GB); - PS/DMA 访问这段地址就是读写 DDR,burst、刷新、调度全由 MIG 处理;
- 跨时钟域(你的逻辑时钟 ≠ ui_clk)由 SmartConnect 内置异步 FIFO 解决,不用自己做 CDC。
四、带宽为什么打不满、怎么提
DDR 峰值带宽 = 数据位宽 × 数据速率 × 2(DDR 双沿),但有效带宽拿不到 100%,损耗来源:
| 损耗来源 | 说明 | 对策 |
|---|---|---|
| 刷新 refresh | 周期性必须刷新,期间不能访问 | 无法避免,占比几个百分点 |
| 行激活/预充电 | 换页要 tRCD(开行)+ tRP(关行),空泡 | 顺序大块访问,让一行内连续读 |
| 读写切换 | 读转写/写转读要切换总线方向,气泡 | 同区域尽量批量读或批量写 |
| bank/行冲突 | 随机跳址频繁开不同行 | 减少随机访问,数据布局连续化 |
| 命令不够密 | outstanding 不足,DRAM 延迟期间空等 | AXI 侧加大 outstanding(8~32) |
经验值:DMA 顺序搬大块数据可达峰值 70~90%;随机小访问可能只有 20~40%。所以图像帧缓存、流式缓冲这类顺序负载适合放 DDR;频繁随机查找的小表放 BRAM。
五、bring-up 顺序与 calibration 排查
MIG 上板推荐的验证顺序(Xilinx 也提供 MIG example design):
- 先用 MIG 自带 example design 上板:它产生连续读写并比较、拉 LED/状态。example 过了说明硬件/管脚/时钟没问题,问题在你的封装;过不了就是板子或约束问题。
- 确认
init_calib_complete拉高——这是 PHY 训练成功的唯一标志,不拉高后面都不工作。 - 自己逻辑接 native/AXI 后,先做固定地址写→读回比较,再做连续大块,再上 AXI DMA。
- ILA 挂 ui_clk 域抓
app_*,看命令是否被app_rdy接受、app_rd_data_valid是否回来。
calibration 不通过的排查清单(按概率排序):
- 管脚 XDC 不对:没用 MIG 生成的那份、或 DDR 管脚/byte group/VRP/VRN 和原理图不一致(最常见);
- 时钟不对:sys_clk/ref_clk 频率、差分电平、晶振和 MIG 配置不匹配;
- 供电/硬件:VDDQ/VTT/VREF 电源、走线、颗粒虚焊;
- 复位:复位极性/时钟没接对,MIG 一直处于复位。
一个判断小技巧:MIG 会把校准状态码输出到
dbg_*/状态端口(或 example design 的 LED),不同失败码对应读训练/写训练/VTC 等阶段,能快速缩小到是时钟还是管脚问题。
相关与延伸
前置/配套:BRAM/URAM 存储设计(片内存储,小容量用它)、AXI4 完整协议与互联(MIG 的 AXI 接口就是 Full slave,outstanding 提带宽见该篇)、CDC 亚稳态、FIFO 深度设计(读写侧缓冲)。 DDR 是 DMA 大缓冲和后面 PCIe/以太网数据通路的落点;DDR PHY 的训练/时序深水区、读写调序见专家层「DDR 接口深水区」规划篇。
一句话总结:DDR 的 PHY/刷新/训练全由 MIG IP 承担,你只用它的 native app_* 握手(cmd+addr+wdf 写、rd_data_valid 收读)或更主流的 AXI4 接口(PS/DMA 经 SmartConnect 直接访问);带宽靠连续大 burst + 足够 outstanding 才能到峰值七八成;上板先过 init_calib_complete、先用 example design 区分硬件问题还是逻辑问题。