RTL库

开始使用xilinx 7 系列内存控制器核

RTL库 FPGA开发框架 提供了DDR控制器使用模板可以例化使用,提供基于vivado软件的读写时序样板代码,方便二次开发……

开始使用7系列 DDR3 Ctrl IP核


7系列 DDR3 Ctrl IP 核是基于user interface或者AXI接口进行读写,RTL库框架提供了一个标准的驱动时序,有了示例代码框架方便用户二次开发。

A7DDR3控制器配置

IP核配置

  • MIG Output Options=Create Design创建IP核设计
  • Component Name = mig_7series_0 设置IP例化名称
  • Multi-Controller = 1控制器数量
  • AXI4 Interface = Disable不选中AXI接口
  • Pin Compatible FPGAs = Default目标FPGA芯片保持默认即可
  • Controller type = DDR3内存控制器类型选择
  • Clock Period = 2500ps设置内存时钟主频
  • Phy to Controller clock ratio = 4:1内存主时钟和控制器时钟比值
  • vccaux_io = 1.5v设置vccaux电压
  • Memory type = Components 内存类型
  • Memory Part = MT41K128M16XX-15E内存器件型号
  • Memory Voltage = 1.35v内存电压1.35v
  • Data Width = 16内存数据位宽
  • ECC = Disable校验纠错
  • Data Mask = selected数据掩码
  • Number of bank Machines = 4
  • ORDEGING = Normal命令执行模式
  • Input Clock Period = 5000ps(200Mhz)设置输入时钟频率
  • Read Burst Type and Length = 8 Sequential 设置突发类型和长度
  • Output Driver Impedance Control = RZQ/7阻抗配置
  • Controller Chip Select Pin = Enable 使用片选信号
  • On die Termination = RZQ/4
  • Memory Address Mapping Selection = BANK-ROW-COLUMN控制器地址的映射关系
  • System Clock = Single-Ended系统时钟选择
  • Reference Clock = Use System Clock参考时钟选择
  • System Reset Polarity = ACTIVE LOW系统复位有效电平
  • Debug Signals for Memory Controller = OFF监测Debug信号
  • Internal Vref = no select内部参考电压
  • IO Power Reduction = ON降低IO功耗
  • XADC Instantiation = Enable是否例化XADC
  • Internal Termination Impedance = 40 Ohms内部终端阻抗
  • Fixed Pin Out = Selected选择管脚分配方式
  • Read XDC/UCF = 请点击我获取通过UCF/XDC设置管脚
  • validate = Current pin out is valid验证管脚(设置完成之后点击)
  • System clock and Reference clock status signals = Default系统时钟和参考时钟以及状态信号默认选择
DDR3管脚分析示例

#save as a7ddr3pin.ucf
#另存为 a7ddr3pin.ucf
NET "ddr3_dq[0]"         LOC = "A1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[1]"         LOC = "C2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[2]"         LOC = "B2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[3]"         LOC = "E2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[4]"         LOC = "D2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[5]"         LOC = "G1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[6]"         LOC = "F1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[7]"         LOC = "F3"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[8]"         LOC = "J1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[9]"         LOC = "H2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[10]"        LOC = "G2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[11]"        LOC = "J5"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[12]"        LOC = "H5"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[13]"        LOC = "H3"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[14]"        LOC = "G3"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dq[15]"        LOC = "H4"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dm[0]"         LOC = "B1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dm[1]"         LOC = "K1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_dqs_p[0]"      LOC = "E1"  |   IOSTANDARD = DIFF_SSTL135 ;                                 
NET "ddr3_dqs_n[0]"      LOC = "D1"  |   IOSTANDARD = DIFF_SSTL135 ;                                 
NET "ddr3_dqs_p[1]"      LOC = "K2"  |   IOSTANDARD = DIFF_SSTL135 ;                                 
NET "ddr3_dqs_n[1]"      LOC = "J2"  |   IOSTANDARD = DIFF_SSTL135 ;                                 
NET "ddr3_addr[13]"      LOC = "AB5" |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[12]"      LOC = "U3"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[11]"      LOC = "Y2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[10]"      LOC = "T1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[9]"       LOC = "AA5" |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[8]"       LOC = "AA1" |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[7]"       LOC = "AA4" |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[6]"       LOC = "Y1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[5]"       LOC = "AA3" |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[4]"       LOC = "W1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[3]"       LOC = "Y3"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[2]"       LOC = "AB3" |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[1]"       LOC = "W2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_addr[0]"       LOC = "AB2" |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_ba[2]"         LOC = "AB1" |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_ba[1]"         LOC = "U2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_ba[0]"         LOC = "W4"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_ck_p[0]"       LOC = "R3"  |   IOSTANDARD = DIFF_SSTL135 ;                                 
NET "ddr3_ck_n[0]"       LOC = "R2"  |   IOSTANDARD = DIFF_SSTL135 ;                                 
NET "ddr3_ras_n"         LOC = "U1"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_cas_n"         LOC = "V2"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_we_n"          LOC = "V4"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_reset_n"       LOC = "G4"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_cke[0]"        LOC = "W6"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_odt[0]"        LOC = "W5"  |   IOSTANDARD = SSTL135      ;                                 
NET "ddr3_cs_n[0]"       LOC = "V3"  |   IOSTANDARD = SSTL135      ;                                                              
  
  

