DDR3调试总结
一、开发板介绍
本实验所用开发板为xc7a35t-2fgg484I
FPGA设备名称命名规则如上图所示
XC7A:代表FPGA的家族系列 (X表示XILINX产品,C表示商用(Commercial),7A表示7系列产品)
35t:代表拥有的Logic Cells的数目
-2:表示速度等级
fgg484:表示封装类型和引脚数目
二、DDR3介绍
DDR SDRAM 全称为 Double Data Rate SDRAM,中文名为“双倍数据流 SDRAM”。DDR SDRAM 在原有的 SDRAM 的基础上改进而来。
存储器分类:
RAM:即随机存储内存,就是计算机的内存,这种存储器在断电时将丢失其存储内容,故主要用于存储短时间使用的程序。
ROM:即只读内存,是一种只能读出事先所存储数据的固态半导体存储器。
FLASH:它结合了ROM和RAM的长处,不仅具备电子可擦出可编程(EEPROM)的性能,还不会断电丢失数据同时可以快速读取数据(NVRAM的优势), 用作存储Bootloader以及操作系统或者程序代码或者直接当硬盘使用(U盘).
AX7035开发板上搭载1个Micron DDR3的颗MT41J128M16HA,芯片容量为2Gb,即256MB。在 DDR SDRAM 中,突发长度只有 2、4、8 三种选择,没有了随机存取的操作(突发长度为 1)和全页式突发。这是因为L-Bank 一次就存取两倍于芯片位宽的数据,所以芯片至少也要进行两次传输才可以。
三、DDR3设计
MIG IP 控制器是 Xilinx 为用户提供的一个 DDR 控制的 IP,用户可以不需要了解DDR3的控制和读写时序,只需要通过DDR3控制器对DDR3的读写进行控制。
7系列的DDR3控制器接口如下:
DDR3控制器包含3个部分:用户接口模块(User interface Block),存储器控制模块(Memory Controller),DDR3的物理接口(Physical Layer)。
DDR3使用流程:
①打开IP Catalog界面
②在 IP Catalog 界面里双击 Memories & Storage Elements\ Memory Interface Generators 下的Memory Interface Generator (MIG 7 Series)。
③点击next
④修改 Component Name 为你想要命名的IP名字,点击 Next。
⑤这里可以选择兼容芯片,不需要直接点击next。
⑥选择默认的DDR3
⑦DDR3设置
7035 开发板 Memory Part 选择开发板的型号"MT41J128m16xx-125", Data Width 数据宽度选择 16 位。
⑧选择 PLL 输入时钟的频率为 200Mhz, 这个时钟需要跟开发板上的时钟频率一致,其它设置输出阻抗值和内部的 ODT 内部上拉电阻值来改善 DDR3 的信号完整性,一般不需要修改。
⑨System Clock 选择差分"No Buffer", Reference Clock 因为开发板上没有提供单独的 DDR 参考时钟,所以选择"Use System Clock"。System Reset Polarity 选择"ACTIVE LOW",AX7035 还需勾选 Internal Verf,其它保留默认配置。
⑩之后按照默认值进行设定。在最后的界面需要设置DDR3的数据、地址和控制信号的FPGA管脚分配和IO电平,用户可以根据开发板原理图进行手动配置或者使用Read XDC/UCF按键导入.xdc或者.ucf文件进行自动配置。最后validate成功即可正确生成DDR3的IP核。
DDR3的时钟分析总结:
步骤⑦中:
Clock_Period:表示MIG核对DDR3接口的速率,乘以2则表示双边沿传输。AX7035开发板设置成400MHz,根据不同开发板设置。
PHY to Controller Clock Ratio:一个比值,表示MIG输出到app接口上的时钟ui_clk。AX7035开发板设置成4:1,表明ui_clk为100MHz。
步骤⑧中:
Input Clock Period:表示DDR3中top层输入时钟,即用户需要输入到DDR3的时钟。AX7035设置为200MHz。
步骤⑨中:
reference clock:use system clock,与步骤⑧中的Input Clock Period表示同一个时钟,如果不设置此时钟,最后在工程中MIG生成.v会生成一个接口向我们索要该200MHz的时钟。
DDR3用户接口时序图分析:
写数据时序图:
add_cmd:输入信号。DDR3命令端口,0为写入,1为读出。
app_addr:输入信号。DDR3的地址信号。
app_en:DDR3使能信号。
app_rdy:输出端口。高电平有效,表示MIG准备好接收命令和数据了。
app_wdf_mask:高电平有效,将其置1可以屏蔽某些字节。可以指示哪些字节写入外部存储器,哪些保持其当前状态。
app_wdf_rdy:输出端口,表示mig准备好接收写数据和写命令。
app_wdf_data:写入的数据。
app_wdf_wren:写入数据接口app_wdf_data的使能信号。
app_wdf_end:指示当前周期数据已经写结束了。
提示:只有当app_en和app_rdy同时为高电平时add_cmd和app_addr才有效。app_name和app_wdf_name是分开的,若app_wdf_rdy有效,而app_rdy无效,数据也会被存入fifo中,只有app_rdy有效了,数据才会写入到DDR3中。由时序图可知,写数据和写控制信号可以早于写命令、写当前地址、其他写控制信号一个时钟周期或迟于两个时钟周期之内都可。
读数据时序:只要当读命令(app_cmd)和当前读地址(app_addr)以及读控制信号(app_en,app_rdy)同时有效时,等待读数据有效信号(app_rd_data_valid)有效时读数据(app_rd_data)有效。
DDR3用户接口驱动代码:
module mem_burst
#(
parameter MEM_DATA_BITS = 128,
parameter ADDR_BITS = 28
)
(
//用户控制接口
input rst, /*复位*/
input mem_clk, /*接口时钟*/
input rd_burst_req, /*读请求*/
input wr_burst_req, /*写请求*/
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_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, /*读或写完成*/
//DDR接口
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_end_r_m0;
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_m0; //延迟了一个时钟周期
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
always@(posedge mem_clk or posedge rst)
begin
if(rst)
app_wdf_end_r_m0 <= 1'b0;
else
app_wdf_end_r_m0 <= app_wdf_end_r;
end
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};
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};
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 + 8;
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
// app_addr_r <= app_addr_r + 8;
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
注意:wr_burst_addr,rd_burst_addr和app_addr的区别,DDR3的突发长度为8,wr_burst_addr或者rd_burst_addr地址以1递增,而DDR3的内部地址以8递增。
app_addr_r <= {rd_burst_addr,3’d0}; //ddr3每8个数据进行传输一次,因此每次读写的时候起始地址为用户操纵地址的8倍,因此左移三位。
app_addr_r <= app_addr_r + 8; //ddr3每读写一次其地址自增8