本文区别于DDR颗粒的配置,记录几个与颗粒配置不同的地方,具体DDR的原理请查看DDR3的应用总结(一)DDR3的应用总结(二)
1.确认板卡FPGA型号为xc7k325tffg900 -2,据此创建FPGA工程。
2.添加MIG IP核。具体配置如下
①第一页描述当前工程信息
②第二页,可设置IP核的名称,保持默认。
③第三页 设置兼容情况,保持默认。
④第四页 保持默认选择DDR3 SDRAM
⑤第五页如图所示配置。其中①指DDR3颗粒的物理时钟,例如一颗16bit位宽的DDR设置为400MHz,则它的传输速度为每一个800MHz(DDR双沿传输的原因)周期传输16bit。这里时钟的范围受到FPGA芯片速度等级和型号的制约,以及与内存条的支持速度也有关。如我用的内存条MT16KTF1G64HZ-1G6,速度范围在1500ps-3000ps之间。②指的是用户时钟,4:1的4指的是①设置的物理时钟。第③部分指的是类型选择,内存条选择SODIMMs。④处是DDR3内存条的型号,如果不在列表需要根据速度参数,位宽大小找一个兼容的型号。⑤处勾选上mask后,如果相应的管脚不连接,会造成DDR3初始化失败。其余配置保持默认即可。
⑥第六页 此处①代表参考时钟,选择200MHz为固定大小。②处如果是内存条就选择RZQ/4,如果是颗粒就选择RZQ/6。
⑦第七页,参考时钟和系统时钟均由时钟IP生成给到,因此选择no buffer,其他的按照默认配置即可。
⑧DDR3颗粒要勾选DCI Cascade,内存条不用勾选。后面就是选择引脚,其他的都保持默认,即可。
3.测试程序
如果需要修改这段程序,需要注意MIG的接口各信号的位宽应该保持一致,另外程序中设计了两个LED灯,读写测试正确的时候,指示灯led1常亮,反之则闪烁。LED2只是容量,当测试到所设置的容量的时候常量。代码中TEST_LENGTH指示的含义是突发次数,也可以说是容量。每一次突发是512bit数据,使用内存的容量除以512bit,即为最大的突发次数。当然使用的芯片的物理位宽不同,例如只有一片16bit位宽的DDR颗粒,那一次突发的数据为8*16bit=128bit,那最大的突发次数就要用容量除以128bit了。
module dimm_top(
input sys_clk_p,
input sys_clk_n,
inout [63:0] ddr3_dq ,
inout [7:0] ddr3_dqs_n ,
inout [7:0] ddr3_dqs_p ,
output [15: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 [1:0] ddr3_ck_p ,
output [1:0] ddr3_ck_n ,
output [1:0] ddr3_cke ,
output [1:0] ddr3_cs_n ,
output [7:0] ddr3_dm ,
output [1:0] ddr3_odt ,
output reg led1,
output reg led2
);
wire clk_rst;
wire clk_200;
reg [29:0] app_addr_begin=0;
wire app_en; //写命令使能
wire [2:0] app_cmd; //用户读写命令
wire app_wdf_wren; //DDR3写使能
wire app_wdf_end; //突发写最后一个数标识
wire [29:0] app_addr; //用户平面地址
wire app_rdy; //设备接收准备就绪
wire app_wdf_rdy; //写响应
wire [511:0] app_rd_data; //用户读数据
wire app_rd_data_end; //突发读当前时钟最后一个数据
wire app_rd_data_valid; //读数据有效
wire [511:0] app_wdf_data; //用户写数据
wire app_sr_active; //保留
wire app_ref_ack; //刷新请求
wire app_zq_ack; //ZQ 校准请求
wire init_calib_complete; //校准完成信号
wire ui_clk ; //用户时钟
wire ui_clk_sync_rst;
clk_wiz_0 u_clk_wiz_0(.clk_out1(clk_200), .reset(1'b0), .locked(clk_rst), .clk_in1_p(sys_clk_p),.clk_in1_n(sys_clk_n));
mig_7series_0 mig_JC (
// Memory interface ports
.ddr3_addr (ddr3_addr), // output [15:0]
.ddr3_ba (ddr3_ba), // output [2:0]
.ddr3_cas_n (ddr3_cas_n), // output
.ddr3_ck_n (ddr3_ck_n), // output [1:0]
.ddr3_ck_p (ddr3_ck_p), // output [1:0]
.ddr3_cke (ddr3_cke), // output [1:0]
.ddr3_ras_n (ddr3_ras_n), // output
.ddr3_reset_n (ddr3_reset_n), // output
.ddr3_we_n (ddr3_we_n), // output
.ddr3_dq (ddr3_dq), // inout [63:0]
.ddr3_dqs_n (ddr3_dqs_n), // inout [7:0]
.ddr3_dqs_p (ddr3_dqs_p), // inout [7:0]
.init_calib_complete (init_calib_complete), // output
.ddr3_cs_n (ddr3_cs_n), // output [1:0]
.ddr3_dm (ddr3_dm), // output [7:0]
.ddr3_odt (ddr3_odt), // output [1:0]
// Application interface ports
.app_addr (app_addr), // input [29:0]
.app_cmd (app_cmd), // input [2:0]
.app_en (app_en), // input
.app_wdf_data (app_wdf_data), // input [511:0]
.app_wdf_end (app_wdf_end), // input
.app_wdf_wren (app_wdf_wren), // input
.app_rd_data (app_rd_data), // output [511:0]
.app_rd_data_end (app_rd_data_end), // output
.app_rd_data_valid (app_rd_data_valid), // output
.app_rdy (app_rdy), // output
.app_wdf_rdy (app_wdf_rdy), // output
.app_sr_req (1'b0), // input
.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 (ui_clk), // output用户时钟输出,其实是通过IP配置自己配出来的
.ui_clk_sync_rst (ui_clk_sync_rst), // output
.app_wdf_mask (64'b0), // input [63:0] //写数据屏蔽
.sys_clk_i (clk_200),//输入IP的时钟
// Reference Clock Ports
.clk_ref_i (clk_200),//参考时钟
.sys_rst (clk_rst) // input sys_rst
);
parameter TEST_LENGTH = 27'd134200000; //每一次突发是512bit 8GB可以支持134217728次突发 99.98%
// parameter TEST_LENGTH = 32'd60000000;
//**************1.先写后读状态机state machine
parameter IDLE = 2'd0;
parameter WRITE = 2'd1;
parameter WAIT = 2'd2;
parameter READ = 2'd3;
reg [511:0]my_512_data;
reg [26:0] wr_addr_cnt;
reg [26:0] rd_addr_cnt;
reg [1:0] state;
always @(posedge ui_clk or negedge rst_n) begin
if((~rst_n)||(error_flag)) begin
state <= IDLE;
my_512_data <= 512'd0;
wr_addr_cnt <= 27'd0;
rd_addr_cnt <= 27'd0;
app_addr_begin<= 30'd0;
end
else if(init_calib_complete)begin //MIG IP核初始化完成
case(state)
IDLE:begin
state <= WRITE;
my_512_data <= 512'd0;
wr_addr_cnt <= 27'd0;
rd_addr_cnt <= 27'd0;
app_addr_begin <= 30'd0;
end
WRITE:begin
if((wr_addr_cnt == TEST_LENGTH-1) &&(app_rdy && app_wdf_rdy))
state <= WAIT; //写到设定的长度跳到等待状态
else if(app_rdy && app_wdf_rdy)begin //写条件满足
my_512_data <= my_512_data + 1; //写数据自增
wr_addr_cnt <= wr_addr_cnt + 1; //写计数自增
app_addr_begin<= app_addr_begin + 8; //DDR3 地址自增
end else begin //写条件不满足,保持当前状态
my_512_data <= my_512_data;
wr_addr_cnt <= wr_addr_cnt;
app_addr_begin<= app_addr_begin;
end
end
WAIT:begin
state <= READ; //下一个时钟,跳到读状态
rd_addr_cnt <= 27'd0; //读地址复位
app_addr_begin<= 30'd0; //DDR3读从地址0
end
READ:begin //读到设定的地址长度
if((rd_addr_cnt == TEST_LENGTH -1 ) && app_rdy)
state <= IDLE; //则跳到空闲状态
else if(app_rdy)begin //若MIG已经准备就绪,则开始读
rd_addr_cnt <= rd_addr_cnt + 1'd1; //用户地址每次加一
app_addr_begin <= app_addr_begin + 8; //DDR3地址加8
end else begin //若MIG没准备好,则保持原
rd_addr_cnt <= rd_addr_cnt;
app_addr_begin <= app_addr_begin;
end
end
default:begin
state <= IDLE;
my_512_data <= 512'd0;
wr_addr_cnt <= 27'd0;
rd_addr_cnt <= 27'd0;
app_addr_begin <= 30'd0;
end
endcase
end
end
//**************2.根据状态机与MIG指示信号为app信号赋值
assign app_en =((state == WRITE && (app_rdy && app_wdf_rdy))||(state == READ && app_rdy)) ? 1'b1:1'b0;
assign app_cmd =(state == READ) ? 3'd1 :3'd0;
assign app_wdf_wren=(state == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0;
assign app_wdf_end =app_wdf_wren;
assign app_addr =app_addr_begin;
assign app_wdf_data=my_512_data;
//*******************3.用户判错逻辑
reg [26:0] rd_cnt;
wire rst_n; //复位,低有效
reg error_flag;
parameter L_TIME = 28'd200_000_000;
reg [27:0] led_cnt; //led计数
wire error; //读写错误标记
assign rst_n = ~ui_clk_sync_rst;//&&myrst
always @(posedge ui_clk or negedge rst_n) begin
if(~rst_n)
rd_cnt <= 0; //若计数到读写长度,且读有效,地址计数器则�?0
else if(app_rd_data_valid&&(rd_cnt == TEST_LENGTH - 1))
rd_cnt <= 0; //其他条件只要读有效,每个时钟自增1
else if (app_rd_data_valid)
rd_cnt <= rd_cnt + 1;
end
//判断错误,读出数据应为计数递增数据
assign error = (app_rd_data_valid && (rd_cnt!=app_rd_data));
always @(posedge ui_clk or negedge rst_n) begin
if(~rst_n)
led2<=0;
else if(rd_cnt==32'd134200000-1)
led2<=1;
end
always @(posedge ui_clk or negedge rst_n) begin
if(~rst_n)
error_flag <= 0;
else if(error)
error_flag <= 1;
end
//读写测试正确,指示灯led1常亮,反之则闪烁
always @(posedge ui_clk or negedge rst_n) begin
if((~rst_n) || (~init_calib_complete )) begin
led_cnt <= 28'd0;
led1 <= 1'b0;
end
else begin
if(~error_flag) //常亮代表正常,闪烁代表故障
led1 <= 1'b1;
else begin
led_cnt <= led_cnt + 28'd1;
if(led_cnt == L_TIME - 1'b1) begin
led_cnt <= 25'd0;
led1 <= ~led1;
end
end
end
end
endmodule