17、DDR3内存控制器:MIG IP核配置、读写时序、AXI4接口封装、带宽测试

DDR3,说白了就是咱们FPGA设计里的大仓库。数据要存、要取,都得靠它。但DDR3的接口协议相当复杂,直接操作的话,你会被那些刷新、预充电、激活的时序搞得头大。好在紫光同创提供了MIG IP核,帮我们封装了这些繁琐的细节。

我个人习惯,拿到一个新板子,第一件事就是先把DDR3调通。因为后面所有的算法、图像处理、高速数据采集,都离不开它。这一章,我就带你走一遍完整的DDR3控制器开发流程。

17.1 MIG IP核的配置要点

MIG,全称是Memory Interface Generator。说白了,就是一个图形化的配置工具。你告诉它你的DDR3颗粒型号、时钟频率、数据位宽,它就能自动生成一套完整的控制器代码。

配置的时候,有几个地方特别容易踩坑。我一个个说。

17.1.1 时钟与复位

MIG IP核需要一个参考时钟,通常是200MHz。这个时钟必须由板上的差分晶振提供,或者通过PLL生成。我建议你直接用差分晶振,因为PLL生成的时钟可能会有抖动,影响DDR3的稳定性。

复位信号也很关键。MIG IP核内部有一个自带的复位同步逻辑,你只需要给它一个异步复位信号就行。注意,这个复位信号必须保持至少100us的低电平,等DDR3的电源和时钟稳定后再释放。

⚠️ 注意: 我曾经遇到过一个问题,复位信号释放得太快,导致DDR3初始化失败。后来查了手册才发现,DDR3的初始化需要至少500us的复位时间。所以,我一般会在复位信号上做一个延时,确保万无一失。

17.1.2 内存颗粒参数

这里需要你对照着DDR3颗粒的数据手册,把参数填进去。主要关注这几个:

  • Memory Type: 选DDR3 SDRAM。
  • Memory Part: 如果列表里有你的颗粒型号,直接选。没有的话,就手动配置。
  • Data Width: 常见的有16位、32位、64位。这个取决于你的硬件设计。
  • Burst Length: 一般选8。这是DDR3的最优性能配置。
  • CAS Latency: 这个值决定了读命令发出后,多久能读到数据。通常选7或8。

嗯,这里要注意。CAS Latency的值不能随便填。它必须和DDR3的工作频率匹配。比如,在800MHz的频率下,CAS Latency通常设为7。你可以在颗粒的数据手册里找到一张“频率 vs CL”的表格。

17.1.3 接口类型选择

MIG IP核提供了两种接口:

  • Native接口: 这是最底层的接口,信号多,时序复杂。但灵活性最高。
  • AXI4接口: 这是ARM公司推出的总线标准,接口简单,容易上手。我强烈推荐你用这个。

你想想看,用Native接口的话,你得自己处理读、写、地址、数据、字节使能等一大堆信号。而用AXI4接口,你只需要关心几个核心信号:AW、W、B、AR、R。是不是清爽多了?

17.2 读写时序分析

配置好MIG IP核之后,我们来看看它的读写时序。虽然AXI4接口帮我们简化了很多,但理解底层时序,对调试问题很有帮助。

17.2.1 写操作时序

写操作分为两步:写地址通道和写数据通道。这两个通道是独立的,可以同时进行。

  1. 写地址通道: 你先把地址、突发长度等信息放到AW通道上,然后等待AWREADY信号拉高,表示从机准备好了。
  2. 写数据通道: 你把要写的数据放到W通道上,同时拉高WVALID。当WREADY拉高时,数据就被写入DDR3了。
  3. 写响应通道: 数据写完后,从机会通过B通道返回一个响应信号,告诉你写操作是否成功。

我记得有一次,我写数据的时候,WVALID和WREADY的握手时序没处理好,导致数据丢失。后来我加了一个FIFO来缓存数据,问题就解决了。

17.2.2 读操作时序

