DDR3是一种内存规格,它是SDRAM家族的内存产品。DDR3之前的产品有DDR和DDR2。DDR(Double Data Rate)是双倍速率同步动态随机存储器,严格的说DDR应该叫DDR SDRAM。DDR2是DDR产品的升级产品,它是四倍速率同步动态随机存储器。DDR3在DDR2的基础上实现了更高的性能(增加到八倍)和更低的电压。
DDR内部结构框图如下所示:
如上图所示:
标号1:逻辑控制单元,用于输入命令解析和模式控制等。
标号2:行地址选通单元。
标号3:内部存储阵列,上图中分为8个bank。
标号4:bank控制逻辑单元。
标号5:IO锁存和DM标记单元,用于刷新和预充电。
标号6:数据读写驱动接口。
标号7:列地址选择单元。
在7系列FPGA芯片中,Xilinx 为用户提供了一个 DDR 控制的 IP核,称为MIG IP 控制器。这样用户可以很方便的通过 MIG 控制器读写 DDR 存储器。7 系列的 DDR 控制器的解决方案如下所示:
如上图所示,DDR3 控制器包含 3 部分:
- 用户接口模块(User interface Block);
- 存储器控制模块(Memory Controller);
- DDR3 的物理接口(Physical Layer)。
在vivado中创建DDR3的MIG控制器,步骤如下:
-
在vivado中,点击 Project Manager 界面下的 IP Catalog,打开 IP Catalog 界面。如下所示:
-
在IP Catalog 界面里找到 Memories & Storage Elements\Memory Interface Generators 下的 Memory
Interface Generator (MIG 7 Series),如下图所示:
3.双击Memory Interface Generator (MIG 7 Series)后出现下图所示界面:
如果想了解更多的 MIG 的信息,可以点击左边的 User Guide 按钮来打开 Xilinx 的相关文档来查看。
4.点击 Next,如下图所示:
5.修改 Component Name 为"ddr3",点击 Next。如下图所示:
6.继续点 Next。如下图所示:
7.选择默认的 DDR3 SDRAM。如下图所示:
8.在clock period选项中选择2500ps,Memory Part 中选择匹配型号如"MT41J256m16xx-125", Data Width 数据宽度选择 32 位,ORDERING选择normal。然后点击next,如下图所示:
9.选择Input clock Period为 200Mhz, 这个时钟需要跟所用电路板上的外部时钟频率一致,其它设置输出阻抗值和内部的 ODT 内部上拉电阻值来改善 DDR3 的信号完整性,一般不需要修改。如下图所示:
10.System Clock 选择差分"No Buffer", 因为电路板上没有提供单独的 DDR 参考时钟,所以Reference Clock 选择"Use System Clock"。System Reset
Polarity 选择"ACTIVE LOW",其它保留默认配置。
11.如果内部端接阻抗为 50 ohms,则不用修改。如下图所示:
12.点击第二项,我们需要设定一下 DDR 的管脚,点击 Next。
13.在这个界面里设置 DDR3 的数据、地址和控制信号的 FPGA 管脚分配和 IO 电平。这个手工分配起来还是比较费劲的,为了方便可以点击“Read XDC/UCF”按键直接导入现成的管脚分配文件。导入后 ddr3 的管脚分配如下:
14.点击Validate,校验通过后点击Next。如下图所示:
15.这里软件默认设置,直接点击 Next,如下图所示:
16.这里会出现DDR3的配置信息表,如果检查没问题后可点击next。如下图所示:
17.选择 Accept,点击 Next。如下图所示:
18.继续点击Next,如下图所示:
19.点击 Generate 按钮生成 MIG 控制器。如下图所示:
20.点击 Generate 按钮生成 MIG 相关的设计文档。如下图所示:
21.然后就可以生成DDR3的IP核。
以下为DDR3驱动文件:mem_burst.v(此代码来源于网络,只是在学习中添加了注解)
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, /*读或写完成*/
output[ADDR_BITS-1:0] app_addr, //读写地址
output[2:0] app_cmd, //读写命令,001代表读;000代表写
output app_en, //app使能
output [MEM_DATA_BITS-1:0] app_wdf_data, //写入DDR3 MIG的数据
output app_wdf_end, //表示写入DDR3 MIG的数据完成。
output [MEM_DATA_BITS/8-1:0] app_wdf_mask,
output app_wdf_wren, //表示可以向DDR3 MIG写入数据。
input [MEM_DATA_BITS-1:0] app_rd_data, //从DDR3 MIG中读出数据。
input app_rd_data_end, //表示从DDR3 MIG中完成读数据
input
app_rd_data_valid, //读数据有效
input app_rdy, //表示DDR3 MIG已经准备好
input app_wdf_rdy,
input ui_clk_sync_rst,
input init_calib_complete //初始化校验完成
);
assign app_wdf_mask = {MEM_DATA_BITS/8{1'b0}};
// app_wdf_mask清零
//以下为数据读写的几种状态
parameter IDLE = 3'd0;
parameter MEM_READ = 3'd1;
parameter MEM_READ_WAIT = 3'd2;
parameter MEM_WRITE = 3'd3;
parameter MEM_WRITE_WAIT = 3'd4;
parameter READ_END = 3'd5;
parameter WRITE_END = 3'd6;
parameter 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;
assignrd_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)
//复位后系数清零
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) //IDLE状态下,如果是读数据请求
begin
state <= MEM_READ; //下一个状态设置为READ。
app_cmd_r <= 3'b001; //设置为读命令
app_addr_r <= {rd_burst_addr,3'd0}; //设置读地址
app_en_r <= 1'b1; // app使能
end
else
if(wr_burst_req) //IDLE状态下,如果是写数据请求
begin
state <= MEM_WRITE; //下一个状态设置为WRITE。
app_cmd_r <= 3'b000; //设置为写命令
app_addr_r <= {wr_burst_addr,3'd0}; //设置写地址
app_en_r <= 1'b1; // app使能
wr_addr_cnt <= 0;
app_wdf_end_r <= 1'b1;
wr_data_cnt <= 0;
end
end
MEM_READ:
begin
if(app_rdy) //READ状态下,读地址
begin
app_addr_r <= app_addr_r + 8;
if(rd_addr_cnt == rd_burst_len - 1) //地址计数完成后,进入MEM_READ_WAIT状态
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