FPGA 系统实战 · 03 运行第一笔 DDR 往返与软件校验
从 XSA 建立 standalone 应用,先接收后发送,加入超时、状态与实际长度检查。
先修知识完成 FPGA AXI4-Stream / 异步 FIFO 实战;会读 AXI 时序、C 指针和链接 map。
适用环境ZedBoard Zynq-7020 / PS DDR;Vivado、Vitis 2024.1 参考;本地 Python 3 + cc + Icarus Verilog。
验证状态13 项本地用例通过:C 主机后端与教学 FIFO / RTL 组合;Vivado、Vitis BSP、厂商 IP、DDR 和板上 ILA 尚未实测。
用同一份应用驱动两种后端
dma_app.c 包含真正要学习的缓冲区准备、所有权转移、命令顺序和结果校验。定义 HOST_TEST 时选择行为模型;目标板构建不定义该宏,使用 XAxiDma 驱动和 XTime 定时器。主机编译成功不等于 Vitis BSP 编译成功;目标环境仍须核对 xparameters.h 的实例宏、standalone 域以及 PS UART 输出配置。
建立平台并验证内存放置
从匹配的 system.xsa 创建 ps7_cortexa9_0 standalone 平台和 Empty Application。把 dma_app.c/platform_hw.h 加入应用,不复制主机后端作为硬件驱动。链接脚本必须将数据数组放进 PS DDR,检查 map 而不只查看源文件中的 static 关键字;每块65536字节且64字节对齐,栈不能与其重叠。
| 项目 | 设置 / 预期 / 处理 |
|---|---|
| 新式 SDT BSP | XAxiDma_LookupConfig(XPAR_XAXIDMA_0_BASEADDR) |
| 旧式 BSP | XAxiDma_LookupConfig(XPAR_AXIDMA_0_DEVICE_ID) |
| 地址 | 打印 tx/rx,与 link map 和 HP0 DDR 可达范围对照 |
| 目标日志 | HARDWARE 开头;12条 PASS;suite=12 transfers |
| 实际长度 | 完成后读取 S2MM_LENGTH;必须等于请求长度 |
完整应用;需同时使用下载包中的 platform_hw.h 或 platform_host.h。
/* Original teaching application; identity-mapped standalone Cortex-A9 only.
* HOST_TEST executes this exact orchestration with a cache/DDR behavioral model. */
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define CAP 65536U
static uint8_t tx[CAP] __attribute__((aligned(64)));
static uint8_t rx[CAP] __attribute__((aligned(64)));
#ifdef HOST_TEST
#include "platform_host.h"
#else
#include "platform_hw.h"
#endif
static int transfer(unsigned n,unsigned seed) {
for(unsigned i=0;i<n;i++)tx[i]=(uint8_t)(i*17+seed);
memset(rx,0xcc,CAP);
uint64_t begin=now();
clean(tx,CAP); clean(rx,CAP); barrier();
uint64_t start=now();
if(submit_rx(rx,n)||submit_tx(tx,n))return fail("SUBMIT");
while(busy()){
if(errors())return fail("DMA_ERROR");
if(now()-start>ticks_per_second())return fail("DMA_TIMEOUT");
}
if(errors())return fail("DMA_ERROR");
uint64_t done=now();
barrier(); invalidate(rx,CAP); barrier();
uint64_t acquired=now();
if(actual()!=n)return fail("LENGTH_MISMATCH");
for(unsigned i=0;i<n;i++)if(rx[i]!=(uint8_t)(tx[i]^0xa5)){
printf("mismatch offset=%u expected=%02x actual=%02x\n",i,(unsigned)(tx[i]^0xa5),(unsigned)rx[i]);
return fail("DATA_MISMATCH");
}
printf("PASS bytes=%u actual=%u dma_ticks=%llu ownership_ticks=%llu timer_hz=%llu\n",
n,actual(),(unsigned long long)(done-start),(unsigned long long)(acquired-begin),
(unsigned long long)ticks_per_second());
return 0;
}
int main(void){
if(setup())return 1;
const unsigned sizes[]={64,4094,4096,65536};
for(unsigned s=0;s<4;s++)for(unsigned rep=0;rep<3;rep++)
if(transfer(sizes[s],3+rep))return 1;
puts("PASS suite=12 transfers; bytes checked; one TLAST per transfer");return 0;
}
上板执行的四个检查点
板卡上电并下载 PL 位流后,DDR 仍需要 PS 初始化。使用匹配 XSA 的 Vitis 调试启动配置执行 ps7_init/ps7_post_config,或使用同一硬件导出的 FSBL。不要拿旧工程的初始化脚本配新地址图。软件先提交 RX,再提交 TX,每轮等待两通道结束,同时检查错误状态;超时后保留现场并停止,不能继续比较旧缓冲区。
- 构建 platform/application
预期与判定:BSP 编译通过,map 中 TX/RX 位于 DDR。
- 下载位流、关联 LTX 并初始化 PS
预期与判定:UART 正常;DDR 可访问;ILA 时钟运行。
- 下载 ELF 并 Run
预期与判定:看到 HARDWARE 和12次 PASS,无 LENGTH_MISMATCH。
- 保存日志和构建摘要
预期与判定:包含 bit/xsa/elf 版本、地址、实际字节数和 timer_hz。
调用关系示意;请运行上方完整应用,不要省略其错误处理。
/* 目标驱动调用:先 RX,后 TX */
XAxiDma_SimpleTransfer(&dma, (UINTPTR)rx, n, XAXIDMA_DEVICE_TO_DMA);
XAxiDma_SimpleTransfer(&dma, (UINTPTR)tx, n, XAXIDMA_DMA_TO_DEVICE);
/* 每次调用必须检查返回值;完整封装会检查错误、超时及实际长度。 */典型故障:应用永远等待接收
如果发送已结束而接收迟迟不结束,先保存两通道 DMASR、S2MM_LENGTH 和最后一次握手。缺失 TLAST 是一种原因,接收未启动、流式复位未释放或源被反压也可能表现为超时。程序用真实定时器限制等待约一秒;发生异常后复位 DMA 并停止,本轮数据不得作为有效输出。
完成检查
- 已记录本章配置、版本及预期结果。
- 能区分本地模型验证与实际板上证据。
- 能复现典型故障并说明修复后的复测标准。
延伸阅读与来源
本文为黄河电子原创教学说明。产品命令与支持范围请以实际安装版本的官方文档为准。
- AMD PG021 · AXI DMA 7.1
Direct Register Mode、S2MM_LENGTH、DMA 状态位及 DRE。
- AMD embeddedsw 2024.1 · Simple Poll Example
对照先接收后发送、cache maintenance 与驱动 API;本站示例为原创实现。
- AMD PG085 · AXI4-Stream Infrastructure
AXI4-Stream Data FIFO、独立时钟与复位配置。
- AMD UG908 · Programming and Debugging 2024.1
Hardware Manager、ILA 触发、探针文件与位流匹配。
继续阅读
FPGA从 RTL 到板上运行的完整流程
建立约束、实现、下载与观测的最小工程闭环。
第一个工程的引脚与时钟约束
区分电气连接与时间要求,避免“能生成位流就算正确”。
如何读懂时序报告
从起点、终点、时钟和裕量找出失败路径的原因。
黄河电子