快速上手Xilinx DDR3 IP核(3)——把MIG IP核封装成一个FIFO(上)(Native接口)

作者:孤独的单刀,文章来源:CSDN博客

写在前面
本文将把Xilinx的MIG IP核DDR3的Native接口进行二次封装,将其封装成一个类似FIFO的接口,使其应用起来更加方便简单。

1、框架
这个类FIFO模块主要由以下几个部分组成:

mig_ctrl:顶层模块,使用该模块通过控制MIG IP核间接实现对DDR3芯片的突发写、突发读。分为用户接口与DDR3控制接口,用于只需要控制用户接口即可实现对DDR3芯片的突发写、突发读。用户接口的应用类似FIFO接口,用户只需要提供写使能信号与写入的数据即可实现突发写操作(实际上还需要提供其他信息:如突发长度、起止地址等信息);用户只需要提供读使能信号即可实现突发读操作(实际上还需要提供其他信息:如突发长度、起止地址等信息)。

------fifo_ctrl:分别例化写FIFO与读FIFO,用来缓存用户要写入DDR3的数据与从DDR3中读取的数据,实现DDR3端与用户端的跨时钟域处理。同时生成MIG读写的地址、读写请求信号。每当写FIFO中的数据量超过设置阈值时,即生成ddr3写请求信号,同时更新写入地址,使ddr3_wr生成控制MIG IP的时序来将数据写入DDR3;每当读FIFO中的数据量不足设置阈值时,即生成ddr3读请求信号,同时更新读取地址,使ddr3_rd生成控制MIG IP的时序来从DDR3中读取数据;

-------------写FIFO:写位宽16bit,写端口与用户端相连,写入数据来自用户端;读位宽128bit(DDR3固定突发长度8,位宽16bit),读端口与ddr3_wr模块相连,从FIFO读取数据写入DDR3。

-------------读FIFO:写位宽128bit(DDR3固定突发长度8,位宽16bit),写端口与ddr3_rd模块相连,写入数据来自DDR3;读位宽16bit,读端口与用户端相连,用户从FIFO读取数据即为从DDR3中的缓存数据。

------ddr3_wr:突发写入模块,驱动MIG IP将数据以突发写的方式写入DDR3芯片。

------ddr3_rd:突发读取模块,驱动MIG IP以突发读的方式将数据从DDR3芯片中读出。

MIG IP:Xilinx提供的DDR3 P核,将DDR3读写的复杂时序封装成非常简单的接口供用户使用。

该模块的使用流程:

写端口:

1. 提供写使能信号、写入数据就可实现DDR3的突发写操作,类似FIFO。之所以说类FIFO,是因为还需要提供其他必要信息:突发写的突发长度、DDR3写入的起始地址与结束地址;

2. 用户写入的数据先是被缓存到写FIFO,当写FIFO中的数据量满足设定的阈值(该阈值默认为突发长度)时,生成突发写请求信号,与DDR3突发写的首地址(每一次新的突发写请求都会生成新的首地址(累加));

3. ddr3_wr模块根据突发写信息驱动MIG IP核对DDR3芯片操作,实现一次突发写操作;

4. 此时写FIFO中的数据被读出,写入到DDR3中,FIFO中的数据数量减少,等待用户继续写入数据直到再次超过设定阈值,从而开始新的一次突发写操作。

读端口:

1. 提供读使能信号就可实现DDR3的突发读操作,类似FIFO。之所以说类FIFO,是因为还需要提供其他必要信息:突发读的突发长度、DDR3读取的起始地址与结束地址;
2. 每当读FIFO中的数据量不足设定的阈值(该阈值默认为突发长度)时,生成突发读请求信号,与DDR3突发读的首地址(每一次新的突发读请求都会生成新的首地址(累加));
3. ddr3_rd模块根据突发读信息驱动MIG IP核对DDR3芯片操作,实现一次突发读操作;
4. 此时DDR3中的数据被读出,写入到读FIFO中,FIFO中的数据数量增加,等待用户将数据读出,直到数据量不足设定阈值,从而开始新的一次突发读操作。

