本节介绍一个米联客DDR3读写测试的工程,把一些难以理解的代码进行了注释,如果哪里有问题的话,感谢大家指出,最后对波形进行分析。
一、DDR3读写测试代码
前面的内容基本不需要看,重点在后面的assign赋值语句与两个状态机模块。
代码语言:javascript复制`timescale 1ps/1ps
module example_top #
(
//***************************************************************************
// Traffic Gen related parameters
//***************************************************************************
parameter PORT_MODE = "BI_MODE",
parameter DATA_MODE = 4'b0010,
parameter TST_MEM_INSTR_MODE = "R_W_INSTR_MODE",
parameter EYE_TEST = "FALSE",
// set EYE_TEST = "TRUE" to probe memory
// signals. Traffic Generator will only
// write to one single location and no
// read transactions will be generated.
parameter DATA_PATTERN = "DGEN_ALL",
// For small devices, choose one only.
// For large device, choose "DGEN_ALL"
// "DGEN_HAMMER", "DGEN_WALKING1",
// "DGEN_WALKING0","DGEN_ADDR","
// "DGEN_NEIGHBOR","DGEN_PRBS","DGEN_ALL"
parameter CMD_PATTERN = "CGEN_ALL",
// "CGEN_PRBS","CGEN_FIXED","CGEN_BRAM",
// "CGEN_SEQUENTIAL", "CGEN_ALL"
parameter CMD_WDT = 'h3FF,
parameter WR_WDT = 'h1FFF,
parameter RD_WDT = 'h3FF,
parameter SEL_VICTIM_LINE = 0,
parameter BEGIN_ADDRESS = 32'h00000000,
parameter END_ADDRESS = 32'h00ffffff,
parameter PRBS_EADDR_MASK_POS = 32'hff000000,
//***************************************************************************
// The following parameters refer to width of various ports
//***************************************************************************
parameter CK_WIDTH = 1,
// # of CK/CK# outputs to memory.
parameter nCS_PER_RANK = 1,
// # of unique CS outputs per rank for phy
parameter CKE_WIDTH = 1,
// # 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,
// # 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,
parameter nBANK_MACHS = 4,
parameter RANKS = 1,
// # of Ranks.
parameter ROW_WIDTH = 15,
// # of memory Row Address bits.
parameter ADDR_WIDTH = 29,
// # = RANK_WIDTH (1) BANK_WIDTH(3)
// ROW_WIDTH(15) COL_WIDTH;(10)
// 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,
// # 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.
)
(
// Inouts
inout [31:0] ddr3_dq,
inout [3:0] ddr3_dqs_n,
inout [3:0] ddr3_dqs_p,
// Outputs
output [14:0] ddr3_addr,
output [2:0] ddr3_ba,
output ddr3_ras_n,
output ddr3_cas_n,
output ddr3_we_n,
output ddr3_reset_n,
output [0:0] ddr3_ck_p,
output [0:0] ddr3_ck_n,
output [0:0] ddr3_cke,
output [0:0] ddr3_cs_n,
output [3:0] ddr3_dm,
output [0:0] ddr3_odt,
// Inputs
output tg_compare_error,
output init_calib_complete,
input clk100m_i,
input rst_key
);
wire sys_rst;
wire locked;
wire clk_ref_i;
wire sys_clk_i;
wire clk_200;
assign sys_rst = ~rst_key;//复位信号
assign clk_ref_i = clk_200;//200M的参考时钟
assign sys_clk_i = clk_200;//200M的系统时钟
//时钟管理产生DDR需要的时钟
clk_wiz_0 CLK_WIZ_DDR( .clk_out1(clk_200),
.reset(sys_rst),
.locked(locked),
.clk_in1(clk100m_i)
);
function integer clogb2 (input integer size);
begin
size = size - 1;
for (clogb2=1; size>1; clogb2=clogb2 1)
size = size >> 1;
end
endfunction // clogb2
function integer STR_TO_INT;
input [7:0] in;
begin
if(in == "8")
STR_TO_INT = 8;
else if(in == "4")
STR_TO_INT = 4;
else
STR_TO_INT = 0;
end
endfunction
localparam DATA_WIDTH = 32;
localparam RANK_WIDTH = clogb2(RANKS);
localparam PAYLOAD_WIDTH = (ECC_TEST == "OFF") ? DATA_WIDTH : DQ_WIDTH;
localparam BURST_LENGTH = STR_TO_INT(BURST_MODE);
localparam APP_DATA_WIDTH = 2 * nCK_PER_CLK * PAYLOAD_WIDTH;
localparam APP_MASK_WIDTH = APP_DATA_WIDTH / 8;
//***************************************************************************
// Traffic Gen related parameters (derived)
//***************************************************************************
localparam TG_ADDR_WIDTH = ((CS_WIDTH == 1) ? 0 : RANK_WIDTH)
BANK_WIDTH ROW_WIDTH COL_WIDTH;
localparam MASK_SIZE = DATA_WIDTH/8;
// Wire declarations
wire [(2*nCK_PER_CLK)-1:0] app_ecc_multiple_err;
wire [(2*nCK_PER_CLK)-1:0] app_ecc_single_err;
wire [ADDR_WIDTH-1:0] app_addr;
wire [2:0] app_cmd;
wire app_en;
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;
wire app_wdf_end;
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 [(64 (2*APP_DATA_WIDTH))-1:0] error_status;
wire [(PAYLOAD_WIDTH/8)-1:0] cumlative_dq_lane_error;
wire mem_pattern_init_done;
wire [47:0] tg_wr_data_counts;
wire [47:0] tg_rd_data_counts;
wire modify_enable_sel;
wire [2:0] data_mode_manual_sel;
wire [2:0] addr_mode_manual_sel;
wire [APP_DATA_WIDTH-1:0] cmp_data;
wire cmp_data_valid;
reg cmp_data_valid_r;
wire cmp_error;
wire [(PAYLOAD_WIDTH/8)-1:0] dq_error_bytelane_cmp;
wire clk;
wire rst;
wire [11:0] device_temp;
//***************************************************************************
// 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.
//***************************************************************************
mig_7series_0 u_mig_7series_0
(
// Memory interface ports
.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_we_n (ddr3_we_n),
.ddr3_dq (ddr3_dq),
.ddr3_dqs_n (ddr3_dqs_n),
.ddr3_dqs_p (ddr3_dqs_p),
.ddr3_reset_n (ddr3_reset_n),
.init_calib_complete (init_calib_complete),
.ddr3_cs_n (ddr3_cs_n),
.ddr3_dm (ddr3_dm),
.ddr3_odt (ddr3_odt),
// Application interface ports
.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_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),
.app_sr_req (1'b0),
.app_ref_req (1'b0),
.app_zq_req (1'b0),
.app_sr_active (app_sr_active),
.app_ref_ack (app_ref_ack),
.app_zq_ack (app_zq_ack),
.ui_clk (clk),
.ui_clk_sync_rst (rst),
.app_wdf_mask (32'd0),
// System Clock Ports
.sys_clk_i (sys_clk_i),
// Reference Clock Ports
.clk_ref_i (clk_ref_i),
.device_temp (device_temp),
.sys_rst (locked)
);
//以下是读写测试
// End of User Design top instance
parameter [2:0]CMD_WRITE =3'd0;
parameter [2:0]CMD_READ =3'd1;
//parameter TEST_DATA_RANGE=24'd16777215;//全地址测试
parameter TEST_DATA_RANGE=24'd2000;//部分测试
(*mark_debug="true"*) wire init_calib_complete;
(*mark_debug="true"*) reg [3:0]state=0;
(*mark_debug="true"*) reg [23:0]Count_64=0;// 128M*2*16/256
(*mark_debug="true"*) reg [23:0]Count_64_1=0;
(*mark_debug="true"*) reg ProsessIn=0;//表示读写操作的包络
(*mark_debug="true"*) reg WriteSign=0;//表示是写操作
(*mark_debug="true"*) reg ProsessIn1=0;//表示写操作的包络
reg [ADDR_WIDTH-1:0]app_addr_begin=0;
reg [29:0]CountRead_tem=0;
(*mark_debug="true"*) wire error_rddata=0;
assign app_wdf_end =app_wdf_wren;//两个相等即可
//根据是否允许数据写入,之后等待app_wdf_rdy为一,再等待app_rdy的应答信号,在输入请求中使用
assign app_en =ProsessIn?(WriteSign?app_rdy&&app_wdf_rdy:app_rdy):1'd0;//控制命令使能
//根据是否写数据使能,发送数据写入指令,或者数据读取指令
assign app_cmd =WriteSign?CMD_WRITE:CMD_READ;
//数据操作地址为begin~~~~~后续自加8
assign app_addr =app_addr_begin;
assign app_wdf_data =Count_64_1;//写入的数据是计数器
//根据是否允许数据写入,且DDR3准备好&MIG内部fifo准备好,使能写入数据
assign app_wdf_wren =ProsessIn1?app_rdy&&app_wdf_rdy:1'd0;
//app_rdy:用户提交的请求已经被接受
//app_wdf_rdy:写入数据fifo已经准备好接收数据
always@(posedge clk)
if(rst&!init_calib_complete)//MIG复位&未初始化结束
begin
state <=4'd0; //初始状态
app_addr_begin <=28'd0; //起始地址
WriteSign <=1'd0; //写标志
ProsessIn <=1'd0; //输入写入
Count_64 <=24'd0; //数据写入个数计数器
end
else case(state)
4'd0: begin
state <=4'd1; //在状态0时跳转到状态1
app_addr_begin <=28'd0; //传输起始地址为0
WriteSign <=1'd0; //写数据清零
ProsessIn <=1'd0; //写指令清零
Count_64 <=24'd0; //数据写入个数计数器清零
end
4'd1: begin
state <=4'd2;
WriteSign <=1'd1; //允许写入数据
ProsessIn <=1'd1; //允许发送写指令
Count_64 <=24'd0; //发送数据次数计数器清零
app_addr_begin <=28'd0; //写数据的起始地址
end
4'd2: begin//写整片的DDR3
//未发送完指定长度时,在该状态保持
state <=(Count_64==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?4'd3:4'd2;//最后一个地址写完之后跳出状态
//未写入到指定长度时,一直写数据使能
WriteSign <=(Count_64==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?1'd0:1'd1;//写数据使能
//未写入到指定长度时,每次MIG准备好且MIG数据fifo准备好,写指令使能
ProsessIn <=(Count_64==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?1'd0:1'd1;//写命令使能
//MIG准备好且接收数据fifo已经准备好,则发送数据寄存器自加一
Count_64 <=app_rdy&&app_wdf_rdy?(Count_64 24'd1):Count_64;
// 当提交给UI的指令已经被接受且写入数据fifo准备好接收数据,发送给数据,DDR的工作频率是800Mhz,数据宽度32,上下沿都写入数据,每次需要256bit数据
//之后需要跳转到下一个数据地址 8
app_addr_begin <=app_rdy&&app_wdf_rdy?(app_addr_begin 28'd8):app_addr_begin;//跳到下一个(8*32=256)bit数据地址
end
4'd3: begin
state <=(state1==4'd0)?4'd4:state; //等待数据全部写入
WriteSign <=1'd0; //将写数据使能清零,表示读取数据使能
ProsessIn <=(state1==4'd0)?1'd1:1'd0; //将写数据指令清零 表示读取指令使能
Count_64 <=24'd0; //写数据计数器清零
app_addr_begin <=28'd0; //操作起始地址清零
end
4'd4: begin//读整片的DDR3
state <=(Count_64==TEST_DATA_RANGE)&&app_rdy?4'd0:state;
WriteSign <=1'd0;
ProsessIn <=(Count_64==TEST_DATA_RANGE)&&app_rdy?1'd0:1'd1;
Count_64 <=app_rdy?(Count_64 24'd1):Count_64;
app_addr_begin <=app_rdy?(app_addr_begin 28'd8):app_addr_begin;
end
default:begin
state <=4'd1;
app_addr_begin <=28'd0;
WriteSign <=1'd0;
ProsessIn <=1'd0;
Count_64 <=24'd0;
end
endcase
(*mark_debug="true"*) reg [3:0]state1=0;
always@(posedge clk)//单独将写操作从上面的状态机提出来,当然也可以和上面的状态机合并到一起
//当DDR处于复位状态且为初始化完成
if(rst&!init_calib_complete)//
begin
state1 <=4'd0; //状态机初始状态
ProsessIn1 <=1'd0; //写指令不使能
end
else case(state1)
4'd0: begin
//空闲状态时,等待主状态机跳转到state==1
state1 <=(state==4'd1)?4'd1:4'd0;
ProsessIn1 <=(state==4'd1)?1'd1:1'd0;
Count_64_1 <=24'd0;
end
4'd1: begin
//当写入数据写到指定长度,且UI接受写指令&MIG接收数据fifo准备好时返回到状态0,否则在状态0维持
state1 <=(Count_64_1==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?4'd0:4'd1;
//当写入数据写到指定长度,且UI接受写指令&MIG接收数据fifo准备好时返回到状态0,否则不断使能输入写入指令
ProsessIn1 <=(Count_64_1==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?1'd0:1'd1;
Count_64_1 <=app_rdy&&app_wdf_rdy?(Count_64_1 24'd1):Count_64_1;
end
default:begin
state1 <=(state==4'd1)?4'd1:4'd0;
ProsessIn1 <=(state==4'd1)?1'd1:1'd0;
Count_64_1 <=24'd0;
end
endcase
(*mark_debug="true"*) reg [23:0]app_rd_data_tem=0;
(*mark_debug="true"*) reg [23:0]cmp_data_r1=0;
reg [23:0]cmp_data_r;
always@(posedge clk)
if(rst&!init_calib_complete)begin
cmp_data_r <= 24'd0;
end
else if(cmp_data_r==TEST_DATA_RANGE)begin
cmp_data_r <= 24'd0;
end
else if(app_rd_data_valid) begin
cmp_data_r<=cmp_data_r 1'b1; //每次检测到有效数据标志时,将数据计数器自加一
end
reg app_rd_data_valid_r=1'b0;
always@(posedge clk) begin
app_rd_data_valid_r <= app_rd_data_valid; //寄存读取数据有效标志
app_rd_data_tem <= app_rd_data; //寄存读取得到的数据
cmp_data_r1 <= cmp_data_r; //寄存计数器产生的数据
end
/*
错误校验,当读取得到的数据与计数器产生的数据大小不一致时,且经过寄存的数据有效信号为一时,将错误标志位置一
*/
assign error_rddata=(app_rd_data_tem!=cmp_data_r1)&app_rd_data_valid_r;
endmodule
二、DDR3波形分析
1、状态分析
state=4表示处于数据读取状态,state=2表示处于数据写入状态。
2、读取数据的有效标志
数据读取阶段,发出读取指令后,经过一段时间的延迟,读取数据显示到数据总线,同时data_valid信号为1.
3、数据读取操作
此处仍在进行数据单独,但是后半部分与数据写入部分重合,这说明从DDR中读取得到的数据保存在MIG内部的fifo中,从而不会影响到数据写入操作。
4、数据的写入与读取
当WriteSign为0时进行数据读取,为1时进行数据写入操作