##~~~~~~~~~ 写在前面 ~~~~~~~~~##
本系列为 DDR3 控制器设计总结,此系列包含 DDR3 控制器相关设计:认识 MIG、初始化、读写操作、FIFO 接口等。通过此系列的学习可以加深对 DDR3 读写时序的理解以及 FIFO 接口设计等,附上汇总博客直达链接。
【DDR3 控制器设计】系列博客汇总篇(附直达链接)
目录
实验任务
实验环境
实验介绍
接口详解
写时序
实验设计
写控制模块
顶层模块
testbench 设计
仿真波形
熟悉 DDR3 的 IP 核的写时序,并利用 Verilog 实现对 DDR3 进行写操作,并带有仿真时序图。
开发环境:Vivado 2018.2,
FPGA 芯片型号:xc7a100tffg484-2
DDR3 型号:MT41J256M16HA-125
直接对 DDR3 进行读写时序控制是比较困难的,接口复杂且多,但是通过调取 DDR3 控制器 MIG IP 核,间接的对 DDR3 进行控制就会方便很多,控制器给用户端预留了接口,通过查看 MIG IP 核用户手册,对 IP 核进行写控制。
以下为 MIG 和 DDR 之间的连接框图,可以看到红框图是用户接口,中间是 MIG 核,右边蓝框图是需要控制的 DDR 接口,用户只需要去配置红框中的接口信号,就可以对 DDR 进行控制读写等操作。
以下 MIG 对用户端的部分接口进行解释
端口名称 |
端口类型 |
端口解释 |
rst |
输出 |
MIG 提供给用户端的复位信号,高有效 |
clk |
输出 |
MIG 提供给用户端的时钟信号 |
app_addr |
输入 |
地址总线。29 bit |
app_cmd |
输入 |
命令总线,3’b000代表写,3’b001 代表读。3bit |
app_en |
输入 |
命令使能信号,该信号有效(高电平),且 app_rdy 也有效时,MIG IP 核才可以接收到用户端发送的app_cmd 和 app_addr。1bit |
app_hi_pri |
输入 |
指令的优先级 |
app_wdf_data |
输入 |
写数据总线,数据位宽为16bit,每次突发长度为8,因此数据总线位宽为128bit。 |
app_wdf_end |
输入 |
最后一个写数据的标志,该信号有效(高电平)时,代表对应的 app_wdf_data 为当前写的最后一个数据。1bit ,当ui_clk的比例为4:1时,此信号和 app_wdf_wren 一致。 |
app_wdf_mask |
输入 |
写数据掩码,该信号为写数据的掩码。16bit,每 1 bit对应 1 byte数据的掩码。 |
app_wdf_wren |
输入 |
写数据有效标志,该信号有效(高电平),且 app_wdf_rdy 也有效时,MIG IP 核才可以接收到用户端发送的 app_wdf_data。1bit |
app_rdy |
输出 |
命令空闲信号,该信号有效(高电平),且 app_en 也有效时,MIG IP 核才可以接收到用户端发送的 app_cmd 和 app_addr指令 。1bit |
app_rd_data |
输出 |
读数据总线,数据位宽为16bit,每次突发长度为8,因此数据总线位宽为128bit。 |
app_rd_data_end |
输出 |
最后一个读数据的标志,该信号有效(高电平)时,代表对应的 app_rd_data_end 为当前读的最后一个数据。1bit ,当ui_clk的比例为4:1时,此信号和 app_rd_data_valid 一致。 |
app_rd_data_valid |
输出 |
读数据有效信号,此信号为高表示读出的数据有效,和读出的数据同步。1bit |
app_wdf_rdy |
输出 |
写数据空闲信号,表示 MIG 可以接收数据,该信号有效(高电平),且 app_wdf_wren 也有时, MIG IP 核才可以接收到用户端发送的 app_wdf_data。1bit |
app_sr_req |
输入 |
输入保留位,置 0 |
app_sr_active |
输出 |
输出保留位 |
app_ref_req |
输入 |
刷新请求信号,此信号为高表示请求向 DRAM 发出刷新命令。 |
app_ref_ack |
输出 |
刷新请求确认信号,此信号为高表示内存控制器已将请求的刷新命令发送到PHY接口。 |
app_zq_req |
输入 |
ZQ校准请求信号 |
app_zq_ack |
输出 |
ZQ校准请求确认信号 |
写命令的时序如下,写命令与要写入的初始地址保持同步,并且app_en 和 app_rdy 信号同时拉高时,此时写命令才会被 MIG IP 核所接受。即当图中红框的时刻写指令才会被 MIG IP 核接受。
关于写数据的三个信号 app_wdf_data,app_wdf_wren 和 app_wdf_end,这三个信号之间的关系如下时序图,以突发长度 8 为例。主要有三种情况:
本次实验使用第二种对应关系,这样时序比较好控制。
另外说明一下 app_wdf_wren 和 app_wdf_end 两个信号之间的关系,在 A7 DDR3 控制器 IP 核中,突发长度只能为8,而数据为16bit,因此每次突发的数据位宽为16 * 8 = 128bit,同时在 IP 核配置的时候,有个配置选项为配置 DDR3 的物理层与用户端之间的速率关系,如果选择 4:1时,由于DDR为双边采样,因此每次的app_wdf_data为128bit,因此此时 app_wdf_end 信号与 app_wdf_wren 信号同步。当速率比为 2:1 时 app_wdf_data 为 64bit,此时为突发长度数据的前4个,因此 app_wdf_end 为 app_wdf_wren 信号的后半部分,并不是同步关系。如下图所示。
实验设计框图如下,通过设计一个写控制模块,对 MIG IP 核进行写控制,在用户端的信号交互只需要提供写使能、数据、地址等信号即可实现对 DDR3 进行写操作。
对上一节的 DDR 初始化实验工程保存名为 ddr3_wr 的工程,基本设计思想为设计写控制模块,并设计顶层模块,顶层模块包含各个模块、IP等,并testbench编写测试文件,最终查看仿真波形验证设计的正确性。
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Engineer : Linest-5
/* File : ddr3_wr_ctrl.v
/* Create : 2022-09-19 11:05:07
/* Revise : 2022-09-19 14:51:14
/* Module Name : ddr3_wr_ctrl
/* Description : ddr3写操作模块
/* Editor : sublime text3, tab size (4)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
module ddr3_wr_ctrl(
//时钟和复位
input ui_clk,
input rst,
//user interface
input init_calib_complete,
input [127:0] wr_data,
input [7:0] wr_brust_len,
input wr_start,
input [28:0] wr_addr,
input [2:0] wr_cmd,
input [15:0] wr_mask,
output data_req,
output reg wr_end,
//app interface
input app_rdy,
input app_wdf_rdy,
output [2:0] app_cmd,
output reg app_en,
output reg [28:0] app_addr,
output [127:0] app_wdf_data,
output reg app_wdf_wren,
output [15:0] app_wdf_mask,
output app_wdf_end
);
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* 信号申明 */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
reg [7:0] wr_brust_len_reg;
reg [7:0] wr_data_cnt;
reg [7:0] app_cmd_cnt;
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Main Code */
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
assign app_cmd = 3'b000; //3'b000为写操作,3'b001为读操作
assign app_wdf_mask = 16'd0; //对所有数据都不使用掩码
assign app_wdf_end = app_wdf_wren; //采用的是4:1,所以end和wren信号一致
assign data_req = app_wdf_wren && app_wdf_rdy;
assign app_wdf_data = wr_data;
//当开始写信号有效时,对突发长度进行寄存
always @(posedge ui_clk or posedge rst) begin
if (rst) begin
wr_brust_len_reg <= 'd0;
end
else if (wr_start) begin
wr_brust_len_reg <= wr_brust_len;
end
else begin
wr_brust_len_reg <= wr_brust_len_reg;
end
end
//wr_data_cnt写入数据个数计数
always @(posedge ui_clk or posedge rst) begin
if (rst) begin
wr_data_cnt <= 'd0;
end
else if (data_req && (wr_data_cnt == (wr_brust_len_reg - 'd1))) begin
wr_data_cnt <= 'd0;
end
else if (data_req) begin
wr_data_cnt <= wr_data_cnt + 'd1;
end
else begin
wr_data_cnt <= wr_data_cnt;
end
end
//app_wdf_wren写使能信号
always @(posedge ui_clk or posedge rst) begin
if (rst) begin
app_wdf_wren <= 'd0;
end
else if (app_wdf_wren && app_wdf_rdy && (wr_data_cnt == (wr_brust_len_reg - 'd1))) begin
app_wdf_wren <= 'd0;
end
else if (wr_start) begin
app_wdf_wren <= 'd1;
end
else begin
app_wdf_wren <= app_wdf_wren;
end
end
//命令计数信号
always @(posedge ui_clk or posedge rst) begin
if (rst) begin
app_cmd_cnt <= 'd0;
end
else if (app_en && app_rdy && (app_cmd_cnt == (wr_brust_len_reg - 'd1))) begin
app_cmd_cnt <= 'd0;
end
else if (app_en && app_rdy) begin
app_cmd_cnt <= app_cmd_cnt + 'd1;
end
else begin
app_cmd_cnt <= app_cmd_cnt;
end
end
//命令使能信号,当app_en拉高时,命令和地址信号才有效
always @(posedge ui_clk or posedge rst) begin
if (rst) begin
app_en <= 'd0;
end
else if (app_en && app_rdy && (app_cmd_cnt == (wr_brust_len_reg - 'd1))) begin
app_en <= 'd0;
end
else if (data_req) begin
app_en <= 'd1;
end
else begin
app_en <= app_en;
end
end
//写地址app_addr
always @(posedge ui_clk or posedge rst) begin
if (rst) begin
app_addr <= 'd0;
end
else if (wr_end) begin
app_addr <= 'd0;
end
else if (wr_start) begin
app_addr <= wr_addr;
end
else if (app_en && app_rdy) begin
app_addr <= app_addr + 'd8;
end
else begin
app_addr <= app_addr;
end
end
//写完成标志信号
always @(posedge ui_clk or posedge rst) begin
if (rst) begin
wr_end <= 'd0;
end
else if (app_en && app_rdy && (app_cmd_cnt == (wr_brust_len_reg - 'd1))) begin
wr_end <= 'd1;
end
else begin
wr_end <= 'd0;
end
end
endmodule
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Engineer : Linest-5
/* File : top_ddr3_init.v
/* Create : 2022-09-15 09:58:59
/* Revise : 2022-09-19 15:45:17
/* Module Name :
/* Description :
/* Editor : sublime text3, tab size (2)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
module top_ddr3_init(
// Inouts
inout [15:0] ddr3_dq,
inout [1:0] ddr3_dqs_n,
inout [1: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 [1:0] ddr3_dm,
output [0:0] ddr3_odt,
// Inputs
// Differential system clocks
input sys_clk,
input rst_n
);
wire init_calib_complete;
wire ui_clk;
wire ui_clk_sync_rst;
wire [127:0] wr_data;
wire [7:0] wr_brust_len;
wire wr_start;
wire [28:0] wr_addr;
wire [2:0] wr_cmd;
wire [15:0] wr_mask;
wire data_req;
wire wr_end;
wire app_rdy;
wire app_wdf_rdy;
wire [2:0] app_cmd;
wire app_en;
wire [28:0] app_addr;
wire [127:0] app_wdf_data;
wire app_wdf_wren;
wire [15:0] app_wdf_mask;
wire app_wdf_end;
ddr3_clock ddr3_clock_inst(
// Clock out ports
.clk_out1(sys_clk_in), // output clk_out1
// Clock in ports
.clk_in1(sys_clk)
); // input clk_in1
ddr3_wr_ctrl inst_ddr3_wr_ctrl (
.ui_clk (ui_clk),
.rst (ui_clk_sync_rst || (~init_calib_complete)),
.wr_data (wr_data),
.wr_brust_len (wr_brust_len),
.wr_start (wr_start),
.wr_addr (wr_addr),
.wr_cmd (wr_cmd),
.wr_mask (wr_mask),
.data_req (data_req),
.wr_end (wr_end),
.app_rdy (app_rdy),
.app_wdf_rdy (app_wdf_rdy),
.app_cmd (app_cmd),
.app_en (app_en),
.app_addr (app_addr),
.app_wdf_data (app_wdf_data),
.app_wdf_wren (app_wdf_wren),
.app_wdf_mask (app_wdf_mask),
.app_wdf_end (app_wdf_end)
);
ddr3_init u_ddr3_init (
// Memory interface ports
.ddr3_addr (ddr3_addr), // output [14:0] ddr3_addr
.ddr3_ba (ddr3_ba), // output [2:0] ddr3_ba
.ddr3_cas_n (ddr3_cas_n), // output ddr3_cas_n
.ddr3_ck_n (ddr3_ck_n), // output [0:0] ddr3_ck_n
.ddr3_ck_p (ddr3_ck_p), // output [0:0] ddr3_ck_p
.ddr3_cke (ddr3_cke), // output [0:0] ddr3_cke
.ddr3_ras_n (ddr3_ras_n), // output ddr3_ras_n
.ddr3_reset_n (ddr3_reset_n), // output ddr3_reset_n
.ddr3_we_n (ddr3_we_n), // output ddr3_we_n
.ddr3_dq (ddr3_dq), // inout [15:0] ddr3_dq
.ddr3_dqs_n (ddr3_dqs_n), // inout [1:0] ddr3_dqs_n
.ddr3_dqs_p (ddr3_dqs_p), // inout [1:0] ddr3_dqs_p
.init_calib_complete (init_calib_complete), // output init_calib_complete
.ddr3_cs_n (ddr3_cs_n), // output [0:0] ddr3_cs_n
.ddr3_dm (ddr3_dm), // output [1:0] ddr3_dm
.ddr3_odt (ddr3_odt), // output [0:0] ddr3_odt
// Application interface ports
.app_addr (app_addr), // input [28:0] app_addr
.app_cmd (app_cmd), // input [2:0] app_cmd
.app_en (app_en), // input app_en
.app_wdf_data (app_wdf_data), // input [127:0] app_wdf_data
.app_wdf_end (app_wdf_wren), // input app_wdf_end
.app_wdf_wren (app_wdf_wren), // input app_wdf_wren
.app_rd_data (app_rd_data), // output [127:0] app_rd_data
.app_rd_data_end (app_rd_data_end), // output app_rd_data_end
.app_rd_data_valid (app_rd_data_valid), // output app_rd_data_valid
.app_rdy (app_rdy), // output app_rdy
.app_wdf_rdy (app_wdf_rdy), // output app_wdf_rdy
.app_sr_req (1'b0), // input app_sr_req
.app_ref_req (1'b0), // input app_ref_req
.app_zq_req (1'b0), // input app_zq_req
.app_sr_active (app_sr_active), // output app_sr_active
.app_ref_ack (app_ref_ack), // output app_ref_ack
.app_zq_ack (app_zq_ack), // output app_zq_ack
.ui_clk (ui_clk), // output ui_clk
.ui_clk_sync_rst (ui_clk_sync_rst), // output ui_clk_sync_rst
.app_wdf_mask (app_wdf_mask), // input [15:0] app_wdf_mask
// System Clock Ports
.sys_clk_i (sys_clk_in), // input sys_clk_i
.sys_rst (rst_n) // input sys_rst
);
endmodule
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/* Engineer : Linest-5
/* File : tb_top_ddr3_init.v
/* Create : 2022-09-15 10:10:36
/* Revise : 2022-09-19 19:48:35
/* Module Name :
/* Description :
/* Editor : sublime text3, tab size (4)
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
`timescale 1ns / 1ps
module tb_top_ddr3_init();
reg sys_clk;
reg rst_n;
wire [15:0] ddr3_dq;
wire [1:0] ddr3_dqs_n;
wire [1:0] ddr3_dqs_p;
wire [14:0] ddr3_addr;
wire [2:0] ddr3_ba;
wire ddr3_ras_n;
wire ddr3_cas_n;
wire ddr3_we_n;
wire ddr3_reset_n;
wire [0:0] ddr3_ck_p;
wire [0:0] ddr3_ck_n;
wire [0:0] ddr3_cke;
wire [0:0] ddr3_cs_n;
wire [1:0] ddr3_dm;
wire [0:0] ddr3_odt;
//wr_ddr
reg ui_clk;
reg rst;
reg data_req;
reg [127:0] wr_data;
reg [7:0] wr_brust_len;
reg wr_start;
reg [28:0] wr_addr;
reg [2:0] wr_cmd;
initial begin
sys_clk = 'd1;
rst_n <= 'd0;
#200
rst_n <= 'd1;
end
initial begin
data_req = 'd0;
wr_data = 'd0;
wr_brust_len = 'd64;
wr_start = 'd0;
wr_addr = 'd0;
wr_cmd = 'd0;
force ui_clk = inst_top_ddr3_init.inst_ddr3_wr_ctrl.ui_clk;
force rst = inst_top_ddr3_init.inst_ddr3_wr_ctrl.rst;
force data_req = inst_top_ddr3_init.inst_ddr3_wr_ctrl.data_req;
force inst_top_ddr3_init.wr_brust_len = wr_brust_len;
force inst_top_ddr3_init.wr_start = wr_start;
force inst_top_ddr3_init.wr_addr = wr_addr;
force inst_top_ddr3_init.wr_cmd = wr_cmd;
force inst_top_ddr3_init.wr_data = wr_data;
end
initial begin
#100
gen_cmd();
end
always @(posedge ui_clk or posedge rst) begin
if (rst) begin
wr_data <= 'd0;
end
else if (wr_data == 'd63) begin
wr_data <= 'd0;
end
else if (data_req) begin
wr_data <= wr_data + 'd1;
end
else begin
wr_data <= wr_data;
end
end
// initial begin
// #100
// gen_data();
// end
task gen_cmd;
begin
@ (negedge rst);
@ (posedge ui_clk);
@ (posedge ui_clk);
@ (posedge ui_clk);
@ (posedge ui_clk);
@ (posedge ui_clk);
wr_start = 'd1;
@ (posedge ui_clk);
wr_start = 'd0;
end
endtask
// task gen_data;
// integer i;
// begin
// @ (posedge data_req);
// for (i=0;i<64;i=i+1) begin
// wr_data = {96'd0,i[31:0]};
// @ (posedge ui_clk);
// if (data_req == 'd0) begin
// i = i - 1;
// end
// end
// wr_data = 'd0;
// @ (posedge ui_clk);
// end
// endtask
always #10 sys_clk = ~sys_clk;
top_ddr3_init inst_top_ddr3_init (
.ddr3_dq (ddr3_dq),
.ddr3_dqs_n (ddr3_dqs_n),
.ddr3_dqs_p (ddr3_dqs_p),
.ddr3_addr (ddr3_addr),
.ddr3_ba (ddr3_ba),
.ddr3_ras_n (ddr3_ras_n),
.ddr3_cas_n (ddr3_cas_n),
.ddr3_we_n (ddr3_we_n),
.ddr3_reset_n (ddr3_reset_n),
.ddr3_ck_p (ddr3_ck_p),
.ddr3_ck_n (ddr3_ck_n),
.ddr3_cke (ddr3_cke),
.ddr3_cs_n (ddr3_cs_n),
.ddr3_dm (ddr3_dm),
.ddr3_odt (ddr3_odt),
.sys_clk (sys_clk),
.rst_n (rst_n)
);
ddr3_model u_comp_ddr3 (
.rst_n (ddr3_reset_n),
.ck (ddr3_ck_p),
.ck_n (ddr3_ck_n),
.cke (ddr3_cke),
.cs_n (ddr3_cs_n),
.ras_n (ddr3_ras_n),
.cas_n (ddr3_cas_n),
.we_n (ddr3_we_n),
.dm_tdqs ({ddr3_dm[1],ddr3_dm[0]}),
.ba (ddr3_ba),
.addr (ddr3_addr),
.dq (ddr3_dq[15:0]),
.dqs ({ddr3_dqs_p[1],
ddr3_dqs_p[0]}),
.dqs_n ({ddr3_dqs_n[1],
ddr3_dqs_n[0]}),
.tdqs_n (),
.odt (ddr3_odt)
);
endmodule
先看总体的仿真波形,当 wr_start 信号拉高标志开始进行写指令的下发,根据wr_req 信号依次对写入的数据进行累加,最终完成写操作,将 wr_end 信号拉高。
可以看到以下仿真图形,采用的是第二种写时序,先给出要写入的数据,然后再给出写命令以及要写入的地址,每次数据累加1,地址累加8(因为突发长度为8),当 data_req 拉低表示 FIFO 已满,停止接收数据请求,同时 app_rdy 拉低表示 MIG IP 未准备好接收数据,因此 app_addr 停止累加。
当数据写完成后,可以看到一共写入的数据为0-63,地址累加至 504,最后写完成信号 wr_end 表示此次突发写操作完成。
在控制台看到的打印信息也可以清晰的看出,进行写操作,初始写数据为128位的0,地址依次累加。
在写完成后,可以看到最后写入的数据为 0000000000000000000000000000003f,即 128'd63,最后写入的地址为000001ff,即 29'd511,与设计的一致。
本系列为 DDR3 控制器设计总结,此系列包含 DDR3 控制器相关设计:认识 MIG、初始化、读写操作、FIFO 接口等。通过此系列的学习可以加深对 DDR3 读写时序的理解以及 FIFO 接口设计等,附上汇总博客直达链接。
【DDR3 控制器设计】系列博客汇总篇(附直达链接)