南京观海微电子---快速上手DDR读写例程——DDR接口专栏(三)

简介: 本文介绍如何使用FPGA中DDR IP核的Native接口进行读写操作,详细解析用户接口信号及时序控制,并结合Verilog代码实现DDR3的初始化、连续写入与读取验证,帮助开发者掌握DDR高速存储应用。

1. 前言

本文将向大家介绍如何使用DDR IP核的Native接口来对DDR进行读写操作。

2. DDR IP核接口介绍

要想把DDR3 IP核使用起来,必先需要了解下该IP核有哪些接口。DDR3 IP核接口图如下所示。注:图中展示的为DDR IP的Native接口,除了Native接口,该IP核还支持AXI4接口。

图中黄色的区域为“用户接口(UI)”,是DDR IP核对外的读写接口。绿色的框是用户通过代码写的逻辑电路,用户逻辑直接操作“用户接口”,实现对DDR数据的读写。右边蓝色的框是FPGA与DDR颗粒之间的物理接口。

下表列出了用户接口的定义清单,其中被黄色标注出来的几个信号是用户需要重点操作的信号。

  • app_addr:当前读写请求地址;
  • app_cmd:当前读写请求命令,读命令为3’b001,写命令为3’b000;
  • app_en:高有效,使能app_cmd、app_addr、app_sz和app_hi_pri信号;
  • app_rdy:该信号指示用户接口(UI)是否可以接收命令。当app_en使能时,如果该信号无效,则app_cmd、app_addr必须保持,等待app_rdy信号有效时再释放;
  • app_rd_data:用户接口读回数据;
  • app_rd_data_end:高有效,指示当前app_rd_data为最后一个数据,该信号仅当app_rd_data_valid为高时有效;
  • app_rd_data_valid:高有效,指示app_rd_data数据有效;
  • app_wdf_end:高有效,指示当前app_wdf_data为最后一个数据;
  • app_wdf_mask:提供app_wdf_data屏蔽码;
  • app_wdf_rdy:指示UI可以接收写数据写入;
  • app_wdf_wren:高有效,指示app_wdf_data数据有效;
  • app_wdf_data:用户待写入DDR的数据。

以上接口功能描述的翻译仅供参考,详细的还是参考上面两张官方的表格。

3. 用户接口的读写时序

操作DDR的UI接口时,要特别注意app_rdy信号的状态。如下图所示,app_cmd、app_addr和app_en分别给了3次写addr0地址的指令。但指令只有在app_rdy为高时才被接受,即图中只有前两次写指令写给了DDR IP核。

3.1 写数据至DDR

在本文讲述读写DDR时序时,均采用的是4:1模式,即FPGA的用户逻辑采用时钟频率为DDR工作频率的四分之一,该设置需要在建立DDR IP时进行设置,如不了解,可以参考上一篇文章《MIG IP核的使用——DDR接口专栏(二)》。

往DDR UI接口写数据时,时序如下图所示:

图中上半部给出了写控制指令的操作时序图,下半部分别有3个虚线框,给出了3种写数据总线的操作时序图。方案1为作者推荐的方法,即写数据操作和写指令操作对齐。方案2意思为写数据操作可以提前写指令操作一个时钟周期。方案3表明,写数据操作最多可以落后写指令操作两个时钟周期。

上图给了一个实际的写数据例子。红色框为写指令的操作,总共向8个地址(0x0a00~0x0a38)进行写操作。由于第1个时刻到第6个时刻app_rdy一直为高,所以往地址0x0a00~0x0a28写指令立即写入了DDR IP核的FIFO中。但由于第7个时刻app_rdy突然拉低,往地址0x0a30写数据的指令没有成功写入DDR IP核中,因此必须等待。此时app_addr、app_en、app_cmd这些信号都必须保持,直到app_rdy再度拉高,该指令才会被成功写入给DDR。

蓝色框为写具体的DDR数据操作,由于app_wdf_rdy一直为高,因此UI接口上一次性将8个数据都写入到DDR IP核的数据缓存FIFO中。

显然上面实例是采用方案3的写数据方式,但作者还是推荐初学者采用方案1的方式写数据。即判断app_rdy和app_wdf_rdy都为高时,再同时写入指令和数据。

3.2 读数据

从DDR UI接口读数据时,时序如下图所示:

图中上半部给出了读控制指令的操作时序图,下半部分为读出的数据结果。从读指令被UI接口接收后到数据被读出来的延时时间是随机的,没有具体对应关系。

4. 读写DDR例程代码

话不多说,上读写DDR例程代码吧。本文引用了CSDN博主“孤独的单刀”编写的代码。这段代码非常好的向大家展示了UI接口的使用方法。

代码功能描述:(1)等待DDR初始化成功;(2)往DDR的地址连续写入了1024个数据;(3)从DDR中读出刚写入相同地址段的数据,并进行比对。

//**************************************************************************

// *** 名称 : ddr3_rw

// *** 作者 : 孤独的单刀

// *** 博客 : https://blog.csdn.net/wuzhikaidetb

// *** 日期 : 2021.12

// *** 描述 : 对DDR3进行循环读写

//**************************************************************************


