简介:面向使用Vivado 2020.2进行开发的FPGA工程师,提供一套基于Kintex-7系列器件的软核处理器与DDR3存储控制器读写验证工程,同时集成串口调试链路。工程在Vitis开发环境中已预置内存读写测试功能,可对DDR3进行读写校验;DDR3型号与引脚独立配置,换用其他板卡时仅需修改对应参数即可复用。压缩包内共1364个文件,整体大小约2.67MB,主要文件包括C语言源码、头文件、Verilog与VHDL硬件描述、IP核配置、Tcl脚本、引脚约束以及编译构建脚本,此外还附带块设计文件、硬件导出文件和可执行程序,工程结构清晰。目前已有781人次学习并下载,适合需要快速搭建MicroBlaze访问DDR3验证平台、开展存储器测试或研究相关IP集成流程的工程师与学习者。通过该工程可掌握软核系统构建、DDR3控制器参数配置、串口打印回读等关键环节,有助于直接复用并减少初始调试时间。
1. Vivado 2020.2 MicroBlaze + MIG + UART Lite:DDR3 测试工程解决的不只是读写问题
拿到一块带 DDR3 的新板子,第一件事永远是确认内存颗粒和 PCB 布线没有物理缺陷。很多人直接拉一个 MIG IP,写个状态机填 pattern,跑完看结果,出错时定位却很痛苦:你不知道是控制器配置错了、引脚约束错了,还是 PCB 上 DQ/DQS 走线有问题。
在 Vivado 2020.2 里把 MicroBlaze、MIG、UART Lite 组合成一个 DDR3 测试工程,是更快的路径。软核负责调度测试算法,MIG 负责和 DDR3 颗粒打交道,UART Lite 把每一条失败信息送回串口助手。你甚至不用接 JTAG 看信号,就能判断内存训练是否完成、读写是否稳定。
这个方案适合做板级验证的工程师,也适合给后续驱动开发打底子。只要跟着配一遍 IP,几段 C 代码就能把 DDR3 从“黑匣子”变成可对话的外设。
2. 选型和连接关系:MIG、MicroBlaze、UART Lite 三者的分工与边界
在这个组合里,MicroBlaze 是大脑,MIG 是翻译,UART Lite 是嘴。DDR3 颗粒本身只是存储阵列,MIG 负责初始化、校准、刷新和读写时序;MicroBlaze 不直接碰颗粒,它只知道 AXI 地址空间里有一块可以读写的区域;UART Lite 则把测试结果传到电脑上。搞清楚这条链路上的每一层职责,后面出问题才知道去查谁。
2.1 为什么用软核而不是状态机:测试算法需要灵活度
早期 DDR3 验证常用状态机,写入几个固定 pattern 再读出来,好处是 LUT 占用少、没有软件初始化成本。但一旦出现偶发错误,状态机很难帮你区分地址线错误、数据线错误还是控制时序错误。换一种 pattern、加一段回读、调整访问边界,都要改 RTL 再重新综合,一趟就是三四十分钟。
换成 MicroBlaze 之后,测试逻辑变成 C 代码,改参数只改变量、重编译几秒钟。而且要验证 DDR3 作为系统内存的可靠性,最终逃不开跑一段代码、开中断、查看链接脚本这类实际使用场景,这些是状态机给不了你的。代价则是软核占用几千个 LUT 和 BRAM,对一个测试工程来说完全可接受。
一个关键决定在 MicroBlaze 配置界面就要做:测试 DDR3 时建议把 Data Cache 关掉。DDR3 是给 MicroBlaze 当主存用的,如果开了 Cache,读操作会被缓存命中遮盖,写操作也可能留在缓存里没真正到颗粒,测试结果就成了缓存测试而不是 DDR3 测试。加上 volatile 只能阻止编译器优化,不能阻止缓存。我一般直接把 MicroBlaze 的 Data Cache 关掉,或者用不缓存的外设地址区域访问 DDR3,省掉一致性问题的排查。第一次做这个方案的人,十有八九会在 cache 上踩一次坑。
2.2 MIG 配置的硬参数:从颗粒手册抄数字
MIG 是整个工程里最不能偷懒的部分。配置界面上几十个参数,绝大多数不是随便填的,要对着板上的 DDR3 颗粒手册和原理图一项一项核对。颗粒型号错了,控制器用错误的时序发命令,校准大概率失败;数据位宽错了,地址空间和 DQ 映射全部错位;时钟频率错了,眼图余量不足,读写结果时好时坏。
以下参数是必核项:
| 参数 | 来源 | 测试工程建议 |
|---|---|---|
| Memory Part | DDR3 颗粒丝印和数据手册 | 必须与板上型号完全一致 |
| Memory Type | 颗粒类型 | 选择 DDR3,不要选 DDR2/DDR3L 混用 |
| Data Width | PCB 原理图 IP 核连接 | 按实际颗粒位宽和片选数量填 16/32/64 |
| Burst Length | 颗粒手册 | 常用 8,不要随便改 |
| CK Clock Frequency | 用户时钟和速率等级 | 例如 400 MHz(等效 800 Mbps),参考速度等级 |
| Input Clock Period | 板级晶振/MIG 参考时钟 | 常见 3000 ps、3333 ps、4000 ps,由硬件决定 |
| AXI Interface | 与 MicroBlaze 连接 | 选 AXI4,便于直接挂到系统总线上 |
| Pin Bank / Pin Assignment | 原理图 FPGA 管脚 | 必须用 MIG 的 "Read XDC/XML" 导入板级约束 |
MIG 配置完成后要保留生成的 .prj 文件。这个文件记录了所有颗粒参数和引脚映射,换人换机器重新建工程时可以直接复用。没有它,你就要重新面对几十个下拉框,而且任何一个参数填错都可能让 DDR3 测试工程变成一次纯玄学调试。
2.3 UART Lite:轮询比中断更适合测试
AXI UART Lite 是 Xilinx 官方 IP 里的轻量级串口,寄存器数量少,驱动函数简单。波特率常见 115200,测试阶段只需要输出文本,速率足够。它的发送和接收 FIFO 分别只有 16 字节,不适合大吞吐量回传,但 DDR3 测试结果本来就只有几十条日志,完全匹配。
测试工程里我一般用轮询方式初始化 UART Lite,不使用中断。原因很简单:DDR3 测试阶段不想让串口中断打断时序循环,尤其在做压力测试时,中断回调里如果碰了 DDR3 变量,容易让错误现象变得不可复现。轮询方式每次发送一个字节,慢一点,但是行为可以预测。
使用 UART Lite 还有一个小前提:它的 IP 配置里默认不启用校验、没有流控引脚。接线就三根:TX、RX、地。如果你在原理图里发现串口芯片还有 RTS/CTS,而这边 IP 又没有流控,那软件上就把流控完全忽略,否则回显消息会丢字。
2.4 顶层连接与地址映射:先画清楚再动手
在 Block Design 里,MicroBlaze 通过 M_AXI_DP 主接口连到一个 AXI Interconnect,再从 Interconnect 分出三路:一路连 MIG 的 S_AXI,一路连 AXI UART Lite,一路连 AXI GPIO。MIG 的 ui_clk 作为系统时钟源,经过频率设置后同时驱动 MicroBlaze、Interconnect 和外设时钟;MIG 的 init_calib_complete 信号接到 AXI GPIO 输入通道,供软件读取校准状态。
时钟和复位也有讲究。MIG 的 ui_clk 和 ui_clk_sync_rst 是控制器内部逻辑的时基,MicroBlaze 和 AXI Interconnect 必须在同一时钟域里,否则跨时钟域读写会偶发丢数据。复位信号建议用板级复位,经过几个周期的同步之后接到 MIG 和 MicroBlaze 的 reset 端口,不要做太复杂的复位树。
地址映射用一个典型例子说明:
| 外设 | 起始地址 | 大小 |
|---|---|---|
| DDR3 | 0x80000000 | 按实际容量 |
| AXI UART Lite | 0x40600000 | 16 KB |
| AXI GPIO | 0x40000000 | 4 KB |
这只是常见分配结果,具体以 Address Editor 自动分配为准。关键是 DDR3 的区域要足够大,不能和 UART、GPIO 重叠。地址重叠的表现在软件里很隐蔽:写 DDR3 时感觉正常,读回来某些地址里出现的是串口寄存器内容,这类问题不画地址表很难看出。
3. Vivado 2020.2 建工程流程:从空工程到第一个串口输出
这一章把整个搭建流程按步骤拆开。我用的是 Vivado 2020.2 自带 Block Design 流程,先建 IP 子系统,再导出硬件给 Vitis 写 C 代码。2020.2 之后的版本界面有变化,但核心逻辑一致。
3.1 用 TCL 脚本创建工程和 IP 骨架
通过 GUI 点一遍也能完成,但用 TCL 脚本的好处是可复现:参数写在文本里,换一张板卡只需改 part 和 MIG 配置,不用重新记忆操作路径。下面是最小骨架:
# 创建工程,part 按实际板卡型号修改 create_project ddr3_test ./ddr3_test -part xc7a35tcsg324-1 # 创建块设计 create_bd_design system # 依次添加 MicroBlaze、MIG 7 Series、UART Lite、GPIO create_ip -name microblaze -vendor xilinx.com -library ip \ -module_name microblaze_0 create_ip -name mig_7series -vendor xilinx.com -library ip \ -module_name mig_7series_0 create_ip -name axi_uartlite -vendor xilinx.com -library ip \ -module_name axi_uartlite_0 create_ip -name axi_gpio -vendor xilinx.com -library ip \ -module_name axi_gpio_0这里 create_project 的 part 参数是硬约束,必须换成实际 FPGA 型号。create_ip 只需要写 IP 名称和模块名,参数赋值放在之后的配置步骤里。MIG 这个 IP 比较特殊,单纯靠 create_ip 拿到的是一个未配置的空壳,它的参数适合通过 MIG 自带的 .prj 文件批量导入,而不是一条条 set_property。
如果你手里已经有上一版工程保存的 mig.prj,可以在 Vivado 里右键 MIG IP,选择配置,在向导里加载 .prj 文件。没有的话,就在 GUI 里对照原理图把参数填完,最终保存出新的 .prj。这一步不要跳,后面所有 DDR3 时序行为都由它决定。
3.2 配置 MIG 与 Block Automation 连线
创建 IP 后,先在 Block Design 里添加 MicroBlaze,然后运行 Block Automation。这个工具会自动生成 MicroBlaze 需要的 LMB、中断控制器和外设互联,省去手工连接一堆管理接口的时间。
MIG 不会自动接入自动化流程,需要手动连。我常用的连接动作如下:
# 把 MIG 的 AXI 从端口接到 Interconnect 的 S00_AXI connect_bd_intf_net [get_bd_intf_pins axi_interconnect_0/S00_AXI] \ [get_bd_intf_pins mig_7series_0/S_AXI] # 把 MIG 校准完成信号送到 AXI GPIO 输入 connect_bd_net [get_bd_pins mig_7series_0/init_calib_complete] \ [get_bd_pins axi_gpio_0/gpio_io_i]connect_bd_intf_net 连接的是 AXI 接口整体,包括地址、数据、握手的全部信号;connect_bd_net 连接的则是单比特信号。校准完成信号如果直接留在悬空状态,软件就不知道 DDR3 到底有没有准备好,所以必须接到一个可读位置。除了校准信号,MIG 的 sys_clk_i 要连到板级时钟源,reset 接到系统复位,ui_clk 要作为 MicroBlaze 外部时钟输入。
手动连接时最常见的错误是 MIG 的 AXI 接口没有接上,导致之后编译时综合报一堆 unresolvable pin。连接完成后,在 Diagram 页面查看是否还有警告小图标,逐个连通再进下一步。
3.3 Address Editor 分配地址与导出硬件
Block Design 里外设很多时,地址靠 Address Editor 统一分配。打开地址编辑器,可以看到 MicroBlaze M_AXI_DP 下挂的各个外设段。选中资源,点击“自动分配地址”,Vivado 会给每个从接口分配一段空间。
分配完地址后做设计校验,然后生成比特流。流程是:
# Vivado TCL Console 内依次执行 validate_bd_design generate_target all [get_bd_designs system] launch_runs impl_1 -to_step write_bitstream -jobs 4generate_target all 生成各 IP 的仿真和行为模型,launch_runs 跑综合、实现并生成比特流。这一步耗时取决于工程规模,MicroBlaze 加 MIG 的组合通常在十分钟到半小时之间。
导出硬件时要特别注意勾选 Include bitstream。Vitis 里如果只拿到 .xsa 而没有比特流,下载 Program FPGA 时就会报找不到 bitstream。历史经验是,很多人换了 Vitis 版本后遇到这类报错,第一反应是重新装软件,其实只是导出时少勾了一个选项。
3.4 最小 C 工程框架:先打印再测内存
导出 .xsa 后,打开 Vitis,创建 platform 工程,再创建一个 application project。编程语言选 C,模板可以选空模板,不需要复杂例程。第一个程序只做一件事:初始化串口,打印软核看到的 DDR3 地址范围。
#include "xparameters.h" #include "xil_printf.h" #include "xuartlite.h" XUartLite uart; int main(void) { XUartLite_Initialize(&uart, XPAR_AXI_UARTLITE_0_DEVICE_ID); XUartLite_ResetFifos(&uart); xil_printf("DDR3 test start\r\n"); xil_printf("DDR base: 0x%08X\r\n", (unsigned int) XPAR_MIG_7SERIES_0_BASEADDR); xil_printf("DDR high: 0x%08X\r\n", (unsigned int) XPAR_MIG_7SERIES_0_HIGHADDR); return 0; }这里 XPAR_MIG_7SERIES_0_BASEADDR 和 HIGHADDR 是根据 Block Design 里 MIG 地址段自动生成的宏,名字可能因为 IP 实例名变化略有差异。编译通过后,连接 USB 串口线,打开串口助手设置 115200、8、N、1,下载程序。如果打印出来的地址范围和 Address Editor 一致,说明 MicroBlaze 到 MIG 的 AXI 通路已经通了,可以进入真正的 DDR3 测试。
4. DDR3 读写测试的 C 代码:从 pattern 到地址解码错误定位
串口通路建立后,核心工作就是写测试代码。DDR3 测试不是简单写一遍读一遍,而是在不同层次暴露不同问题。先做数据线检查,再做地址线检查,最后做大范围压力遍历。
4.1 最小读写模型:全 0、全 1、AA55 与反地址
第一个测试是数据完整性测试。向一段连续地址写入固定 pattern,再读回比较。常用 pattern 包括 0x00000000、0xFFFFFFFF、0xAA55AA55、0x55AA55AA。全 0 和全 1 能快速暴露 DQ 被短路或上拉的物理问题,AA55 系列能检查相邻位之间的短路。
#include "xil_printf.h" #include "xuartlite.h" volatile uint32_t *ddr_base = (volatile uint32_t *) 0x80000000; #define TEST_WORDS 1024 int pattern_test(uint32_t pattern) { uint32_t i; /* 写阶段:连续写入 pattern */ for (i = 0; i < TEST_WORDS; i++) { ddr_base[i] = pattern; } /* 读阶段:逐个回读比较 */ for (i = 0; i < TEST_WORDS; i++) { if (ddr_base[i] != pattern) { xil_printf("PATTERN FAIL: addr=0x%08X exp=0x%08X got=0x%08X\r\n", (unsigned int)(uintptr_t)&ddr_base[i], pattern, ddr_base[i]); return -1; } } xil_printf("PATTERN PASS: 0x%08X\r\n", pattern); return 0; }代码里有三个细节需要注意。ddr_base 声明成 volatile,告诉编译器每次访问都必须真实读写内存,否则循环里连续读写会被寄存器优化掉。指针类型是 uint32_t,也就是 32 位访问粒度,如果 DDR3 数据位宽是 16 位,MIG 会帮你拼装,但软件上按 32 位读写最方便。TEST_WORDS 初始值不要太大,1024 个字节能快速确认通路,压力测试单独做。
出错信息里打印实际地址和期望值、实际值。这样一旦失败,可以根据错误 pattern 反推是哪一位 DQ 出问题。比如期望 0xAA55AA55,读到 0xAA55AA57,最低两位变化,先查 DQM、DQS 和对应 byte lane 的走线。
4.2 地址模式:把地址线错误从数据线错误里分出来
数据 pattern 测试通过后,还要测地址线。方法很直白:往每个地址写入它自身的地址值,再读回比较。如果某条地址线断开、短路,CPU 发出的地址会被硬件解码到另一个物理位置,读回的值会变成另一个地址的数据。
int address_test(volatile uint32_t *base, uint32_t words) { uint32_t i; /* 写阶段:每个地址写入自身偏移 */ for (i = 0; i < words; i++) { base[i] = i; } /* 读阶段:核对每个地址的值 */ for (i = 0; i < words; i++) { if (base[i] != i) { xil_printf("ADDR FAIL: base+0x%08X exp=0x%08X got=0x%08X\r\n", (unsigned int)(uintptr_t)&base[i], i, base[i]); return -1; } } xil_printf("ADDRESS TEST PASS\r\n"); return 0; }地址测试的失败现象很有特征。如果地址高位线 A24 短路接地,那么寻址空间前半段会被折叠,写入后半段的数据会覆盖前半段,读回时出现整块重复。如果数据线只有一条松动,则失败地址往往是规律的二进制错位。靠这个测试能把地址线问题定位到具体是哪一根,比反复换 pattern 高效得多。
4.3 校准完成信号与串口输出
DDR3 上电后必须经过 MIG 内部一段训练流程,包括 ZQ 校准、读写延迟校准,完成后 init_calib_complete 信号才会拉高。软件在 DDR3 未完成训练时就访问内存,轻则读到垃圾,重则 AXI 总线死锁。所以测试程序第一步应该先读这个信号。
接着硬件连接里把 init_calib_complete 接到了 AXI GPIO,软件读 GPIO 通道即可:
#include "xgpio.h" #define CALIB_OK 0x01 XGpio gpio; void wait_mig_calibrated(void) { XGpio_Initialize(&gpio, XPAR_AXI_GPIO_0_DEVICE_ID); /* 通道 1 全部设为输入 */ XGpio_SetDataDirection(&gpio, 1, 0xFFFFFFFF); while ((XGpio_DiscreteRead(&gpio, 1) & CALIB_OK) == 0) { /* 轮询等待,不中断 */ } xil_printf("MIG calibration done\r\n"); }XGpio_SetDataDirection 参数里的 1 表示通道编号,0xFFFFFFFF 表示所有位都是输入。DiscreteRead 读到的 bit0 就是 init_calib_complete。这里用轮询,不在中断里等,因为等待时间取决于颗粒和配置,可能几十毫秒也可能几百毫秒,轮询逻辑最可控。
有一点要注意:有些工程师图省事,把 init_calib_complete 直接接到 MicroBlaze 的复位释放逻辑,试图让软核在校准结束后再启动。这个想法没问题,但调试时一旦校准失败,你连一个打印都看不到,完全不知道卡在哪一步。我习惯把校准信号读出来,先打印再测试,至少失败时能确认是校准没过还是测试没过。
4.4 遍历 DDR3 全空间的循环策略
单块区域测试通过后,要把测试范围扩大。DDR3 容量可能从 128 MB 到 4 GB,32 位指针访问需要遍历全部地址,但一次性跑完所有地址可能耗时太久,尤其是串口回传错误信息时,大量打印会拖慢系统。
常见做法是分块循环,每块测试固定大小,记录失败块号,跳过失败块继续跑完。块大小根据 DDR3 容量选择,我常用 64 MB 一块。这样既能覆盖整个物理空间,又不会让单次测试时间不可控。
#define BLOCK_WORDS (64 * 1024 * 1024 / 4) int full_space_test(volatile uint32_t *base, uint32_t total_words) { uint32_t offset; int ret = 0; for (offset = 0; offset < total_words; offset += BLOCK_WORDS) { xil_printf("Testing block: 0x%08X\r\n", (unsigned int)(uintptr_t)(base + offset)); ret |= pattern_test(0xAA55AA55); ret |= address_test(base + offset, BLOCK_WORDS); if (ret != 0) { /* 打印失败块,但不要 break,继续测其他块 */ xil_printf("Block FAIL at offset 0x%08X\r\n", offset); } } return ret; }循环策略的关键是失败后不立即停止。DDR3 偶发错误往往只出现在某几个行地址或 bank 区域,提前中断会漏掉其他区域的问题。把错误汇聚后看分布规律:错误集中在固定偏移,多数是地址线或 bank 映射问题;错误随机分布,则优先怀疑电源纹波、时序余量或温度。
5. 避坑指南:DDR3 测试工程里最容易翻车的 5 个现场
这一章写调试中真实的踩坑记录。每条都是我先看到现象、再剖析原因、最后给出解决办法的套路,希望能帮你少走弯路。
5.1 串口一直没有任何打印
现象:程序下载后,串口助手没有任何输出,Vivado 里显示写入成功,Vitis 调试器也能连接。
原因:最常见是 MicroBlaze 时钟没有起来,或者 UART Lite 的引脚没有约束到实际串口芯片。MIG 配置完成后,ui_clk 如果没接到 MicroBlaze,整个块设计里软核根本没有时钟,程序自然一条指令都执行不了。另一种常见原因是 UART Lite 波特率虽然配了 115200,但 FPGA 引脚在 XDC 里没有被声明,工具按默认引脚布线,串口芯片收不到数据。
解决:先检查 Block Design 里 MicroBlaze 的外部时钟引脚是否有效连接。然后在综合后的 XDC 里确认 uart_tx 和 uart_rx 引脚是否与原理图一致。最后用串口助手的十六进制模式看有没有裸数据,排除终端软件的编码干扰。只要按照“时钟、约束、物理连接”三层排查,这个坑通常半小时内能定位。
5.2 MIG 校准一直不结束
现象:程序卡在 wait_mig_calibrated 的循环里,打印没有出现 “MIG calibration done”。或者 Vitis 启动后直接访问 DDR3,程序跑飞。
原因:校准不结束主要是 MIG 配置和硬件不匹配。常见错法有三种:颗粒型号选错;DDR3 时钟频率超出速率等级;引脚约束里 DQS 和 CLK 极性写反。另一个容易被忽略的原因是 MIG 参考时钟太小,比如板上只有 100 MHz,而 MIG 需要匹配的输入时钟周期与内部 PLL 参数不一致。
解决:回到 MIG 配置界面重新核对 .prj 中的每个参数,重点看频率、颗粒型号、数据位宽。然后用 Vivado 的 Hardware Manager 连接 JTAG,观察 MIG 内部校准状态寄存器,它会告诉你停在哪个阶段,是 ZQ 校准还是读写训练。这个阶段不能靠猜,寄存器是唯一可靠的信息源。
5.3 读写偶发错误,且集中在某个区域
现象:pattern 测试大部分时间通过,偶尔失败;扩大测试范围后,失败地址集中在某几个连续区域,错误值通常只有某一位或两位变化。
原因:这类问题往往不是逻辑错误,而是信号完整性问题。DDR3 高频率下,数据信号在特定 bank 或行地址切换时产生串扰,或 DQS 与 DQ 的走线长度差在某个 pattern 下被放大。如果错误区域固定,还有可能是 PCB 上某个去耦电容失效或电源层分割。
解决:先用地址模式测试确认是不是地址线问题,再用 ILA 抓 MIG 的读数据和对应的 data_valid,把失败时刻的读写命令、数据、字节使能一起抓到,可以看清错误发生在写命令阶段还是读命令阶段。若怀疑信号完整性,降低 MIG 频率跑同一条测试,错误消失说明是时序余量不足,需要检查 PCB 走线或调整读写延迟参数。
5.4 Vitis 编译不了、找不到驱动 API
现象:导入 .xsa 后创建 application,编译报 undefined reference to XUartLite_Initialize,或者 xparameters.h 里找不到 XPAR_AXI_UARTLITE_0_BASEADDR。
原因:大概率是导出 .xsa 时没有包含 bitstream,导致 platform 没有生成完整的 BSP。另一个原因是 Vitis 里选择的 platform 不是从当前 .xsa 创建的,而是沿用旧工程缓存,BSP 没有刷新。还有可能是手动把 .h 文件拷贝进工程,路径混乱,编译器引用了旧头文件。
解决:回到 Vivado,重新执行 Export Hardware,记得勾选 Include bitstream,然后重新生成 .xsa。在 Vitis 里删掉旧 platform 和 application,重新创建,不要复用工作空间。检查 BSP 里的 xparameters.h 是否包含 uartlite 地址宏,如果确实没有,说明 Block Design 里 UART Lite 没连接成功或地址段没有分配。
5.5 地址空间配置错误导致 AXI 总线死锁
现象:程序运行到访问 DDR3 的循环时,软件卡死,Debugger 暂停地址在某个 AXI 访问相关的位置。重启后现象可复现或不完全复现。
原因:MicroBlaze 访问 DDR3 地址落在了 MIG 地址段之外,AXI Interconnect 发现地址无法路由,总线事务挂起没有 slave 响应。还有一种是 DDR3 地址段与 LMX 或 LMB 地址重叠,导致同一地址映射到两个从设备。
解决:打开 Address Editor,把 DDR3 区域记录下来,再打开 Vitis 的 lscript.ld,确认系统内存的地址范围一致。测试程序里的 DDR_BASE 不要用字面量硬编码,尽量通过 xparameters.h 的宏获取,避免人为输入错误。如果 MicroBlaze 开了 Cache,还要确认 DDR3 所在的地址区域是否配置为 non-cacheable,否则死锁可能从地址段问题变成缓存一致性问题。
6. 把测试工程变成验证工具:加性能测量与自动判定
DDR3 读写测试通过后,工程不应该只停留在“能跑通”。把它升级成一个可量化、可对比的验证工具,后续调试才有数据支撑。最常见的升级是测量 DDR3 读写带宽,判断当前配置下是否跑到了理论速率附近。
MicroBlaze 内部有全局定时器,Vitis BSP 里把定时器驱动加入后,可以用 XTime 测量一次大块写的周期数。先取开始计数,执行写入,再取结束计数,两者差值换算成时间,就能估算出软核视角的写带宽。注意耗时和 DDR3 刷新也有关系,测试时保持重复循环取平均值。
#include "xtime_l.h" void perf_write_test(void) { XTime start, stop; u32 i; u32 words = 8 * 1024 * 1024 / 4; XTime_GetTime(&start); for (i = 0; i < words; i++) { ddr_base[i] = 0x12345678; } XTime_GetTime(&stop); xil_printf("write ticks: %llu\r\n", (unsigned long long)(stop - start)); }XTime_GetTime 读取的是 MicroBlaze 内部定时器计数,计数器频率由 BSP 配置决定,一般等于 CPU 频率。如果你看到带宽明显低于预期,先别急着怀疑颗粒,把 Cache 关闭状态下再测一次。若关闭 Cache 后带宽反而比开着还高,说明 Cache 配置或内存属性有问题,数据根本没走到 DDR3 颗粒上。这一步能帮你把“测试工具”和“性能验证工具”之间的差距补上。
第二个值得养成的习惯是给测试安排自动判定流程。写一个总控函数,依次跑数据 pattern、地址模式、指定次数压力测试,把失败码通过 UART 返回。失败码由 8 位组合:bit0 表示数据线测试失败,bit1 表示地址线失败,bit2 表示块写入失败。这样量产板卡的产测脚本只需要读一个字节,就能判断故障类型,比人工看日志高效得多。
我在多个项目里用这套方案做过 DDR3 验证,最后养成的习惯是:每次拿到新板子,先花半天搭这个 MicroBlaze + MIG + UART Lite 工程,再花一天把读写测试跑满 24 小时。很多看似颗粒的问题,最后都追溯到 MIG 引脚约束或电源纹波,而不是内存芯片本身。这点血泪经验希望帮到你。
本文还有配套的精品资源,点击获取