1、由于一直在PL侧做算法,外设接口接触的比较少,目前只做了sfp的UDP传输,但是由于课题的原因需要将一部分PL计算数据存储,而RAM存储空间比较小,因此本次给大带来了ZCU106的PL侧读写ddr4的教程,本教程是全网ZCU106 DDR4 PL侧读写的唯一一篇教程。下面是4个参考资料:
①:ZCU106开发之PL侧DDR4_lixiaolin126的博客-CSDN博客_zcu106开发板ddr4感谢大家漫长的等待!!我们团队从2017底拿到ZCU106后就一直在进行相关研发,由于手头上的活比较多就把ZCU106开发详解的发布给延迟了。现在我们将ZCU106开发过程中遇到的问题和解决办法跟大家进行分享。大家如若对博客中内容有疑问,请加我们的QQ群836535064进行交流。希望跟大家一起玩好这一个牛逼的芯片。版权声明:欢迎技术交流和帮助,提供IT相关服务,索要源码请联系博主QQ,若该文...https://blog.csdn.net/lixiaolin126/article/details/83958665?spm=1001.2014.3001.5501这个是在PS侧对PL侧通过bram进行的读写,操作难度较高。
②:FPGA学习之路-zynq7000-PS侧读写ddr_发光的沙子的博客-CSDN博客_ps读写ddr1、由于ZCU106板子的sdk调试一直没有成功,因此还是基于ZYNQ7000板子写了一个ps侧读写内存的内容2、实验软硬件:Vivado 2019.1、Xilinx sdk 2019.1、ZYBO板子(xc7z010clg400-1 )3、实验过程:step1:在VIvado中建立工程:miz701_sys,芯片选择xc7z010clg400-1step2:在IP INERGATOR中点击创建,然后搜索ZYNQ双击。 然后点击Diagram的自动运行模块,弹出的窗口点击OK。.https://blog.csdn.net/qq_37912811/article/details/121921754?spm=1001.2014.3001.5502这个是本人的z7的PS侧操作ddr3的教程
③:FPGA----PL侧对ram的读写_发光的沙子的博客-CSDN博客_fpga ram读写1、例化模块的输出端口只能是wire型,而不能是reg型,也就是不能更改输出。2、input\output都需要有位宽表示。3、assign变量也是wire型。4、①实验任务:将32位宽的22个数放入PL侧的ddr,并且读取(本实验可以为数组在模块间的传递提供解决方案【数组在Verilog中是不可以作为端口传递的,但是在SystemVerilog中可以】)②实验软硬件:Vivado 2019.1、ZCU106③实验过程:step1:建立test_pl_ddr项目,过程不再赘述sthttps://blog.csdn.net/qq_37912811/article/details/122204870?spm=1001.2014.3001.5502这个是本人一直被说诟病的ZCU106的ram读写
2、本教程采用的是xilinx的ddr4的IP核的AXI4接口开发的,因此需要先了解AXI4总线协议。
①AXI4接口描述
通道 | 信号 | 主/从 | 信号描述 |
---|---|---|---|
全局 信号 |
aclk | 主机 | AXI4总线时钟 |
aresetn | 主机 | AXI4总线复位 | |
写通 道地 址与 控制 信号 通道 |
M_AXI_WR_awid | 主机 | 写地址ID,用来表示哪个主机写入的数据,一般是一台主机对应一个从机,因此这个可以随便写。 |
M_AXI_WR_awaddr | 主机 | 写地址,给出一次写突发传输的写地址 | |
M_AXI_WR_awlen | 主机 | 突发长度,给出突发传输的次数 | |
M_AXI_WR_awsize | 主机 | 突发大小,给出每次突发传输的字节数 | |
M_AXI_WR_awburst | 主机 | 突发类型 | |
M_AXI_WR_awlock | 主机 | 总线锁信号,可提供操作的原子性 | |
M_AXI_WR_awcache | 主机 | 内存类型,表明一次传输是怎样通过系统 的 |
|
M_AXI_WR_awprot | 主机 | 保护类型,表明一次传输的特权级及安全 等级 |
|
M_AXI_WR_awqos | 主机 | 质量服务QoS | |
M_AXI_WR_awvalid | 主机 | 有效信号,表明此通道的地址控制信号有 效 |
|
M_AXI_WR_awready | 从机 | 表明“从”可以接收地址和对应的控制信 号 |
|
写通 道数 据通 道 |
M_AXI_WR_wdata | 主机 | 写入的数据 |
M_AXI_WR_wstrb | 主机 | 写数据有效的字节线,用来表明哪8bits 数据是有效的(例如32位的数据[4个8bit数据],他的wstrb一般是4位宽,一般设置为F,表示全数据位有效) | |
M_AXI_WR_wlast | 主机 | 表明此次传输是最后一个突发传输 | |
M_AXI_WR_wvalid | 主机 | 写有效,表明此次写有效 | |
M_AXI_WR_wready | 从机 | 表明从机可以接收写数据 | |
写通 道响 应通 道 |
M_AXI_WR_bid | 从机 | 写响应ID |
M_AXI_WR_bresp | 从机 | 写响应,表明写传输的状态 | |
M_AXI_WR_bvalid | 从机 | 写响应有效 | |
M_AXI_WR_bready | 主机 | 表明主机能够接收写响应 | |
读通 道地 址与 控制 信号 通道 |
M_AXI_RD_arid | 主机 | 读地址ID,与M_AXI_WR_awid类似 |
M_AXI_RD_araddr | 主机 | 读地址,给出一次写突发传输的读地址 | |
M_AXI_RD_arlen | 主机 | 突发长度,给出突发传输的次数 | |
M_AXI_RD_arsize | 主机 | 突发大小,给出每次突发传输的字节数 | |
M_AXI_RD_arburst | 主机 | 突发类型 | |
M_AXI_RD_arlock | 主机 | 总线锁信号,可提供操作的原子性 | |
M_AXI_RD_arcache | 主机 | 内存类型,表明一次传输是怎样通过系统 的 |
|
M_AXI_RD_arprot | 主机 | 保护类型,表明一次传输的特权级及安全 等级 |
|
M_AXI_RD_arqos | 主机 | 质量服务QOS | |
M_AXI_RD_arvalid | 主机 | 有效信号,表明此通道的地址控制信号有 效 |
|
M_AXI_RD_arready | 从机 | 表明“从”可以接收地址和对应的控制信 号 |
|
读通 道数 据通 道 |
M_AXI_RD_rid | 从机 | 读IDtag |
M_AXI_RD_rdata | 从机 | 读数据 | |
M_AXI_RD_rresp | 从机 | 读响应,表明读传输的状态 | |
M_AXI_RD_rlast | 从机 | 表明读突发的最后一次传输 | |
M_AXI_RD_rvalid | 从机 | 表明此通道信号有效 | |
M_AXI_RD_rready | 主机 | 表明主机能够接收读数据和响应信息 |
②写数据:
写空闲:等待触发突发信号。
写通道写地址等待:准备好写地址AWADDR,然后拉高AWVALID。
写通道写地址:从机接受到AWVALID,发出AWREADY。
写数据等待:准备好数据WDATA,拉高WVALID。
写数据循环: 从机接受WVALID, 确认数据WDATA 有效并且接受, 发出WREADY,AXI 是突发传输:循环该操作到接受到WLAST 最后一个数据标志位。
接受写应答:接受到从机发出的BVALID,主机发出BREADY。
写结束:拉低未拉低的信号,进入写空闲。
③读数据:
读空闲:等待触发突发信号。
读通道写地址等待:准备好写地址ARADDR,然后拉高ARVALID。
读通道写地址:从机接受到ARVALID,发出ARREADY。
读数据等待:从机器准备好数据WDATA,从机拉高RVALID。
读数据循环:主机接受RVALID,确认数据RDATA 有效并且接受,发出RREADY 给从机,AXI 是突发传输:循环该操作到接受到RLAST 最后一个数据标志位。
读结束:拉低未拉低的信号,进入读空闲。
④总结:X代表W/R
空闲:等待突发读写信号
写地址等待:主机发出AXADDR,并且AXVALID置1
从机地址握手:从机发出AXREADY
写/读数据等待:主机发出WDATA,并且WVALID置1。/从机发出RDATA,并且RVALID置1。
主/从机数据循环:从机发出WREADY/主机发出RREADY,循环该操作到接受到XLAST 最后一个数据标志位。
接受写应答(写数据才有):接受到从机发出的BVALID,主机发出BREADY。
注:任何时候都是求偶方放出VALID信号,被求偶方放出READY信号
3、实验过程
①试验任务:完成ZCU106板卡PL侧对的ddr4的读写操作
②实验软硬件:ZCU106、Vivado 2019.1
③实验过程:
step1:建立项目:ddr4_test
step2:设置IP核
ddr4 IP:
wr_fifo:
rd_fifo:除了下面两个设置都一样
③项目结构:
④代码部分:只给出用户生成数据、顶层、xdc文件,剩余文件请联系作者。
`timescale 1ns / 1ps
//
// Company: 东北电力大学
// Engineer: Yang Zheng
//
// Create Date: 2022/07/21 12:46:13
// Design Name:
// Module Name: ddr_data
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module ddr_data(input sys_clk, //系统时钟
input sys_rst_n, //系统复位,低电平有效
output reg wr_en, //写使能
output reg [127:0] wr_data, //写数据
output reg rd_en, //读使能
input [127:0] rd_data, //读数据
input rd_fifo_valid,//读出数据时为高电平,由于ddr是512位的,因此我们需要计数4次,即每次读回4条数据
output read_enable); //写完成,开始读
//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//
//parameter define
parameter COUTER_MAX = 30'd200;//0.2s
//reg define
reg [29:0] count; //写入计数器
reg wr_flag; //写标志
//记录数据
reg [127:0] rd_data_r0 = 128'b0;
reg [127:0] rd_data_r1 = 128'b0;
reg [127:0] rd_data_r2 = 128'b0;
reg [127:0] rd_data_r3 = 128'b0;
//********************************************************************//
//***************************** Main Code ****************************//
//********************************************************************//
assign read_enable = wr_flag;
reg [2:0] rd_data_count = 3'b0;
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0) begin
rd_data_count <= 1'b0;
rd_data_r0 <= 128'b0;
rd_data_r1 <= 128'b0;
rd_data_r2 <= 128'b0;
rd_data_r3 <= 128'b0;
end
else if (rd_fifo_valid && rd_data_count == 'd0) begin
rd_data_r0 <= rd_data;
rd_data_count <= rd_data_count + 1'b1;
end
else if (rd_fifo_valid && rd_data_count == 'd1) begin
rd_data_r1 <= rd_data;
rd_data_count <= rd_data_count + 1'b1;
end
else if (rd_fifo_valid && rd_data_count == 'd2) begin
rd_data_r2 <= rd_data;
rd_data_count <= rd_data_count + 1'b1;
end
else if (rd_fifo_valid && rd_data_count == 'd3) begin
rd_data_r3 <= rd_data;
rd_data_count <= 1'b0;
end
end
//0.2s 计数器
always@(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0) begin
count <= 30'd0;
end
else begin
if (~rd_en) begin
count <= count+1;
if (count == COUTER_MAX) begin
count <= 30'd0;
end
end
end
end
//生成1 到200 个数据并且写入ddr 中
//这里的 wr_data是输入到ddr的数据,wr_en是使能端口
//这里的 wr_flag是可以ddr存有数据,因此可读标志
always@(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0) begin
wr_data <= 127'd0;
wr_en <= 1'd0;
wr_flag <= 1'b0;
end
else if (wr_flag == 1'b0) begin//写过程
wr_data <= {32'h00000001 + count,32'h00000002 + count,
32'h00000003 + count,32'h00000004 + count};
wr_en <= 1'd1;
if (count == 'd200) begin
//wr_data <= 127'd0;
wr_flag <= 1'b1;
end
end
else begin
wr_data <= wr_data;
wr_en <= 1'd0;
wr_flag <= wr_flag;//写完成
end
end
//根据写完成拉高读使能数据
//这里的rd_en可以控制是否读取ddr,因此每次读取给出一个周期的高电平即可
//一直读取
always@(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0) begin
rd_en <= 1'd0;
end
//0.2s 读取一次数据
else if (count == COUTER_MAX - 1) begin//(wr_flag == 1'b1)&&(count == COUTER_MAX)
rd_en <= 1'd1;
end
end
//每次读取4个
/*reg [1:0] COUNT_FLAG = 2'b0;
always@(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0) begin
rd_en <= 1'd0;
end
//0.2s 读取一次数据
else if (wr_flag && COUNT_FLAG == 2'd0) begin//(wr_flag == 1'b1)&&(count == COUTER_MAX)
rd_en <= 1'd1;
COUNT_FLAG <= 2'd1;
end
else begin
rd_en <= 1'd0;
end
if (COUNT_FLAG) begin
COUNT_FLAG <= 2'd2;
end
end*/
endmodule
`timescale 1ns / 1ps
//
// Company: 东北电力大学
// Engineer: Yang Zheng
//
// Create Date: 2022/07/21 12:28:54
// Design Name:
// Module Name: ddr
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module ddr(input sys_clk_p, //系统时钟
input sys_clk_n, //系统时钟
input sys_rst, //系统复位
output [16:0] c0_ddr4_adr,
output [1:0] c0_ddr4_ba,
output [0:0] c0_ddr4_cke,
output [0:0] c0_ddr4_cs_n,
inout [7:0] c0_ddr4_dm_dbi_n,
inout [63:0] c0_ddr4_dq,
inout [7:0] c0_ddr4_dqs_c,
inout [7:0] c0_ddr4_dqs_t,
output [0:0] c0_ddr4_odt,
output [0:0] c0_ddr4_bg,
output c0_ddr4_reset_n,
output c0_ddr4_act_n,
output [0:0] c0_ddr4_ck_c,
output [0:0] c0_ddr4_ck_t,
output c0_init_calib_complete,
output use_clk100m
);
reg pingpang = 0 ;
reg [30:0] wr_b_addr = 0 ;
reg [30:0] wr_e_addr = 200*16 ;
wire data_wren;
wire [127:0] data_wr;
reg wr_rst = 0 ;
reg [30:0] rd_b_addr = 0 ;
reg [30:0] rd_e_addr = 200*16 ;
reg user_rd_clk = 0 ;
wire data_rden;
reg rd_rst = 0 ;
wire read_enable;
// axi_ddr_top Outputs
wire [127:0] data_rd ;
wire ui_clk ;
wire ui_rst ;
wire rd_fifo_empty ;
axi_ddr_top #(
.DDR_WR_LEN ( 1 ),
.DDR_RD_LEN ( 1 ))
u_axi_ddr_top (
.sys_rst ( sys_rst ),//低电平复位
.c0_sys_clk_p ( sys_clk_p ),
.c0_sys_clk_n ( sys_clk_n ),
.pingpang ( pingpang ),
.wr_b_addr ( wr_b_addr [30:0] ),
.wr_e_addr ( wr_e_addr [30:0] ),
.user_wr_clk ( use_clk100m ),
.data_wren ( data_wren ),
.data_wr ( data_wr [127:0] ),
.wr_rst ( wr_rst ),
.rd_b_addr ( rd_b_addr [30:0] ),
.rd_e_addr ( rd_e_addr [30:0] ),
.user_rd_clk ( use_clk100m ),
.data_rden ( data_rden ),
.rd_rst ( rd_rst ),
.read_enable ( read_enable ),
.rd_fifo_valid (rd_fifo_valid ),
.c0_ddr4_adr ( c0_ddr4_adr [16:0] ),
.c0_ddr4_ba ( c0_ddr4_ba [1:0] ),
.c0_ddr4_cke ( c0_ddr4_cke [0:0] ),
.c0_ddr4_cs_n ( c0_ddr4_cs_n [0:0] ),
.c0_ddr4_odt ( c0_ddr4_odt [0:0] ),
.c0_ddr4_bg ( c0_ddr4_bg [0:0] ),
.c0_ddr4_reset_n ( c0_ddr4_reset_n ),
.c0_ddr4_act_n ( c0_ddr4_act_n ),
.c0_ddr4_ck_c ( c0_ddr4_ck_c [0:0] ),
.c0_ddr4_ck_t ( c0_ddr4_ck_t [0:0] ),
.data_rd ( data_rd [127:0] ),
.ui_clk ( ui_clk ),
.ui_rst ( ui_rst ),
.use_clk1 ( use_clk100m ),
.c0_init_calib_complete ( c0_init_calib_complete ),
.c0_ddr4_dm_dbi_n ( c0_ddr4_dm_dbi_n [7:0] ),
.c0_ddr4_dq ( c0_ddr4_dq [63:0] ),
.c0_ddr4_dqs_c ( c0_ddr4_dqs_c [7:0] ),
.c0_ddr4_dqs_t ( c0_ddr4_dqs_t [7:0] )
);
ddr_data u_ddr_data (
.sys_clk ( use_clk100m ),
.sys_rst_n ( c0_init_calib_complete ),
.rd_data ( data_rd [127:0] ),
.rd_fifo_valid (rd_fifo_valid ),
.wr_en ( data_wren ),
.wr_data ( data_wr [127:0] ),
.rd_en ( data_rden ),
.read_enable ( read_enable )
);
endmodule
set_property IOSTANDARD LVCMOS12 [get_ports sys_rst]
set_property PACKAGE_PIN AK12 [get_ports sys_rst]
set_property PACKAGE_PIN AL11 [get_ports c0_init_calib_complete]
set_property PACKAGE_PIN AK13 [get_ports use_clk100m]
set_property IOSTANDARD LVCMOS12 [get_ports c0_init_calib_complete]
set_property IOSTANDARD LVCMOS12 [get_ports use_clk100m]
set_property C_CLK_INPUT_FREQ_HZ 300000000 [get_debug_cores dbg_hub]
set_property C_ENABLE_CLK_DIVIDER false [get_debug_cores dbg_hub]
set_property C_USER_SCAN_CHAIN 1 [get_debug_cores dbg_hub]
connect_debug_port dbg_hub/clk [get_nets clk]
⑤仿真:ddr4仿真一般必须上板调试,而xilinx太牛了,直接用sverilog写了一个ddr4的模型应该。因此本人直接采用了xilinx自带的ddr4仿真文件进行的仿真,方便快捷。
可以看到用户侧的rd_data_rx已经收到了之前存入ddr4中数据。
4、DDR4 MIG的BUG
我们可以看到00地址记录01020304,40地址记录05060708,80地址记录09abc ,而读出数据是40地址读取的00,80地址才开始读取05060708。也就是读取时,00地址正常读取,40地址读到0数据,80地址以后对应的才是写入时的40地址的数据,这个原因我试了一天我也不清楚为啥,我反正觉得我代码没问题,哈哈哈。