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

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

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

本文为下篇,建议与上篇一起阅读,有利于理解:

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

1、FIFO控制模块
本模块的主要作用是例化两个FIFO:写FIFO、读FIFO。

写FIFO:写位宽16bit,写端口与用户端相连,写入数据来自用户端;读位宽128bit(DDR3固定突发长度8,位宽16bit),读端口与ddr3_wr模块相连,从FIFO读取数据写入DDR3。
读FIFO:写位宽128bit(DDR3固定突发长度8,位宽16bit),写端口与ddr3_rd模块相连,写入数据来自DDR3;读位宽16bit,读端口与用户端相连,用户从FIFO读取数据即为从DDR3中的缓存数据。
同时在每次突发操作结束后生成下次突发操作的首地址,以及新的突发请求。

1.1、端口
端口主要分为3个部分:

全局信号:时钟、复位以及MIG初始化完成信号;

1.2、Verilog代码
完整的Verilog代码如下(代码不复杂,主要实现读写请求已经突发操作的首地址,其他请参考注释):用户端:
写FIFO端口:提供必要信息(起止地址、突发长度等),然后就可以像使用FIFO一样进行写入操作
读FIFO端口:提供必要信息(起止地址、突发长度等),然后就可以像使用FIFO一样进行读取操作
突发读写模块端:控制突发读写模块对MIG IP核进行操作

1.2、Verilog代码
完整的Verilog代码如下(代码不复杂,主要实现读写请求已经突发操作的首地址,其他请参考注释):

//**************************************************************************
// *** 名称 : fifo_ctrl
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 例化FIFO,并生成地址、突发长度等信息
//**************************************************************************
module fifo_ctrl #(
parameter integer FIFO_DATA_WIDTH = 16 ,
parameter integer FIFO_ADDR_WIDTH = 28 ,
parameter integer MIG_DATA_WIDTH = 128 , //数据位宽,突发长度为8,16bit,共128bit
parameter integer MIG_ADDR_WIDTH = 28 //根据MIG例化而来
)(
//时钟和复位 -----------------------------------------------------
input ui_clk , //用户时钟
input ui_clk_sync_rst , //复位,高有效
input init_calib_complete , //MIG初始化完成标志
//用户操作信号 --------------------------------------------
//写fifo信号-------------------------------------------------
input wr_fifo_wr_clk , //写FIFO写时钟
input wr_fifo_wr_req , //写FIFO写请求
input [FIFO_DATA_WIDTH - 1:0] wr_fifo_wr_data , //写FIFO写数据
input wr_fifo_rst , //写FIFO复位信号
input [FIFO_ADDR_WIDTH - 1:0] wr_b_addr , //写DDR3首地址
input [FIFO_ADDR_WIDTH - 1:0] wr_e_addr , //写DDR3末地址
input [FIFO_ADDR_WIDTH - 1:0] wr_len , //写DDR3突发长度
output [8:0] wr_fifo_num , //写fifo中的数据量
//读fifo信号-------------------------------------------------
input rd_fifo_rd_clk , //读FIFO读时钟
input rd_fifo_rd_req , //读FIFO读请求
output [FIFO_DATA_WIDTH - 1:0] rd_fifo_rd_data , //读FIFO读数据
input [FIFO_ADDR_WIDTH - 1:0] rd_b_addr , //读DDR3首地址
input [FIFO_ADDR_WIDTH - 1:0] rd_e_addr , //读DDR3末地址
input [FIFO_ADDR_WIDTH - 1:0] rd_len , //读DDR3数据突发长度
input rd_fifo_rst , //读FIFO复位信号
output [8:0] rd_fifo_num , //读fifo中的数据量
//控制信号----------------------------------------------------
input read_valid , //DDR3读使能
//突发读、写模块信号 --------------------------------------------
//突发写模块 ------------------------------------------------
input wr_burst_busy , //高电平表示正在进行突发写操作
input wr_burst_done , //高电平表示完成了一次突发写操作
input wr_burst_ack , //突发写模块要写入的数据有效
output [MIG_DATA_WIDTH - 1:0] wr_burst_data , //突发写模块要写入的数据
output [FIFO_ADDR_WIDTH - 1:0] wr_burst_len , //突发写长度
output reg wr_burst_req , //请求进行突发写
output reg [FIFO_ADDR_WIDTH - 1:0] wr_burst_addr , //突发写入的首地址
//突发读模块 ------------------------------------------------
input rd_burst_busy , //高电平表示正在进行突发读操作
input rd_burst_done , //高电平表示完成了一次突发读操作
input rd_burst_ack , //突发读模块读出的数据有效
output [FIFO_ADDR_WIDTH - 1:0] rd_burst_len , //突发读长度
output [MIG_DATA_WIDTH - 1:0] rd_burst_data , //突发读模块读出的数据
output reg rd_burst_req , //请求进行突发读
output reg [FIFO_ADDR_WIDTH - 1:0] rd_burst_addr //突发读的首地址
);
//*********************************************************************************************
//** main code
//**********************************************************************************************