2、ddr3_wr突发写模块
2.1、端口
MIG IP核提供了Native接口给用户使用,ddr3_wr模块实际上就是对MIG的Native接口的突发写驱动模块,该模块的输入、输出端口如下:

端口主要分为2个部分:

用户端:输入突发写的相关信号;MIG端:自带的Native接口。

用户写操作的顺序如下(待MIG初始化完成后):

拉高wr_burst_start一个时钟,表示要对DDR3进行一次突发写操作,同时输入突发长度与突发的首地址,拉高wr_burst_busy表示此时正在进行突发写操作

根据MIG Native接口的时序,生成突发写响应信号,该响应信号被连接到上层的写FIFO模块的读端口,这样就把用户写入缓存在写FIFO的数据读出,从而写入DDR3芯片

待写入数据个数满足突发长度后,拉高wr_burst_done信号一个周期,表示一次突发写操作完成

2.2、Verilog代码
完整的Verilog代码如下(代码不复杂,只要实现native接口的时序就可以了,其他请参考注释):

//**************************************************************************
// *** 名称 : ddr3_rw
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 对DDR3进行循环读写
//**************************************************************************
module ddr3_wr #
(
parameter integer DATA_WIDTH = 128 , //数据位宽,突发长度为8,16bit,共128bit
parameter integer ADDR_WIDTH = 28 //根据MIG例化而来
)(
//时钟与复位-------------------------------------------------
input ui_clk , //用户时钟
input ui_clk_sync_rst , //复位,高有效
//用户端信号-------------------------------------------------
input wr_burst_start , //一次突发写开始
input [ADDR_WIDTH - 1:0] wr_burst_len , //突发写入的长度
input [ADDR_WIDTH - 1:0] wr_burst_addr , //突发写入的首地址
input [DATA_WIDTH - 1:0] wr_burst_data , //需要突发写入的数据。来源写FIFO
output wr_burst_ack , //突发写响应,高电平表示正在进行突发写操作
output reg wr_burst_done , //一次突发写完成
output reg wr_burst_busy , //突发写忙状态,高电平有效
//MIG端控制信号----------------------------------------------
output reg app_en , //MIG IP发送命令使能
input app_rdy , //MIG IP命令接收准备好标致
output [2:0] app_cmd , //MIG IP操作命令,读或者写
output reg [ADDR_WIDTH - 1:0] app_addr , //MIG IP操作地址
input app_wdf_rdy , //MIG IP数据接收准备好
output app_wdf_wren , //MIG IP写数据使能
output app_wdf_end , //MIG IP突发写当前时钟最后一个数据
output [(DATA_WIDTH/8) - 1:0] app_wdf_mask , //MIG IP数据掩码
output [DATA_WIDTH - 1:0] app_wdf_data //MIG IP写数据
);

//parameter define
localparam WRITE = 3'b000; //MIG写命令
//reg define
reg [ADDR_WIDTH - 1:0] wr_burst_cnt ; //写入数据个数计数器计数
reg [ADDR_WIDTH - 1:0] wr_burst_len_r ; //锁存突发长度
reg [ADDR_WIDTH - 1:0] wr_burst_addr_r ; //锁存突发地址
reg wr_burst_start_r ; //突发开始打一拍,用于锁存突发地址、突发长度
//wire define
wire wr_burst_last ; //拉高表示写入最后一个数据

//*********************************************************************************************
//** main code
//**********************************************************************************************

//不使用掩码
assign app_wdf_mask = 0 ;
//固定为写状态
assign app_cmd = WRITE;
//写指令,命令接收和数据接收都准备好,此时拉高写使能
assign app_wdf_wren = wr_burst_ack;
//从用户端输入的数据直接赋值给MIG IP核
assign app_wdf_data = wr_burst_data;
//由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同
assign app_wdf_end = app_wdf_wren;
//处于写使能且是最后一个数据
assign wr_burst_last = app_wdf_wren && (wr_burst_cnt == wr_burst_len_r - 1) ;
//写响应信号,用于从前级获取数据
assign wr_burst_ack = app_en && app_wdf_rdy && app_rdy;

