基于Vivado MIG IP核的DDR3读写实验(top_rom_ddr/ddr_top)
一、前言
-
关于Vivado MIG IP核详细配置可以参考我之前的文章:基于Vivado MIG IP核的DDR3控制器(DDR3_CONTROL)
-
关于MIG IP核的用户端的接口时序可以参考这篇文章:XILINX 的 MIG IP(非AXI4)接口时序以及控制
二、实验内容
本次实验的内容主要是通过MIG IP核向DDR3读写数据,DDR3的接口时序由ddr_top
模块提供:
ddr_top
模块的数据来源是wr_fifo,wr_fifo的数据实际来自top_sd_photo
模块(本实验仿真时在tb文件中手动提供数据)ddr_top
模块的数据输出到rd_fifo,rd_fifo的数据被timing_gen
模块读出(本实验只输出观察)。
top_sd_photo
和timing_gen
模块在本专栏中前面的文章中都介绍过,这里就不说了,重点只是如何提供MIG IP核用户端的接口时序,从而对DDR3完成读写操作,也即ddr_top
模块的设计。
1、顶层模块:
2、ddr_top模块:
mem_test
模块:发送读写请求mem_burst
模块:提供MIG IP核用户端的接口时序DDR3_CONTROL
:MIG IP核,用来将用户端的读写时序转换成直接提供给DDR3的读写时序
三、程序设计
这里我们只讲解ddr_top中的三个子模块。
1、mem_test 模块:就是利用状态机生成读写请求、读写数据长度、读写起始地址的信号,并发送到mem_burst模块。
module mem_test
#(
parameter MEM_DATA_BITS = 256, //8突发,8*32=256
parameter ADDR_BITS = 29
)
(
input rd_fifo_full, //来自rd_fifo的满信号
input rst, //复位
input mem_clk, //来自mig IP核的用户时钟
//mem_burst连接信号
input wr_burst_data_req, //ddr写数据信号
input rd_burst_finish, //ddr读完成
input wr_burst_finish, //ddr写完成
output reg rd_burst_req, //ddr读请求
output reg wr_burst_req, //ddr写请求
output reg[15:0] rd_burst_len, //读ddr数据长度
output reg[15:0] wr_burst_len, //写ddr数据长度
output reg[ADDR_BITS - 1:0] rd_burst_addr, //读ddr首地址
output reg[ADDR_BITS - 1:0] wr_burst_addr, //写ddr首地址
//output[MEM_DATA_BITS - 1:0] wr_burst_data, //输出给ddr的测试数据 正常应该是wr_fifo给数据
output reg read_ddr //输出给外部,用来标志rd_fifo可以读取的信号
);
localparam IDLE = 3'd0;
localparam MEM_WRITE = 3'd1;
localparam MEM_READ = 3'd2;
reg[2:0] state;
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
state <= IDLE;
wr_burst_req <= 1'b0;
rd_burst_req <= 1'b0;
rd_burst_len <= 16'd0;
wr_burst_len <= 16'd0; //512*512/8 = 32768个数一次写入
rd_burst_addr <= 0; //读写起始地址均为0
wr_burst_addr <= 0;
read_ddr <= 0;
end
else
begin
case(state)
IDLE:
begin
state <= MEM_WRITE; //复位后进入写操作,将写请求拉高,发送到mem_burst模块
wr_burst_req <= 1'b1;
wr_burst_len <= 16'd30;
wr_burst_addr <= 29'h0;
end
MEM_WRITE:
begin
if(wr_burst_finish)//写突发结束进入读突发阶段
begin
state <= MEM_READ; //检测到wr_burst_finish后,将状态切换至read状态
wr_burst_req <= 1'b0; //写请求拉低,读请求拉高,给出读数据个数,并给出读的起始地址,发送到mem_burst模块
rd_burst_len <= 16'd30;
rd_burst_addr <= 29'h0;
end
end
MEM_READ:
begin
if(rd_burst_finish)//读突发结束拉高read_ddr,告诉外部可以读取rd_fifo了
//因为从ddr读出的数据会先缓存到rd_fifo中
begin
read_ddr <= 1;
rd_burst_req <= 1'b0;
end
else if(!rd_fifo_full)
rd_burst_req <= 1'b1;
else
rd_burst_req <= 0;
end
default:
state <= IDLE;
endcase
end
end
endmodule
2、mem_burst模块:也是利用状态机以及其他逻辑生成MIG IP核用户端所需的各种信号。
module mem_burst
#(
parameter MEM_DATA_BITS = 256,//8突发,8*32=256
parameter ADDR_BITS = 29
)
(
input rst,
input mem_clk, /*来自mig IP核的用户时钟*/
//mem_test模块的连接信号
input rd_burst_req, /*读ddr请求*/
input wr_burst_req, /*写ddr请求*/
input[15:0] rd_burst_len, /*读数据长度*/
input[15:0] wr_burst_len, /*写数据长度*/
input[ADDR_BITS - 1:0] rd_burst_addr, /*读首地址*/
input[ADDR_BITS - 1:0] wr_burst_addr, /*写首地址*/
output rd_burst_finish, /*读完成*/
output wr_burst_finish, /*写完成*/
//wr_fifo的连接信号
input wr_fifo_empty, /*来自wr_fifo的fifo空信号,因为要向该fifo读数据,所以关注空信号*/
input[MEM_DATA_BITS - 1:0] wr_burst_data, /*来自wr_fifo的要写入ddr的数据*/
output wr_burst_data_req, /*输出给wr_fifo的数据请求信号,作为其rd_en信号*/
//rd_fifo的连接信号
input rd_fifo_full, /*来自rd_fifo的满信号,因为要往该fifo写数据,所以关注满信号*/
output rd_burst_data_valid, /*数据有效信号,作为rd_fifo的wr_en信号*/
output[MEM_DATA_BITS - 1:0] rd_burst_data, /*ddr读出的要写入rd_fifo的数据*/
//下面的信号输出给mig IP核
output[ADDR_BITS-1:0] app_addr,
output[2:0] app_cmd,
output app_en,
output [MEM_DATA_BITS-1:0] app_wdf_data,
output app_wdf_end,
output [MEM_DATA_BITS/8-1:0] app_wdf_mask,
output app_wdf_wren,
//下面的信号来自mig IP核
input [MEM_DATA_BITS-1:0] app_rd_data,
input app_rd_data_end,
input app_rd_data_valid,
input app_rdy,
input app_wdf_rdy,
input init_calib_complete,
output reg write_done
);
assign app_wdf_mask = {MEM_DATA_BITS/8{1'b0}};
localparam IDLE = 3'd0;
localparam MEM_READ = 3'd1;
localparam MEM_READ_WAIT = 3'd2;
localparam MEM_WRITE = 3'd3;
localparam MEM_WRITE_WAIT = 3'd4;
localparam READ_END = 3'd5;
localparam WRITE_END = 3'd6;
reg[2:0] state;
reg[15:0] rd_addr_cnt;
reg[15:0] rd_data_cnt;
reg[15:0] wr_addr_cnt;
reg[15:0] wr_data_cnt;
reg[2:0] app_cmd_r;
reg[ADDR_BITS-1:0] app_addr_r;
reg app_en_r;
reg app_wdf_wren_r;
/**********************************************************************************/
//读写突发结束标志
assign rd_burst_finish = (state == READ_END);
assign wr_burst_finish = (state == WRITE_END);
/**********************************************************************************/
//读取的ddr的数据要输出给rd_fifo,信号来自mig IP核的数据输出端口
assign rd_burst_data = app_rd_data;
assign rd_burst_data_valid = app_rd_data_valid;
/**********************************************************************************/
always @(posedge mem_clk or posedge rst)
begin
if (rst)
write_done <= 0;
else if(wr_burst_finish)
write_done <=1;
else
write_done <= write_done;
end
重点是以下信号的生成:
1、app_en信号的生成逻辑:
- 写数据时,当app_rdy和wr_burst_data_req有效(包含app_wdf_rdy有效)时拉高app_en信号,这是因为app_rdy和app_en同时有效时命令和地址可以被写入,所以干脆让app_en跟随app_rdy。
- 但又加了一个wr_burst_data_req信号,这是因为由于数据来自fifo,fifo存在空满现象,也即有时读数据这个动作并不是连续的。既然此时不读新数据,那么app_en也拉低以避免命令和地址的写入。
assign app_en = app_en_r;
always @(*) begin
if (state == MEM_WRITE) begin
app_en_r <= app_rdy && wr_burst_data_req;
end
else if (state == MEM_WRITE_WAIT) begin
app_en_r <= app_rdy;
end
else if (state == MEM_READ) begin
app_en_r <= app_rdy && !rd_fifo_full;
end
else
app_en_r <= 0;
end
2、写数据时的app_wdf_wren信号的生成逻辑:
- 由于读数据请求信号wr_burst_data_req有效时,读出的数据会延迟一拍,所以刚好也让app_wdf_wren_r比wr_burst_data_req延迟一个周期,这样读出的数据wr_burst_data和app_wdf_wren_r同步。
- 由于wr_burst_data_req有效包含app_wdf_rdy有效,也符合app_wdf_rdy和app_wdf_wren都有效时,数据wr_burst_data才能被写入的逻辑。
//当app_wdf_rdy和app_wdf_wren_r都有效时,IP核才会接收到用户端发送的app_wdf_data
assign app_wdf_wren = wr_burst_finish? 0 : app_wdf_wren_r;
assign app_wdf_end = app_wdf_wren;
always@(posedge mem_clk)
begin
if (wr_data_cnt < wr_burst_len) begin
//app_wdf_wren_r比wr_burst_data_req延迟一个周期
//wr_burst_data_req连接到wr_fifo的rd_en,刚好读出的wr_burst_data和app_wdf_wren_r同步
app_wdf_wren_r <= wr_burst_data_req;
end
/*
else if (wr_data_cnt == wr_burst_len) begin
app_wdf_wren_r <= 0;
end*/
else
app_wdf_wren_r <= 0;
end
3、wr_burst_data_req(也即app_wdf_data)的生成逻辑:处于写状态、mig IP核写空闲信号有效、wr_fifo中有数据,上述条件同时满足时就拉高wr_burst_data_req,开始请求读取wr_fifo中的数据wr_burst_data(app_wdf_data)
assign app_wdf_data = wr_burst_data;
//也可以再加一个条件:app_rdy拉高,但是由于该信号会忽高忽低,所以读出的数据也不连续,根据实际情况处理。
assign wr_burst_data_req = (state == MEM_WRITE) & app_wdf_rdy & !wr_fifo_empty;
4、状态机控制 app_cmd、app_addr的生成逻辑:这两个就一直有效即可,在一次写入或者读取完成之后,变化到下一个地址或者命令即可。
- 读操作有效:app_rdy有效、app_en有效、并且rd_fifo不满即可。
- 写操作有效:app_rdy有效、app_en有效、app_wdf_rdy有效、wr_fifo不空;最后两个信号有效其实也就是wr_burst_data_req有效,而wr_burst_data_req有效时,wr_burst_data和app_wdf_wren刚好也有效;这样刚好在命令和地址有效写入时,数据也有效可以被写入。
assign app_cmd = app_cmd_r;
assign app_addr = app_addr_r;
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
state <= IDLE;
app_cmd_r <= 3'b000;
app_addr_r <= 0;
app_en_r <= 1'b0;
rd_addr_cnt <= 0;
rd_data_cnt <= 0;
wr_addr_cnt <= 0;
wr_data_cnt <= 0;
app_wdf_wren_r <= 1'b0;
end
else if(init_calib_complete == 1'b1)//DDR3复位完成
begin
case(state)
IDLE: //复位状态、一次写完成、一次读完成均会回到IDLE状态
begin
if(rd_burst_req)//检测到rd_burst_req 信号后,进入读状态,
//发送读命令、读起始地址、使能信号app_en 到 DDR_CONTROL
begin
state <= MEM_READ; //进入读状态
//下面三个信号一般都是一块给出
app_cmd_r <= 3'b001; //发送读命令
app_addr_r <= rd_burst_addr;//发送读初始地址
app_en_r <= 1'b1;//发送命令使能
rd_addr_cnt <= 0;
rd_data_cnt <= 0;
end
else if(wr_burst_req) //检测到wr_burst_req 信号后,进入写状态,发送 写命令、写起始地址、 使能信号app_en 到DDR_CONTROL
begin
state <= MEM_WRITE; //进入写状态
//下面三个信号一般都是一块给出
app_cmd_r <= 3'b000; //发送写命令
app_addr_r <= wr_burst_addr;//发送写初始地址
app_en_r <= 1'b1;//发送命令使能
wr_addr_cnt <= 0;
wr_data_cnt <= 0;
end
end
MEM_READ:
begin
if(app_rdy && !rd_fifo_full)//等待DDR_CONTROL的app_ready 信号拉高
begin
app_addr_r <= app_addr_r + 8; //地址每次加8
if(rd_addr_cnt == rd_burst_len - 1) //检测读地址是否将要写完成
begin
//因为写地址完成肯定在数据读取完成之前,所以计数时前者先结束
//但此时不能直接进入end状态,所以就定义了一个MEM_READ_WAIT状态
state <= MEM_READ_WAIT;
rd_addr_cnt <= 0;
//app_en_r <= 1'b0; //地址写完成后拉低使能信号app_en
end
else
rd_addr_cnt <= rd_addr_cnt + 1; //每写一次地址,加1,用于检测写入的读地址的个数
end
if(app_rd_data_valid)//读出数据有效信号
begin
if(rd_data_cnt == rd_burst_len - 1)//检测读出的数据的个数
begin
rd_data_cnt <= 0;
state <= READ_END;//读完成后跳转至READ_END状态
end
else
begin
rd_data_cnt <= rd_data_cnt + 1;
end
end
end
MEM_READ_WAIT:
begin
if(app_rd_data_valid)
begin
if(rd_data_cnt == rd_burst_len - 1)
begin
rd_data_cnt <= 0;
state <= READ_END;
end
else
begin
rd_data_cnt <= rd_data_cnt + 1;
end
end
end
MEM_WRITE:
begin
/*
正常情况下,只要app_rdy和app_wdf_rdy都有效,然后写相关信号与数据都有效就可以完成写操作了。
但是这里是要求wr_burst_data_req有效(其中包含app_wdf_rdy有效),也即多了个wr_fifo是否为空
的信号,这是因为写数据来自wr_fifo,当没有数据可写的时候就保持cmd、addr不变,然后将app_en
和app_wdf_wren拉低即可,表示此时不进行写操作了,这里没有直接拉低app_wdf_wren,但由于
app_wdf_wren = wr_burst_data_req,所以,由于此时wr_burst_data_req也为低,因此app_wdf_wren也拉低了。
*/
if(app_rdy && wr_burst_data_req)
begin
//app_en_r <= 1'b1;
app_addr_r <= app_addr_r + 8; //地址每次加8
if(wr_addr_cnt == wr_burst_len - 1) //检测写地址是否将要写完成
begin
wr_addr_cnt <= 0;
//app_en_r <= 1'b0;//地址写完成后拉低使能信号app_en
end
else
begin
wr_addr_cnt <= wr_addr_cnt + 1; //每写一次地址,加1,用于检测写入的写地址的个数
end
end
else
app_en_r <= 1'b0;
if(wr_burst_data_req)
begin
if(wr_data_cnt == wr_burst_len - 1) //检测写数据是否将要完成
begin
wr_data_cnt <= 0;
state <= MEM_WRITE_WAIT;//数据写完成后,跳转至MEM_WRITE_WAIT状态,等待所有地址发送完毕
end
else
begin
wr_data_cnt <= wr_data_cnt + 1; //每写一次数据,加1,用于检测写数据的个数
end
end
end
READ_END:
begin
state <= IDLE; //返回初始状态
app_addr_r <=0;
end
MEM_WRITE_WAIT:
begin
if(app_rdy)
begin
//app_en_r <= 1'b1;
app_addr_r <= app_addr_r + 8; //等待所有地址发送完毕
if(wr_addr_cnt == wr_burst_len - 1)
begin
wr_addr_cnt <= 0;
//app_en_r <= 1'b0;
state <= WRITE_END; //写数据结束,拉高wr_burst_finish,代表本次突发写数据结束,将wr_burst_finish发送到mem_test模块
end //在mem_test模块中,检测到wr_burst_finish后,将状态切换至read状态
else
begin
wr_addr_cnt <= wr_addr_cnt + 1;
end
end
end
WRITE_END:
begin
state <= IDLE; //返回初始状态
app_addr_r <=0;
end
default:
state <= IDLE;
endcase
end
end
endmodule
3、ddr_top模块:就是将三个子模块连接起来。
//`define SKIP_CALIB
`timescale 1ps/1ps
/*module ddr_top #
(
//***************************************************************************
// The following parameters refer to width of various ports
//***************************************************************************
parameter CK_WIDTH = 1, //ck时钟信号的宽度
// # of CK/CK# outputs to memory.
parameter nCS_PER_RANK = 1, //每个RANK的片选信号数量
// # of unique CS outputs per rank for phy
parameter CKE_WIDTH = 1, //ck时钟有效信号的宽度
// # of CKE outputs to memory.
parameter DM_WIDTH = 4, //数据掩码的宽度 一位控制一个字节
// # of DM (data mask)
parameter ODT_WIDTH = 1, //片上终端使能信号的宽度
// # of ODT outputs to memory.
parameter BANK_WIDTH = 3, //bank地址的宽度
// # of memory Bank Address bits.
parameter COL_WIDTH = 10,//列地址宽度
// # of memory Column Address bits.
parameter CS_WIDTH = 1, //片选信号的宽度
// # of unique CS outputs to memory.
parameter DQ_WIDTH = 32, //数据宽度
// # of DQ (data)
parameter DQS_WIDTH = 4, //数据选取脉冲的宽度 一位对应一个字节
parameter DQS_CNT_WIDTH = 2,
// = ceil(log2(DQS_WIDTH))
parameter DRAM_WIDTH = 8,
// # of DQ per DQS
parameter ECC = "OFF",
parameter ECC_TEST = "OFF",
parameter nBANK_MACHS = 4,//管理bank的machine的数量,一个machine管理一个DRAM bank
parameter RANKS = 1,//RANK的数量,每64bit为一个RANK
// # of Ranks.
parameter ROW_WIDTH = 15, //行地址的宽度
// # of memory Row Address bits.
parameter ADDR_WIDTH = 29,
// # = RANK_WIDTH + BANK_WIDTH 1+3+15+10
// + ROW_WIDTH + COL_WIDTH;
// Chip Select is always tied to low for
// single rank devices
//***************************************************************************
// The following parameters are mode register settings
//***************************************************************************
parameter BURST_MODE = "8",//突发模式
// DDR3 SDRAM:
// Burst Length (Mode Register 0).
// # = "8", "4", "OTF".
// DDR2 SDRAM:
// Burst Length (Mode Register).
// # = "8", "4".
//***************************************************************************
// The following parameters are multiplier and divisor factors for PLLE2.
// Based on the selected design frequency these parameters vary.
//***************************************************************************
parameter CLKIN_PERIOD = 5000,
// Input Clock Period
parameter CLKFBOUT_MULT = 8,
// write PLL VCO multiplier
parameter DIVCLK_DIVIDE = 1,
// write PLL VCO divisor
parameter CLKOUT0_PHASE = 337.5,
// Phase for PLL output clock (CLKOUT0)
parameter CLKOUT0_DIVIDE = 2,
// VCO output divisor for PLL output clock (CLKOUT0)
parameter CLKOUT1_DIVIDE = 2,
// VCO output divisor for PLL output clock (CLKOUT1)
parameter CLKOUT2_DIVIDE = 32,
// VCO output divisor for PLL output clock (CLKOUT2)
parameter CLKOUT3_DIVIDE = 8,
// VCO output divisor for PLL output clock (CLKOUT3)
parameter MMCM_VCO = 800,
// Max Freq (MHz) of MMCM VCO
parameter MMCM_MULT_F = 4,
// write MMCM VCO multiplier
parameter MMCM_DIVCLK_DIVIDE = 1,
// write MMCM VCO divisor
//***************************************************************************
// Simulation parameters
//***************************************************************************
parameter SIMULATION = "FALSE",
// Should be TRUE during design simulations and
// FALSE during implementations
//***************************************************************************
// IODELAY and PHY related parameters
//***************************************************************************
parameter TCQ = 100,
parameter DRAM_TYPE = "DDR3",
//***************************************************************************
// System clock frequency parameters
//***************************************************************************
parameter nCK_PER_CLK = 4,//ddr3工作频率:用户端ui_clk
// # of memory CKs per fabric CLK
//***************************************************************************
// Debug parameters
//***************************************************************************
parameter DEBUG_PORT = "OFF",
// # = "ON" Enable debug signals/controls.
// = "OFF" Disable debug signals/controls.
parameter RST_ACT_LOW = 1
// =1 for active low reset,
// =0 for active high.
)*/
module ddr_top(
/*
wr_fifo 是写ddr3前缓存数据的fifo,也即ddr3需要写入的数据从wr_fifo中读出
rd_fifo 是读ddr3后缓存数据的fifo,也即从ddr3读出的数据写入rd_fifo
*/
input wr_fifo_empty,//wr_fifo的空信号
input [255:0]wr_fifo_dout,//wr_fifo的读取数据
output wire wr_fifo_read,//读wr_fifo的使能信号
input rd_fifo_full,//rd_fifo的满信号
output wire rd_burst_data_valid,//写入rd_fifo的数据的有效信号
output wire [255:0]rd_burst_data,//写入rd_fifo的数据
// Inouts
inout [31:0] ddr3_dq,//inout [31:0] 数据
inout [3:0] ddr3_dqs_n,//inout [3:0] 数据选取脉冲
inout [3:0] ddr3_dqs_p,//inout [3:0] 数据选取脉冲
// Outputs
output [14:0] ddr3_addr,//output [14:0] 行列地址
output [2:0] ddr3_ba,//output [2:0] bank地址
output ddr3_ras_n,//output 行地址选通,低电平有效
output ddr3_cas_n,//output 列地址选通,低电平有效
output ddr3_we_n,//output 0-写允许,1-读允许
output ddr3_reset_n,//output 复位信号,低电平有效
output [0:0] ddr3_ck_p,//output [0:0] 差分时钟p端
output [0:0] ddr3_ck_n,//output [0:0] 差分时钟n端
output [0:0] ddr3_cke,//output [0:0] 时钟有效信号,高电平有效
output [0:0] ddr3_cs_n,//output [0:0] 片选信号,低表示命令有效,否则命令屏蔽
output [3:0] ddr3_dm,//output [3:0] 数据掩码 一位控制一个字节
//数据是32位,所以刚好是四位
output [0:0] ddr3_odt,//output [0:0] 片上终端使能,高电平有效
// Inputs
// Differential system clocks
input sys_clk_p,//系统的差分时钟
input sys_clk_n,
output init_calib_complete,//output ddr3 初始化完成信号,高电平有效
// System reset - Default polarity of sys_rst_n pin is Active Low.
// System reset polarity will change based on the option
// selected in GUI.
input sys_rst_n,//复位信号
output clk,//mig ip核生成的ui_clk
output read_ddr,
output write_done
);
localparam ADDR_WIDTH = 29;
localparam DQ_WIDTH = 32;
localparam PAYLOAD_WIDTH = DQ_WIDTH;
localparam APP_DATA_WIDTH = 2 * 4 * PAYLOAD_WIDTH;//256bits
localparam APP_MASK_WIDTH = APP_DATA_WIDTH / 8;//32
// Wire declarations
wire [ADDR_WIDTH-1:0] app_addr;//input [28:0]将要访问的DDR内存地址,具体位宽与用户生成IP核时的设置有关
wire [2:0] app_cmd;//input [2:0]命令总线,3’b000表示写命令,3’b001表示读命令
wire app_en;//input命令使能信号,该信号有效且app_rdy有效时,命令才能被使用,高电平有效
wire app_rdy;
wire [APP_DATA_WIDTH-1:0] app_rd_data;
wire app_rd_data_end;
wire app_rd_data_valid;
wire [APP_DATA_WIDTH-1:0] app_wdf_data;//input [255:0]用户写入IP核的256bit数据
wire app_wdf_end;//input 该信号有效时,表示当前是一次DDR写突发的最后一个数据,高电平有效
wire [APP_MASK_WIDTH-1:0] app_wdf_mask;
wire app_wdf_rdy;
wire app_sr_active;
wire app_ref_ack;
wire app_zq_ack;
wire app_wdf_wren;
wire rst;
wire [11:0] device_temp;
//***************************************************************************
wire wr_burst_finish;
wire rd_burst_finish;
wire rd_burst_req;
wire wr_burst_req;
wire[15:0] rd_burst_len;
wire[15:0] wr_burst_len;
wire[28:0] rd_burst_addr;
wire[28:0] wr_burst_addr;
wire[255 : 0] wr_burst_data;
mem_burst
#(
.MEM_DATA_BITS(APP_DATA_WIDTH),
.ADDR_BITS(ADDR_WIDTH)
)
mem_burst_m0
(
.rd_fifo_full(rd_fifo_full),
.wr_fifo_empty(wr_fifo_empty),
.rst(rst),
.mem_clk(clk), //用户时钟
.rd_burst_req(rd_burst_req),
.wr_burst_req(wr_burst_req),
.rd_burst_len(rd_burst_len),
.wr_burst_len(wr_burst_len),
.rd_burst_addr(rd_burst_addr),
.wr_burst_addr(wr_burst_addr),
.rd_burst_data_valid(rd_burst_data_valid),
.wr_burst_data_req(wr_fifo_read),
.rd_burst_data(rd_burst_data),
.wr_burst_data(wr_fifo_dout),
.rd_burst_finish(rd_burst_finish),
.wr_burst_finish(wr_burst_finish),
.app_addr(app_addr),
.app_cmd(app_cmd),
.app_en(app_en),
.app_wdf_data(app_wdf_data),
.app_wdf_end(app_wdf_end),
.app_wdf_mask(app_wdf_mask),
.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_rdy(app_rdy),
.app_wdf_rdy(app_wdf_rdy),
.init_calib_complete(init_calib_complete),
.write_done(write_done)
);
mem_test
#(
.MEM_DATA_BITS(APP_DATA_WIDTH),
.ADDR_BITS(ADDR_WIDTH)
)
mem_test_m0
(
.rd_fifo_full(rd_fifo_full),
.rst(rst),
.mem_clk(clk), //用户时钟
.read_ddr(read_ddr),
.rd_burst_req(rd_burst_req),
.wr_burst_req(wr_burst_req),
.rd_burst_len(rd_burst_len),
.wr_burst_len(wr_burst_len),
.rd_burst_addr(rd_burst_addr),
.wr_burst_addr(wr_burst_addr),
.wr_burst_data_req(wr_fifo_read),
//.wr_burst_data(wr_burst_data),
.rd_burst_finish(rd_burst_finish),
.wr_burst_finish(wr_burst_finish)
);
// Start of User Design top instance
//***************************************************************************
// The User design is instantiated below. The memory interface ports are
// connected to the top-level and the application interface ports are
// connected to the traffic generator module. This provides a reference
// for connecting the memory controller to system.
//***************************************************************************
always @(posedge clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
app_wdf_wren_r1<=0;
app_wdf_wren_r2<=0;
app_wdf_wren_r3<=0;
app_wdf_data_r1<=0;
app_wdf_data_r2<=0;
app_wdf_data_r3<=0;
end
else
app_wdf_wren_r1<=app_wdf_end;
app_wdf_wren_r2<=app_wdf_wren_r1;
app_wdf_wren_r3<=app_wdf_wren_r2;
app_wdf_data_r1<=app_wdf_data;
app_wdf_data_r2<=app_wdf_data_r1;
app_wdf_data_r3<=app_wdf_data_r2;
end
DDR3_CONTROL u_DDR3_CONTROL
(
// Memory interface ports ddr端接口
.ddr3_addr (ddr3_addr),//output [14:0] 行列地址
.ddr3_ba (ddr3_ba),//output [2:0] bank地址
.ddr3_cas_n (ddr3_cas_n),//output 列地址选通,低电平有效
.ddr3_ck_n (ddr3_ck_n),//output [0:0] 差分时钟n端
.ddr3_ck_p (ddr3_ck_p),//output [0:0] 差分时钟p端
.ddr3_cke (ddr3_cke),//output [0:0] 时钟有效信号,高电平有效
.ddr3_ras_n (ddr3_ras_n),//output 行地址选通,低电平有效
.ddr3_we_n (ddr3_we_n),//output 0-写允许,1-读允许
.ddr3_dq (ddr3_dq),//inout [31:0] 数据
.ddr3_dqs_n (ddr3_dqs_n),//inout [3:0] 数据选取脉冲
.ddr3_dqs_p (ddr3_dqs_p),//inout [3:0] 数据选取脉冲
.ddr3_reset_n (ddr3_reset_n),//output 复位信号,低电平有效
.init_calib_complete (init_calib_complete),//output ddr3 初始化完成信号,高电平有效
.ddr3_cs_n (ddr3_cs_n),//output [0:0] 片选信号,低表示命令有效,否则命令屏蔽
.ddr3_dm (ddr3_dm),//output [3:0] 数据掩码 一位控制一个字节
//数据是32位,所以刚好是四位
.ddr3_odt (ddr3_odt),//output [0:0] 片上终端使能,高电平有效
// Application interface ports 用户端接口
.app_addr (app_addr),//input [28:0]将要访问的DDR内存地址,具体位宽与用户生成IP核时的设置有关
.app_cmd (app_cmd),//input [2:0]命令总线,3’b000表示写命令,3’b001表示读命令
.app_en (app_en),//input命令使能信号,该信号有效且app_rdy有效时,命令才能被使用,高电平有效
.app_wdf_data (app_wdf_data),//input [255:0]用户写入IP核的256bit数据
.app_wdf_end (app_wdf_end),//input该信号有效时,表示当前是一次DDR写突发的最后一个数据,高电平有效
.app_wdf_wren (app_wdf_wren),//input写数据有效信号,当app_wdf_rdy也为有效时,
//IP核才会接收到用户端发送的app_wdf_data,高电平有效
.app_rd_data (app_rd_data),//output [255:0]从DDR中读出得数据,一次突发读出8个32bit数据
.app_rd_data_end (app_rd_data_end),//output指示当前数据是突发读写的最后一个周期的数据,
//这个信号与设置的用户时钟和DDR时钟的比例有关
.app_rd_data_valid (app_rd_data_valid),//output读出数据有效信号,高电平有效,表示从IP核中读出的数据有效
.app_rdy (app_rdy),//output空闲信号,指示当前IP核的工作状态,只有该信号为高时,
//IP核才能正确的使用用户给出的命令,高电平有效
.app_wdf_rdy (app_wdf_rdy),//output写空闲信号,IP核内部的写FIFO能够接收用户数据的标志,高电平有效
.app_sr_req (1'b0),//input一系列的请求信号,一般为0 代表用户对MIG IP没有强力干预
.app_ref_req (1'b0),//input
.app_zq_req (1'b0),//input
.app_sr_active (app_sr_active),//output上面请求的响应信号
.app_ref_ack (app_ref_ack),//output
.app_zq_ack (app_zq_ack),//output
.ui_clk (clk),//output IP核提供给用户端使用的clk,和ddr3_ck_n/p的比例是1:2或者1:4
.ui_clk_sync_rst (rst),//output 是ui_clk的复位指示信号,ui_clk复位完成后拉低
.app_wdf_mask (0),//input [31:0] 32bit数据掩码,每一位对应app_wdf_data的一个8bit数据
// System Clock Ports
.sys_clk_p (sys_clk_p),
.sys_clk_n (sys_clk_n),
.device_temp (device_temp),
`ifdef SKIP_CALIB
.calib_tap_req (calib_tap_req),
.calib_tap_load (calib_tap_load),
.calib_tap_addr (calib_tap_addr),
.calib_tap_val (calib_tap_val),
.calib_tap_load_done (calib_tap_load_done),
`endif
.sys_rst (sys_rst_n)
);
// End of User Design top instance
endmodule
四、仿真结果
写操作时序:
读操作时序:
可以看到,写进去的数据和读出来的数据是一致的!!!