assign wr_burst_len = wr_len;
assign rd_burst_len = rd_len;

//wr_burst_req,rd_burst_req:读写请求信号
always@(posedge ui_clk)begin
if(ui_clk_sync_rst)begin
wr_burst_req <= 1'b0;
rd_burst_req <= 1'b0;
end
//初始化完成后响应读写请求,优先执行写操作,防止写入ddr3中的数据丢失
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; //写请求有效
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; //读请求有效
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
end
else begin //MIG初始化未完成
wr_burst_req <= 1'b0;
rd_burst_req <= 1'b0;
end
end

//wr_burst_addr:ddr3写地址
always@(posedge ui_clk)begin
if(ui_clk_sync_rst)
wr_burst_addr <= wr_b_addr;
else if(wr_fifo_rst)
wr_burst_addr <= wr_b_addr; //复位fifo则地址为初始地址
else if(wr_burst_done) //一次突发写结束,更改写地址
begin
if(wr_burst_addr < (wr_e_addr - wr_len * 8))
wr_burst_addr <= wr_burst_addr + wr_len * 8; //未达到末地址,写地址累加
else
wr_burst_addr <= wr_b_addr; //到达末地址,回到写起始地址
end
end

//rd_burst_addr:ddr3读地址
always@(posedge ui_clk)begin
if(ui_clk_sync_rst)
rd_burst_addr <= rd_b_addr;
else if(rd_fifo_rst)
rd_burst_addr <= rd_b_addr;
else if(rd_burst_done) //一次突发读结束,更改读地址
begin
if(rd_burst_addr < (rd_e_addr - rd_len * 8))
rd_burst_addr <= rd_burst_addr + rd_len * 8; //读地址未达到末地址,读地址累加
else
rd_burst_addr <= rd_b_addr; //到达末地址,回到首地址
end
end

//例化写FIFO-----------------------------------------
wr_fifo_16_128 wr_fifo_16_128_inst (
//写数据接口 ----------------------------------------------
.wr_clk (wr_fifo_wr_clk ),
.wr_rst (wr_fifo_rst ),
.wr_en (wr_fifo_wr_req ),
.din (wr_fifo_wr_data ),
//读数据接口 ----------------------------------------------
.rd_clk (ui_clk ),
.rd_rst (ui_clk_sync_rst ),
.rd_en (wr_burst_ack ),
.dout (wr_burst_data ),
//指示接口 ----------------------------------------------
.full ( ),
.empty ( ),
.rd_data_count (wr_fifo_num ) // output wire [8 : 0] rd_data_count
);

//例化读FIFO-----------------------------------------
rd_fifo_128_16 rd_fifo_128_16_inst (
//写数据接口 ----------------------------------------------
.wr_clk (ui_clk ),
.wr_rst (ui_clk_sync_rst ),
.din (rd_burst_data ),
.wr_en (rd_burst_ack ),
//读数据接口 ----------------------------------------------
.rd_clk (rd_fifo_rd_clk ),
.rd_rst (rd_fifo_rst ),
.rd_en (rd_fifo_rd_req ),
.dout (rd_fifo_rd_data ),
//指示接口 ----------------------------------------------
.full ( ),
.empty ( ),
.wr_data_count (rd_fifo_num ) // output wire [8 : 0] wr_data_count
);

endmodule

2、顶层模块
顶层模块对突发写模块、突发读模块以及FIFO控制模块进行例化实现模块化设计,实现对MIG IP核的控制,从而使得对DDR3芯片的操作就像使用FIFO一样简单。