//wr_burst_start打一拍,锁存突发地址、突发长度
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
wr_burst_start_r <= 0;
else
wr_burst_start_r <= wr_burst_start;
end

//锁存突发长度
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
wr_burst_len_r <= 1'b0;
else if(wr_burst_start)
wr_burst_len_r <= wr_burst_len;
end

//锁存突发地址
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
wr_burst_addr_r <= 1'b0;
else if(wr_burst_start)
wr_burst_addr_r <= wr_burst_addr;
end

//app_en控制:突发开始时一直拉高,直到突发结束
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
app_en <= 0;
else if(wr_burst_start_r)
app_en <= 1;
else if(wr_burst_last)
app_en <= 0;
else
app_en <= app_en;
end

//突发写忙状态,拉高表示其处于突发写状态
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
wr_burst_busy <= 0;
else if(wr_burst_start)
wr_burst_busy <= 1; //进入写忙状态
else if(wr_burst_done)
wr_burst_busy <= 0; //突发写完成,拉低写忙状态
else
wr_burst_busy <= wr_burst_busy;
end

//写入地址
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(app_wdf_wren) //
app_addr <= app_addr + 8; //
else
app_addr <= app_addr;
end

//突发写完成信号
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
wr_burst_done <= 0;
else if(wr_burst_last)
wr_burst_done <= 1;
else
wr_burst_done <= 0;
end

//写入数据个数(突发长度计数器)
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
wr_burst_cnt <= 0;
else if(app_wdf_wren)begin
if(wr_burst_cnt == wr_burst_len_r - 1)
wr_burst_cnt <= 0; //进入写忙状态
else
wr_burst_cnt <= wr_burst_cnt + 1;
end
else
wr_burst_cnt <= wr_burst_cnt;
end

endmodule

3、ddr3_rd突发读模块
3.1、端口
MIG IP核提供了Native接口给用户使用,ddr3_rd模块实际上就是对MIG的Native接口的突发读驱动模块,该模块的输入、输出端口如下:

端口主要分为2个部分:

用户端:输入突发读的相关信号;MIG端:自带的Native接口。

用户读操作的顺序如下(待MIG初始化完成后):

拉高rd_burst_start一个时钟,表示要对DDR3进行一次突发读操作,同时输入突发长度与突发的首地址,拉高rd_burst_busy表示此时正在进行突发读操作

根据MIG Native接口的时序,生成突发读响应信号,该响应信号被连接到下层的读FIFO模块的写端口,这样就把MIG 从DDR3芯片读出的数据写入读FIFO

待读出的数据个数满足突发长度后,拉高rd_burst_done信号一个周期,表示一次突发读操作完成

3.2、Verilog代码
完整的Verilog代码如下(代码不复杂,只要实现native接口的时序就可以了,其他请参考注释):

//**************************************************************************
// *** 名称 : ddr3_rd
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 对DDR3进行循环读写
//**************************************************************************
module ddr3_rd #
(
parameter integer DATA_WIDTH = 128 , //数据位宽,突发长度为8,16bit,共128bit
parameter integer ADDR_WIDTH = 28 //根据MIG例化而来
)
(
//时钟与复位-------------------------------------------------
input ui_clk , //用户时钟
input ui_clk_sync_rst , //复位,高有效
//用户端信号-------------------------------------------------
input rd_burst_start , //一次突发读开始
input [ADDR_WIDTH - 1:0] rd_burst_len , //突发读取的长度
input [ADDR_WIDTH - 1:0] rd_burst_addr , //突发读取的首地址
output [DATA_WIDTH - 1:0] rd_burst_data , //突发读取的数据。存入读FIFO
output rd_burst_ack , //突发读响应,高电平表示正在进行突发读操作
output reg rd_burst_done , //一次突发读完成
output reg rd_burst_busy , //突发读忙状态,高电平有效
//MIG端控制信号----------------------------------------------
output reg app_en , //MIG发送命令使能
input app_rdy , //MIG命令接收准备好标致
output [2:0] app_cmd , //MIG操作命令,读或者写
output reg [ADDR_WIDTH - 1:0] app_addr , //MIG读取DDR3地址
input [DATA_WIDTH - 1:0] app_rd_data , //MIG读出的数据
input app_rd_data_end , //MIG读出的最后一个数据
input app_rd_data_valid //MIG读出的数据有效
);