读操作相对简单一些。你只需要在AR通道上发送读地址,然后等待R通道返回数据。

  1. 读地址通道: 把地址放到AR通道上,拉高ARVALID。当ARREADY拉高时,读命令被接受。
  2. 读数据通道: 经过几个时钟周期的延迟(这个延迟就是CAS Latency),数据会出现在R通道上。同时,RVALID会拉高,表示数据有效。

这里有一个小技巧。读数据的时候,RLAST信号会告诉你这是最后一个数据。你可以用这个信号来结束一次读操作。

17.3 AXI4接口封装

虽然MIG IP核提供了AXI4接口,但它的信号还是有点多。我习惯再封装一层,做成一个简单的用户接口。这样,我的其他模块调用起来就更方便了。

封装的核心思想是:把AXI4的握手协议,转换成简单的读/写使能信号。

// 用户接口定义
module ddr3_axi_wrapper (
    input  wire        clk,
    input  wire        rst_n,
    // 用户接口
    input  wire        wr_en,
    input  wire [31:0] wr_addr,
    input  wire [255:0] wr_data,
    output wire        wr_ready,
    input  wire        rd_en,
    input  wire [31:0] rd_addr,
    output wire [255:0] rd_data,
    output wire        rd_valid,
    // AXI4接口
    // ... (连接到MIG IP核)
);

// 内部逻辑
// 当wr_en拉高时,启动一次AXI4写操作
// 当rd_en拉高时,启动一次AXI4读操作
// wr_ready表示写FIFO已准备好接收数据
// rd_valid表示读数据有效

endmodule

你看,这样封装之后,用户模块只需要关心wr_en、rd_en这些简单的信号,完全不用管AXI4的复杂时序。

💡 提示: 封装的时候,我建议你加上一个FIFO。因为DDR3的读写延迟是不确定的,FIFO可以起到缓冲的作用,让你的用户模块不受DDR3时序的影响。

17.4 带宽测试

DDR3调通之后,我们得测一下它的实际带宽。看看它到底能不能跑满理论值。

测试方法很简单:写一个测试模块,连续向DDR3写入大量数据,然后再读出来。通过计算写入/读取的数据量和耗时,就能算出带宽。

17.4.1 测试代码示例

// 带宽测试模块
module ddr3_bandwidth_test (
    input  wire        clk,
    input  wire        rst_n,
    // 用户接口
    output wire        wr_en,
    output wire [31:0] wr_addr,
    output wire [255:0] wr_data,
    input  wire        wr_ready,
    output wire        rd_en,
    output wire [31:0] rd_addr,
    input  wire [255:0] rd_data,
    input  wire        rd_valid
);

// 测试逻辑
// 1. 先写1024个数据
// 2. 再读1024个数据
// 3. 记录开始和结束的时间戳
// 4. 计算带宽

endmodule

17.4.2 测试结果分析

假设你的DDR3工作频率是800MHz,数据位宽是32位。那么理论带宽是:

800 MHz × 32 bit = 25.6 Gbps

但实际测试下来,你会发现带宽只有理论值的70%~80%。为什么会这样?

原因有几个:

  • 刷新开销: DDR3需要定期刷新,这会占用一部分带宽。
  • 读写切换: 从读切换到写,或者从写切换到读,中间需要插入空闲周期。
  • 地址对齐: 如果地址不是按Burst Length对齐的,也会损失带宽。

嗯,这里要注意。如果你测出来的带宽低于理论值的60%,那就要检查一下你的代码了。可能是握手时序有问题,或者FIFO深度不够。

📌 核心要点:
  • MIG IP核配置时,注意时钟、复位和颗粒参数的准确性。
  • AXI4接口的读写时序,核心是握手信号(VALID/READY)。
  • 封装一层用户接口,可以大大简化后续的开发。
  • 带宽测试是验证DDR3性能的重要手段,实际带宽通常低于理论值。

好了,这一章的内容就到这里。DDR3是FPGA开发中的硬骨头,但只要掌握了MIG IP核的配置和AXI4接口的使用,你会发现它其实没那么可怕。下一章,我们聊聊如何用DDR3做视频缓存,那才是真正发挥它威力的时候。