2.1、端口
端口主要分为3个部分:

全局信号:时钟、复位以及MIG初始化完成信号;

用户端:
写FIFO端口:提供必要信息(起止地址、突发长度等),然后就可以像使用FIFO一样进行写入操作
读FIFO端口:提供必要信息(起止地址、突发长度等),然后就可以像使用FIFO一样进行读取操作
MIG端:对MIG端进行操作从而实现间接控制DDR3芯片的功能

2.2、Verilog代码
完整的Verilog代码如下(代码不复杂,请参考注释。需要注意的是突发读模块与突发写模块对MIG IP核的控制使用的是同一命令线,所以需要用2选1控制器来对其进行分时复用,以免使用冲突):
//**************************************************************************
// *** 名称 : mig_ctrl
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 例化写模块、读模块和FIFO控制模块来实现对MIG IP核的控制
//**************************************************************************
module mig_ctrl #(
parameter integer FIFO_DATA_WIDTH = 16 ,
parameter integer FIFO_ADDR_WIDTH = 28 ,
parameter integer MIG_DATA_WIDTH = 128 , //数据位宽,突发长度为8,16bit,共128bit
parameter integer MIG_ADDR_WIDTH = 28 //根据MIG例化而来
)(
//时钟和复位 ------------------------------------------------------
input ui_clk , //用户时钟
input ui_clk_sync_rst , //复位,高有效
input init_calib_complete , //MIG初始化完成标志
//用户操作信号 -------------------------------------------------
//写fifo信号---------------------------------------------------
input wr_fifo_wr_clk , //写FIFO写时钟
input wr_fifo_wr_req , //写FIFO写请求
input [FIFO_DATA_WIDTH - 1:0] wr_fifo_wr_data , //写FIFO写数据
input wr_fifo_rst , //写FIFO复位信号
input [FIFO_ADDR_WIDTH - 1:0] wr_b_addr , //写DDR3首地址
input [FIFO_ADDR_WIDTH - 1:0] wr_e_addr , //写DDR3末地址
input [FIFO_ADDR_WIDTH - 1:0] wr_len , //写DDR3突发长度
output [8:0] wr_fifo_num , //写fifo中的数据量
//读fifo信号----------------------------------------------
input rd_fifo_rd_clk , //读FIFO读时钟
input rd_fifo_rd_req , //读FIFO读请求
output [FIFO_DATA_WIDTH - 1:0] rd_fifo_rd_data , //读FIFO读数据
input [FIFO_ADDR_WIDTH - 1:0] rd_b_addr , //读DDR3首地址
input [FIFO_ADDR_WIDTH - 1:0] rd_e_addr , //读DDR3末地址
input [FIFO_ADDR_WIDTH - 1:0] rd_len , //读DDR3数据突发长度
input rd_fifo_rst , //读FIFO复位信号
output [8:0] rd_fifo_num , //读fifo中的数据量
//其他----------------------------------------------
input read_valid , //DDR3读使能
//MIG接口 --------------------------------------------------------------------------------------
//命令接口 ----------------------------------------------
input app_rdy , //MIG 命令接收准备好标
output app_en , //MIG IP发送命令使能
output [2:0] app_cmd , //MIG IP核操作命令,读或者写
output [MIG_ADDR_WIDTH - 1:0] app_addr , //DDR3地址
//读接口 ----------------------------------------------
input [MIG_DATA_WIDTH - 1:0] app_rd_data , //从MIG中读出的数据
input app_rd_data_end , //从MIG中读出的最后一个数据
input app_rd_data_valid , //从MIG中读出的数据有效
//写接口 ----------------------------------------------
input app_wdf_rdy , //MIG数据接收准备好
output app_wdf_wren , //用户写数据使能
output app_wdf_end , //突发写当前时钟最后一个数据
output [(MIG_DATA_WIDTH/8) - 1:0] app_wdf_mask ,
output [MIG_DATA_WIDTH - 1:0] app_wdf_data //用户写数据
);

