一、原理介绍
FIFO(First in, First out),顾名思义是先入先出存储器,数据的写入顺序和读出顺序一致。
一条数据流中有两个模块A和B,B接收A处理好的数据。假如A处理10个数据的时间,B只能处理5个数据,那么就会丢失5个数据,FIFO的作用就是存储A处理好的数据,B每处理完一个数据,就从FIFO中取下一个处理。
1.1 同步FIFO原理
同步FIFO和异步FIFO相比,核心区别是读写使用同一个时钟。
核心功能是:作为一个数据缓冲区,允许数据以写入的顺序被读出,同时通过空满状态标志来防止数据读空或溢出。
1.2 同步FIFO的构成
- 双端口RAM:读写操作独立,可同时进行;
- 写指针:总是指向下一个写入数据的存储地址,如果已经写入了4个数据,则指向地址5;
- 读指针:总是指向下一个要被读取数据的存储地址,如果已经读取了8个数据,则指向地址9;
- 满标志:写入的数据加上未被读出的数据个数等于FIFO的深度,输出满信号,阻止上游模块继续写入数据;
- 空标志:FIFO中所有数据被读出后,输出空信号,阻止下游模块继续读出数据。
1.3 FIFO的精华:空满状态的判断
本文使用计数器的方法实现空满状态判断。
定义一个计数器,计数器的量程等于FIFO的深度,即假设FIFO深度为8,那么计数器的计数范围是0~8。
计数器自增自减有如下5种情况:
- 上下游模块同时读写FIFO,且FIFO非空非满:计数器不变;
- 上游模块写FIFO,且非满:计数器自增;
- 下游模块读FIFO,且非空:计数器自减;
- 上游模块写FIFO,但满:计数器不变;
- 下游模块读FIFO,但空:计数器不变。
空满状态判断:
- 当计数器等于FIFO深度,输出满信号;
- 当计数器等于0,输出空信号。
二、同步FIFO代码
2.1 接口
module sfifo #(parameter DATA_WIDTH = 8,parameter FIFO_DEPTH = 8
)(input wire clk_i,input wire rstn_i,input wire wr_en_i,input wire [DATA_WIDTH-1:0] wr_data_i,output wire fifo_full_o,input wire rd_en_i,output wire [DATA_WIDTH-1:0] rd_data_o,output wire fifo_empty_o
);
- wr_en_i:写使能;
- wr_data_i:写数据;
- rd_en_i:读使能;
- rd_data_o:读数据;
- fifo_full_o:写满信号;
- fifo_empty_o:读空信号。
2.2 读写指针
reg [$clog2(FIFO_DEPTH)-1:0] wr_ptr_d;
reg [$clog2(FIFO_DEPTH)-1:0] rd_ptr_d;
wire [$clog2(FIFO_DEPTH)-1:0] wr_ptr = (wr_en_i && !fifo_full_o) ? wr_ptr_d + 1'b1 : wr_ptr_d;
wire [$clog2(FIFO_DEPTH)-1:0] rd_ptr = (rd_en_i && !fifo_empty_o) ? rd_ptr_d + 1'b1 : rd_ptr_d;always @(posedge clk_i or negedge rstn_i) if(!rstn_i) wr_ptr_d <= 'd0; else wr_ptr_d <= wr_ptr;
always @(posedge clk_i or negedge rstn_i) if(!rstn_i) rd_ptr_d <= 'd0; else rd_ptr_d <= rd_ptr;
- wr_ptr:写指针,当FIFO没有写满时才能自增,永远指向下一个数据被写到的地址;
- rd_ptr:读指针,当FIFO没有读空时才能自增,永远指向下一个被读出的数据的地址;
- wr_ptr_d:指向当前被写入的数据的地址;
- rd_ptr_d:指向当前被读出的数据的地址。
2.3 空满状态判断
reg [$clog2(FIFO_DEPTH):0] fifo_cnt_d;
wire [$clog2(FIFO_DEPTH):0] fifo_cnt = ((!fifo_full_o && wr_en_i) && (!fifo_empty_o && rd_en_i)) ? fifo_cnt_d :( !fifo_full_o && wr_en_i) ? fifo_cnt_d + 1'b1 :( !fifo_empty_o && rd_en_i) ? fifo_cnt_d - 1'b1 : fifo_cnt_d;always @(posedge clk_i or negedge rstn_i) if(!rstn_i) fifo_cnt_d <= 'd0; else fifo_cnt_d <= fifo_cnt;assign fifo_full_o = (fifo_cnt_d == FIFO_DEPTH) ? 1'b1 : 1'b0;
assign fifo_empty_o = (fifo_cnt_d == 'd0) ? 1'b1 : 1'b0;
2.4 数据读写
reg [DATA_WIDTH-1:0] mem [0:FIFO_DEPTH-1];
reg [DATA_WIDTH-1:0] rd_data;always @(posedge clk_i or negedge rstn_i) if(!fifo_full_o && wr_en_i) mem[wr_ptr_d] <= wr_data_i;
always @(posedge clk_i or negedge rstn_i) if(!fifo_empty_o && rd_en_i) rd_data <= mem[rd_ptr_d];assign rd_data_o = rd_data;
数据读写独立进行,可同时读同时写。
2.5 仿真
`timescale 1ns/1ns
module sfifo_tb();
reg clk_i;
reg rstn_i;reg wr_en_i;
reg [7:0] wr_data_i;
wire fifo_full_o;reg rd_en_i;
wire [7:0] rd_data_o;
wire fifo_empty_o;sfifo #(.DATA_WIDTH(8),.FIFO_DEPTH(8)
)my_sfifo(.clk_i (clk_i),.rstn_i (rstn_i),.wr_en_i (wr_en_i),.wr_data_i (wr_data_i),.fifo_full_o (fifo_full_o),.rd_en_i (rd_en_i),.rd_data_o (rd_data_o),.fifo_empty_o (fifo_empty_o)
);always #10 clk_i = ~clk_i;initial beginclk_i = 0;rstn_i = 1;wr_en_i = 0;wr_data_i = 0;rd_en_i = 0;#20 rstn_i = 0;#20 rstn_i = 1;@(negedge clk_i);pop();push(1);pop();@(posedge clk_i) $finish;
endtask push(input [7:0] data);if(fifo_full_o) beginwr_en_i = 1'b1;wr_data_i = data;$display("FULL! Can't push now!");@(negedge clk_i) wr_en_i = 1'b0;end else beginwr_en_i = 1'b1;wr_data_i = data;$display("Push: %0d", data);@(negedge clk_i) wr_en_i = 1'b0;end
endtasktask pop();if(fifo_empty_o) begin$display("Empty! Can't pop now!");end else beginrd_en_i = 1'b1;@(negedge clk_i) rd_en_i = 1'b0;$display("Pop: %0d", rd_data_o);end
endtaskendmodule
2.6 部分仿真结果
写入8个数据,
读出8个数据,
同时读写,
本文的原理部分和代码均参考自
掰开揉碎讲 FIFO(同步FIFO和异步FIFO) - Doreen的FPGA自留地 - 博客园
https://www.cnblogs.com/DoreenLiu/p/17348480.html