FPGA实现DDR3读写操作,乒乓操作——FPGA学习笔记1

前言笔者:人生建议从第四章开始看。。。。

目录

一、初认SDRAM

物理 Bank:传统内存系统为了保证 CPU 的正常工作,必须一次传输完 CPU 在一个传输周期内所需的数据。而CPU 在一个传输周期能接受的数据容量就是 CPU 数据总线的位宽当时控制内存与 CPU之间数据交换的北桥芯片也因此将内存总线的数据单位是 bit (位)位宽等同于 CPU 数据总线的位宽,而这个位宽就称之为物理 Bank (Physical Bank) 的位宽。

芯片位宽:每一片SDRAM缓存芯片本身的位宽。

CPU需要多少位宽数据,SDRAM就要提供多少位宽数据,位宽不够使用多片SDRAM级联。、

二、SDRAM操作时序

        1、SDRAM操作指令

CS#:片选  RAS#:行选通  CAS#:列选通  WE#:读写切换  DQM:数据掩码  ADDR:数据总线  DQ:数据  Notes:参考详细说明

        2、指令时序        

                (1)ACTIVE Command(行激活指令)

                (2)READ Command(读,列激活)

                (3)WRITE Command(写,列激活)

                (4)PRECHARGE Command(手动预充电)

                (5)初始化时序,加载模式寄存器

                (6)配置模式寄存器

A0~2:突发模式        A3:突发类型        A4~6:CL延迟        

                    (7)突发读时序

                (8)连续发送读指令时序

                (9)突发写时序

                (10)连续写时序

                (11)禁止指令

三、DDR2 SDRAM

        1、OCD校准

        2、前置CAS、附加潜伏期、写入潜伏期

四、DDR3 SDRAM

        1、与DDR2的区别之处

                (1)突发长度

                (2)寻址时序

                (3)新增重置功能

                (4)新增ZQ校准功能

                (5)参考电压分成两个

                (6)点对点连接

        2、DDR3硬件设计与时序

DDR3

        3、初始化时序

        (1)上电一瞬间RESET拉低保持200us(再次期间其他信号无效)。

        (2)RESET拉高前,CKE至少保持10ns拉低。

        (3)至少等待500us后(期间CK差分始终提前CKSRX时间稳定),CKE拉高。

        (4)CKE拉高后,等待tls时间,后发送NOP指令,继续等待tXPR时间。(期间ODT处于工作状态)

        (5)发送MRS配置指令,每配置一个等待tXPR时间,MRX寄存器配置完成等待tDLIK=tMOD+tZQinit时间。

        (6)在tDLIK等待时间内,发送ZQCL指令,以及NOP指令。到此整个DDR激活结束。

        4、DDR3模式寄存器配置

                (1)MR0寄存器

BL:突发长度        CL:等待        RBT:读的突发类型        CAS Latency:延时配置

TM:工作模式(常规/测试)        DLL:重置(back返回值归零)

WR:写复原(重新写回原本数据)        PPD:退出速度( )

                (2)MR1寄存器

DLL:使能(必须使能DDL才可以工作)        D.I.C:输出驱动阻抗        Rtt_Nom:终止值

AL:加性延迟        Level:写均衡       

TDQS:终止数据频闪(TDQS功能仅在x8 DDR3(L)SDRAM中可用,对于x16配置必须通过MR1中的模式寄存器A11=0禁用)

                (3)MR2寄存器

 PASR:默认关闭        CWL:CAS写延迟        ASR:自动刷新        SRT:自刷新温度

Rtt_WR:动态ODT        

                (4)MR3寄存器

五、DDR3读写操作时序

        1、读时序

                (1)突发长度:8

                (2)突发长度:4

                (3)突发长度:8和4混用

                (4)读后跟写 

        2、写时序

                (1)写数据

        注意延迟时间!!

                (2)写后跟读 

六、DDR3读写时序程序设计

        1、程序框图

        2、MIG IP核内部原理

                (1)DDR3接口说明

ddr_ad_dr:地址线        ddr_ba:bank地址线        ddr_cas_n:指令线        ddr_ck:差分时钟线

ddr_ck:类似使能线        ddr_cs_n:片选        ddr_dm:数据屏蔽        ddr_o_dt:ODT校准

ddr_parity:(未使用到)

                (2)MIG IP核用户接口说明

app_addr:用户地址输入        app_cmd:读写控制命令        app_en:命令写入使能,高有效

app_hi_pri:改变优先级        app_wdf_data:用户写入数据

app_wdf_end:当前时钟突发写最后一个时钟,高有效

app_wdf_wren:数据写使能,高有效        app_rdy:读写命令接受准备完毕,高有效

app_wdf_rdy:数据接收准备完毕,高有效

        3、用户接口写命令时序

                (1)等待app_rdy拉高(表示可以接受数据)

                (2)app_cmd给WRITE指令,同时app_addr给地址(DDR3地址),app_en同步拉高

        4、用户接口读命令时序

                与写时序不同之处,app_rd_data_valid拉高是数据有效

        5、vivado下创建MIG IP核

        6、代码讲解(参考正点原子DDR3代码)

        先往DDR3 的若干连续地址中分别写入数据,再读出来进行比较

                (1)ddr3_rw_top