//parameter define
localparam READ = 3'b001; //MIG读命令
//reg define
reg [ADDR_WIDTH - 1:0] rd_addr_cnt ; //读取地址个数计数
reg [ADDR_WIDTH - 1:0] rd_data_cnt ; //读取数据个数计数
reg [ADDR_WIDTH - 1:0] rd_burst_len_r ; //锁存突发长度
reg [ADDR_WIDTH - 1:0] rd_burst_addr_r ; //锁存突发地址
reg rd_burst_start_r ; //突发开始打一拍,用于锁存突发地址、突发长度
//wire define
wire rd_addr_last ; //拉高
wire rd_data_last ; //拉高读出最后一个数据

//*********************************************************************************************
//** main code
//**********************************************************************************************

//固定为读状态
assign app_cmd = READ;
//将从MIG中读出的数据直接赋值给上级模块
assign rd_burst_data = app_rd_data;
//读出的最后一个地址
assign rd_addr_last = app_en && app_rdy && (rd_addr_cnt == rd_burst_len_r - 1) ;
//读出的最后一个数据
assign rd_data_last = app_rd_data_valid && (rd_data_cnt == rd_burst_len_r - 1) ;
//读响应信号,用于将MIG中的数据输出给上级模块(读出)
assign rd_burst_ack = app_rd_data_valid;

//rd_burst_start打一拍,锁存突发地址、突发长度
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
rd_burst_start_r <= 0;
else
rd_burst_start_r <= rd_burst_start;
end

//锁存突发长度
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
rd_burst_len_r <= 1'b0;
else if(rd_burst_start)
rd_burst_len_r <= rd_burst_len;
end

//锁存突发地址
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
rd_burst_addr_r <= 1'b0;
else if(rd_burst_start)
rd_burst_addr_r <= rd_burst_addr;
end

//app_en控制:突发开始时一直拉高,直到突发结束
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
app_en <= 0;
else if(rd_burst_start_r)
app_en <= 1;
else if(rd_addr_last)
app_en <= 0;
else
app_en <= app_en;
end

//突发读忙状态,拉高表示其处于突发读状态
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
rd_burst_busy <= 0;
else if(rd_burst_start)
rd_burst_busy <= 1; //进入写忙状态
else if(rd_burst_done)
rd_burst_busy <= 0; //突发传输完成,拉低写忙状态
else
rd_burst_busy <= rd_burst_busy;
end

//读取地址
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

//突发读完成信号
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
rd_burst_done <= 0;
else if(rd_data_last)
rd_burst_done <= 1;
else
rd_burst_done <= 0;
end
//读出地址个数计数(读取突发长度计数器)
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
rd_addr_cnt <= 0;
else if(rd_addr_last)
rd_addr_cnt <= 0;
else if(app_en && app_rdy)begin
rd_addr_cnt <= rd_addr_cnt + 1;
end
else
rd_addr_cnt <= rd_addr_cnt;
end

//读出数据个数计数
always @(posedge ui_clk) begin
if(ui_clk_sync_rst)
rd_data_cnt <= 0;
else if(rd_data_last)
rd_data_cnt <= 0;
else if(app_rd_data_valid)begin
rd_data_cnt <= rd_data_cnt + 1;
end
else
rd_data_cnt <= rd_data_cnt;
end

endmodule

