ZYNQ PL操作DDR内存读写测试

 一,用户FPGA逻辑接口和搭建FPGA工程

AXI4  从接口块:AXI4 从站接口将 AXI4 事务映射到 UI,以向内存控制器提供行业标准总线协议接口。

用户界面块:UI 块向用户提供 FPGA 逻辑块。它通过呈现平面地址空间和缓冲读写数据来提供对本机接口的简单替代。
内存控制器和本机接口:内存控制器(MC)的前端显示 UI 块的本机接口。本地接口允许用户设计提交存储器读写请求,并提供将数据从用户设计移动到外部存储器件的机制,反之亦然。内存控制器的后端连接到物理接口,并处理该模块的所有接口要求。 内存控制器还提供了重新排序选项,重新排序接收的请求以优化数据吞吐量和延迟。

用户接口:连接到 FPGA 用户设计,以允许访问外部存储设备。

1,一路NEXT选择芯片型号

ZYNQ PL操作DDR内存读写测试

ZYNQ PL操作DDR内存读写测试

ZYNQ PL操作DDR内存读写测试

2,设置 MIG 内核时钟频率为 800M(数据:1600MX32bit)、内存型号、内存的数据位宽

ZYNQ PL操作DDR内存读写测试3,设置输入频率为 200M,不使用调试信号

ZYNQ PL操作DDR内存读写测试

4,系统和参考时钟时钟选择no buffer,MIG低电平复位,XADC补偿使能

ZYNQ PL操作DDR内存读写测试

5,终端阻抗选择 50hms

ZYNQ PL操作DDR内存读写测试

6,先选择 Fixed Pin Out,点击NEXT,根据原理图手动填写 PIN 脚定义。填写完成后填写validate,选择NEXT

ZYNQ PL操作DDR内存读写测试

ZYNQ PL操作DDR内存读写测试

7,连续单击 NEXT

ZYNQ PL操作DDR内存读写测试

ZYNQ PL操作DDR内存读写测试

8,Accept,然后NEXT,单击 Generate

ZYNQ PL操作DDR内存读写测试

ZYNQ PL操作DDR内存读写测试

9,右击 Open IP_Example Design

ZYNQ PL操作DDR内存读写测试

10,修改顶层接口信号,ZYNQ的核心板上是 100MHZ 差分时钟,以及复位信号是通过按钮提供,所以修改以下接口,并且增加 clk100m_p/clk100m_n 信号以及 rst_key 信号。增加 PLL 时钟管理模块,从 100MHZ 输入,输出 200MHZ 时钟提供给 MIG 系统时钟和系统参考时钟,这个 200MHZ 时钟就是刚才设置的,必须确保一致。

ZYNQ PL操作DDR内存读写测试

ZYNQ PL操作DDR内存读写测试

ZYNQ PL操作DDR内存读写测试

11,修补并且增加如下代码

wire sys_rst;

wire locked;

wire clk_ref_i;

wire sys_clk_i;

wire clk_200;

assign sys_rst = ~rst_key;//复位信号

assign clk_ref_i = clk_200;//200M的参考时钟

assign sys_clk_i = clk_200;//200M的系统时钟

12,修改 MIG IP CORE 输入信号

mig_7series_0 u_mig_7series_0