//============================< 端口 >======================================

module ddr3_rw #

(

 parameter  integer          WR_LEN    = 1024    ,  //读、写长度

 parameter  integer          DATA_WIDTH  = 128   ,  //数据位宽,突发长度为8,16bit,共128bit

 parameter  integer          ADDR_WIDTH  = 28       //根据MIG例化而来

)(  

//DDR3相关 ------------------------------------------------------      

   input                         ui_clk               ,  //用户时钟

   input                         ui_clk_sync_rst      ,  //复位,高有效

   input                         init_calib_complete  ,  //DDR3初始化完成

//DDR3相关 ------------------------------------------------------  

   input                         app_rdy              ,  //MIG 命令接收准备好标致

   input                         app_wdf_rdy          ,  //MIG数据接收准备好

   input                         app_rd_data_valid    ,  //读数据有效

   input    [DATA_WIDTH - 1:0]   app_rd_data          ,  //用户读数据

   output  reg  [ADDR_WIDTH - 1: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  [DATA_WIDTH - 1:0]    app_wdf_data    ,  //用户写数据

//指示 ----------------------------------------------------------    

   output  reg                   error_flag                  //读写错误标志

   );


//============================< 信号定义 >======================================

//测试状态机-----------------------------------------        

localparam          IDLE  = 4'b0001    ;              //空闲状态

localparam          WRITE = 4'b0010    ;              //写状态

localparam          WAIT  = 4'b0100    ;              //读到写过度等待

localparam          READ  = 4'b1000    ;              //读状态

//reg define ----------------------------------------

reg  [3:0]          cur_state        ;        //三段式状态机现态

reg  [3:0]          next_state       ;        //三段式状态机次态

reg  [ADDR_WIDTH - 1:0]    rd_addr_cnt     ;        //用户读地址计数

reg  [ADDR_WIDTH - 1:0]    wr_addr_cnt     ;        //用户写地址计数

reg  [ADDR_WIDTH - 1:0]    rd_cnt          ;        //实际读地址标记

//wire define ---------------------------------------                    

wire            error          ;        //读写错误标记

wire            rst_n          ;        //复位,低有效

wire            wr_proc        ;        //拉高表示写过程进行

wire            wr_last        ;        //拉高表示写入最后一个数据

wire            rd_addr_last   ;        //拉高表示是最后一个读地址


//*********************************************************************************************

//**                    main code

//**********************************************************************************************

//==========================================================================

//==    信号赋值

//==========================================================================  

assign rst_n = ~ui_clk_sync_rst;

//当MIG准备好后,用户同步准备好

assign app_en = app_rdy && ((cur_state == WRITE && app_wdf_rdy) || cur_state == READ);              

//写指令,命令接收和数据接收都准备好,此时拉高写使能

assign app_wdf_wren = (cur_state == WRITE) && wr_proc;

//由于DDR3芯片时钟和用户时钟的分频选择4:1,突发长度为8,故两个信号相同

assign app_wdf_end = app_wdf_wren;

assign app_cmd = (cur_state == READ) ? 3'd1 :3'd0;          //处于读的时候命令值为1,其他时候命令值为0  

assign wr_proc = ~app_cmd && app_rdy && app_wdf_rdy;        //拉高表示写过程进行

//处于写使能且是最后一个数据

assign wr_last = app_wdf_wren && (wr_addr_cnt == WR_LEN - 1) ;

//处于读指令、读有效且是最后一个数据

assign rd_addr_last = (rd_addr_cnt == WR_LEN - 1) && app_rdy && app_cmd;


//==========================================================================

//==    状态机

//==========================================================================    


always @(posedge ui_clk or negedge rst_n) begin

 if(~rst_n)

   cur_state <= IDLE;

 else

   cur_state <= next_state;

end


always @(*) begin

 if(~rst_n)

   next_state = IDLE;

 else

   case(cur_state)

     IDLE:

       if(init_calib_complete)   //MIG IP核初始化完成

         next_state = WRITE;        

       else        

         next_state = IDLE;        

     WRITE:        

       if(wr_last)               //写入最后一个数据

         next_state = WAIT;        

       else        

         next_state = WRITE;              

     WAIT:        

         next_state = READ;        

     READ:        

       if(rd_addr_last)          //写入最后一个读地址,数据读出需要时间

         next_state = IDLE;

       else

         next_state = READ;          

     default:;

   endcase

end


always @(posedge ui_clk or negedge rst_n) begin

   if(~rst_n) begin        

       app_wdf_data <= 0;    

       wr_addr_cnt  <= 0;      

       rd_addr_cnt  <= 0;      

       app_addr     <= 0;          

   end

 else

       case(cur_state)

           IDLE:begin

               app_wdf_data <= 0;  

               wr_addr_cnt  <= 0;    

               rd_addr_cnt  <= 0;      

               app_addr     <= 0;

            end

           WRITE:begin

               if(wr_proc)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                                                  

               rd_addr_cnt <= 0;                    //读地址复位

               app_addr    <= 0;                    //DDR3读从地址0开始

             end

           READ:begin                                //读到设定的地址长度    

               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

               app_wdf_data <= 0;

               wr_addr_cnt  <= 0;

               rd_addr_cnt  <= 0;

               app_addr     <= 0;

           end

       endcase

end  

//==========================================================================

//==    其他

//==========================================================================

//读信号有效,且读出的数不是写入的数时,将错误标志位拉高

assign error = (app_rd_data_valid && (rd_cnt!=app_rd_data));  


//寄存状态标志位

always @(posedge ui_clk or negedge rst_n) begin

   if(~rst_n)

       error_flag <= 0;

   else if(error)

       error_flag <= 1;

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 == WR_LEN - 1)

        rd_cnt <= 0;              

   else if (app_rd_data_valid )  //读有效情况下每个时钟+1

       rd_cnt <= rd_cnt + 1;