4、Testbench与仿真结果
4.1、Testbench
Testbench我们把突发写模块与突发读模块一起例化来测试。预期实现功能:1、从地址0开始,突发写入512个数据,写入的数据从0累加到511;2、从地址0开始突发读取512个数据。根据读出的数据与写入数据是否一致来判断突发写、读模块是否设计成功。

需要生成MIG IP核与DDR3的仿真模型,具体方法不讲了,可以参考:

 快速上手Xilinx DDR3 IP核(1)----MIG IP核的介绍及配置(Native接口)

快速上手Xilinx DDR3 IP核(2)----MIG IP核的官方例程与读写测试模块(Native接口)

完整的testbench如下:

//**************************************************************************
// *** 名称 : tb_ddr3_wr_rd
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 对DDR3写模块、读模块进行测试
//**************************************************************************
`timescale 1ns/100ps

module tb_ddr3_wr_rd();
//============================< 信号定义 >======================================
//parameter define-----------------------------------
parameter integer DATA_WIDTH = 128 ; //数据位宽,突发长度为8,16bit,共128bit
parameter integer ADDR_WIDTH = 28 ; //根据MIG例化而来
parameter integer TEST_BURST_LEN = 512 ; //测试单次突发长度
//时钟和复位 ------------------------------------
reg sys_clk ; //系统时钟
reg sys_rst_n ; //系统复位
wire ui_clk ; //用户时钟
wire locked ; //锁相环频率稳定标志
wire clk_ref_i ; //DDR3参考时钟
wire sys_clk_i ; //MIG IP核输入时钟
wire clk_200 ; //200M时钟
wire ui_clk_sync_rst ; //用户复位信号
wire init_calib_complete ; //校准完成信号
//DDR3相关 --------------------------------------
wire [15:0] ddr3_dq ; //DDR3 数据
wire [1:0] ddr3_dqs_n ; //DDR3 dqs负
wire [1:0] ddr3_dqs_p ; //DDR3 dqs正
wire [13:0] ddr3_addr ; //DDR3 地址
wire [2:0] ddr3_ba ; //DDR3 banck 选择
wire ddr3_ras_n ; //DDR3 行选择
wire ddr3_cas_n ; //DDR3 列选择
wire ddr3_we_n ; //DDR3 读写选择
wire ddr3_reset_n ; //DDR3 复位
wire [0:0] ddr3_ck_p ; //DDR3 时钟正
wire [0:0] ddr3_ck_n ; //DDR3 时钟负
wire [0:0] ddr3_cke ; //DDR3 时钟使能
wire [0:0] ddr3_cs_n ; //DDR3 片选
wire [1:0] ddr3_dm ; //DDR3_dm
wire [0:0] ddr3_odt ; //DDR3_odt
//用户端信号 -----------------------------------
wire rd_burst_ack ; //突发读响应
wire rd_burst_done ; //突发读完成
wire rd_burst_busy ; //突发忙状态
wire [DATA_WIDTH - 1:0] rd_burst_data ; //突发读出的数据
wire wr_burst_ack ; //突发写响应
wire wr_burst_done ; //突发写完成
wire wr_burst_busy ; //突发写忙状态
reg rd_burst_start ; //突发读开始
reg [ADDR_WIDTH - 1:0] rd_burst_len ; //突发长度
reg [ADDR_WIDTH - 1:0] rd_burst_addr ; //突发读首地址
reg wr_burst_start ; //突发写开始
reg [ADDR_WIDTH - 1:0] wr_burst_len ; //突发写长度
reg [ADDR_WIDTH - 1:0] wr_burst_addr ; //突发首地址
reg [DATA_WIDTH - 1:0] wr_burst_data ; //突发数据
//MIG端信号--------------------------------------
wire [ADDR_WIDTH - 1:0] app_addr ; //DDR3 地址
wire [ADDR_WIDTH - 1:0] app_addr_wr ; //DDR3 地址
wire [ADDR_WIDTH - 1:0] app_addr_rd ; //DDR3 地址
wire [2:0] app_cmd ; //用户读写命令
wire [2:0] app_cmd_wr ; //用户读写命令
wire [2:0] app_cmd_rd ; //用户读写命令
wire app_en ; //MIG IP核使能
wire app_en_wr ; //MIG IP核使能
wire app_en_rd ; //MIG IP核使能
wire app_rdy ; //MIG IP核空闲
wire [DATA_WIDTH - 1:0] app_wdf_data ; //用户写数据
wire app_wdf_end ; //突发写当前时钟最后一个数据
wire [15:0] app_wdf_mask ; //写数据屏蔽
wire app_wdf_rdy ; //写空闲
wire app_wdf_wren ; //DDR3 写使能
wire [DATA_WIDTH - 1:0] app_rd_data ; //MIG读出的数据
wire app_rd_data_end ; //MIG读出的最后一个数据
wire app_rd_data_valid ; //MIG读出的数据有效

integer i;
//MIG 时钟赋值,200M-----------------------------
assign clk_ref_i = clk_200 ; //DDR3参考时钟,200M
assign sys_clk_i = clk_200 ; //MIG IP核输入时钟,200M
//分时复用写、读模块
assign app_addr = (rd_burst_busy) ? app_addr_rd : app_addr_wr;
assign app_cmd = (rd_burst_busy) ? app_cmd_rd : app_cmd_wr;
assign app_en = (rd_burst_busy) ? app_en_rd : app_en_wr;
//============================< 测试条件设置 >===============================
//设置初始测试条件--------------------------------------------------------
initial begin
sys_clk = 0 ; //初始时钟为0
sys_rst_n <= 0 ; //初始复位
rd_burst_start <= 0 ;
rd_burst_len <= 0 ;
rd_burst_addr <= 0 ;
wr_burst_start <= 0 ;
wr_burst_len <= 0 ;
wr_burst_addr <= 0 ;
wr_burst_data <= 0 ;
#50 //50个时钟周期后
sys_rst_n <= 1'b1 ; //拉高复位,系统进入工作状态
//写数据---------------------------------------------------------------
@(posedge init_calib_complete) //MIG初始化完成
wr_burst_start <= 1'b1; //开始一次突发写
wr_burst_len <= TEST_BURST_LEN; //突发长度512
wr_burst_addr <= 0; //突发起始地址0
@(posedge ui_clk)
wr_burst_start <= 1'b0;
for(i = 0; i<= TEST_BURST_LEN - 1; i = i+1)begin
@(posedge ui_clk)
if(wr_burst_ack)
wr_burst_data <= wr_burst_data + 1; //突发写入的数据从0开始累加1
else
i = i - 1;
end

//读数据---------------------------------------------------------------
@(posedge wr_burst_done) //突发写完成
rd_burst_start <= 1'b1; //开始一次突发读
rd_burst_len <= TEST_BURST_LEN; //突发长度512
rd_burst_addr <= 0; //突发起始地址0
@(posedge ui_clk)
rd_burst_start <= 1'b0;
end

//设置时钟----------------------------------------------------------------
always #10 sys_clk = ~sys_clk; //系统时钟周期20ns

//============================< 例化突发读模块 >===============================================
ddr3_rd #(
.DATA_WIDTH (DATA_WIDTH ),
.ADDR_WIDTH (ADDR_WIDTH )
)
u_ddr3_rd(
.ui_clk (ui_clk ),
.ui_clk_sync_rst (ui_clk_sync_rst ),
//MIG端信号
.app_rdy (app_rdy ),
.app_addr (app_addr_rd ),
.app_en (app_en_rd ),
.app_cmd (app_cmd_rd ),
.app_rd_data (app_rd_data ),
.app_rd_data_end (app_rd_data_end ),
.app_rd_data_valid (app_rd_data_valid ),
//用户端信号
.rd_burst_start (rd_burst_start ),
.rd_burst_len (rd_burst_len ),
.rd_burst_addr (rd_burst_addr ),
.rd_burst_data (rd_burst_data ),
.rd_burst_ack (rd_burst_ack ),
.rd_burst_done (rd_burst_done ),
.rd_burst_busy (rd_burst_busy )
);
//============================< 例化突发写模块 >===============================================
ddr3_wr #(
.DATA_WIDTH (DATA_WIDTH ),
.ADDR_WIDTH (ADDR_WIDTH )
)
u_ddr3_wr(
.ui_clk (ui_clk ),
.ui_clk_sync_rst (ui_clk_sync_rst ),
//MIG端信号------------------------------------------
.app_rdy (app_rdy ),
.app_addr (app_addr_wr ),
.app_en (app_en_wr ),
.app_cmd (app_cmd_wr ),
.app_wdf_wren (app_wdf_wren ),
.app_wdf_end (app_wdf_end ),
.app_wdf_data (app_wdf_data ),
.app_wdf_rdy (app_wdf_rdy ),
.app_wdf_mask (app_wdf_mask ),
//用户端信号-----------------------------------------
.wr_burst_start (wr_burst_start ),
.wr_burst_len (wr_burst_len ),
.wr_burst_addr (wr_burst_addr ),
.wr_burst_data (wr_burst_data ),
.wr_burst_ack (wr_burst_ack ),
.wr_burst_done (wr_burst_done ),
.wr_burst_busy (wr_burst_busy )
);
//============================< 例化MIG IP核模块 >===============================================
mig_7series_0 u_mig_7series_0 (
//DDR3接口-------------------------------------------
.ddr3_addr (ddr3_addr ),
.ddr3_ba (ddr3_ba ),
.ddr3_cas_n (ddr3_cas_n ),
.ddr3_ck_n (ddr3_ck_n ),
.ddr3_ck_p (ddr3_ck_p ),
.ddr3_cke (ddr3_cke ),
.ddr3_ras_n (ddr3_ras_n ),
.ddr3_reset_n (ddr3_reset_n ),
.ddr3_we_n (ddr3_we_n ),
.ddr3_dq (ddr3_dq ),
.ddr3_dqs_n (ddr3_dqs_n ),
.ddr3_dqs_p (ddr3_dqs_p ),
.ddr3_cs_n (ddr3_cs_n ),
.ddr3_dm (ddr3_dm ),
.ddr3_odt (ddr3_odt ),
//用户接口-------------------------------------------
.app_addr (app_addr ),
.app_cmd (app_cmd ),
.app_en (app_en ),
.app_rdy (app_rdy ),
.app_wdf_mask (app_wdf_mask ),
.app_wdf_rdy (app_wdf_rdy ),
.app_wdf_data (app_wdf_data ),
.app_wdf_end (app_wdf_end ),
.app_wdf_wren (app_wdf_wren ),
.app_rd_data (app_rd_data ),
.app_rd_data_end (app_rd_data_end ),
.app_rd_data_valid (app_rd_data_valid ),
.app_sr_req (1'b0 ),
.app_ref_req (1'b0 ),
.app_zq_req (1'b0 ),
.app_sr_active ( ),
.app_ref_ack ( ),
.app_zq_ack ( ),
//全局信号-------------------------------------------
.ui_clk (ui_clk ),
.ui_clk_sync_rst (ui_clk_sync_rst ),
.init_calib_complete (init_calib_complete),
.sys_clk_i (sys_clk_i ),
.clk_ref_i (clk_ref_i ),
.sys_rst (sys_rst_n )
);
//============================< 例化PLL模块 >===============================================
clk_wiz_0 u_clk_wiz_0
(
.clk_out1 (clk_200 ), // output clk_out1
.reset (1'b0 ), // input resetn
.locked (locked ), // output locked
.clk_in1 (sys_clk ) // input clk_in1
);
//============================< 例化DDR3模型 >===============================================
ddr3_model ddr3_model_inst
(
.rst_n (sys_rst_n ),
.ck (ddr3_ck_p ),
.ck_n (ddr3_ck_n ),
.cke (ddr3_cke ),
.cs_n (ddr3_cs_n ),
.ras_n (ddr3_ras_n ),
.cas_n (ddr3_cas_n ),
.we_n (ddr3_we_n ),
.dm_tdqs (ddr3_dm ),
.ba (ddr3_ba ),
.addr (ddr3_addr ),
.dq (ddr3_dq ),
.dqs (ddr3_dqs_p ),
.dqs_n (ddr3_dqs_n ),
.tdqs_n ( ), //NULL
.odt (ddr3_odt )
);

endmodule

4.2、仿真结果
接下来我们分别观看各个子模块的仿真结果来验证设计功能。

4.2.1、MIG IP模块
在本次设计中,MIG IP模块是最直接对DDR3芯片操作的模块,我们先来观察MIG IP的仿真结果如何:

从上图中可以看到:在MIG初始化完成后,开始了一轮突发写数据的过程,然后突发写停止,开始一轮突发读数据的过程。虽然数据的具体大小从上图无法看到,但至少整个流程是符合我们的预先设计的。再分别截取局部图。

上图中:MIG初始化完成后开始突发写数据,写入的地址从0开始,往后每写入一个数据,地址累加8----DDR3固定突发长度为8,写入的数据从0开始累加1.目前是符合设计要求的。

上图中:是突发写的结束阶段。写入的最后一个地址是4088,写入的最后一个数据是511,说明一共写入了512个数据(0-511)。最后阶段已经开始进行了突发读操作。目前也是符合设计要求的。

看完了突发写操作,再看看突发读操作相关截图。

上图中:是突发读的开始阶段。MIG从地址0开始进行读取操作,其后读取的地址累加8。从发出第一条读指令到第一个读取的数据(伴随读取有效信号)出现需要几个周期的时间,可以看到读出的数据也是从0开始累加1,与写入的数据一致。证明我们的读写操作无误。

上图中:是突发读的结束阶段。最后一个读取的地址是4088,读出的最后一个数据是511,同样与写入的数据一致。

从这里可以看到整个仿真是符合我们预期设计的。但是我们还需要看一下设计的突发写模块ddr3_wr以及突发读模块ddr3_rd的仿真波形。

4.2.2、突发写模块ddr3_wr
老规矩,先看下整体过程,了解下大概:

上图中:拉高了突发写开始信号,标志开始一轮突发写操作(突发长度512、突发首地址0),然后突发写忙信号被拉高,表示此时正在进行突发写操作(可用于后续写、读仲裁的判断),接着不停地写入数据,待写入最后一个数据后,拉高突发写完成标志,表示一次突发写操作完成,同时拉低突发写忙信号,表示此时没有进行突发写操作。

上图中:突发写操作的首地址为0,其后地址累加8,写入的数据从0开始累加1。

上图中:突发写操作的地址累加8,累加1直到511。完成最后一个突发写操作后(写入数据511),拉高突发写完成标志,表示一次突发写操作完成,同时拉低突发写忙信号,表示此时没有进行突发写操作。

4.2.3、突发读模块ddr3_rd
老规矩,先看下整体过程,了解下大概:

上图中:拉高了突发读开始信号,标志开始一轮突发读操作(突发长度512、突发首地址0),然后突发读忙信号被拉高,表示此时正在进行突发读操作(可用于后续写、读仲裁的判断),一段时间后不停地读出数据,待读出最后一个数据后,拉高突发读完成标志,表示一次突发读操作完成,同时拉低突发读忙信号,表示此时没有进行突发读操作。

上图中:从地址0开始读取数据,其后地址累加8,发出读指令后需要一定的时间才能读到对应地址的数据。读出的数据与写入的数据一致。

上图中:对最后一个地址进行读取后,一段时间读到最后一个数据511。然后拉高突发读完成标志,表示一次突发读操作完成,同时拉低突发读忙信号,表示此时没有进行突发读操作。

5、其他
我们下一篇再来讲讲FIFO控制模块,以及将整个类FIFO模块仿真和下板验证。

最新文章

最新文章