我们上一节介绍了mig ip核的时钟结构,搞清楚时钟结构后我们尝试着读写下ddr3芯片。我们使用黑金的AX7325平台进行实验。
1.我们建立vivado该工程
2.我们建立好工程,然后添加设计文件
3.添加代码
`timescale 1ps/1ps
module top
(
// Inouts
inout [63:0] ddr3_dq,
inout [7:0] ddr3_dqs_n,
inout [7: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 [7:0] ddr3_dm,
output [0:0] ddr3_odt,
//Differential system clocks
input sys_clk_p,
input sys_clk_n,
input sys_clk,
output error,
output fan_pwm,
input rst_n
);
localparam nCK_PER_CLK = 4;
localparam DQ_WIDTH = 64;
localparam ADDR_WIDTH = 29;
localparam DATA_WIDTH = 64;
localparam PAYLOAD_WIDTH = 64;
localparam APP_DATA_WIDTH = 2 * nCK_PER_CLK * PAYLOAD_WIDTH;
localparam APP_MASK_WIDTH = APP_DATA_WIDTH / 8;
wire init_calib_complete;
assign fan_pwm = 1'b0;
// Wire declarations
wire sys_clk_200MHz;
IBUFDS sys_clk_ibufgds
(
.O (sys_clk_200MHz),
.I (sys_clk_p),
.IB (sys_clk_n)
);
//下面接口与ddr核相连
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 clk;
wire rst;
ddr3 u_ddr3 //参考 Xilinx 提供的文档“ug586_7Series_MIS.pdf”
(
// 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 (app_wdf_mask),
// System Clock Ports
.sys_clk_i (sys_clk_200MHz),
.sys_rst (rst_n)
);
// End of User Design top instance
wire wr_burst_data_req;
wire wr_burst_finish;
wire rd_burst_finish;
wire rd_burst_req;
wire wr_burst_req;
wire[9:0] rd_burst_len;
wire[9:0] wr_burst_len;
wire[28:0] rd_burst_addr;
wire[28:0] wr_burst_addr;
wire rd_burst_data_valid;
wire[512 - 1 : 0] rd_burst_data;
wire[512 - 1 : 0] wr_burst_data;
//对ddr ip核包装,只用对注释的接口进行控制就可实现对ddr的控制
mem_burst
#(
.MEM_DATA_BITS(APP_DATA_WIDTH),
.ADDR_BITS(ADDR_WIDTH)
)
mem_burst_m0
(
.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_burst_data_req), /*写数据信号*/
.rd_burst_data(rd_burst_data), /*读出的数据*/
.wr_burst_data(wr_burst_data), /*写入的数据*/
.rd_burst_finish(rd_burst_finish), /*读完成*/
.wr_burst_finish(wr_burst_finish), /*写完成*/
.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),
.ui_clk_sync_rst(),
.init_calib_complete(init_calib_complete)
);
//测试文件,读写数据
mem_test
#(
.MEM_DATA_BITS(APP_DATA_WIDTH),
.ADDR_BITS(ADDR_WIDTH)
)
mem_test_m0
(
.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_burst_data_req),
.rd_burst_data(rd_burst_data),
.wr_burst_data(wr_burst_data),
.rd_burst_finish(rd_burst_finish),
.wr_burst_finish(wr_burst_finish),
.error(error)
);
wire probe0;
wire probe1;
wire probe2;
wire probe3;
wire probe4;
wire probe5;
wire probe6;
wire probe7;
wire [511 : 0] probe8;
wire [511 : 0] probe9;
wire [28 : 0] probe10;
ila_0 u_ila_0(
.clk(clk),
.probe0(probe0),
.probe1(probe1),
.probe2(probe2),
.probe3(probe3),
.probe4(probe4),
.probe5(probe5),
.probe6(probe6),
.probe7(probe7),
.probe8(probe8),
.probe9(probe9),
.probe10(probe10)
);
assign probe0 = rd_burst_req;
assign probe1 = wr_burst_req;
assign probe2 = rd_burst_data_valid;
assign probe3 = wr_burst_data_req;
assign probe4 = rd_burst_finish;
assign probe5 = wr_burst_finish;
assign probe6 = error;
assign probe7 = init_calib_complete;
assign probe8 = wr_burst_data[511:0];
assign probe9 = rd_burst_data[511:0];
assign probe10 = app_addr[28:0];
endmodule
module mem_test
#(
parameter MEM_DATA_BITS = 64,
parameter ADDR_BITS = 24
)
(
input rst, /*复位*/
input mem_clk, /*接口时钟*/
output reg rd_burst_req, /*读请求*/
output reg wr_burst_req, /*写请求*/
output reg[9:0] rd_burst_len, /*读数据长度*/
output reg[9:0] wr_burst_len, /*写数据长度*/
output reg[ADDR_BITS - 1:0] rd_burst_addr, /*读首地址*/
output reg[ADDR_BITS - 1:0] wr_burst_addr, /*写首地址*/
input rd_burst_data_valid, /*读出数据有效*/
input wr_burst_data_req, /*写数据信号*/
input[MEM_DATA_BITS - 1:0] rd_burst_data, /*读出的数据*/
output[MEM_DATA_BITS - 1:0] wr_burst_data, /*写入的数据*/
input rd_burst_finish, /*读完成*/
input wr_burst_finish, /*写完成*/
output reg error
);
localparam IDLE = 3'd0;
localparam MEM_READ = 3'd1;
localparam MEM_WRITE = 3'd2;
reg[2:0] state;
reg[7:0] wr_cnt;
reg[MEM_DATA_BITS - 1:0] wr_burst_data_reg;
assign wr_burst_data = wr_burst_data_reg;
reg[7:0] rd_cnt;
always@(posedge mem_clk or posedge rst)
begin
if(rst)
error <= 1'b0;
else if((state == MEM_READ) && rd_burst_data_valid && (rd_burst_data != {(MEM_DATA_BITS/8){rd_cnt}}))
error <= 1'b1;
else
error <= error;//写入数据和读出数据不一致
end
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
wr_burst_data_reg <= {MEM_DATA_BITS{1'b0}};
wr_cnt <= 8'd0;
end
else if(state == MEM_WRITE)
begin
if(wr_burst_data_req)
begin
wr_burst_data_reg <= {(MEM_DATA_BITS/8){wr_cnt}};//写入数据
wr_cnt <= wr_cnt + 8'd1;
end
else if(wr_burst_finish)
wr_cnt <= 8'd0;
end
end
//读数据
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
rd_cnt <= 8'd0;
end
else if(state == MEM_READ)
begin
if(rd_burst_data_valid)
begin
rd_cnt <= rd_cnt + 8'd1;
end
else if(rd_burst_finish)
rd_cnt <= 8'd0;
end
else
rd_cnt <= 8'd0;
end
//状态机
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 <= 10'd128;//数据长度
wr_burst_len <= 10'd128;
rd_burst_addr <= 0;
wr_burst_addr <= 0;
end
else
begin
case(state)
IDLE:
begin
state <= MEM_WRITE;
wr_burst_req <= 1'b1;
wr_burst_len <= 10'd128;
end
MEM_WRITE:
begin
if(wr_burst_finish)
begin
state <= MEM_READ;
wr_burst_req <= 1'b0;
rd_burst_req <= 1'b1;
rd_burst_len <= 10'd128;
rd_burst_addr <= wr_burst_addr;//地址
end
end
MEM_READ:
begin
if(rd_burst_finish)
begin
state <= MEM_WRITE;
wr_burst_req <= 1'b1;
wr_burst_len <= 10'd128;
rd_burst_req <= 1'b0;
wr_burst_addr <= wr_burst_addr + 128;
end
end
default:
state <= IDLE;
endcase
end
end
endmodule
/*本模块完成对ddr2 IP的包装,方便后续模块使用,也方便程序的移植,如果更换平台,更新这个文件即可
*/
module mem_burst
#(
parameter MEM_DATA_BITS = 64,
parameter ADDR_BITS = 24
)
(
input rst, /*复位*/
input mem_clk, /*接口时钟*/
input rd_burst_req, /*读请求*/
input wr_burst_req, /*写请求*/
input[9:0] rd_burst_len, /*读数据长度*/
input[9:0] wr_burst_len, /*写数据长度*/
input[ADDR_BITS - 1:0] rd_burst_addr, /*读首地址*/
input[ADDR_BITS - 1:0] wr_burst_addr, /*写首地址*/
output rd_burst_data_valid, /*读出数据有效*/
output wr_burst_data_req, /*写数据信号*/
output[MEM_DATA_BITS - 1:0] rd_burst_data, /*读出的数据*/
input[MEM_DATA_BITS - 1:0] wr_burst_data, /*写入的数据*/
output rd_burst_finish, /*读完成*/
output wr_burst_finish, /*写完成*/
output burst_finish, /*读或写完成*/
///与ddrIP核相连的接口
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,
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 ui_clk_sync_rst,
input init_calib_complete
);
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;
localparam MEM_WRITE_FIRST_READ = 3'd7;
reg[2:0] state;
reg[9:0] rd_addr_cnt;
reg[9:0] rd_data_cnt;
reg[9:0] wr_addr_cnt;
reg[9: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_end_r;
reg app_wdf_wren_r;
assign app_cmd = app_cmd_r;
assign app_addr = app_addr_r;
assign app_en = app_en_r;
assign app_wdf_end = app_wdf_end_r;
assign app_wdf_data = wr_burst_data;
assign app_wdf_wren = app_wdf_wren_r & app_wdf_rdy;
assign rd_burst_finish = (state == READ_END);
assign wr_burst_finish = (state == WRITE_END);
assign burst_finish = rd_burst_finish | wr_burst_finish;
assign rd_burst_data = app_rd_data;
assign rd_burst_data_valid = app_rd_data_valid;
assign wr_burst_data_req = (state == MEM_WRITE) & app_wdf_rdy ;
always@(posedge mem_clk or posedge rst)
begin
if(rst)
begin
app_wdf_wren_r <= 1'b0;
end
else if(app_wdf_rdy)
app_wdf_wren_r <= wr_burst_data_req;
end
//DDR Burst 写和读,可以参考实验教程“DDR读写测试实验.pdf”
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_end_r <= 1'b0;
end
else if(init_calib_complete === 1'b1)
begin
case(state)
IDLE:
begin
if(rd_burst_req)//读请求
begin
state <= MEM_READ;
app_cmd_r <= 3'b001;
app_addr_r <= {rd_burst_addr,3'd0};//向IP核发送指令
app_en_r <= 1'b1;
end
else if(wr_burst_req)//写请求
begin
state <= MEM_WRITE;
app_cmd_r <= 3'b000;
app_addr_r <= {wr_burst_addr,3'd0};//向IP核发送指令
app_en_r <= 1'b1;
wr_addr_cnt <= 0;
app_wdf_end_r <= 1'b1;
wr_data_cnt <= 0;
end
end
MEM_READ:
begin
if(app_rdy)
begin
app_addr_r <= app_addr_r + 8;
if(rd_addr_cnt == rd_burst_len - 1)
begin
state <= MEM_READ_WAIT;
rd_addr_cnt <= 0;
app_en_r <= 1'b0;
end
else
rd_addr_cnt <= rd_addr_cnt + 1;
end
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_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_FIRST_READ:
begin
app_en_r <= 1'b1;
state <= MEM_WRITE;
wr_addr_cnt <= 0;
end
MEM_WRITE:
begin
if(app_rdy)
begin
app_addr_r <= app_addr_r + 'b1000;
if(wr_addr_cnt == wr_burst_len - 1)
begin
app_wdf_end_r <= 1'b0;
app_en_r <= 1'b0;
end
else
begin
wr_addr_cnt <= wr_addr_cnt + 1;
end
end
if(wr_burst_data_req)
begin
if(wr_data_cnt == wr_burst_len - 1)
begin
state <= MEM_WRITE_WAIT;
end
else
begin
wr_data_cnt <= wr_data_cnt + 1;
end
end
end
READ_END:
state <= IDLE;
MEM_WRITE_WAIT:
begin
if(app_rdy)
begin
app_addr_r <= app_addr_r + 'b1000;
if(wr_addr_cnt == wr_burst_len - 1)
begin
app_wdf_end_r <= 1'b0;
app_en_r <= 1'b0;
if(app_wdf_rdy)
state <= WRITE_END;
end
else
begin
wr_addr_cnt <= wr_addr_cnt + 1;
end
end
else if(~app_en_r & app_wdf_rdy)
state <= WRITE_END;
end
WRITE_END:
state <= IDLE;
default:
state <= IDLE;
endcase
end
end
endmodule
4.添加ip核
这里我们暂时不选择axi接口,使用native
不需要兼容其他型号
我们使用的芯片位ddr3
如下图,1处为给ddr3芯片的时钟,我们使用800M,2处comporment表示是芯片颗粒,下拉菜单里有其他选项,表示内存条。3处是我们使用的芯片型号,我们这里使用MT41J256M16。4处表示总位宽,这里我们看下四片ddr3芯片的连接方式为16*4=64位。
input clock period表示ip核的输入时钟,我们这里提供200M的时钟。
下图1处表示输入时钟的形式,有单端,差分和单端no buff,我们这里选择nobuff形式。2处表示参考时钟来源,我们参考时钟使用系统时钟就行。
然后进行管脚约束
添加管脚文件
验证通过
默认下一步
如下图,IP核生成成功。
下来我们生成ila的ip核
如下设置
两个ip核都能使用了
5.我们再添加下约束文件,这里主要约束了时钟周期和一些管脚。
接下来进行网表编译
网表编译完进行布局布线。
布局布线完成后生成下载文件
然后连接芯片
连接上芯片后就下载程序
7.下载完我们可以看到波形,我们详细看下:
可以看到在1处我们发出了wr_burst_req,当wr_burst_data_valid拉高时我们开始往芯片里面写数据,每次数据累加1,直到7f为止。
然后我们将写入的数据读出来,我们先发出读请求,当rd_burst_data_valid拉高时,表示读取数据有效。直到读取完毕,我们可以看到写入的数据与读出的数据一致。
到这里我们就初步完成了读写实验,但是代码逻辑是怎样的,我们后面再分析,进行仿真实验。