end


endmodule

相关文章
|
4月前
|
存储 芯片 异构计算
南京观海微电子---MIG IP核的使用——DDR接口专栏(二)
本文介绍Xilinx FPGA中MIG IP核的使用方法,涵盖DDR3颗粒选型、FPGA匹配、MIG参数配置及管脚分配等关键步骤,帮助用户实现对片外DDR存储器的高效读写操作。
南京观海微电子---MIG IP核的使用——DDR接口专栏(二)
|
4月前
|
存储 芯片 内存技术
南京观海微电子----DDR的工作原理——DDR接口专栏(一)
DDR从SDR发展至今已至DDR5,历经多代演进。每代通过降低电压、提升预取(Prefetch)、引入Bank Group等技术,显著提高带宽与能效。DDR4采用16bit预取与Bank Group交错,实现高速传输;其寻址利用分时复用,大幅提升容量。文章详解了DDR架构、信号引脚及内存颗粒内部结构,揭示高容量与高性能实现原理。
南京观海微电子----DDR的工作原理——DDR接口专栏(一)
|
编解码 网络协议 前端开发
OFDM深入学习及MATLAB仿真(一)
OFDM深入学习及MATLAB仿真
2135 1
|
4月前
|
弹性计算 运维 应用服务中间件
阿里云轻量应用服务器 vs 云服务器 ECS:全方位深度对比与选购指南
在阿里云的服务器产品体系中,轻量应用服务器与云服务器 ECS 是面向不同需求的核心产品。前者以 “简单易用、高性价比” 为核心,后者以 “功能全面、弹性灵活” 为优势。本文从适用人群、业务场景、功能配置、计费价格等 8 大维度展开深度对比,结合阿里云最新优惠政策,帮你精准匹配最适合的服务器方案。
|
4月前
|
缓存 前端开发 芯片
南京观海微电子---AXI总线技术简介——ZYNQ PS和PL的互联技术
AXI是Xilinx ZYNQ系列中实现ARM与FPGA高速通信的核心协议,支持Lite、4和Stream三种总线,分别适用于控制、批量传输和数据流场景。通过AXI Interconnect实现多设备互联,结合DMA等IP核,可高效完成数据交互,广泛应用于嵌入式系统开发。
南京观海微电子---AXI总线技术简介——ZYNQ PS和PL的互联技术
|
网络性能优化
【AXI】解读AXI协议的额外信号(QOS信号,REGION信号,与USER信号)
【AXI】解读AXI协议的额外信号(QOS信号,REGION信号,与USER信号)
【AXI】解读AXI协议的额外信号(QOS信号,REGION信号,与USER信号)
|
4月前
|
芯片
南京观海微电子----LDO低压差线性稳压
本文详解AMS1117/LM1117低压差线性稳压芯片原理与应用,涵盖固定及可调输出型号的工作机制、典型电路设计、热损耗计算与效率分析,对比传统与新型LDO结构,介绍LM317、78XX系列等常用稳压器特点,适用于电源电路设计参考。(239字符)
南京观海微电子----LDO低压差线性稳压
|
4月前
|
弹性计算 搜索推荐 应用服务中间件
阿里云服务器收费标准_云服务器ECS价格表_轻量优惠活动
阿里云服务器优惠汇总:轻量应用服务器200M带宽38元起/年,ECS云服务器2核2G 99元/年,2核4G 199元/年,4核16G 89元/月,8核32G 160元/月,香港轻量服务器25元/月起,支持按小时计费,新老用户同享,续费同价,限时秒杀低至1折。
1136 18
南京观海微电子----GH700X国语测试架操作指南
本文介绍SPI与I2C接口的应用,涵盖SPI端口连接、读写格式及按键烧录流程,并说明I2C的连接方式与通信格式,配合代码示例和图示,详解硬件控制过程,适用于嵌入式显示模块开发调试。
南京观海微电子----GH700X国语测试架操作指南
|
4月前
|
编解码
南京观海微电子----DAC/ADC原理
DAC将数字量按权值转换为模拟量,核心由锁存器、电子开关、基准源、权电阻网络和求和电路组成。常见结构有倒T型电阻网络和权电流转换器。ADC则通过取样、保持、量化、编码将模拟信号转为数字量,主要类型包括逐次逼近型、积分型和并行比较型,关键指标有分辨率、转换速率和基准源精度。
南京观海微电子----DAC/ADC原理