写在前面
本系列为 DDR3 控制器设计总结,此系列包含 DDR3 控制器相关设计:认识 MIG、初始化、读写操作、FIFO 接口等。通过此系列的学习可以加深对 DDR3 读写时序的理解以及 FIFO 接口设计等,附上汇总博客直达链接。
目录
实验任务
熟悉 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校准请求确认信号 |
读时序
读时序分为两种情况,在前面的写操作提到过,当 UI 速率比配置为 4:1 时,一次写入的数据为一次突发的数据;而当 UI 速率比配置为 2:1 时,一次写入的数据为一次突发的数据一半。
读操作也是如此,当 UI 速率比配置为 4:1 时,一次读出的数据为一次突发的数据,每次读操作只需要给一个初始地址。给出读指令、地址和命令握手信号后,若干个时钟周期后,数据被读出。

而当 UI 速率比配置为 2:1 时,一次读出的数据为一次突发的数据一半,每次读操作需要给两次初始地址。给出读指令、地址和命令握手信号后,若干个时钟周期后,数据被读出。

实验设计
实验设计框图如下,通过设计一个读控制模块,对 MIG IP 核进行读控制,在用户端的信号交互只需要提供读使能、地址等信号即可实现对 DDR3 进行读操作。本次实验仍可沿用之前的写控制模块。