A7DDR3控制器结构框图

  1. 框图左侧为用户接口,用户可以通过编程此接口完成对DDR内存的读写(这部分时序我们需要重点关注);
  2. 框图中间部分为7系列xilinx FPGA的内存接口解决方案,此部分也就是我们调用的xilinx的IPcore;
  3. 框图右侧是内存芯片接口,用户无需关心此处时序(IPcore完成此部分时序);

7系列DDR控制器用户接口模式

User interface 模式

User interface 模式是IP核接口的其中的一种形式,目的把实际DDR内存接口复杂的指令周期和读写突发结构简化为用户简单易控的接口。其中用户控制的地址映射到实际内存的BANK,ROW,COLUMN地址中;对IP核接口读写操作映射到内存的读写突发;刷新内存操作不是必须选项,再特定需求场景用户也可以控制。

内存刷新的理解,对于第一次接触动态内存的开发人员,我这里举个例子助于理解刷新机制。根据内存颗粒芯片要求,每隔一定周期需要对内存刷新一次,不然呢数据就会丢失,类似一个流沙的漏斗,一旦在一定周期内不加沙就会漏光。实际对应内存中就是充电操作,每隔一定周期对内存充电一次保证数据的完整性。

DDR内存地址和用户地址映射关系

地址映射分为两种中方式

第一种BANK-ROW-COLUMN,这种模式当地址连续递增时,第一行数据写满会跳入下一行进行继续写,一个BANK写满会继续下一个BANK写,读也是如此。这种模式适用于碎片数据读写,当然读写连续突发小于等于一行长度。

第一种ROW-BANK-COLUMN,这种模式当地址连续递增时,第一行数据写满会跳入下一BANK的同一行进行继续写,一个全部BANK的同一行被写满后跳转到下一行,读也是如此。此种模式适用于连续读写大于一行的操作,为什么呢,这种模式在控制器中可以连续激活多个BANK的同一行,这样在连续突发过程中无需激活操作,增加数据连续性和数据吞吐量。
激活操作意义:在读写内存数据中为了寻址,每次只能读写某个BANK中某一行的数据,所以需要先激活BANK和ROW才可以读写。

DDR控制器地址接口定义

CMD接口中PX_CMD_addr定义

因为PX_CMD_addr地址位字节地址,所以在当用户接口选择32位、64位、128位时地址定义需要进行按照4字节、8字节、16字节对齐。

用户接口位宽芯片接口位宽接口地址定义
32位16位px_cmd_addr[0]=1'b0(低一位必须等于0)
64位16位px_cmd_addr[1:0]=2'b00(低两位必须等于0)
128位16位px_cmd_addr[2:0]=3'b000(低三位必须等于0)

DDR控制器用户模式读写时序图

DDR控制器写时序图 app_cmd=3'd0

  • 注意: 官方手册支持3种写时序,我这里以命令和数据写对齐方式给大家讲解时序,其中黑色字体信号为用户编程信号,蓝色信号为IPcore输出信号作为参考信号。
  • 第一步: 编程app_en信号,当app_rdy和app_wdf_rdy同时为高时可以产生app_en为高启动写操作,当app_rdy或app_wdf_rdy为低时app_en同时也拉低,目的为了写指令和写数据同步操作,这样容易控制。
    app_addr地址按照(用户接口位宽=128除以内存接口位宽16=8)8递增。
    app_mask为掩码,设置为0保证每个字节数据可以被写入内存,反之如果不想把128位全部写入内存,可以把掩码对应比特位设置为高,其中一个掩码位控制8比特数据。
  • 第二步: 此时写数据使能信号等效于app_en,例如app_wdf_wren=app_wdf_end=app_en,这样减少逻辑编程工作,app_en 和app_wdf_wren同步很好理解,命令和数据对齐,那么app_wdf_end是什么作用呢?
    app_wdf_end是给内部控制器提供突发结束标志的,但是这个标志依赖于内存内部突发操作设置,比如用户接口位宽128位,内存接口是16位,内存芯片内部突发长度是8那么,用户每写一次数据正好是内存内部一次突发(8*16=128bit)所以每次数据写都是一个完整突发;那么当用户接口为64位,内存接口为16位,内存芯片内部突发长度是8,那么用户每次写入的64位数据是完整突发的一半,所以用户需要写入两次64位数据才能完成一次内存芯片内部的突发,这样就需要第一个写用户接口64位数据时app_wdf_end=0,第二个写用户接口64位数据时app_wdf_end=1,以此类推第三次为0,第四次为1....
    app_data数据可以来源于数据源,ram或fifo缓冲器;

