赞
踩
DDR SDRAM(Double Data Rate SDRAM),即(第一代)双倍速率同步动态随机存储器,它的主要结构示意图如上图所示。相较于SDRAM,它的主要有以下几点不同:
CK
的上升沿和下降沿,在上升沿和下降沿都可以传输数据。DQ
上。写入操作也与之类似,先将数据读入到缓存电路中,将数据拼接后存入对应的存储单元中。DQS
:数据变化只在DQS
的边沿改变,此信号只由数据的发送方给出,提供给数据的接收方用于同步数据信号,目的就是为了在高速的数据传输中保持数据传输的稳定性。
DDR2的主体结构与第一代DDR没有太大的不同,但是读取速度提升了一倍。可以看到DDR2的内部结构相较于之前也更复杂,在这里仅仅对一些知识点作一些科普性的说明:
可以看到,DDR3 相较于前几代版本越来越复杂,很多工程师添加和很多的新功能,提升了数据的存储容量和传输效率。但是笔者学习了相关内容之后发现,想要自行编写 DDR3 的 Verilog HDL 程序是非常不现实的,其中涉及非常多时序等待时间的计算和校准,调用官方自带的IP核对于开发效率的提升还是很大的。
DDR3相较于前几代添加的核心概念有以下几点(科普性质内容,了解即可):
突发长度(Burst Length,BL)与前几代不同:由于DDR3的预取为8bit,所以突发传输周期(Burst Length,BL)也固定为8,而对于DDR2和早期的DDR架构系统,BL=4也是常用的,DDR3为此增加了一个4bit Burst Chop(突发突变)模式,即由一个BL=4的读取操作加上一个BL=4的写入操作来合成一个BL=8的数据突发传输,届时可通过地址线来控制这一突发模式。而且需要指出的是,任何突发中断操作都将在ddr3中予以禁止,且不予支持,取而代之的是更灵活的突发传输控制(如4bit顺序突发)。
寻址时序(Timing)改变:就像DDR2从DDR转变而来后延迟周期数增加一样,DDR3的CL周期也将比DDR2有所提高。DDR2的CL范围一般在2~5之间,而DDR3则在5~11之间,且附加延迟(AL)的设计也有所变化。DDR2时AL的范围是0~4,而DDR3时AL有三种选项,分别是0、CL-1和CL-2。另外,DDR3还新增加了一个时序参数——写入延迟(CWD),这一参数将根据具体的工作频率而定。
新增重置(Reset)功能:重置是DDR3新增的一项重要功能,并为此专门准备了一个引脚。DRAM业界很早以前就要求增加这一功能,如今终于在DDR3上实现了。这一引脚将使DDR3的初始化处理变得简单。当Reset命令有效时,DDR3内存将停止所有操作,并切换至最少量活动状态,以节约电力。
在Reset期间,DDR3内存将关闭内在的大部分功能,所有数据接收与发送器都将关闭,所有内部的程序装置将复位,DLL(延迟锁相环路)与时钟电路将停止工作,而且不理睬数据总线上的任何动静。这样一来,将使DDR3达到最节省电力的目的。
新增ZQ校准功能:ZQ也是一个新增的引脚,在这个引脚上接有一个240欧姆的低公差参考电阻。这个引脚通过一个命令集,通过片上校准引擎(On-Die Calibration Engine,ODCE)来自动校验数据输出驱动器导通电阻与ODT的终结电阻值。当系统发出这一指令后,将用相应的时钟周期(在加电与初始化之后用512个时钟周期,在退出自刷新操作后用256个时钟周期、在其他情况下用64个时钟周期)对导通电阻和ODT电阻进行重新校准。、
两个参考电压:在DDR3系统中,对于内存系统工作非常重要的参考电压信号VREF将分为两个信号,即为命令与地址信号服务的VREFCA和为数据总线服务的VREFDQ,这将有效地提高系统数据总线的信噪等级。
点对点连接(Point-to-Point,P2P):这是为了提高系统性能而进行的重要改动,也是DDR3与DDR2的一个关键区别。在DDR3系统中,一个内存控制器只与一个内存通道打交道,而且这个内存通道只能有一个插槽,因此,内存控制器与DDR3内存模组之间是点对点(P2P)的关系(单物理Bank的模组),或者是点对双点(Point-to-two-Point,P22P)的关系(双物理Bank的模组),从而大大地减轻了地址/命令/控制与数据总线的负载。而在内存模组方面,与DDR2的类别相类似,也有标准DIMM(台式PC)、SO-DIMM/Micro-DIMM(笔记本电脑)、FB-DIMM2(服务器)之分,其中第二代FB-DIMM将采用规格更高的AMB2(高级内存缓冲器)。
面向64位构架的DDR3显然在频率和速度上拥有更多的优势,此外,由于DDR3所采用的根据温度自动自刷新、局部自刷新等其它一些功能,在功耗方面DDR3也要出色得多,因此,它可能首先受到移动设备的欢迎,就像最先迎接DDR2内存的不是台式机而是服务器一样。在CPU外频提升最迅速的PC台式机领域,DDR3未来也是一片光明。Intel所推出的新芯片-熊湖(Bear Lake),其将支持DDR3规格,而AMD也预计同时在K9平台上支持DDR2及DDR3两种规格。
达芬奇板载的DDR3 SDRAM电路图如下,可以当作DDR3硬件设计的一个参考示例:
使用FPGA的MIG IP核操作DDR3的一些相关概念
操作DDR3的重点是操作链接DDR3的MIG IP,只需要设计这一部分的读写时序,就可以完成对DDR3的读写操作。
MIG IP核需要输入两个时钟,一个时钟为系统时钟sys_clk
,一个时钟为FPGA(用户端)给出的参考时钟ref_clk
。
DDR3芯片本身工作也需要一个时钟,这个工作时钟一般由sys_clk通过MIG IP核内部的PLL倍频得到。DDR3的工作时钟是差分时钟信号。用户端工作同样需要一个时钟ui_clk
,这个时钟同样一般由sys_clk通过MIG IP核内部的PLL倍频得到。
DDR3芯片工作的时钟与用户时钟存在一个比例关系。DDR3芯片的工作时钟频率一般是ui_clk的2、4倍。当DDR3的时钟工作频率为800MHz时,ui_clk只能是200MHz,也就是只能维持4:1的比例。用户端的ui_clk虽然是PLL倍频得到的,但是不需要在Vivado中手动生成PLL IP核,MIG IP帮助我们完成了这一项操作。
MIG IP核有两个用户接口,一个是Native接口,即本地接口,可以理解成普通的、通过普通数据线的接口,它是比较简单比较常见的接口;另一个接口是AXI4接口,类似ZYNQ芯片,需要在用户端定义一个AXI4主机,连接MIG IP核的AXI4从机,通过AXI4总线进行数据交互。
学习MIG IP核有以下重要的几点(学习顺序):
- 明确MIG IP有哪些输入信号,有哪些输出信号,这些信号都代表什么含义;
- 用户端怎么向MIG IP核中读写数据?具体的读写时序是什么?
具体可以参考Xilinx官方的用户参考文档ug586,其中包含ZYNQ和A7系列芯片如何使用存储器接口。也可以参考官方给出的example design代码。
设计用户端读写时序的关键在于明确读写的时序图。端口信号可以分为三组:命令信号、读信号、写信号。每一组信号中的数据有效通过数据握手和数据有效信号确定。
MIG(Memory Interface Generate)是Xilinx Vivado自带的一个IP核,Native接口的结构以及连接关系如下图所示。可以看到,MIG IP核相当于一个连接FPGA用户逻辑和DDR3存储器的桥梁。用户仅需要关心左侧的用户接口的定义和时序即可。在这里需要明确,系统时钟sys_clk
由PLL IP核产生,输入到MIG IP核中,MIG IP核产生两路时钟,一路是提供给外部DDR芯片的差分时钟ddr_ck
和ddr_ck_n
,一路是提供给用户端的用户时钟ui_clk
,即下面图中的clk
。
在IP Catalog中搜索mig
,即可找到MIG IP核。
开始配置MIG,首先配置下列信息
选择是否兼容其他型号的FPGA(本次实验不需要)
选择DDR3控制器
配置时钟选项。这里配置的时钟是DDR3芯片工作的物理器件差分时钟。包括器件时钟、器件时钟与用户时钟的比率(本次实验器件时钟为400MHz,用户时钟为100MHz)。Vccaux_io是一个与MIG性能有关的电压,本次实验同样保持默认。
配置存储器选项、ECC和数据屏蔽。Memory Type
选择Components;存储芯片只能选择美光(MT)的器件,具体的存储芯片型号要和实际的存储大小一致(达芬奇开发板保证设置为128M16即可)。ECC选项只有当数据位宽达到72位才可以开启。数据屏蔽功能本实验没有用到,这里开启或关闭对本次实验没有影响。
Bank Machine
的数量决定了MIG的性能,但相对的,使用过多也会增大芯片内部的逻辑资源消耗,一般选择一个适中的数值。ORDERING
规定是否允许MIG对指令进行重排序,在这里选择开启。
配置MIG IP核输入时钟频率(即MIG IP核的工作时钟,一般可以理解成系统时钟sys_clk
,在这里为200MHz,有可能会出现Bug,选择为200MHz后打开依然是400MHz)、突发类型选择(这里选择顺序突发)、输出阻抗控制(默认选择RZQ/7)、终结电阻控制(默认选择RZQ/4)、片选信号输出使能(是否将#CS
信号引出,选择Enable)、寻址模式(颠倒Bank和行的寻址次序可以提高效率,连续地址可以并行处理)
配置FPGA内部相关信息。第一步中配置的是MIG IP核的工作时钟和工作过程中需要的参考时钟。如果工作时钟是PLL IP核倍频得到的,这里就不需要额外添加缓冲了(PLL IP产生的内部时钟默认勾选Buffer),选择No Buffer;如果工作时钟是FPGA芯片外部晶振产生直接通过FPGA管脚进入MIG IP的,那么就要根据进入时钟的形式选择单端或差分时钟。参考时钟的频率一般确定为200MHz,不能随意更改。如果系统时钟就是200MHz,就可以选择“使用系统时钟”,如果系统时钟不是200MHz,就要根据和系统时钟类似的原理选择输入时钟的形式。 关于最后一项使用XADC进行MIG IP温度补偿:由于XADC是Xilinx7系列特有的硬核,且只能使用一次,所以只要其他地方使能了XADC,这里就只能选择Disable。
阻抗匹配设置,这里保持默认。
引脚模式设置。如果仅进行DDR3的仿真学习就可以选择第一个,如果要上板验证就要选择第二个。
设置引脚。可以事先写好.xdc
文件,再导入直接读取引脚电平设置,点击Validate使引脚设置生效即可。
之后的选项均为总结页面,协议声明等,全部保持默认即可。
关于数据带宽:数据位宽的计算方法是:数据频率×单/双沿传输×数据位宽,例如:假设DDR3的工作频率为800MHz,在上升沿和下降沿都传输数据,数据深度为16bit,那么数据带宽就是800M×2×16。这个带宽必须要与用户端和MIG IP核的带宽保持一致,如果用户端工作频率为200MHz,仅在上升沿传输数据,那么数据深度(位宽)就必须是128bit。
ddr3_rw_top.v
module ddr3_rw_top( input sys_clk, // 系统时钟 input sys_rst_n, // 复位,低有效 // DDR3 inout [31:0] ddr3_dq , // DDR3 数据 inout [3:0] ddr3_dqs_n , // DDR3 dqs负 inout [3:0] ddr3_dqs_p , // DDR3 dqs正 output [13:0] ddr3_addr , // DDR3 地址 output [2:0] ddr3_ba , // DDR3 banck 选择 output ddr3_ras_n , // DDR3 行选择 output ddr3_cas_n , // DDR3 列选择 output ddr3_we_n , // DDR3 读写选择 output ddr3_reset_n , // DDR3 复位 output [0:0] ddr3_ck_p , // DDR3 时钟正 output [0:0] ddr3_ck_n , // DDR3 时钟负 output [0:0] ddr3_cke , // DDR3 时钟使能 output [0:0] ddr3_cs_n , // DDR3 片选 output [3:0] ddr3_dm , // DDR3_dm output [0:0] ddr3_odt , // DDR3_odt //用户 output led // 错误指示信号 ); // wire define wire error_flag; wire ui_clk ; // 用户时钟 wire [27:0] app_addr; // DDR3 地址 wire [2:0] app_cmd; // 用户读写命令 wire app_en; // MIG IP核使能 wire app_rdy; // MIG IP核空闲 wire [255:0] app_rd_data; // 用户读数据 wire app_rd_data_end; // 突发读当前时钟最后一个数据 wire app_rd_data_valid; // 读数据有效 wire [255:0] app_wdf_data; // 用户写数据 wire app_wdf_end; // 突发写当前时钟最后一个数据 wire [31:0] app_wdf_mask; // 写数据屏蔽 wire app_wdf_rdy; // 写空闲 wire app_sr_active; // 保留 wire app_ref_ack; // 刷新请求 wire app_zq_ack; // ZQ 校准请求 wire app_wdf_wren; // DDR3 写使能 wire locked; // 锁相环频率稳定标志 wire clk_ref_i; // DDR3参考时钟 wire sys_clk_i; // MIG IP核输入时钟 wire clk_200; // 200M时钟 wire ui_clk_sync_rst; // 用户复位信号 wire init_calib_complete; // 校准完成信号 //***************************************************** //** main code //***************************************************** //读写模块 ddr3_rw u_ddr3_rw( .ui_clk (ui_clk), .ui_clk_sync_rst (ui_clk_sync_rst), .init_calib_complete (init_calib_complete), .app_rdy (app_rdy), .app_wdf_rdy (app_wdf_rdy), .app_rd_data_valid (app_rd_data_valid), .app_rd_data (app_rd_data), .app_addr (app_addr), .app_en (app_en), .app_wdf_wren (app_wdf_wren), .app_wdf_end (app_wdf_end), .app_cmd (app_cmd), .app_wdf_data (app_wdf_data), .state (state), .rd_addr_cnt (rd_addr_cnt), .wr_addr_cnt (wr_addr_cnt), .rd_cnt (rd_cnt), .error_flag (error_flag), .led (led) ); // MIG IP核模块 mig_7series_0 u_mig_7series_0 ( // 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 [31:0] ddr3_dq .ddr3_dqs_n (ddr3_dqs_n), // inout [3:0] ddr3_dqs_n .ddr3_dqs_p (ddr3_dqs_p), // inout [3:0] ddr3_dqs_p .init_calib_complete (init_calib_complete), // init_calib_complete .ddr3_cs_n (ddr3_cs_n), // output [0:0] ddr3_cs_n .ddr3_dm (ddr3_dm), // output [3: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 [255:0] app_wdf_data .app_wdf_end (app_wdf_end), // input app_wdf_end .app_wdf_wren (app_wdf_wren),// input app_wdf_wren .app_rd_data (app_rd_data), // output [255: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 (), // input app_sr_req .app_ref_req (), // input app_ref_req .app_zq_req (), // 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 (32'b0), // input [31:0] app_wdf_mask // System Clock Ports .sys_clk_i (clk_200), // Reference Clock Ports .clk_ref_i (clk_200), .sys_rst (sys_rst_n) // input sys_rst ); //PLL模块 clk_wiz_0 u_clk_wiz_0 ( // Clock out ports .clk_out1(clk_200), // output clk_out1 .clk_out2(clk_50), // Status and control signals .reset(1'b0), // input resetn .locked(locked), // output locked // Clock in ports .clk_in1(sys_clk) ); // input clk_in1 endmodule
ddr3_rw.v
module ddr3_rw ( input ui_clk, //用户时钟 input ui_clk_sync_rst, //复位,高有效 input init_calib_complete, //DDR3初始化完成 input app_rdy, //MIG 命令接收准备好标致 input app_wdf_rdy, //MIG数据接收准备好 input app_rd_data_valid, //读数据有效 input [255:0] app_rd_data, //用户读数据 output reg [27:0] app_addr, //DDR3地址 output app_en, //MIG IP发送命令使能 output app_wdf_wren, //用户写数据使能 output app_wdf_end, //突发写当前时钟最后一个数据 output [2:0] app_cmd, //MIG IP核操作命令,读或者写 output reg [255:0] app_wdf_data, //用户写数据 output reg [1 :0] state, //读写状态 output reg [23:0] rd_addr_cnt, //用户读地址计数 output reg [23:0] wr_addr_cnt, //用户写地址计数 output reg [20:0] rd_cnt, //实际读地址标记 output reg error_flag, //读写错误标志 output reg led //读写测试结果指示灯 ); //parameter define parameter TEST_LENGTH = 1000; parameter L_TIME = 25'd25_000_000; parameter IDLE = 2'd0; //空闲状态 parameter WRITE = 2'd1; //写状态 parameter WAIT = 2'd2; //读到写过度等待 parameter READ = 2'd3; //读状态 //reg define reg [24:0] led_cnt; //led计数 //wire define wire error; //读写错误标记 wire rst_n; //复位,低有效 //***************************************************** //** main code //***************************************************** assign rst_n = ~ui_clk_sync_rst; //读信号有效,且读出的数不是写入的数时,将错误标志位拉高 assign error = (app_rd_data_valid && (rd_cnt!=app_rd_data)); //在写状态MIG IP 命令接收和数据接收都准备好,或者在读状态命令接收准备好,此时拉高使能信号, assign app_en = ((state == WRITE && (app_rdy && app_wdf_rdy)) ||(state == READ && app_rdy)) ? 1'b1:1'b0; //在写状态,命令接收和数据接收都准备好,此时拉高写使能 assign app_wdf_wren = (state == WRITE && (app_rdy && app_wdf_rdy)) ? 1'b1:1'b0; //由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同 assign app_wdf_end = app_wdf_wren; //处于读的时候命令值为1,其他时候命令值为0 assign app_cmd = (state == READ) ? 3'd1 :3'd0; //DDR3读写逻辑实现 always @(posedge ui_clk or negedge rst_n) begin if((~rst_n)||(error_flag)) begin state <= IDLE; app_wdf_data <= 128'd0; wr_addr_cnt <= 24'd0; rd_addr_cnt <= 24'd0; app_addr <= 28'd0; end else if(init_calib_complete) begin //MIG IP核初始化完成 case(state) IDLE: begin state <= WRITE; app_wdf_data <= 256'd0; wr_addr_cnt <= 24'd0; rd_addr_cnt <= 24'd0; app_addr <= 28'd0; end WRITE: begin if(wr_addr_cnt == TEST_LENGTH - 1 &&(app_rdy && app_wdf_rdy)) state <= WAIT; //写到设定的长度跳到等待状态 else if(app_rdy && app_wdf_rdy) begin //写条件满足 app_wdf_data <= app_wdf_data + 1; //写数据自加 wr_addr_cnt <= wr_addr_cnt + 1; //写地址自加 app_addr <= app_addr + 8; //DDR3 地址加8 end else begin //写条件不满足,保持当前值 app_wdf_data <= app_wdf_data; wr_addr_cnt <= wr_addr_cnt; app_addr <= app_addr; end end WAIT: begin state <= READ; //下一个时钟,跳到读状态 rd_addr_cnt <= 24'd0; //读地址复位 app_addr <= 28'd0; //DDR3读从地址0开始 end READ: begin //读到设定的地址长度 if(rd_addr_cnt == TEST_LENGTH - 1 && app_rdy) state <= IDLE; //则跳到空闲状态 else if(app_rdy) begin //若MIG已经准备好,则开始读 rd_addr_cnt <= rd_addr_cnt + 1'd1; //用户地址每次加一 app_addr <= app_addr + 8; //DDR3地址加8 end else begin //若MIG没准备好,则保持原值 rd_addr_cnt <= rd_addr_cnt; app_addr <= app_addr; end end default: begin state <= IDLE; app_wdf_data <= 256'd0; wr_addr_cnt <= 24'd0; rd_addr_cnt <= 24'd0; app_addr <= 28'd0; end endcase end end //对DDR3实际读数据个数编号计数 always @(posedge ui_clk or negedge rst_n) begin if(~rst_n) rd_cnt <= 0; //若计数到读写长度,且读有效,地址计数器则置0 else if(app_rd_data_valid && rd_cnt == TEST_LENGTH - 1) rd_cnt <= 0; //其他条件只要读有效,每个时钟自增1 else if (app_rd_data_valid ) rd_cnt <= rd_cnt + 1; end //寄存状态标志位 always @(posedge ui_clk or negedge rst_n) begin if(~rst_n) error_flag <= 0; else if(error) error_flag <= 1; end //led指示效果控制 always @(posedge ui_clk or negedge rst_n) begin if((~rst_n) || (~init_calib_complete )) begin led_cnt <= 25'd0; led <= 1'b0; end else begin if(~error_flag) //读写测试正确 led <= 1'b1; //led灯常亮 else begin //读写测试错误 led_cnt <= led_cnt + 25'd1; if(led_cnt == L_TIME - 1'b1) begin led_cnt <= 25'd0; led <= ~led; //led灯闪烁 end end end end endmodule
原创笔记,码字不易,欢迎点赞,收藏~ 如有谬误敬请在评论区不吝告知,感激不尽!博主将持续更新有关嵌入式开发、FPGA方面的学习笔记。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。