南京观海微电子---快速上手DDR读写例程——DDR接口专栏(三)

1. 前言

本文将向大家介绍如何使用DDR IP核的Native接口来对DDR进行读写操作。

2. DDR IP核接口介绍

要想把DDR3 IP核使用起来,必先需要了解下该IP核有哪些接口。DDR3 IP核接口图如下所示。注:图中展示的为DDR IP的Native接口,除了Native接口,该IP核还支持AXI4接口。

图中黄色的区域为“用户接口(UI)”,是DDR IP核对外的读写接口。绿色的框是用户通过代码写的逻辑电路,用户逻辑直接操作“用户接口”,实现对DDR数据的读写。右边蓝色的框是FPGA与DDR颗粒之间的物理接口。

下表列出了用户接口的定义清单,其中被黄色标注出来的几个信号是用户需要重点操作的信号。

  • app_addr:当前读写请求地址;
  • app_cmd:当前读写请求命令,读命令为3’b001,写命令为3’b000;
  • app_en:高有效,使能app_cmd、app_addr、app_sz和app_hi_pri信号;
  • app_rdy:该信号指示用户接口(UI)是否可以接收命令。当app_en使能时,如果该信号无效,则app_cmd、app_addr必须保持,等待app_rdy信号有效时再释放;
  • app_rd_data:用户接口读回数据;
  • app_rd_data_end:高有效,指示当前app_rd_data为最后一个数据,该信号仅当app_rd_data_valid为高时有效;
  • app_rd_data_valid:高有效,指示app_rd_data数据有效;
  • app_wdf_end:高有效,指示当前app_wdf_data为最后一个数据;
  • app_wdf_mask:提供app_wdf_data屏蔽码;
  • app_wdf_rdy:指示UI可以接收写数据写入;
  • app_wdf_wren:高有效,指示app_wdf_data数据有效;
  • app_wdf_data:用户待写入DDR的数据。

以上接口功能描述的翻译仅供参考,详细的还是参考上面两张官方的表格。

3. 用户接口的读写时序

操作DDR的UI接口时,要特别注意app_rdy信号的状态。如下图所示,app_cmd、app_addr和app_en分别给了3次写addr0地址的指令。但指令只有在app_rdy为高时才被接受,即图中只有前两次写指令写给了DDR IP核。

3.1 写数据至DDR

在本文讲述读写DDR时序时,均采用的是4:1模式,即FPGA的用户逻辑采用时钟频率为DDR工作频率的四分之一,该设置需要在建立DDR IP时进行设置,如不了解,可以参考上一篇文章《MIG IP核的使用——DDR接口专栏(二)》。

往DDR UI接口写数据时,时序如下图所示:

图中上半部给出了写控制指令的操作时序图,下半部分别有3个虚线框,给出了3种写数据总线的操作时序图。方案1为作者推荐的方法,即写数据操作和写指令操作对齐。方案2意思为写数据操作可以提前写指令操作一个时钟周期。方案3表明,写数据操作最多可以落后写指令操作两个时钟周期。

上图给了一个实际的写数据例子。红色框为写指令的操作,总共向8个地址(0x0a000x0a38)进行写操作。由于第1个时刻到第6个时刻app_rdy一直为高,所以往地址0x0a000x0a28写指令立即写入了DDR IP核的FIFO中。但由于第7个时刻app_rdy突然拉低,往地址0x0a30写数据的指令没有成功写入DDR IP核中,因此必须等待。此时app_addr、app_en、app_cmd这些信号都必须保持,直到app_rdy再度拉高,该指令才会被成功写入给DDR。

蓝色框为写具体的DDR数据操作,由于app_wdf_rdy一直为高,因此UI接口上一次性将8个数据都写入到DDR IP核的数据缓存FIFO中。

显然上面实例是采用方案3的写数据方式,但作者还是推荐初学者采用方案1的方式写数据。即判断app_rdy和app_wdf_rdy都为高时,再同时写入指令和数据。

3.2 读数据

从DDR UI接口读数据时,时序如下图所示:

图中上半部给出了读控制指令的操作时序图,下半部分为读出的数据结果。从读指令被UI接口接收后到数据被读出来的延时时间是随机的,没有具体对应关系。

4. 读写DDR例程代码

话不多说,上读写DDR例程代码吧。本文引用了CSDN博主“孤独的单刀”编写的代码。这段代码非常好的向大家展示了UI接口的使用方法。

代码功能描述:(1)等待DDR初始化成功;(2)往DDR的地址连续写入了1024个数据;(3)从DDR中读出刚写入相同地址段的数据,并进行比对。

//**************************************************************************

// *** 名称 : ddr3_rw

// *** 作者 : 孤独的单刀

// *** 博客 : https://blog.csdn.net/wuzhikaidetb

// *** 日期 : 2021.12

// *** 描述 : 对DDR3进行循环读写

//**************************************************************************

//< 端口 >==========

module ddr3_rw #

(

parameter integer WR_LEN = 1024 , //读、写长度

parameter integer DATA_WIDTH = 128 , //数据位宽,突发长度为8,16bit,共128bit

parameter integer ADDR_WIDTH = 28 //根据MIG例化而来

)(

//DDR3相关 ------------------------------------------------------

input ui_clk , //用户时钟

input ui_clk_sync_rst , //复位,高有效

input init_calib_complete , //DDR3初始化完成

//DDR3相关 ------------------------------------------------------

input app_rdy , //MIG 命令接收准备好标致

input app_wdf_rdy , //MIG数据接收准备好

input app_rd_data_valid , //读数据有效

input [DATA_WIDTH - 1:0] app_rd_data , //用户读数据

output reg [ADDR_WIDTH - 1:0] app_addr , //DDR3地址

output app_en , //MIG IP发送命令使能

output app_wdf_wren , //用户写数据使能

output app_wdf_end , //突发写当前时钟最后一个数据

output [2:0] app_cmd , //MIG IP核操作命令,读或者写

output reg [DATA_WIDTH - 1:0] app_wdf_data , //用户写数据

//指示 ----------------------------------------------------------

output reg error_flag //读写错误标志

);