wire wr_burst_req ; //请求对DDR3写入数据
wire [FIFO_ADDR_WIDTH - 1:0] wr_burst_addr ; //当前写入DDR3的地址
wire wr_burst_busy ; //高电平表示正在进行突发写操作
wire wr_burst_done ; //高电平表示完成了一次突发写操作
wire [MIG_DATA_WIDTH - 1:0] wr_burst_data ; //突发写模块要写入的数据
wire wr_burst_ack ; //突发写模块要写入的数据有效
wire [FIFO_ADDR_WIDTH - 1:0] wr_burst_len ; //突发写长度
wire rd_burst_req ; //请求从DDR3读出数据
wire [FIFO_ADDR_WIDTH - 1:0] rd_burst_addr ; //当前读出DDR3的地址
wire rd_burst_busy ; //高电平表示正在进行突发读操作
wire rd_burst_done ; //高电平表示完成了一次突发读操作
wire [MIG_DATA_WIDTH - 1:0] rd_burst_data ; //突发读模块读出的数据
wire rd_burst_ack ; //突发读模块读出的数据有效
wire [FIFO_ADDR_WIDTH - 1:0] rd_burst_len ; //突发读长度

//MIG接口 -----------------------------------------------------
wire app_en_wr ;
wire [2:0] app_cmd_wr ;
wire [MIG_ADDR_WIDTH - 1:0] app_addr_wr ;
wire app_en_rd ;
wire [2:0] app_cmd_rd ;
wire [MIG_ADDR_WIDTH - 1:0] app_addr_rd ;

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

//突发读写模块共用MIG的控制信号,所以需要分时复用
assign app_en = wr_burst_busy ? app_en_wr : app_en_rd ;
assign app_cmd = wr_burst_busy ? app_cmd_wr : app_cmd_rd ;
assign app_addr = wr_burst_busy ? app_addr_wr : app_addr_rd ;

fifo_ctrl #(
.FIFO_DATA_WIDTH (FIFO_DATA_WIDTH ),
.FIFO_ADDR_WIDTH (FIFO_ADDR_WIDTH ),
.MIG_DATA_WIDTH (MIG_DATA_WIDTH ), //数据位宽,突发长度为8,16bit,共128bit
.MIG_ADDR_WIDTH (MIG_ADDR_WIDTH ) //根据MIG例化而来
)
fifo_ctrl_inst(
//时钟和复位 -----------------------------------------------------
.ui_clk (ui_clk ), //用户时钟
.ui_clk_sync_rst (ui_clk_sync_rst ), //复位,高有效
.init_calib_complete (init_calib_complete), //MIG初始化完成标志
//用户操作信号 ----------------------------------------------------
//写fifo信号-------------------------------------------------
.wr_fifo_wr_clk (wr_fifo_wr_clk ), //写FIFO写时钟
.wr_fifo_wr_req (wr_fifo_wr_req ), //写FIFO写请求
.wr_fifo_wr_data (wr_fifo_wr_data ), //写FIFO写数据
.wr_fifo_rst (wr_fifo_rst ), //写FIFO复位信号
.wr_b_addr (wr_b_addr ), //写DDR3首地址
.wr_e_addr (wr_e_addr ), //写DDR3末地址
.wr_len (wr_len ), //写DDR3突发长度
.wr_fifo_num (wr_fifo_num ), //写fifo中的数据量
//读fifo信号-------------------------------------------------
.rd_fifo_rd_clk (rd_fifo_rd_clk ), //读FIFO读时钟
.rd_fifo_rd_req (rd_fifo_rd_req ), //读FIFO读请求
.rd_fifo_rd_data (rd_fifo_rd_data ), //读FIFO读数据
.rd_b_addr (rd_b_addr ), //读DDR3首地址
.rd_e_addr (rd_e_addr ), //读DDR3末地址
.rd_len (rd_len ), //读DDR3数据突发长度
.rd_fifo_rst (rd_fifo_rst ), //读FIFO复位信号
.rd_fifo_num (rd_fifo_num ), //读fifo中的数据量
//控制信号----------------------------------------------------
.read_valid (read_valid ), //DDR3读使能
//突发读、写模块信号 --------------------------------------------
//突发写模块 ------------------------------------------------
.wr_burst_busy (wr_burst_busy ), //高电平表示正在进行突发写操作
.wr_burst_done (wr_burst_done ), //高电平表示完成了一次突发写操作
.wr_burst_ack (wr_burst_ack ), //突发写模块要写入的数据有效
.wr_burst_data (wr_burst_data ), //突发写模块要写入的数据
.wr_burst_req (wr_burst_req ), //请求进行突发写
.wr_burst_addr (wr_burst_addr ), //突发写入的首地址
.wr_burst_len (wr_burst_len ), //突发写长度
//突发读模块 ------------------------------------------------
.rd_burst_busy (rd_burst_busy ), //高电平表示正在进行突发读操作
.rd_burst_done (rd_burst_done ), //高电平表示完成了一次突发读操作
.rd_burst_ack (rd_burst_ack ), //突发读模块读出的数据有效
.rd_burst_data (rd_burst_data ), //突发读模块读出的数据
.rd_burst_req (rd_burst_req ), //请求进行突发读
.rd_burst_addr (rd_burst_addr ), //突发读的首地址
.rd_burst_len (rd_burst_len ) //突发读长度
);

