DDR3 控制器学习

文章目录

修订日期

  1. 开始 2025/8/7
  2. 2025/8/24

参考链接

Bilibili 视频 FPGA:DDR保姆级教程、轻松掌握DDR
DDR3 控制器 MIG IP 详解完整版 (native&Vivado&Verilog)

Xilinxddr3 mig ip核:基于native接口的ddr3读写控制(代码有bug)

MIG IP核接口

用户接口


DDR3 的读或者写都包含写命令操作,其中写操作命令(app_cmd)的值等于 0,读操作 app_cmd 的值等于 1。首先来看写命令时序,如下图所示。首先检查 app_rdy,为高则表明此时 IP 核命令接收处于准备好状态,可以接收用户命令,在当前时钟拉高 app_en,同时发送命令(app_cmd)和地址(app_addr),此时命令和地址被写入。

下面来看写数据的时序,如下图所示。

如上图所示,写数据有三种情形均可以正确写入:

(1)写数据时序和写命令时序发生在同一拍;

(2)写数据时序比写命令时序提前一拍;

(3)写数据时序比写命令时序至多延迟晚两拍;

结合上图,写时序总结如下:首先需要检查 app_wdf_rdy,该信号为高表明此时 IP 核数据接收处于准备完成状态,可以接收用户发过来的数据,在当前时钟拉高写使能(app_wdf_wren),给出写数据(app_wdf_data)。这样加上发起的写命令操作就可以成功向 IP 核写数据。这里有一个信号 app_wdf_mask,它是用来屏蔽写入数据的,该信号为高则屏蔽相应的字节,该信号为 0 默认不屏蔽任何字节。

这里需要指出的是 DDR3 的读或者写操作都可以分为背靠背和非背靠背两种情形。背靠背,即读或者写每个时钟都连续进行,中间没有间隙。非背靠背写则是非连续的读写。对于背靠背写,其实也有三种情形,唯一点不同的是,它没有最大延迟限制,如下图所示。

接着来看读数据,如下图所示:

读时序比较简单,发出读命令后,用户只需等待数据有效信号(app_rd_data_valid)拉高,为高表明此时数据总线上的数据是有效的返回数据。需要注意的是,在发出读命令后,有效读数据要晚若干周期才出现在数据总线上。下面是背靠背读的情况,如下图所示。

这里还需要注意一点,在连续读的时候,读到的数据顺序跟请求的命令/地址是相对应的。通常使用DDR3 的时候,为了最大限度地提高 DDR3 效能,充分利用突发写的特点,非背靠背很少用,而更多地采用背靠背操作

基于native接口的ddr3读写控制的模块设计

bug记录

  1. 为什么rd fifo装入数据吐出数据间隔这么久
  2. 为什么rd_fifo只装一次数据?
    发现代码有误:FIFO_crl的rd_burst_data定义为输出, 但非主要原因

bug调试

  1. 因为wren和ack同步,导致wr_fifo输出的第一个数据是128位的0, 将wren改为ack的下一拍, 并且由ack担任其余变量控制, 结果正确
  2. burst_req_wr/rd 防冲突机制有bug(依赖busy)当两者预期连续产生, busy来不及阻止。那么为什么rd_fifo只装一次数据? , 错误的req_rd信号让burst_rd模块意外工作,各变量逐次变化(但其挂不上DDR命令总线, 因此不会与DDR发生数据交互) 当burst_wr结束时, burst_rd挂载总线, 但由于此时rd_addr_cnt已经记满, ena失效, 因此无法获取DDR数据, 于是rd_fifo只装载一次数据
    修改代码如下
csharp 复制代码
else if(init_calib_complete) begin
        if(~wr_burst_busy && ~rd_burst_busy)begin
			//写FIFO中的数据量达到写突发长度
            if(wr_fifo_num >= wr_len && ~wr_burst_req)begin
                //wr_burst_req <= 1'b1;
                wr_burst_req <= (rd_burst_req)? 0:1;//避免burst_req_wr/rd连续发生导致busy信号来不及判断                               //写请求有效
                rd_burst_req <= 1'b0;
            end
			//读FIFO中的数据量小于读突发长度,且读使能信号有效
            else if((rd_fifo_num < rd_len) && read_valid && ~rd_burst_req)begin
                wr_burst_req <= 1'b0;
                //rd_burst_req <= 1'b1;                               //读请求有效
                rd_burst_req <= (wr_burst_req)? 0:1;//避免burst_req_wr/rd连续发生导致busy信号来不及判断
            end
            else begin
                wr_burst_req <= 1'b0;
                rd_burst_req <= 1'b0;
            end
        end
        else begin                                                  //非空闲状态
            wr_burst_req <= 1'b0;
            rd_burst_req <= 1'b0;
        end

总结

  1. DDR的读写控制可以分为三层:fifo_crl, burst_wr/req, MIG; 作用分别是:
    1.1 控制用户数据流进出(缓存):实现16b的用户数据与128b的DDR数据位宽转换, 当FIFO内数据过高/低时, 发送burst_req信号请求数据交互, 经由burst_wr/req批准后, 可以实现数据传输,因此用户只需要在传输数据流前,在用户端设计DDR读写的首地址与末地址, 单次burst长度, 就能实现将数据缓存或提取出DDR
    1.2 实现一次符合MIG时序的突发读写:由于MIG一次只能传输128位数据, 因此如果我们需要连续传输多个128位数据就需要建立一个符合MIG传输数据时序的burst模块
    1.3 DDR物理芯片交互控制器
  2. 对MIG控制时序有了更深的了解
    2.1 app_rdy、app_wrf_rdy、app_valid是真的会突然无效的, 因此此时交互的地址、数据必须保持不变, 而FIFO。在两个burst模块中的保护逻辑分别如下
csharp 复制代码
//写响应信号,用于从前级(FIFO)获取数据
assign wr_burst_ack = app_en && app_wdf_rdy && app_rdy;
***
//写入地址
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        app_addr <= 0;
	else if(wr_burst_start_r)					
		app_addr <= wr_burst_addr_r;	//将突发写的初始地址赋值给MIG
	else if(wr_burst_ack)				//
		app_addr <= app_addr + 8;		//
	else 
		app_addr <= app_addr;
end
csharp 复制代码
//读取地址
always @(posedge ui_clk) begin
    if(ui_clk_sync_rst)
        app_addr <= 0;
	else if(rd_burst_start_r)					
		app_addr <= rd_burst_addr_r;		//将突发写的初始地址赋值给MIG
	else if(app_en && app_rdy)	
		app_addr <= app_addr + 8;		//
	else 
		app_addr <= app_addr;
end

//读响应信号,用于将MIG中的数据输出给上级模块(读出)
assign rd_burst_ack = app_rd_data_valid;
  1. 在写代码前根据数据流向,分离模块功能、设计端口,、绘制波形图 这三步特别重要, 否则缺乏全局观念根本想不到该怎么写, 磨刀不误砍柴工啊!!