1.DDR3 IP简单读写测试实验例程
1.1** 实验目的**
MES22GP 开发板上有一片 Micron 的 DDR3(MT41K256M16 TW107:P)内存组件,拥有 16bit 位宽的存储空间(MT41J系列是旧的产品,目前很多型号已经停产,后续替代就是MT41K系列。硬件上的差异是MT41K支持1.35V低电压,同时也兼容1.5V电压,所以可以用MT41K直接替换相应型号的MT41J芯片)。该DDR3 存储系统直接连接到了 PGL22G 的 Bank L1 及 Bank L2 上。PGL22G的DDR IP为硬核IP,需选择正确的IP添加。
本次实验目的为生成DDR3 IP,实现DDR3的基于AXI4的简单读写控制,了解其工作原理和用户接口,然后通过在线Debugger工具查看写入和读出的数据是否一致。
1.2** DDR3控制器简介**
HMIC_H IP 是深圳市紫光同创电子有限公司FPGA 产品中用于实现对SDRAM 读写而设计的IP,通过公司Pango Design Suite 套件(后文简称PDS)中IP Compiler 工具(后文简称IPC)例化生成IP 模块。
HMIC_H IP 系统框图如下图所示:
HMIC_H IP 包括了DDR Controller、DDR PHY 和 PLL,用户通过 AXI4 接口实现数据的 读写,通过 APB 接口可配置 DDR Controller 内部寄存器,PLL 用于产生需要的各种时钟。
AXI4 接口:HMIC_H IP 提供三组 AXI4 Host Port:AXI4 Port0(128bit)、AXI4 Port1(64bit)、
AXI4 Port2(64bit)。用户通过 HMIC_H IP 界面可以选择使能这三组 AXI4 Port。三组 AXI4 Host Port 均为标准 AXI4 接口。
APB 接口:HMIC_H IP 提供一个 APB 配置接口,通过该接口,可配置 DDR Controller 内部寄存器。HMIC_H IP 初始化完成后使能该接口。
详细的端口说明请点击IP配置界面的View Datasheet查看IP手册。
DDR的IP需要手动添加,操作流程请查阅文件目录1_Demo_document/工具使用篇的《03_IP核安装与查看用户指南》。
1.2.1** DDR3 IP例化流程简述**
打开IPC 软件,进入 IP 选择界面,如下图所示,选取 System/DDR/Hard 目录下的 Logos HMIC_H,然后在右侧页面设置 Instance Name 名称,并选择 FPGA 的器件类型。
IP 选择完成后点击Customize 进入Logos HMIC_H IP 参数设置界面,如下图所示,左边Symbol 为接口框图,右边为参数配置窗口:
参数配置完成后点击左上角的Generate按钮,生成 IP,即可生成相应于用户特定设置
的 HMIC_H IP 代码。生成 IP 的信息报告界面如下图所示:
注 : IP 自带生成的.pds 文件和.fdc 文件仅供参考,需要根据实际单板进行修改。成功生成 IP 后会在生产IP时指定的Pathname 路径下输出如下文件:
1.2.2** DDR3 IP配置说明**
HMIC_H IP 配置分为四个页面,分别为 Step1: Basic Options,Step2: Memory Options,Step3: Interface Options,Step4: Summary,请务必按照该页面顺序配置。
1.2.2.1 Step 1: Basic Options
是 IP 的基本配置页面,页面如下图所示:
1.2.2.2 Step 2: Memory Options
是Memory 参数的配置页面,页面如下图所示:
1.2.2.3 Step 3: Interface Options
是接口参数的配置页面,页面如下图所示:
1.2.2.41 Step 4: Summary
用于打印当前的配置信息,不需要配置参数,页面如下图所示:
1.3** 实验源码**
DDR3 IP配置完成后会生成一个可用于例化的模块。
1.3.1** DDR3 IP 模块接口说明**
如下图所示为DDR3 IP的Memory Interface(PHY),不需要我们直接操作。
以下所示为外部输入时钟,复位,输出的用户时钟axi4 port0/port1/port2以及一些复位或者初始化完成的标志信号(可以通过连接LED灯来直观显示,更易观察)。
以下所示为AXI4协议的读写控制端口,也是用户可以直接操作用于控制DDR3读写的端口。
AXI4协议的读写控制这里不进行具体讲解。
1.3.2** DDR3 读写测试顶层模块设计**
顶层模块的输入输出端口便是DDR3例化模块中的Memory Interface(PHY)和一些直连LED灯的用于观察的标志信号,因为本次实验通过按键来控制开始向DDR3写入数据,所以还需要一个输入按键。
然后对应DDR3的AXI4读写我们单独用一个模块来控制,顶层模块中的例化如下所示。
使用按键,所以需要一个按键消抖模块,顶层模块中的例化如下所示。
1.3.3** DDR3 AXI4读写控制模块**
本次实验只是一个简单的读写测试实验,故可以将一些AXI4的信号配置为常量。
使用按键控制数据开始写入DDR3,通过一个移位寄存器来产生这个写标志。
使用状态机来控制写地址信号,写数据信号,读地址信号,读数据信号的产生及状态的切换和跳转。
当按键按下,写标志触发,状态机进入写地址状态,awvalid_0信号为高电平,当awready_0和awvalid_0同时为高电平时,写地址被有效写入,下一个周期awvalid_0为低电平同时状态机跳转到写数据的状态;
写数据状态中,wvalid_0为高电平,当wready_0和wvalid_0同时为高电平时,数据开始被写入,一共写入5~20总计16个数据(从0开始计数长度便为15)。当写到最后一个数据时wlast_0保持一个周期的高电平。这里我们用一个计数器来产生写入数据,当wready_0和wvalid_0同时为高电平时开始计数器开始自加,当计数到15时(最后一个写入数据),下一个周期计数器清零,状态机跳转至等待写响应的状态;
写响应的状态中,通过一个移位寄存器抓取写响应有效bvalid_0信号为高电平的时刻,当bvalid_0且bresp_0为2’b00(表示写响应ok)时触发读开始的标志,状态机进入读地址写入状态;
读地址写入状态中,arvalid_0为高电平,当arready_0和arvalid_0同时为高电平时,读地址被有效写入(地址与写数据地址一致),下一个周期arvalid_0为低电平同时状态机跳转到读数据的状态;
读数据状态中,当rvalid_0 和 rready_和 rlast_0均为高电平时,状态机跳转至最初的状态等待按键被再次按下。读出的数据可以通过在线调试Debugger工具来查看。
1.3.4DDR3 IP的时钟约束
IP 有 5 个时钟,分别为 pll_refclk_in、phy_clk、pll_aclk0、pll_aclk1、pll_aclk2、pll_pclk,其中 pll_refclk_in 是输入时钟,phy_clk、pll_aclk0、pll_aclk1、pll_aclk2、pll_pclk 都是 PLL 倍频得到,phy_clk 用作 HMIC_H 硬核的输入时钟,pll_aclk0 用做 AXI4 port0 的输入时钟,pll_aclk1 用做 AXI4 port1 的输入时钟,pll_aclk2 用做 AXI4 port2 的输入时钟,pll_pclk 用做APB port的输入时钟。phy_clk 是HMIC_H专用时钟,在IP内部使用,不允许外接使用。pll_pclk,pll_aclk_0,pll_aclk_1,pll_aclk_2 四路时钟供外部逻辑使用,彼此没有相位关系,都是异步时钟。
外部输入时钟约束如下。
Pll产生的时钟约束如下。
1.4实验现象
点击Debugger按钮,下载程序,便可通过Debugger工具进行在线调试,查看具体信号的波形情况。
按下开发板的按键,产生写触发信号,awvalid_0信号为高电平,当awready_0和awvalid_0同时为高电平时,awvalid_0拉低同时进入写数据状态,wvalid_0拉高,随后wready_0和wvalid_0同时为高电平时开始写入数据5~20,wlast_0在写入最后一个数据时拉高。一段时间后bvalid_0拉高且bresp_0为2’b00,表示写入数据成功,然后进入读数据状态。
读地址写入状态中,arvalid_0为高电平,当arready_0和arvalid_0同时为高电平时,读地址被有效写入(地址与写数据地址一致),下一个周期arvalid_0拉低同时状态机跳转到读数据的状态。
通过Debugger工具查看DDR3先写入后读出的数据是一致的,表明DDR3的读写测试正常。
同时在烧录程序后,可以观察LED灯的亮灭情况来查看DDR的PLL输出的时钟是否已经稳定,DDR PHY复位是否完成和DDR的控制器是否初始化成功。
ddr3 axi4 读写控制模块源码如下:
`timescale 1ps/1ps
module axi4_rw(
// axi4 write&read ctrl
input areset_0 ,
input aclk_0 ,
// Write address
output [7:0] awid_0 ,
output reg [31:0] awaddr_0 ,
output [7:0] awlen_0 ,
output [2:0] awsize_0 ,
output [1:0] awburst_0 ,
output awlock_0 ,
output reg awvalid_0 ,
input awready_0 ,
output awurgent_0 ,
output awpoison_0 ,
// Write data
output reg [127:0] wdata_0 ,
output [15:0] wstrb_0 ,
output reg wlast_0 ,
output reg wvalid_0 ,
input wready_0 ,
// Write response
input [7:0] bid_0 ,
input [1:0] bresp_0 ,
input bvalid_0 ,
output bready_0 ,
// Read address
output [7:0] arid_0 ,
output reg [31:0] araddr_0 ,
output [7:0] arlen_0 ,
output [2:0] arsize_0 ,
output [1:0] arburst_0 ,
output arlock_0 ,
output reg arvalid_0 ,
input arready_0 ,
output arpoison_0 ,
output arurgent_0 ,
// Read response
input [7:0] rid_0 ,
input [127:0] rdata_0 ,
input [1:0] rresp_0 ,
input rlast_0 ,
input rvalid_0 ,
output rready_0 ,
input key_flag
);
assign awid_0 = 8'b0 ;
assign awlock_0 = 1'b0 ;
assign awurgent_0 = 1'b0 ;
assign awpoison_0 = 1'b0 ;
assign bready_0 = 1'b1 ;
assign wstrb_0 = {16{1'b1}} ;
assign awsize_0 = 3'b100 ;
assign awburst_0 = 2'd1 ;
assign awlen_0 = 15 ;
assign bid_0 = 0 ;
assign arid_0 = 8'b0 ;
assign arlock_0 = 1'b0 ;
assign arurgent_0 = 1'b0 ;
assign arpoison_0 = 1'b0 ;
assign arsize_0 = 3'b100 ;
assign arburst_0 = 2'd1 ;
assign rready_0 = 1 ;
assign arlen_0 = 15 ;
assign rid_0 = 0 ;
reg [10:0] cnt_w; /*synthesis PAP_MARK_DEBUG="1"*/
reg [1:0] w_key_reg;/*synthesis PAP_MARK_DEBUG="1"*/
reg [1:0] r_key_reg;/*synthesis PAP_MARK_DEBUG="1"*/
wire w_flag;/*synthesis PAP_MARK_DEBUG="1"*/
wire r_flag;/*synthesis PAP_MARK_DEBUG="1"*/
reg [2:0] state;
always@(posedge aclk_0)
if(areset_0)
w_key_reg <= 2'b00;
else
w_key_reg <= {w_key_reg[0],key_flag};
assign w_flag = (w_key_reg == 2'b01)?1:0;
always@(posedge aclk_0)
if(areset_0)
r_key_reg <= 2'b00;
else
r_key_reg <= {r_key_reg[0],bvalid_0};
assign r_flag = (r_key_reg == 2'b10 && bresp_0 == 2'b00)?1:0;
always@(posedge aclk_0)
if(w_flag)
awaddr_0 <= 'd1;
else
awaddr_0 <= 'd2;
always@(posedge aclk_0)
if(areset_0 || wlast_0)
wdata_0 <= 'd5;
else if(wvalid_0 && wready_0 )
wdata_0 <= wdata_0 + 1;
else
wdata_0 <= wdata_0;
always@(posedge aclk_0)
if( wvalid_0 && wready_0 && cnt_w == awlen_0-1)
wlast_0 <= 'd1;
else
wlast_0 <= 'd0;
always@(posedge aclk_0)
if(areset_0 || wlast_0)
cnt_w <= 'd0;
else if(wvalid_0 && wready_0)
begin
if(cnt_w == awlen_0)
cnt_w <= 0;
else
cnt_w <= cnt_w + 1;
end
else
cnt_w <= 0;
always@(posedge aclk_0)
if(r_flag)
araddr_0 <= 'd1;
else
araddr_0 <= 'd2;
always@(posedge aclk_0)
if(areset_0)
begin
state <= 0;
awvalid_0 <= 0;
wvalid_0 <= 0;
arvalid_0 <= 0;
end
else
begin
case(state)
0:
begin
if( w_flag)
begin
state <= 1;
end
end
1:
begin
awvalid_0 <= 1;
if(awvalid_0 && awready_0)
begin
awvalid_0 <= 0;
state <= 2;
end
end
2:
begin
if(wvalid_0 && wready_0 && wlast_0)
begin
state <= 3;
wvalid_0 <= 0;
end
else
begin
wvalid_0 <= 1;
end
end
3:
begin
if(r_flag)
begin
state <= 4;
end
end
4:
begin
arvalid_0 <= 1;
if(arvalid_0 && arready_0)
begin
arvalid_0 <= 0;
state <= 5;
end
end
5:
begin
if(rvalid_0 && rready_0 && rlast_0)
begin
state <= 0;
end
end
endcase
end
endmodule