ddr3_wr #(
.DATA_WIDTH (MIG_DATA_WIDTH ), //数据位宽,突发长度为8,16bit,共128bit
.ADDR_WIDTH (MIG_ADDR_WIDTH ) //根据MIG例化而来
)
ddr3_wr_inst(
.ui_clk (ui_clk ), //用户时钟
.ui_clk_sync_rst (ui_clk_sync_rst ), //复位,高有效
//突发写相关信号
.wr_burst_start (wr_burst_req ),
.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 用户端控制接口
.app_en (app_en_wr ),
.app_cmd (app_cmd_wr ),
.app_addr (app_addr_wr ),

.app_rdy (app_rdy ), //MIG 命令接收准备好标致
.app_wdf_rdy (app_wdf_rdy ), //MIG数据接收准备好
.app_wdf_wren (app_wdf_wren ), //用户写数据使能
.app_wdf_end (app_wdf_end ), //突发写当前时钟最后一个数据
.app_wdf_mask (app_wdf_mask ),
.app_wdf_data (app_wdf_data ) //用户写数据
);

ddr3_rd #(
.DATA_WIDTH (MIG_DATA_WIDTH ), //数据位宽,突发长度为8,16bit,共128bit
.ADDR_WIDTH (MIG_ADDR_WIDTH ) //根据MIG例化而来
)
ddr3_rd_inst(
.ui_clk (ui_clk ), //用户时钟
.ui_clk_sync_rst (ui_clk_sync_rst ), //复位,高有效
//突发读相关信号
.rd_burst_start (rd_burst_req ),
.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 ), //突发写忙状态
//MIG 用户端控制接口
.app_en (app_en_rd ),
.app_cmd (app_cmd_rd ),
.app_addr (app_addr_rd ),

.app_rdy (app_rdy ), //MIG 命令接收准备好标致
.app_rd_data (app_rd_data ), //从MIG中读出的数据
.app_rd_data_end (app_rd_data_end ), //从MIG中读出的最后一个数据
.app_rd_data_valid (app_rd_data_valid ) //从MIG中读出的数据有效
);

endmodule

3、Testbench与仿真结果

3.1、Testbench
Testbench直接来对顶层进行测试。预期实现功能:1、从地址1开始,突发写入512个数据,写入的数据从1累加到512;2、从地址0开始突发读取512个数据。根据读出的数据与写入数据是否一致来判断类FIFO模块是否设计成功;3、读取操作一直循环进行。

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

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

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

需要注意的是:读FIFO的读策略是其中的数据量小于一定的阈值(突发读长度)时就会发起对DDR3的读取请求。所以在MIG初始化完成后,此时读FIFO中的数据量小于设定阈值,会对DDR3进行读取,但此时DDR3的突发写操作还未完成,所以读取的数据都是无效数据。为避免这种情况的发生,设计了read_valid这个信号,只有在这个信号为高时,才可以对DDR3进行读取操作,所以在testbench中一直等到数据全部写完后才拉高了read_valid信号。在大数据量的情况下(比如摄像头数据缓存到DDR3),该信号一直拉高即可。