DDR控制器读时序图 app_cmd=3'd1

  • 注意:读指令执行后有数据输出潜伏期,也就事read latency,这个时钟周期数不固定。
  • 第一步:检测当app_rdy为高时可以启动app_en和app_cmd以及app_addr这样命令被控制器所接收,当app_rdy为有效时可以连续发送读命令
  • 第二步:可以把app_rd_data_valid作为缓冲器Ram或Fifo的写使能,把app_rd_data写入缓冲。

DDR控制器读写时序实例代码


module a7_ddr3_wr_rd_ctrl (
  input wire      sclk,//system clk
  input wire      rst,//reset active high
  //user interface of ddr3 ctrl
  output  wire  [2:0] app_cmd,
  output  wire  [27:0]  app_addr,
  output  wire      app_en,
  input wire      app_rdy,
  output  wire  [15:0]  app_wdf_mask,
  input wire      app_wdf_rdy,
  output  wire      app_wdf_wren,
  output  wire      app_wdf_end,
  output  wire  [127:0] app_wdf_data,
  input wire      app_rd_valid,
  input wire  [127:0] app_rd_data

);
//This example writes 256 increments, and then reads 256 increments to circle,Address increment from zero.
//这个例子写256递增数然后读256递增数,以此循环,地址从零递增。
reg     wr_rd_flag=0;//0 is write flag ;1 is read flag;
reg [7:0] wr_cnt;//write data counters;
reg [7:0] rd_cnt;//read data counters;
reg     wr_en;// write enable;
reg [27:0]  wr_rd_addr;
reg     rd_en;// read enable;

assign app_wdf_mask = 0;
assign app_addr =wr_rd_addr ;
assign app_cmd = (wr_rd_flag == 0)?3'b000:3'b001;
assign app_wdf_data = {120'd0,wr_cnt};
assign app_wdf_wren =wr_en & app_rdy & app_wdf_rdy ;
assign app_wdf_end =wr_en & app_rdy & app_wdf_rdy ;
assign  app_en = (wr_en & app_rdy & app_wdf_rdy)|(rd_en & app_rdy);
//读写标志产生 generate write and read flag
always @ (posedge sclk) begin
  if(rst == 1'b1)
    wr_rd_flag<= 'd0;
  else if(wr_cnt == 'd255 && app_rdy == 1'b1 && app_wdf_rdy == 1'b1 && app_en ==1'b1)
    wr_rd_flag<= 1'b1;//turn to read flag
  else if(rd_cnt == 'd255 && app_rdy == 1'b1 && app_en == 1'b1)
    wr_rd_flag<= 1'b0;//turn to write flag
end
//写使能
always @ (posedge sclk) begin
  if(rst == 1'b1)
    wr_en<= 'd0;
  else if(wr_cnt == 'd255 && app_rdy == 1'b1 && app_wdf_rdy == 1'b1 && app_en ==1'b1)
    wr_en <= 1'b0;
  else if(wr_rd_flag == 1'b0)
    wr_en<= 'd1;
end
//写数据计数器
always @ (posedge sclk) begin
  if(rst == 1'b1)
    wr_cnt<= 'd0;
  else if(wr_rd_flag == 1'b0 &&app_en == 1'b1)
    wr_cnt<= wr_cnt + 1'b1;
  else if(wr_rd_flag == 1'b1)
    wr_cnt <='d0;
end
//读写地址 read and write address
always @ (posedge sclk) begin
  if(rst == 1'b1)
    wr_rd_addr<= 'd0;
  else if(wr_rd_flag == 1'b0 && wr_cnt == 'd255 && app_rdy == 1'b1 && app_wdf_rdy == 1'b1 && app_en ==1'b1)
    wr_rd_addr <= 'd0;
  else if(wr_rd_flag == 1'b1 && rd_cnt == 'd255  && app_rdy == 1'b1 && app_en ==1'b1)
    wr_rd_addr <= 'd0;
  else if(app_en == 1'b1)
    wr_rd_addr<= wr_rd_addr + 8;//8 is 128/16; 16 is memory data bus bits
end
//读使能 
always @ (posedge sclk) begin
  if(rst == 1'b1)
    rd_en<= 'd0;
  else if(rd_cnt == 'd255 && app_rdy == 1'b1 && app_en ==1'b1)
    rd_en <= 1'b0;
  else if(wr_rd_flag == 1'b1)
    rd_en<= 1'b1;
end
//读计数器
always @ (posedge sclk) begin
  if(rst == 1'b1)
    rd_cnt<= 'd0;
  else if(wr_rd_flag == 1'b1 &&app_en == 1'b1)
    rd_cnt<= rd_cnt + 1'b1;
  else if(wr_rd_flag == 1'b0)
    rd_cnt <='d0;
end
endmodule

跟着视频创建DDR3控制器读写功能框架(注册登录后观看)

侧栏导航