//< 信号定义 >==========

//测试状态机-----------------------------------------

localparam IDLE = 4'b0001 ; //空闲状态

localparam WRITE = 4'b0010 ; //写状态

localparam WAIT = 4'b0100 ; //读到写过度等待

localparam READ = 4'b1000 ; //读状态

//reg define ----------------------------------------

reg [3:0] cur_state ; //三段式状态机现态

reg [3:0] next_state ; //三段式状态机次态

reg [ADDR_WIDTH - 1:0] rd_addr_cnt ; //用户读地址计数

reg [ADDR_WIDTH - 1:0] wr_addr_cnt ; //用户写地址计数

reg [ADDR_WIDTH - 1:0] rd_cnt ; //实际读地址标记

//wire define ---------------------------------------

wire error ; //读写错误标记

wire rst_n ; //复位,低有效

wire wr_proc ; //拉高表示写过程进行

wire wr_last ; //拉高表示写入最后一个数据

wire rd_addr_last ; //拉高表示是最后一个读地址

//*********************************************************************************************

//** main code

//**********************************************************************************************

//==========================================================================

//== 信号赋值

//==========================================================================

assign rst_n = ~ui_clk_sync_rst;

//当MIG准备好后,用户同步准备好

assign app_en = app_rdy && ((cur_state == WRITE && app_wdf_rdy) || cur_state == READ);

//写指令,命令接收和数据接收都准备好,此时拉高写使能

assign app_wdf_wren = (cur_state == WRITE) && wr_proc;

//由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同

assign app_wdf_end = app_wdf_wren;

assign app_cmd = (cur_state == READ) ? 3'd1 :3'd0; //处于读的时候命令值为1,其他时候命令值为0

assign wr_proc = ~app_cmd && app_rdy && app_wdf_rdy; //拉高表示写过程进行

//处于写使能且是最后一个数据

assign wr_last = app_wdf_wren && (wr_addr_cnt == WR_LEN - 1) ;

//处于读指令、读有效且是最后一个数据

assign rd_addr_last = (rd_addr_cnt == WR_LEN - 1) && app_rdy && app_cmd;

//==========================================================================

//== 状态机

//==========================================================================

always @(posedge ui_clk or negedge rst_n) begin

if(~rst_n)

cur_state <= IDLE;

else

cur_state <= next_state;

end

always @(*) begin

if(~rst_n)

next_state = IDLE;

else

case(cur_state)

IDLE:

if(init_calib_complete) //MIG IP核初始化完成

next_state = WRITE;

else

next_state = IDLE;

WRITE:

if(wr_last) //写入最后一个数据

next_state = WAIT;

else

next_state = WRITE;

WAIT:

next_state = READ;

READ:

if(rd_addr_last) //写入最后一个读地址,数据读出需要时间

next_state = IDLE;

else

next_state = READ;

default:;

endcase

end

always @(posedge ui_clk or negedge rst_n) begin

if(~rst_n) begin

app_wdf_data <= 0;

wr_addr_cnt <= 0;

rd_addr_cnt <= 0;

app_addr <= 0;

end

else

case(cur_state)

IDLE:begin

app_wdf_data <= 0;

wr_addr_cnt <= 0;

rd_addr_cnt <= 0;

app_addr <= 0;

end

WRITE:begin

if(wr_proc)begin //写条件满足

app_wdf_data <= app_wdf_data + 1; //写数据自加

wr_addr_cnt <= wr_addr_cnt + 1; //写地址自加

app_addr <= app_addr + 8; //DDR3 地址加8

end

else begin //写条件不满足,保持当前值

app_wdf_data <= app_wdf_data;

wr_addr_cnt <= wr_addr_cnt;

app_addr <= app_addr;

end

end

WAIT:begin

rd_addr_cnt <= 0; //读地址复位

app_addr <= 0; //DDR3读从地址0开始

end

READ:begin //读到设定的地址长度

if(app_rdy)begin //若MIG已经准备好,则开始读

rd_addr_cnt <= rd_addr_cnt + 1'd1;//用户地址每次加一

app_addr <= app_addr + 8; //DDR3地址加8

end

else begin //若MIG没准备好,则保持原值

rd_addr_cnt <= rd_addr_cnt;

app_addr <= app_addr;

end

end

default:begin

app_wdf_data <= 0;

wr_addr_cnt <= 0;

rd_addr_cnt <= 0;

app_addr <= 0;

end

endcase

end

//==========================================================================

//== 其他

//==========================================================================

//读信号有效,且读出的数不是写入的数时,将错误标志位拉高

assign error = (app_rd_data_valid && (rd_cnt!=app_rd_data));

//寄存状态标志位

always @(posedge ui_clk or negedge rst_n) begin

if(~rst_n)

error_flag <= 0;

else if(error)

error_flag <= 1;

end

//对DDR3实际读数据个数编号计数

always @(posedge ui_clk or negedge rst_n) begin

if(~rst_n)

rd_cnt <= 0;

//若计数到读写长度,且读有效,地址计数器则置0

else if(app_rd_data_valid && rd_cnt == WR_LEN - 1)

rd_cnt <= 0;

else if (app_rd_data_valid ) //读有效情况下每个时钟+1

rd_cnt <= rd_cnt + 1;

end

endmodule

posted @ 2025-12-08 17:22  观海微电子  阅读(7)  评论(0)    收藏  举报