module ddr3_rw_top(
   input              sys_clk,         //系统时钟
   input              sys_rst_n,       //复位,低有效
    // DDR3
   inout [15:0]       ddr3_dq,         //DDR3 数据
   inout [1:0]        ddr3_dqs_n,      //DDR3 dqs负
   inout [1: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 [1:0]       ddr3_dm,         //DDR3_dm
   output [0:0]       ddr3_odt,        //DDR3_odt
   //用户
   output             led              //错误指示信号
    );                
                      
 //wire define  
wire                 clk_330;
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 [127:0]          app_rd_data;         //用户读数据
wire                  app_rd_data_end;     //突发读当前时钟最后一个数据 
wire                  app_rd_data_valid;   //读数据有效
wire [127:0]          app_wdf_data;        //用户写数据 
wire                  app_wdf_end;         //突发写当前时钟最后一个数据 
wire [15: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; //校准完成信号
wire [20:0]           rd_cnt;              //实际读地址计数
wire [1 :0]           state;                //状态计数器
wire [23:0]           rd_addr_cnt;         //用户读地址计数器
wire [23:0]           wr_addr_cnt;         //用户写地址计数器

//*****************************************************
//**                    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                   (31'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
    // Status and control signals
    .reset(1'b0),           // input resetn
    .locked(locked),        // output locked
   // Clock in ports
    .clk_in1(sys_clk)
    );                      // input clk_in1

endmodule
                         ①PLL模块

        产生200Mhz时钟给DDR3 IP核使用(FPGA内部使用,并非给到DDR3颗粒)。MIG IP核内部会自己进行倍频到400MHz(ddr3_ck_n/p)给到DDR3颗粒使用,同时会降频到100MHz(ui_clk)给到DDR3读写模块用户时钟使用。当100MHz进行DDR读写上升沿都进行,速度提升一倍(2倍)。又因为DDR3颗粒时钟为400MHz,所以实际读写速度再翻4倍(8倍),由此实现16bit位宽达到16x8bit的吞吐速度(八倍预取)。

                        ②MIG IP核模块

        注意复位是高电平有效,使用过程可能需要取反。

                (2)DDR3读写模块

 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          [127: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     [127: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 <= 128'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 <= 128'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

七、DDR3乒乓操作

        1、乒乓操作简介

        外部输入数据流通过输入数据流选择单元将数据流输入到数据缓存模块,比较常用的存储单元有双口RAM,FIFO,SDRAM等。在第一个缓冲周期,数据流通过“输入数据流选择单元”将数据写入“数据缓冲模块1”。写完之后进入第二个缓冲周期,在第二个缓冲周期数据流通过“输入数 据流选择单元”将数据写入到“数据缓冲模块2”的同时“输出数据流选择单元”将“数据缓冲模块1”的数据流读出,此时进入第三个缓冲周期。在第三个缓冲周期数据流通过“输入数据流选择单元”将数据写入到“数据缓存模块1”的同时将“数据缓冲模块2”的数据读出。如此反复循环地操作,即为乒乓操作。

        2、乒乓操作特点

        乒乓操作的最大特点是通过“输入数据流选择单元”和“输出数据选择单元”按节拍、相互配合的切换,将经过缓冲的数据流没有停顿地送到“数据流运算处理模块”进行运算与处理。把乒乓操作模块当做一个整体,站在这个模块的两端看数据,输入数据流和输出数据流都是连续不断的,没有任何停顿的,因此非常适合对数据流进行流水线 式处理。所以乒乓操作常常应用于流水线式算法,完成数据的无缝缓冲与处理。

         乒乓操作的第二个特点是可以节约缓存空间,使用双存储单元比单存储单元更节省存储空间,这是很明显的。同时在某些数据处理时,必须要数据达到一定个数才能进行运算,故还可以达到数据缓存的目的。 乒乓操作还可以实现低速模块处理高速数据,这种处理方式可以实现数据的串并转换,就是数据位宽之间的转换,是面积与速度互换原则的体现。

        3、面积与速度互换原则

        例如设置写入的数据位宽为8位,时钟频率50MHz。读出的数据位宽为16位,时钟频率25MHz,深度都设置为128。当然大家也可自行设置时钟频率与数据位宽,只要频率与位宽的乘积相等即可。

        4、乒乓状态机

IDLE:初始状态,在不工作或复位时就让状态机置为初始状态。

WRAM1:写RAM1状态。该状态我们开始往RAM1中写入数据,此时由于RAM2中并没有写入数据,所以我们不用对RAM2进行读取。那什么时候跳转到这个状态呢?从前面的数据生成模块中我们可知,当输入数据使能为高时,数据有效开始传输,所以当数据使能为高时我们让状态跳转到写RAM1状态,在该状态下将第一个 数据包(8’d0~8’d99)写入RAM1之中。

WRAM2_RRAM1:写RAM2读RAM1状态,当第一包数据写入完毕之后,马上跳到该状态,将第二包数据写入到RAM2中的同时读出RAM1中的写入的第一包数据。当第二包数据写完之后,我们的第一包数据应该也是刚好读完的,此时我们跳转到下一状态。

WRAM1_RRAM2:写RAM1读RAM2状态。在该状态下我们开始向RAM1中写入第三包数据,此时第三包数据会把第一包数据覆盖,而我们的第一包数据已经读取出来了,并不会使数据丢失。在往RAM1中写入第三包数据的同时,我们读出RAM2中的第二包数据,当读写完成之后,跳回WRAM2_RRAM1状态开始 下一包的数据写入以及读取,如此循环我们就能无缝地将连续的输入数据读取出来了。

5、RAM写入波形

ram1_wr_en:ram1写使能,初始值为0。

ram1_wr_addr:ram1写地址,初使值为0。

ram1_wr_data:ram1写数据。

6、RAM读出时序

ram1_rd_en:ram1读使能,初始值为0。

ram1_rd_addr:ram1读地址,初使值为0。

ram1_rd_data:ram1读数据。

版权声明:本文为博主作者:楠了个难原创文章,版权归属原作者,如果侵权,请联系我们删除!

原文链接:https://blog.csdn.net/m0_69082048/article/details/135869040

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
社会演员多的头像社会演员多普通用户
上一篇 2024年4月16日
下一篇 2024年4月16日

相关推荐