前言
FIFO在系统设计中被广泛应用,但是大多时候使用X家和A家封装的IP核,为了更好的理解FIFO工作原理并且为了方便在不同厂商工作环境之间移植,自己编写一个FIFO IP也是不错的选择。下面将介绍同步FIFO的工作原理和Verilog实现。
什么是FIFO
1. 简介
FIFO(First in first out)是一种先进先出的数据缓冲器,实质上是RAM加上一些外围电路实现而来。它和RAM的区别在于:RAM需要外部的读写地址线,而FIFO只能顺序读出和顺序写入数据,其数据地址会根据读写使能信号自动加减,达到先存入数据先读出的效果,类似于栈的效果。
2. 分类
FIFO根据读写时钟是否同步分为同步时钟和异步时钟,同步FIFO读写时钟相同,异步FIFO读写时钟不同。两者应用场景不同,需要工程师根据实际情况使用。
3. 用途
- 数据缓存:当读写速度不匹配的时候可以使用FIFO,进行数据的一个缓存,比如说外部数据输入不需要马上使用的时候,即读写使能不同时。
- 数据匹配:当读写数据位宽不同时候可以使用FIFO,进行数据的一个拼接或者分割,比如说写入数据位宽为8bit,输出为16bit,通过FIFO可以将8bit的数据拼接缓存再输出16bit,达到数据匹配的功能。
- 多bit数据跨时钟域处理:当读写时钟不同时候可以使用异步FIFO,可以实现在两个不同时钟系统之间进行安全的数据交互。
注意:当需要实现少bit写入多bit读出功能时,需要注意不同厂商提供的FIFO IP核是小端写入还是大端写入。比如说8bit写入16bit写出,X家的是先存高8位,A家的是先存低8位。
同步FIFO的实现原理
同步FIFO的读写时钟相同,FIFO内部均为同步逻辑,可以用于数据的缓存。同步FIFO模块一般会有时钟clk、复位rst_n、读端口和写端口。读端口一般包括读使能(rd_en)、读数据(data_out)、读空(empty)。写端口一般包括写使能(wr_en)、写数据(wr_data)、写满(full)。
实现FIFO难点在于如何得到读空和写满信号。以下以深度为8的FIFO举例说明。在设计FIFO时,可以将读写地址扩展一位,用于判断是否读写完一轮,即深度为8,地址为4bit。
初始状态下,写地址为4’b0000,读地址为4’b0000,此时empty信号拉高。
写入8个数据,写地址为4’b1000,读地址为4’b0000,此时full信号拉高。
读出8个数据,写地址为4’b1000,读地址为4’b1000,此时empty信号拉高。
写入8个数据,写地址为4’b0000,读地址为4’b1000,此时full信号拉高。
综上所述,读空empty信号拉高的判决条件是写地址等于读地址。写满信号拉高的判决条件是写地址和读地址最高位相反其余位相同。
同步FIFO的硬件实现
由前文分析可以知道,同步FIFO的实现包括RAM、读控制、写控制、空满判别四部分。以下为systermverilog代码,如果想用verilog代码可以将localparam ADDR_BIT = $clog2(DEPTH)更改。
module sync_fifo#(
parameter DEPTH = 16,
parameter WIDTH = 16,
parameter RAM_STYLE_VAL = "distributed"
)(
input clk,
input rst_n,
input wr_en,
input [WIDTH-1:0] data_in,
input rd_en,
output [WIDTH-1:0] data_out,
output full,
output empty
);
localparam ADDR_BIT = $clog2(DEPTH);
(*ram_style=RAM_STYLE_VAL*) reg [WIDTH-1:0] mem [DEPTH-1:0]; //generate mem
reg [ADDR_BIT:0] waddr;
reg [ADDR_BIT:0] raddr;
reg [WIDTH-1 :0] data_out_r;
//write address
always@(posedge clk or negedge rst_n)begin
if(!rst_n) waddr <= 'd0;
else if(wr_en == 1'b1) waddr <= waddr + 1;
else waddr <= waddr;
end
//read address
always@(posedge clk or negedge rst_n)begin
if(!rst_n) raddr <= 'd0;
else if(rd_en == 1'b1) raddr <= raddr + 1;
else raddr <= raddr;
end
//write data
always@(posedge clk)begin
if(wr_en) mem[waddr] <= data_in;
else mem[waddr] <= mem[waddr];
end
//read data delay 1clk
always@(posedge clk or negedge rst_n)begin
if(!rst_n) data_out_r <= {WIDTH{1'b0}};
else data_out_r <= mem[raddr];
end
assign data_out = data_out_r;
//Empty signal judgment
assign empty = (waddr == raddr)?1'b1:1'b0;
//Full signal judgment
assign full = ({~waddr[ADDR_BIT],waddr[ADDR_BIT-1:0]} == raddr)?1'b1:1'b0;
endmodule
同步FIFO仿真测试
以下为测试文件。
module tb;
reg clk;
reg rst_n;
reg wr_en;
reg rd_en;
reg [7:0] data_in;
wire [7:0] data_out;
wire full;
wire empty;
sync_fifo#(
.DEPTH(16),
.WIDTH(8))
sync_fifo(
.clk (clk ),
.rst_n (rst_n ),
.wr_en (wr_en ),
.data_in (data_in ),
.rd_en (rd_en ),
.data_out (data_out ),
.full (full ),
.empty (empty) );
initial begin
clk = 0;rst_n = 0;
wr_en = 0;rd_en = 0;data_in = 'd0;
#15 rst_n = 1;
write_data();
#100
read_data();
#100
$finish;
end
always # 5 clk = ~clk;
task write_data;
begin
repeat(16)begin
#10
data_in <= {$random}%256;
wr_en <= 1;
end
#10 wr_en <= 0;data_in <= 'd0;
end
endtask
task read_data;
integer i;
begin
repeat(16)begin
#10
rd_en <= 1;
end
#10 rd_en <= 0;
end
endtask
endmodule
仿真结果如下,可以发现该同步fifo逻辑正确。