DDR3保姆级使用教程:ZYNQ 7010
内容:使用DDR3 IP核,向DDR3写入数据,然后再读出数据,通过串口打印。
设备:ZYNQ 7010 xc7z010clg-400-1。软件VIVADO 2018.3
(1)工程模块:一个写FIFO,一个读FIFO。一个ZYNQ IP核(DDR3在ZYNQ IP核内勾选)。一个AXI4通信接口转换模块。
(2)工程的运行逻辑简介:
AXI4的接口是64位,也就是,两个FIFO(读FIFO、写FIFO)与ZYNQ 的接口位宽是64位。我们进入和出来DDR3的数据都是64位的。
整个工程分为两个流程:
-
突发写DDR3(设置一次突发写的长度是16个64位的数据)
顶层文件内,持续向写FIFO写入数据,写FIFO的读计数会累积增加。每次写入FIFO的数据位宽是16位,当我们写入64个16位的数据,读计数值(rd_cnt)就会等于16(代表16个64位的数据排列在FIFO里)。
当rd_cnt = 16 就代表满足一次突发写DDR3的设置长度“16”,那就会自动执行一次DDR3的突发写,数据就进入到DDR3里了(每次向DDR3里写1个64位数,重复了16次)。 -
突发读DDR3(设置一次突发读的长度是16个64位的数据)
我们要把之前写到DDR3里的数据读出来放到读FIFO里。DDR3的读操作受限于读FIFO的写计数值(wr_cnt)。wr_cnt在读FIFO被写入数据后会自动增加,增加1就代表DDR3给FIFO写了1个64位的数据。在读FIFO复位后,wr_cnt会清零,如果wr_cnt小于等于我们设定的16,DDR就自动往读FIFO搬运数值,一直到wr_cnt等于16了,DDR3就不会往FIFO里搬运数据。(当wr_cnt等于16就等于DDR3给了读FIFO16个64位的数据,代表一次突发读完成)
关于两个过程的一些问题:
红色箭头代表写流程,绿色箭头代表读流程
其中有很多的问题,比如时钟,信号线等等。看看我们怎么代码实现对DDR3的数据写入和读取吧。
**代码块一:**给写FIFO写入数据,这些数据会自动存入到DDR3中
/***********************方式2* 直接使能计数****************************/
reg [15:0] dat2;
wire wrfifo_data_en = dat2 ==1024; //代表输入1024个16位数 rd = 256 *64
always@(posedge loc_clk100m or posedge cnt_en_reg2)
begin
if(cnt_en_reg2)
dat2 <= 16'd0;
else if(!wrfifo_data_en && !cnt_en_reg2)
dat2 <= dat2 + 1'b1;
else
dat2 <= dat2;
end
assign wrfifo_clr = reset;
assign wrfifo_wren =!wrfifo_data_en & !cnt_en_reg2;
assign wrfifo_din = dat2;
assign wrfifo_wren 代表允许写FIFO写入数据的使能信号
assign wrfifo_din 代表给写FIFO写入的数据,在使能信号为1的情况下有效
**代码块二:**读FIFO的读出数据。DDR3会自动把数据给读FIFO,我们要把读FIFO的数据读出
/***********************方式1* 标志位cnt读计数****************************/
wire rdfifo_empty;
wire rdfifo_rd_rst_busy;
wire rd_en;
wire rdfifo_en_reg2;
reg [19:0] rdfifo_en_reg;
reg rd_clr;
assign rdfifo_en_reg2 = rdfifo_en_reg[19];
// //延迟20个时钟周期
always@(posedge loc_clk100m or negedge wrfifo_data_en)
begin
if(!wrfifo_data_en)
rdfifo_en_reg <= {20{1'b1}};
else
rdfifo_en_reg <= rdfifo_en_reg << 1;
end
assign rd_en = rdfifo_empty | rdfifo_rd_rst_busy;
reg [15:0] rd_dat;
reg rd_send_en;
wire rdfifo_data_en = rd_dat ==1024; //代表输入1024个16位数
reg [31:0] rd_cnt;
always @ (posedge loc_clk100m or posedge rdfifo_en_reg2)
if(rdfifo_en_reg2)begin
rd_dat <= 0;
rd_cnt <= 0;
rd_send_en <= 0;
end
else if(!rdfifo_data_en && !rd_en)begin
if(rd_cnt == 32'd9_999_999) begin// d199_999
rd_dat <= rd_dat +1'b1;
rd_send_en <= 1;
rd_cnt <= 0;
end
else begin
rd_cnt <= rd_cnt + 1'b1;
rd_dat <= rd_dat;
rd_send_en <= 0;
end
end
else begin
rd_cnt <= 0 ;
rd_send_en <= 0;
rd_dat <= rd_dat;
end
reg rd_send_en_reg;
always @ (posedge loc_clk100m or posedge rdfifo_en_reg2)
if(rdfifo_en_reg2)
rd_send_en_reg <= 0;
else if(rd_send_en)
rd_send_en_reg <= 1;
else
rd_send_en_reg <= 0;
assign rdfifo_rden =rd_send_en;
assign rdfifo_clr = reset | rd_clr;
这里的rd_send_en 是读FIFO的读使能信号,每隔“d9_999_999”也就是9_999_999 + 1 = 10_000_000 * 10ns = 100 ms读一次"读FIFO"的数据,因为读使能和读数据有一个周期的延迟,因此rd_send_en_reg 出现了,他被用来给uart模块块用来发送读FIFO的数据,来验证我们整个流程是否正确。
**代码块三:**串口打印
uart tx /
uart_byte_tx uart_byte_tx(
.clk(loc_clk100m),
.reset(reset),
.data_byte(rdfifo_dout),
.send_en(rd_send_en_reg),
.baud_set(3'd0),
.uart_tx(uart_tx),
.tx_done( ),
.uart_state()
);
rdfifo_dout就是读FIFO的数据口了,因为rdfifo_dout是16位的,我们这样使用会直接输出低八位的数据。
在代码块一,我们看到
/***********************方式2* 直接使能计数****************************/
reg [15:0] dat2;
wire wrfifo_data_en = dat2 ==1024; //代表输入1024个16位数 rd = 256 *64
always@(posedge loc_clk100m or posedge cnt_en_reg2)
begin
if(cnt_en_reg2)
dat2 <= 16'd0;
else if(!wrfifo_data_en && !cnt_en_reg2)
dat2 <= dat2 + 1'b1;
else
dat2 <= dat2;
end
assign wrfifo_clr = reset;
assign wrfifo_wren =!wrfifo_data_en & !cnt_en_reg2;
assign wrfifo_din = dat2;
assign wrfifo_din = dat2;
dat2作为一直给FIFO,也就是一直给DDR3的数据是从0~1024开始变化的。所以串口打印的数据只有八位8h00到8hFF,一共256个数据。那么根据1024个数据挨个打印出来,在低八位上显示的效果就是4次循环打印输出00到FF
到这里整个流程就结束了,由于这个DDR3的工程涉及到PS与PL侧的资源,导致会有很多出BUG的地方。因此,整个工程代码难度比较高,我还是建议使用一个基础版的工程文件来学习整个DDR3的运行流程,接下来我会上传我自己编写的基础版的代码供大家学习参考
原文地址:https://blog.csdn.net/Ievn_Hare/article/details/144162052
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!