读控制模块
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
/* Engineer : Linest-5
-
/* File : ddr3_rd.v
-
/* Create : 2022-09-23 09:24:30
-
/* Revise : 2022-09-23 09:24:30
-
/* Module Name : ddr3_rd
-
/* Description : ddr3的读操作模块
-
/* Editor : sublime text3, tab size (4)
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-
module ddr3_rd(
-
//时钟和复位
-
input ui_clk,
//用户时钟,由MIG提供
-
input rst,
//用户复位,高有效
-
//user interface
-
input init_calib_complete,
//初始化完成标志信号
-
input [
7:
0] rd_brust_len,
//突发读长度
-
input rd_start,
//开始写操作标志信号
-
input [
28:
0] rd_addr,
//开始读操作的起始地址
-
input [
2:
0] rd_cmd,
//读指令,3'b000为写,3'b001为读
-
output reg [
127:
0] rd_data,
//用户端读出的数据
-
output reg rd_data_valid,
//用户端读出的数据有效信号
-
output reg rd_end,
//读操作结束标志信号
-
//app interface
-
input app_rdy,
//MIG IP可以被下指令,ready
-
input [
127:
0] app_rd_data,
//从DDR中读出的数据
-
input app_rd_data_end,
//从DDR中读出的数据突发中的最后一个数据,这里设置的为4:1,所以直接和app_rd_data_valid信号一直
-
input app_rd_data_valid,
//从DDR中读出的数据有效信号
-
output [
2:
0] app_cmd,
//读写指令
-
output reg app_en,
//开始准备指令下发信号,valid信号,和 app_rdy信号形成握手信号
-
output reg [
28:
0] app_addr
//每次读操作的地址,突发为8,即每次操作地址加8
-
);
-
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
/* 信号申明 */
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
reg [
7:
0] rd_brust_len_reg;
-
reg [
7:
0] cmd_cnt;
-
reg [
7:
0] data_cnt;
-
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
/* Main Code */
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
-
assign app_cmd =
3'b001;
//3'b001表示进行读操作
-
-
//对输入的突发长度进行寄存
-
always @(posedge ui_clk or posedge rst) begin
-
if (rst) begin
-
rd_brust_len_reg <= 'd0;
-
end
-
else
if (rd_start) begin
-
rd_brust_len_reg <= rd_brust_len;
-
end
-
else begin
-
rd_brust_len_reg <= rd_brust_len_reg;
-
end
-
end
-
-
//对指令下达的次数进行计数
-
always @(posedge ui_clk or posedge rst) begin
-
if (rst) begin
-
cmd_cnt <= 'd0;
-
end
-
else
if (app_en && app_rdy && (cmd_cnt == (rd_brust_len_reg - 'd1))) begin
-
cmd_cnt <= 'd0;
-
end
-
else
if (app_en && app_rdy) begin
-
cmd_cnt <= cmd_cnt + 'd1;
-
end
-
else begin
-
cmd_cnt <= cmd_cnt;
-
end
-
end
-
-
//指令使能信号
-
always @(posedge ui_clk or posedge rst) begin
-
if (rst) begin
-
app_en <= 'd0;
-
end
-
else
if (app_en && app_rdy && (cmd_cnt == (rd_brust_len_reg - 'd1))) begin
//握手成功且为达到突发长度
-
app_en <= 'd0;
-
end
-
else
if (rd_start && init_calib_complete) begin
//开始读信号拉高时将指令使能信号拉高
-
app_en <= 'd1;
-
end
-
else begin
-
app_en <= app_en;
-
end
-
end
-
-
//读数据的地址
-
always @(posedge ui_clk or posedge rst) begin
-
if (rst) begin
-
app_addr <= 'd0;
-
end
-
else
if (rd_end) begin
-
app_addr <= 'd0;
-
end
-
else
if (app_en && app_rdy) begin
//当指令信号握手成功,对地址累加8,因为数据位宽为128,单地址的数据位宽为16
-
app_addr <= app_addr + 'd8;
-
end
-
else
if (rd_start && init_calib_complete) begin
//开始读信号拉高时将输入的初始地址赋值
-
app_addr <= rd_addr;
-
end
-
else begin
-
app_addr <= app_addr;
-
end
-
end
-
-
//读数据打拍
-
always @(posedge ui_clk or posedge rst) begin
-
if (rst) begin
-
rd_data <= 'd0;
-
end
-
else begin
-
rd_data <= app_rd_data;
-
end
-
end
-
-
//读数据有效信号打拍
-
always @(posedge ui_clk or posedge rst) begin
-
if (rst) begin
-
rd_data_valid <= 'd0;
-
end
-
else begin
-
rd_data_valid <= app_rd_data_valid;
-
end
-
end
-
-
//读出的数据个数计数
-
always @(posedge ui_clk or posedge rst) begin
-
if (rst) begin
-
data_cnt <= 'd0;
-
end
-
else
if (app_rd_data_valid && (data_cnt == (rd_brust_len_reg - 'd1))) begin
//数据有效信号为高且达到读突发长度
-
data_cnt <= 'd0;
-
end
-
else
if (app_rd_data_valid) begin
-
data_cnt <= data_cnt + 'd1;
-
end
-
else begin
-
data_cnt <= data_cnt;
-
end
-
end
-
-
//读操作计数博标志信号
-
always @(posedge ui_clk or posedge rst) begin
-
if (rst) begin
-
rd_end <= 'd0;
-
end
-
else
if (app_rd_data_valid && (data_cnt == (rd_brust_len_reg - 'd1))) begin
//当读出数据计数到突发数时拉高此信号
-
rd_end <= 'd1;
-
end
-
else begin
-
rd_end <= 'd0;
-
end
-
end
-
-
endmodule
顶层模块
只需要将读控制模块例化,并且先将写模块给注释,此次实验不涉及写数据操作,只验证读操作是否能正常进行。
testbench 设计
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/
-
/* Engineer : Linest-5
-
/* File : tb_top_ddr3_init.v
-
/* Create : 2022-09-15 10:10:36
-
/* Revise : 2022-09-23 10:51:59
-
/* 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;
-
-
-
//rd_ddr
-
reg ui_clk;
-
reg rst;
-
reg [
7:
0] rd_brust_len;
-
reg [
127:
0] rd_data;
-
reg rd_start;
-
reg [
28:
0] rd_addr;
-
reg [
2:
0] rd_cmd;
-
reg app_rdy;
-
reg [
127:
0] app_rd_data;
-
reg app_rd_data_end;
-
reg app_rd_data_valid;
-
reg [
2:
0] app_cmd;
-
-
initial begin
-
sys_clk = 'd1;
-
rst_n <= 'd0;
-
#
200
-
rst_n <= 'd1;
-
end
-
-
initial begin
-
rd_brust_len = 'd64;
-
rd_start = 'd0;
-
rd_addr = 'd0;
-
rd_cmd =
3'b001;
-
force ui_clk = inst_top_ddr3_init.inst_ddr3_rd.ui_clk;
-
force rst = inst_top_ddr3_init.inst_ddr3_rd.rst;
-
force rd_data = inst_top_ddr3_init.inst_ddr3_rd.rd_data;
-
force inst_top_ddr3_init.rd_brust_len = rd_brust_len;
-
force inst_top_ddr3_init.rd_start = rd_start;
-
force inst_top_ddr3_init.rd_addr = rd_addr;
-
force inst_top_ddr3_init.rd_cmd = rd_cmd;
-
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);
-
rd_start = 'd1;
-
@ (posedge ui_clk);
-
rd_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
仿真波形
从下图可以看出,在初始化完成标志信号拉高后,给出开始读操作标志信号,设定读突发长度为64,随后若干个时钟周期后,数据被读出。

由于事先将数据写入至 DDR 中,因此读出的数据都为xxxx,但是可以验证读出的逻辑设计是没问题的。

下图为控制台的打印信息,可以看到依次按地址读出数据,如果先将数据写入,数据就会被正常读出。在下一篇完成完整的读写操作。

汇总篇
本系列为 DDR3 控制器设计总结,此系列包含 DDR3 控制器相关设计:认识 MIG、初始化、读写操作、FIFO 接口等。通过此系列的学习可以加深对 DDR3 读写时序的理解以及 FIFO 接口设计等,附上汇总博客直达链接。
转载:https://blog.csdn.net/m0_61298445/article/details/127500547