(

// Memory interface ports

.ddr3_addr (ddr3_addr),

.ddr3_ba (ddr3_ba),

.ddr3_cas_n (ddr3_cas_n),

.ddr3_ck_n (ddr3_ck_n),

.ddr3_ck_p (ddr3_ck_p),

.ddr3_cke (ddr3_cke),

.ddr3_ras_n (ddr3_ras_n),

.ddr3_we_n (ddr3_we_n),

.ddr3_dq (ddr3_dq),

.ddr3_dqs_n (ddr3_dqs_n),

.ddr3_dqs_p (ddr3_dqs_p),

.ddr3_reset_n (ddr3_reset_n),

.init_calib_complete (init_calib_complete),

.ddr3_cs_n (ddr3_cs_n),

.ddr3_dm (ddr3_dm),

.ddr3_odt (ddr3_odt),

// Application interface ports

.app_addr (app_addr),

.app_cmd (app_cmd),

.app_en (app_en),

.app_wdf_data (app_wdf_data),

.app_wdf_end (app_wdf_end),

.app_wdf_wren (app_wdf_wren),

.app_rd_data (app_rd_data),

.app_rd_data_end (app_rd_data_end),

.app_rd_data_valid (app_rd_data_valid),

.app_rdy (app_rdy),

.app_wdf_rdy (app_wdf_rdy),

.app_sr_req (1'b0),

.app_ref_req (1'b0),

.app_zq_req (1'b0),

.app_sr_active (app_sr_active),

.app_ref_ack (app_ref_ack),

.app_zq_ack (app_zq_ack),

.ui_clk (clk),

.ui_clk_sync_rst (rst),

.app_wdf_mask (32'd0),

// System Clock Ports

.sys_clk_i (sys_clk_i),

// Reference Clock Ports

.clk_ref_i (clk_ref_i),

.device_temp (device_temp),

.sys_rst (locked)

);

13,添加内存测试的读写逻辑控制代码

//以下是读写测试

// End of User Design top instance

parameter [2:0]CMD_WRITE =3'd0;

parameter [2:0]CMD_READ =3'd1;

//parameter TEST_DATA_RANGE=24'd16777215;//全地址测试

parameter TEST_DATA_RANGE=24'd2000;//部分测试

 

(*mark_debug="true"*) wire init_calib_complete;

(*mark_debug="true"*) reg [3:0]state=0;

(*mark_debug="true"*) reg [23:0]Count_64=0;// 128M*2*16/256

(*mark_debug="true"*) reg [23:0]Count_64_1=0;

(*mark_debug="true"*) reg ProsessIn=0;//表示读写操作的包络

(*mark_debug="true"*) reg WriteSign=0;//表示是写操作

(*mark_debug="true"*) reg ProsessIn1=0;//表示写操作的包络

reg [ADDR_WIDTH-1:0]app_addr_begin=0;

reg [29:0]CountWrite_tem=0;

reg [29:0]CountRead_tem=0;

 

 

(*mark_debug="true"*) reg [29:0] CountWrite=0;

(*mark_debug="true"*) reg [29:0] CountRead=0;

(*mark_debug="true"*) wire error_rddata=0;

 

assign app_wdf_end =app_wdf_wren;//两个相等即可

assign app_en =ProsessIn?(WriteSign?app_rdy&&app_wdf_rdy:app_rdy):1'd0;//控制命令使能

assign app_cmd =WriteSign?CMD_WRITE:CMD_READ;

assign app_addr =app_addr_begin;

assign app_wdf_data =Count_64_1;//写入的数据是计数器

assign app_wdf_wren =ProsessIn1?app_rdy&&app_wdf_rdy:1'd0;

[email protected](posedge clk)

if(rst&!init_calib_complete)//

begin

state <=4'd0;

app_addr_begin <=28'd0;

WriteSign <=1'd0;

ProsessIn <=1'd0;

Count_64 <=24'd0;

end

else case(state)

4'd0: begin

state <=4'd1;

app_addr_begin <=28'd0;

WriteSign <=1'd0;

ProsessIn <=1'd0;

Count_64 <=24'd0;

CountWrite_tem <=30'd0;//??0

CountRead_tem <=30'd0;

CountWrite <=CountWrite_tem;//?¨?D?

CountRead <=CountRead_tem;

end

4'd1: begin

state <=4'd2;

WriteSign <=1'd1;

ProsessIn <=1'd1;

Count_64 <=24'd0;

app_addr_begin <=28'd0;

CountWrite_tem <=CountWrite_tem+30'd1;

end

4'd2: begin//写整片的DDR3

state <=(Count_64==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?4'd3:4'd2;//最后一个地址写完之后跳出状态

WriteSign <=(Count_64==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?1'd0:1'd1;//写数据使能

ProsessIn <=(Count_64==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?1'd0:1'd1;//写命令使能

Count_64 <=app_rdy&&app_wdf_rdy?(Count_64+24'd1):Count_64;

app_addr_begin <=app_rdy&&app_wdf_rdy?(app_addr_begin+28'd8):app_addr_begin;//跳到下一个(8*32=256)bit数据地址

CountWrite_tem <=CountWrite_tem+30'd1;

end

4'd3: begin

state <=(state1==4'd0)?4'd4:state;

WriteSign <=1'd0;

ProsessIn <=(state1==4'd0)?1'd1:1'd0;

Count_64 <=24'd0;

app_addr_begin <=28'd0;

CountWrite_tem <=CountWrite_tem+30'd1;

end

4'd4: begin//读整片的DDR3

state <=(Count_64==TEST_DATA_RANGE)&&app_rdy?4'd0:state;

WriteSign <=1'd0;

ProsessIn <=(Count_64==TEST_DATA_RANGE)&&app_rdy?1'd0:1'd1;

Count_64 <=app_rdy?(Count_64+24'd1):Count_64;

app_addr_begin <=app_rdy?(app_addr_begin+28'd8):app_addr_begin;

CountRead_tem <=CountRead_tem+30'd1;

end

default:begin

state <=4'd1;

app_addr_begin <=28'd0;

WriteSign <=1'd0;

ProsessIn <=1'd0;

Count_64 <=24'd0;

end

endcase

(*mark_debug="true"*) reg [3:0]state1=0;

[email protected](posedge clk)//单独将写操作从上面的状态机提出来,当然也可以和上面的状态机合并到一起

if(rst&!init_calib_complete)//

begin

state1 <=4'd0;

ProsessIn1 <=1'd0;

end

else case(state1)

4'd0: begin

state1 <=(state==4'd1)?4'd1:4'd0;

ProsessIn1 <=(state==4'd1)?1'd1:1'd0;

Count_64_1 <=24'd0;

end

4'd1: begin

state1 <=(Count_64_1==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?4'd0:4'd1;

ProsessIn1 <=(Count_64_1==TEST_DATA_RANGE)&&app_rdy&&app_wdf_rdy?1'd0:1'd1;

Count_64_1 <=app_rdy&&app_wdf_rdy?(Count_64_1+24'd1):Count_64_1;

end

default:begin

state1 <=(state==4'd1)?4'd1:4'd0;

ProsessIn1 <=(state==4'd1)?1'd1:1'd0;

Count_64_1 <=24'd0;

end

endcase

(*mark_debug="true"*) reg [23:0]app_rd_data_tem=0;

(*mark_debug="true"*) reg [23:0]cmp_data_r1=0;

reg [23:0]cmp_data_r=0;

[email protected](posedge clk)

if(rst&!init_calib_complete)begin

cmp_data_r <= 24'd0;

end

else if(cmp_data_r==TEST_DATA_RANGE)begin

cmp_data_r <= 24'd0;

end

else if(app_rd_data_valid) begin

cmp_data_r<=cmp_data_r+1'b1;

end

reg app_rd_data_valid_r=1'b0;

[email protected](posedge clk) begin

app_rd_data_valid_r <= app_rd_data_valid;

app_rd_data_tem <= app_rd_data;

cmp_data_r1 <= cmp_data_r;

end

assign error_rddata=(app_rd_data_tem!=cmp_data_r1)&app_rd_data_valid_r;

14,修改仿真代码中的系统时钟,默认是 200MHZ(5ns) 改为 100MHZ(100ns)

parameter CLKIN_PERIOD = 5000

15,修改仿真接口,仿真

ZYNQ PL操作DDR内存读写测试

二,注意:该信号仅提供到 memc_ui_top 模块级别。 只有当 ECC 被使能时才应使用该信号。

ZYNQ PL操作DDR内存读写测试

ZYNQ PL操作DDR内存读写测试

1,app_addr [ADDR_WIDTH - - 1 1 : 0]
此输入表示当前正在提交给用户界面的请求的地址。 UI 聚合外部 SDRAM 的所有地址字段,并向您显示一个平面地址空间。
2,app_cmd[2 : 0]

此输入指定请求的命令如下表所示

ZYNQ PL操作DDR内存读写测试

3,app_en
此信号在输入请求中使用,用户必须赋值于 app_addr [],app_cmd [2:0]和 app_hi_pri,然后断言 app_en 将该请求提交给 UI。这将通过断言 app_rdy 启动 UI 确认的握手。

4,app_hi_pri
该输入表示当前请求是高优先级。
5,app_wdf_data [APP_DATA_WIDTH - - 1 1 : 0]
该总线提供当前正在写入外部存储器的数据。
6,app_wdf_end
该输入表示当前周期中 app_wdf_data []总线上的数据是当前请求的数据。

7,app_wdf_mask [APP_MASK_WIDTH - - 1 1 : 0]
该总线指示 app_wdf_data []的哪些位被写入外部存储器,哪些位保持在当前状态。
8,app_wdf_wren
该输入表示 app_wdf_data []总线上的数据有效。

9,app_rdy
此输出向用户显示当前正在提交给 UI 的请求是否被接受。如果在 app_en 被断言之后,UI 不
会声明此信号,则必须重试当前的请求。如果以下情况,则不会声明 app_rdy 输出:
1)PHY /内存初始化尚未完成
2)所有的银行机器都被占用(可以看作命令缓冲区已满)
- 请求读取,读取缓冲区已满
- 请求写入,并且没有写缓冲区指针可用
3)正在插入定期读取
10,app_rd_data [APP_DATA_WIDTH - - 1 1 : 0]
该输出包含从外部存储器读取的数据

11,app_rd_data_end
此输出表示当前周期中 app_rd_data []总线上的数据为当前请求的最后数据。
12,app_rd_data_valid
该输出表示 app_rd_data []总线上的数据有效。,

13,app_wdf_rdy
该输出表示写入数据 FIFO 准备好接收数据。 接受写入数据当 app_wdf_rdy 和 app_wdf_wren 都被断言时。
14,app_ref_req
当被置位时,该高电平有效输入请求存储器控制器向 DRAM 发送刷新命令。 必须对单个周期进,行脉冲以进行请求,然后至少断言,直到 app_ref_ack 信号被断言以确认请求并指示已经发送请求。
15,app_ref_ack
当置位时,此高电平有效输入确认刷新请求,并指示命令已从存储控制器发送到 PHY。

16,app_zq_req
当置位时,该高电平有效输入请求存储器控制器向 DRAM 发送 ZQ 校准命令。 必须对单个周期进行脉冲以进行请求,然后至少断言,直到 app_zq_ack 信号被断言以确认请求并指示已经发送请求。
17,app_zq_ack

当有效时,此高电平有效输入确认 ZQ 校准请求,并指示命令已从存储控制器发送到 PHY。

18,ui_clk_sync_rst
这是从与 ui_clk 同步的 UI 重置。
19,ui_clk
这是 UI 的输出时钟。 它必须是出口到外部 SDRAM 的时钟频率的一半或四分之一,这取决于,GUI 中选择的 2:1 或 4:1 模式。
20,init_calib_complete e
当校准完成时,PHY 会断言 init_calib_complete。 在向内存控制器发送命令之前,应用程序无需等待 init_calib_complete。

三,MIG内存控制器用户逻辑时序

1,命令路径

当用户逻辑 app_en 信号被断言并且 app_rdy 信号从 UI 被断言时,命令被 UI 接受并写入 FIFO。当 app_rdy 被取消置位时,UI 会忽略该命令。用户逻辑需要将 app_en 保持为高电平以及有效的命令和地址值,直到 app_rdy 被断言。

ZYNQ PL操作DDR内存读写测试

可以发出非背靠背写入命令,此图描述了 app_wdf_data,app_wdf_wren 和app_wdf_end 信号的三种场景,如下所示:
(1).写入数据以及相应的写入命令。
(2).写入数据在相应的写入命令之前。
(3).写入数据在相应的写命令之后,不应超过两个时钟周期的限制。对于在写入命令后输出的写入数据,最大延迟为两个时钟周期。

ZYNQ PL操作DDR内存读写测试

2,写路径

当 app_wdf_wren 被断言并且 app_wdf_rdy 为高时,写数据被写入写入 FIFO(图 1-4-2-1)。
如果 app_wdf_rdy 被取消置位,则用户逻辑需要保留 app_wdf_wren 和 app_wdf_end 以及有效的
app_wdf_data 值,直到 app_wdf_rdy 被断言。app_wdf_mask 信号可用于屏蔽写入外部存储器的字节。

ZYNQ PL操作DDR内存读写测试

写入数据和相关联的写入命令之间的单次写入的最大延迟是两个时钟周期。当发出背靠背写入命令时,写入数据和相关的背靠背写命令之间没有最大延迟。必须使用 app_wdf_end 信号来指示存储器写入突发的结束。对于 8 位的内存突发类型,应该在
第二个写入数据字上断言 app_wdf_end 信号。应用程序接口数据到 DRAM 输出数据的映射可以是以一个例子解释。

ZYNQ PL操作DDR内存读写测试

3,读路径

读取的数据由 UI 以请求的顺序返回,并且在 app_rd_data_valid 被断言时有效。返回的读取数据总是与地址/控制总线上的请求顺序相同。

ZYNQ PL操作DDR内存读写测试

4,用户刷新

对于用户控制的刷新,应通过将 USER_REFRESH 参数设置为“ON”来禁用内存控制器管理的维护。要请求 REF 命令,app_ref_req 会选通一个周期。当存储器控制器将命令发送到 PHY 时,它会将app_ref_ack 选通一个周期,之后可以发送另一个请求。只要遵循上述定义的握手,可以随时执行用户刷新操作。对于其他命令,没有额外的接口要求。但是,待处理的请求会影响操作何时出现。内存控制器在发出刷新命令之前完成所有挂起的数据请求。在确定何时选择 app_ref_req 以避免 tREFI 违规时,必须考虑每个待处理请求的时序参数。为了解决最坏的情况,减去每个银行机器的 tRCD,CL,数据传输时间 tRP,以确保在 tREFI 到期之前所有事务都可以完成。 (tREFI – (tRCD + ((CL + 4) × tCK) + tRP) × nBANK_MACHS)公式 1-1 显示了 REF请求间隔的最大值。公式 1-1 在校准后应立即发出用户 REF,以建立确定何时发送后续请求的时间基准。

ZYNQ PL操作DDR内存读写测试