完整的testbench如下:
//**************************************************************************
// *** 名称 : tb_mig_clrl
// *** 作者 : 孤独的单刀
// *** 博客 : https://blog.csdn.net/wuzhikaidetb
// *** 日期 : 2021.12
// *** 描述 : 对MIG控制模块单独测试
//**************************************************************************
`timescale 1ns/100ps
module tb_mig_clrl();
//============================< 信号定义 >======================================
//parameter define-----------------------------------
parameter integer FIFO_DATA_WIDTH = 16 ;
parameter integer FIFO_ADDR_WIDTH = 28 ;
parameter integer MIG_DATA_WIDTH = 128 ; //数据位宽,突发长度为8,16bit,共128bit
parameter integer MIG_ADDR_WIDTH = 28 ; //根据MIG例化而来

//时钟和复位 ------------------------------------
reg sys_clk ; //系统时钟
//MIG相关----------------------------------------------------
reg sys_rst_n ; //系统复位
wire ui_clk ; //用户时钟
wire ui_clk_sync_rst ; //用户复位信号
wire init_calib_complete ; //校准完成信号
wire clk_ref_i ; //DDR3参考时钟
wire sys_clk_i ; //MIG IP核输入时钟
wire app_rdy ; //MIG 命令接收准备好标
wire app_en ; //MIG IP发送命令使能
wire [2:0] app_cmd ; //MIG IP核操作命令,读或者写
wire [MIG_ADDR_WIDTH - 1:0] app_addr ; //DDR3地址
wire [MIG_DATA_WIDTH - 1:0] app_rd_data ; //从MIG中读出的数据
wire app_rd_data_end ; //从MIG中读出的最后一个数据
wire app_rd_data_valid ; //从MIG中读出的数据有效
wire app_wdf_rdy ; //MIG数据接收准备好
wire app_wdf_wren ; //用户写数据使能
wire app_wdf_end ; //突发写当前时钟最后一个数据
wire [(MIG_DATA_WIDTH/8) - 1:0] app_wdf_mask ;
wire [MIG_DATA_WIDTH - 1:0] app_wdf_data ; //用户写数据
//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 define -----------------------------------
//写fifo信号----------------------------------------------
reg wr_fifo_wr_clk ; //写FIFO写时钟
reg wr_fifo_wr_req ; //写FIFO写请求
reg [FIFO_DATA_WIDTH - 1:0] wr_fifo_wr_data ; //写FIFO写数据

wire [8:0] wr_fifo_num ; //写fifo中的数据量
//读fifo信号----------------------------------------------
reg rd_fifo_rd_clk ; //读FIFO读时钟
reg rd_fifo_rd_req ; //读FIFO读请求
wire [FIFO_DATA_WIDTH - 1:0] rd_fifo_rd_data ; //读FIFO读数据
wire [8:0] rd_fifo_num ; //读fifo中的数据量

reg read_valid ; //读使能,为高才能从DDR3读取数据
//PLL相关----------------------------------------
wire locked ; //锁相环频率稳定标志
wire clk_200 ; //200M时钟

assign clk_ref_i = clk_200 ; //DDR3参考时钟,200M
assign sys_clk_i = clk_200 ; //MIG IP核输入时钟,200M

//============================< 测试条件设置 >===============================
//设置初始测试条件--------------------------------------------------------
initial begin
sys_clk = 0 ; //初始时钟为0
wr_fifo_wr_clk = 0 ; //初始写时钟为0
rd_fifo_rd_clk = 0 ; //初始读时钟为0
rd_fifo_rd_req <= 0 ;
read_valid <= 0 ;
sys_rst_n <= 0 ; //初始复位
#50 //50个时钟周期后
sys_rst_n <= 1'b1 ; //拉高复位,系统进入工作状态
wait(wr_fifo_wr_data == 512) ;
read_valid <= 1 ; //写完数据后才可以从DDR3中读取数据
wait(rd_fifo_num == 16) ;
rd_fifo_rd_req <= 1 ;
end

always@(posedge wr_fifo_wr_clk)begin
if(~sys_rst_n)begin
wr_fifo_wr_data <= 0;
wr_fifo_wr_req <= 1;
end
else if(init_calib_complete)begin
if(wr_fifo_wr_data == 512)begin //512个数据后不再增长
wr_fifo_wr_data <= wr_fifo_wr_data;
wr_fifo_wr_req <= 0;
end
else begin
wr_fifo_wr_data <= wr_fifo_wr_data + 1;
wr_fifo_wr_req <= 1;
end
end
else begin
wr_fifo_wr_data <= 0;
wr_fifo_wr_req <= 0;
end
end

//设置时钟----------------------------------------------------------------
always #10 sys_clk = ~sys_clk; //系统时钟周期20ns
always #5 wr_fifo_wr_clk = ~wr_fifo_wr_clk; //写数据频率100M
always #20 rd_fifo_rd_clk = ~rd_fifo_rd_clk; //读数据频率25M

//============================< 例化mig_ctrl模块 >===============================================
mig_ctrl #(
.FIFO_DATA_WIDTH (FIFO_DATA_WIDTH ),
.FIFO_ADDR_WIDTH (FIFO_ADDR_WIDTH ),
.MIG_DATA_WIDTH (MIG_DATA_WIDTH ), //数据位宽,突发长度为8,16bit,共128bit
.MIG_ADDR_WIDTH (MIG_ADDR_WIDTH ) //根据MIG例化而来
)
mig_ctrl_inst(
.ui_clk (ui_clk ), //用户时钟
.ui_clk_sync_rst (ui_clk_sync_rst ), //复位,高有效
.init_calib_complete (init_calib_complete), //MIG初始化完成标志

.wr_fifo_wr_clk (wr_fifo_wr_clk ), //写FIFO写时钟
.wr_fifo_wr_req (wr_fifo_wr_req ), //写FIFO写请求
.wr_fifo_wr_data (wr_fifo_wr_data ), //写FIFO写数据
.wr_fifo_rst (~sys_rst_n ), //写FIFO复位信号
.wr_b_addr (0 ), //写DDR3首地址
.wr_e_addr (511 ), //写DDR3末地址
.wr_len (16 ), //写DDR3突发长度
.wr_fifo_num (wr_fifo_num ), //写fifo中的数据量

.rd_fifo_rd_clk (rd_fifo_rd_clk ), //读FIFO读时钟
.rd_fifo_rd_req (rd_fifo_rd_req ), //读FIFO读请求
.rd_fifo_rd_data (rd_fifo_rd_data ), //读FIFO读数据
.rd_b_addr (0 ), //读DDR3首地址
.rd_e_addr (511 ), //读DDR3末地址
.rd_len (16 ), //读DDR3数据突发长度
.rd_fifo_rst (~sys_rst_n ), //读FIFO复位信号
.rd_fifo_num (rd_fifo_num ), //读fifo中的数据量

.read_valid (read_valid ), //DDR3读使能

.app_en (app_en ), //MIG IP发送命令使能
.app_cmd (app_cmd ), //MIG IP核操作命令,读或者写
.app_addr (app_addr ), //DDR3地址

.app_rdy (app_rdy ), //MIG 命令接收准备好标
.app_rd_data (app_rd_data ), //从MIG中读出的数据
.app_rd_data_end (app_rd_data_end ), //从MIG中读出的最后一个数据
.app_rd_data_valid (app_rd_data_valid ), //从MIG中读出的数据有效
.app_wdf_rdy (app_wdf_rdy ), //MIG数据接收准备好
.app_wdf_wren (app_wdf_wren ), //用户写数据使能
.app_wdf_end (app_wdf_end ), //突发写当前时钟最后一个数据
.app_wdf_mask (app_wdf_mask ),
.app_wdf_data (app_wdf_data ) //用户写数据
);
//============================< 例化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

3.2、仿真结果
接下来我们观看仿真结果来验证设计功能:

从上图我们可以看到:在MIG IP核完成初始化后,用于开始请求对DDR3进行写入数据;写入数据完成后,拉高了read_valid信号,开始允许对DDR3进行读取操作,然后拉高了对DDR3读操作的请求,同时从DDR3中读到了数据。

上面写入、读取的数据没有看清,接下来看下写入、读取数据的局部图(仅展示开头、结尾):

写开头:写入数据从1开始累加,起止地址分别为:0、511

写结尾:写入数据从1开始累加直到512共512个数据,起止地址分别为:0、511

读开头:读取的数据从1开始累加,与写入数据一致,起止地址分别为:0、511

读结尾(第一次):读取的数据从1开始累加,与写入数据一致,数据从1开始累加直到512共512个数据,起止地址分别为:0、511

4、其他
MIG有bug,每次重新点开,Input Clock Period都会变成800M,需要重新生成一下
上板验证就暂时不做了,下次再一起做个DDR3+HDMI+UART的实验验证一下

最新文章

最新文章