FIFO复习总结
一.具体思路
首先介绍一下FIFO,于我而言,FIFO的作用主要有两个,一个是用于数据的缓存,另一个则是用于跨时钟域的数据传输,比如我要做一个摄像头的实验,摄像头采集到的数据我们要传给DDR3存储器,再从DDR3存储器读出输出到LCD屏幕上进行显示,那么摄像头的时钟频率和DDR3读写的频率完全是天差地别,如果这时候我们将数据塞进去,就可能会造成传非所收的状态,这个时候就需要FIFO来进行一个调整平衡。
那么FIFO介绍完了,重点还是要考虑如何去理解和完成这一实验。
首先我们要考虑的事情是要将这一实验以几个部分,模块来完成。不必多说,首先最必不可少的也就是FIFO IP核部分,由它来进行具体的、细节的数据操作,除此之外,我们打算再设置一个FIFO的读模块(fifo_rd)和写模块(fifo_wr),这两个模块来进行FIFO的读写设定,最后我们再用一个顶层模块(ip_fifo)来将读模块、写模块和FIFO IP核装进去,这就是大致思路。那么接下来我们一部分一部分的粘贴代码进行解释分析(本部分代码采用正点原子例程代码)
FIFO写模块(fifo_wr):
module fifo_wr(
input clk,
input rst_n,
input almost_empty,
input almost_full,
output reg [7:0] fifo_wr_data,
output reg fifo_wr_en
);
首先看端口部分,系统时钟和复位不多赘述,那么我们先看到的是almost_empty和almost_full两个端口,即将空和将满端口,由于FIFO秉持着“不读空,不写满”的原则,因此我们在空、满信号的前一拍提前设置了两个信号,就是将空和将满,这样可以有效的避免数据的溢出和丢失。
其次则是输出的使能和数据,大家可以这样想象,作为写FIFO,我们需要注入需要写入的数据,再输出需要写入的数据,但是由于本次实验的写数据是我们自己造的,因此我们不需要输入数据,只有输出的8位位宽的数据。至于使能则更不难想象,是作为一个开关式的存在控制着读写的操作。
reg almost_empty_d0;
reg almost_empty_syn;
reg [3:0] dly_cnt;
reg [1:0] state;
almost_empty_d0和almost_empty_syn则是almost_empty分别延迟一拍和两拍后的信号,那么不难理解,almost_empty_d0 就相当于empty信号,而almost_empty_syn则相当于比空信号晚一拍,即那时候已经是空的了,没有多余的数据存在了。
接着设置了延迟计数器,用于读写切换时的延迟作用,延迟十拍。
state则是用于状态的转移,简单来说,不同状态,不同操作。
always@(posedge clk)begin
if(!rst_n)begin
almost_empty_d0 <= 1'b0;
almost_empty_syn <= 1'b0;
end
else begin
almost_empty_d0 <= almost_empty;
almost_empty_syn <= almost_empty_d0;
end
end
这一部分进行了打拍的处理。
always@(posedge clk)begin
if(!rst_n)begin
fifo_wr_en <= 1'b0;
fifo_wr_data <= 8'd0;
state <= 2'd0;
dly_cnt <= 4'd0;
end
else begin
case(state)
2'd0:begin
if(almost_empty_syn)begin
state <= 2'd1;
end
else
state <= state;
end
2'd1:begin
if(dly_cnt == 4'd10)begin
dly_cnt <= 4'd0;
state <= 2'd2;
fifo_wr_en <= 1'b1;
end
else
dly_cnt <= dly_cnt + 4'd1;
end
2'd2:begin
if(almost_full)begin
state <= 2'd0;
fifo_wr_en <= 1'b0;
fifo_wr_data <= 8'd0;
end
else begin
fifo_wr_data <= fifo_wr_data + 8'd1;
fifo_wr_en <= 1'd1;
end
end
default : state <= 2'd0;
endcase
end
end
endmodule
当state为0时,我们试着等待着almost_empty_syn(为了方便,接下来我们叫他已空信号)的到来,在最开始,由于没有进行数据的写入,因此FIFO中本身就是空的,即会触发这一条件,使state进入1的状态,1状态的作用就是进行十拍的延迟,十拍过后state进入2状态,2状态进行写操作,如果将满信号没有到来,即没有写满,就继续写,写满了就将使能、数据、状态归零。这就是FIFO写模块的内容。
但是大家要注意:
fifo_wr_data <= fifo_wr_data + 8'd1;
这一部分是我们在手动的造数据。
接下来的FIFO读模块也是一样(fifo_rd):
不多赘述了。
module fifo_rd(
input clk,
input rst_n,
input almost_empty,
input almost_full,
input [7:0] fifo_dout,
output reg fifo_rd_en
);
reg almost_full_d0;
reg almost_full_syn;
reg [3:0] dly_cnt;
reg [1:0] state;
always@(posedge clk)begin
if(!rst_n)begin
almost_full_d0 <= 1'b0;
almost_full_syn <= 1'b0;
end
else begin
almost_full_d0 <= almost_full;
almost_full_syn <= almost_full_d0;
end
end
always@(posedge clk)begin
if(!rst_n)begin
fifo_rd_en <= 1'b0;
state <= 2'd0;
dly_cnt <= 4'd0;
end
else begin
case(state)
2'd0:begin
if(almost_full_syn)begin//已经满了一个周期了,我开始要读了
state <= 2'd1;
end
else
state <= state;
end
2'd1:begin
if(dly_cnt == 4'd10)begin
dly_cnt <= 4'd0;
state <= 2'd2;
//fifo_rd_en <= 1'd1; 为什么?
end
else
dly_cnt <= dly_cnt + 4'd1;
end
2'd2:begin
if(almost_empty)begin
state <= 2'd0;
fifo_rd_en <= 1'b0;
end
else
fifo_rd_en <= 1'b1;
end
default : state <= 2'd0;
endcase
end
end
endmodule
但是大家尤为要注意的是:大家在对代码进行分析理解的时候,一定要将两个模块联系起来理解,不能光去看写或者读模块,而是要将读和写模块一起看,因为它们是一体的,如果只看一个操作,那么其实是不完整、不完全的。
接下来给出顶层模块(ip_fifo)的代码:
module ip_fifo(
input sys_clk,
input sys_rst_n
);
wire almost_empty;
wire almost_full;
wire [7:0] fifo_din;
wire fifo_wr_en;
wire fifo_rd_en;
wire [7:0] fifo_dout;
wire fifo_full;
wire fifo_empty;
wire [7:0] fifo_rd_data_count;
wire [7:0] fifo_wr_data_count;
fifo_wr u_fifo_wr(
.clk (sys_clk),
.rst_n (sys_rst_n),
.almost_empty (almost_empty),
.almost_full (almost_full),
.fifo_wr_data (fifo_din),
.fifo_wr_en (fifo_wr_en)
);
fifo_rd u_fifo_rd(
.clk (sys_clk),
.rst_n (sys_rst_n),
.almost_empty (almost_empty),
.almost_full (almost_full),
.fifo_dout (fifo_dout),
.fifo_rd_en (fifo_rd_en)
);
fifo_generator_0 your_instance_name (
.wr_clk(sys_clk), // input wire wr_clk
.rd_clk(sys_clk), // input wire rd_clk
.din(fifo_din), // input wire [7 : 0] din
.wr_en(fifo_wr_en), // input wire wr_en
.rd_en(fifo_rd_en), // input wire rd_en
.dout(fifo_dout), // output wire [7 : 0] dout
.full(fifo_full), // output wire full
.almost_full(almost_full), // output wire almost_full
.empty(fifo_empty), // output wire empty
.almost_empty(almost_empty), // output wire almost_empty
.rd_data_count(fifo_rd_data_count), // output wire [7 : 0] rd_data_count
.wr_data_count(fifo_wr_data_count) // output wire [7 : 0] wr_data_count
);
endmodule
这一部分也没有什么值得注意的地方。
所有部分搞定之后,我们写个tb,仿真一下看看波形:
tb部分代码:
module tb_ip_fifo();
reg sys_clk;
reg sys_rst_n;
always #10 sys_clk = ~sys_clk;
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#200
sys_rst_n = 1'b1;
end
ip_fifo u_ip_fifo(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n)
);
endmodule
二、波形分析
接下来我们对着波形图来分析一下:
写使能的开始:
大家可以看到在写使能拉高以后的下一个时钟我们就开始进行写的操作。
写使能的结束:
可以看到,在写完254的时候,写使能拉低,宣告写操作的结束,从0-254,也就是我们写入了我们需要的255个数据。刚好大家也可以观察到almost_full的拉高是在开始写入254的时候。
读使能的拉高:
大家不要忘记,读使能拉高前可有10拍的延迟,只是在波形图中我并没有选择观察延迟计数器的信号,所以大家需要记住,读写操作进行切换的时候是要有10拍的延迟的。那么读使能拉高后的一个周期,可以看到开始读出数据。
读使能的结束:
其实说实话,这一部分我是觉得有问题的,此次代码我反复检查过是和正点原子的例程代码一模一样的,但是还是出现了这样的情况,我认为它的这一部分的代码是有问题的,如果我说的不对,也希望大家批评指正。
接下来我说一下结论:我认为只读到253是不对的,因为我们写入了255位数据,但是却只读出了254位数据后,读使能便拉低了,也就是我们写入了255位数据却只读出了254位数据,所以我认为这一部分是有问题的。
那么我们如何更改呢,其实非常简单,我们之前是依靠的是almost_empty,也就是将空信号的到来进行读使能的拉低,如果我们想读到254这一数据,我们只需要依靠almost_empty_d0或是almost_empty_syn信号来进行读使能的拉低,即可达到我们的目的。
那么至此,实验基本圆满完成,如果有不明白的地方,大家可以留言,我非常愿意和大家进行交流探讨。同时如果我有说错或者不足的地方,也希望大家指出,我将积极改进。
谢谢大家,我是FPGA小白,我和大家一同进步!