当前位置: 首页 > news >正文

【DDR3 控制器设计】(4)DDR3 的读操作设计

写在前面

本系列为 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校准请求确认信号

读时序

读时序分为两种情况,在前面的写操作提到过,当 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 接口设计等,附上汇总博客直达链接。

【DDR3 控制器设计】系列博客汇总篇(附直达链接)

相关文章:

  • 基于OpenCV的单目相机标定与三维定位(推广)
  • Java数据结构:单链表的实现与面试题汇总
  • 2022年都说软件测试不香了?在职3年月薪16k我满意了,你们觉得前景怎么样?
  • python做了个自动关机工具,再也不会耽误我下班啦
  • BUUCTF NewStarCTF 公开赛赛道Week5 Writeup
  • @Conditional注解详解
  • 动态路由协议解析(rip)
  • 38、Java 中的正则表达式(单字符匹配和预定义字符)
  • 电气论文实现:基于优化算法和python-pandapower的配电网重构(IEEE33节点算例)
  • 刚来的00后真的卷,听说工作还没两年,跳到我们公司直接起薪20k...
  • 【云原生 · Docker】Docker 镜像操作、容器操作常用指令
  • 基于粒子群优化算法的无人机路径规划与轨迹算法的实现(Matlab代码实现)
  • Spring Cloud基本介绍
  • 【目标检测】使用TensorRT加速YOLOv5
  • python数据分析及可视化(九)pandas数据规整(分组聚合、数据透视表、时间序列、数据分析流程)
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 《微软的软件测试之道》成书始末、出版宣告、补充致谢名单及相关信息
  • Apache Pulsar 2.1 重磅发布
  • CentOS 7 防火墙操作
  • IDEA常用插件整理
  • JavaWeb(学习笔记二)
  • leetcode98. Validate Binary Search Tree
  • python_bomb----数据类型总结
  • Spring Cloud Alibaba迁移指南(一):一行代码从 Hystrix 迁移到 Sentinel
  • Spring Security中异常上抛机制及对于转型处理的一些感悟
  • SpriteKit 技巧之添加背景图片
  • vue-router 实现分析
  • 安卓应用性能调试和优化经验分享
  • 和 || 运算
  • 如何编写一个可升级的智能合约
  • 使用parted解决大于2T的磁盘分区
  • 使用阿里云发布分布式网站,开发时候应该注意什么?
  • 限制Java线程池运行线程以及等待线程数量的策略
  • 一天一个设计模式之JS实现——适配器模式
  • 教程:使用iPhone相机和openCV来完成3D重建(第一部分) ...
  • 支付宝花15年解决的这个问题,顶得上做出十个支付宝 ...
  • ​LeetCode解法汇总518. 零钱兑换 II
  • #!/usr/bin/python与#!/usr/bin/env python的区别
  • #1014 : Trie树
  • (22)C#传智:复习,多态虚方法抽象类接口,静态类,String与StringBuilder,集合泛型List与Dictionary,文件类,结构与类的区别
  • (C语言)strcpy与strcpy详解,与模拟实现
  • (南京观海微电子)——COF介绍
  • (牛客腾讯思维编程题)编码编码分组打印下标(java 版本+ C版本)
  • (四)图像的%2线性拉伸
  • (学习日记)2024.04.10:UCOSIII第三十八节:事件实验
  • (原创)boost.property_tree解析xml的帮助类以及中文解析问题的解决
  • (原创)攻击方式学习之(4) - 拒绝服务(DOS/DDOS/DRDOS)
  • (转)Sublime Text3配置Lua运行环境
  • .net framework 4.0中如何 输出 form 的name属性。
  • .Net 路由处理厉害了
  • .NET程序员迈向卓越的必由之路
  • @RequestMapping处理请求异常
  • [ 渗透测试面试篇 ] 渗透测试面试题大集合(详解)(十)RCE (远程代码/命令执行漏洞)相关面试题
  • [100天算法】-x 的平方根(day 61)
  • [100天算法】-目标和(day 79)