前言
FIFO是数字IC中常用的模块,本文介绍同步FIFO的概念及Verilog代码实现
一、同步FIFO是什么?
FIFO(Fist in Fist Out)是先入先出的存储模块,主要进行数据缓存,进行速率匹配,上下游数据速率不同时进行匹配,同步是指具有一定相位关系的时钟,同步FIFO的读时钟和写时钟为同一个时钟。
二、同步FIFO的组成
同步FIFO由三部分组成:1.FIFO写控制逻辑 2.FIFO读控制逻辑 3.FIFO存储实体
FIFO写控制逻辑主要功能:产生FIFO写地址、写有效有信号,同时产生FIFO写满、写错等状态信号
FIFO读控制逻辑主要功能:产生FIFO读地址、读有效信号,同时产生FIFO读空、读错等状态信号
二、同步FIFO代码
1.信号定义
同步FIFO具有以下引脚
信号名 | 方向 | 说明 |
clk | input | 系统时钟 |
rst_n | input | 复位信号,低有效 |
wr_en | input | 写使能信号 |
rd_en | input | 读使能信号 |
wr_data | input | 写入的数据 |
rd_data | output | 读出的数据 |
empty | output | FIFO中没有数据,空信号 |
full | output | FIFO中写满数据,满信号 |
fifo_cnt | output | FIFO中的数据数量 (非必要,可用于判断空满,可用于一些特殊应用) |
其中使用参数化,DATA_WIDTH指的是数据的位宽,这里设置为8,即一个数据最大为256
FIFO_DEPTH指的是FIFO的深度,这里设置为16,即FIFO最大能容纳16个数
module sync_fifo #(parameter DATA_WIDTH = 8,
parameter FIFO_DEPTH = 16)
( //system signal
input clk, //sys clock
input rst_n, //reset signal ,low active
//write and read signal
input wr_en, //write enable
input rd_en, //read enable
input [DATA_WIDTH-1:0] wr_data, //write data
output reg [DATA_WIDTH-1:0] rd_data, //read data
//flag signal
output empty, //fifo empty
output full, //fifo full
output reg [$clog2(FIFO_DEPTH):0] fifo_cnt //fifo data number
);
2.变量定义
localparam ADDR_WIDTH = $clog2(FIFO_DEPTH)-1; //地址的位宽,深度16的FIFO宽度为3
reg [ADDR_WIDTH:0] rd_ptr; //读指针(地址),大小为3,即[2:0]
reg [ADDR_WIDTH:0] wr_ptr; //写指针(地址),大小为3,即[2:0]
3.读写指针
always@(posedge clk or negedge rst_n)
if(!rst_n)
rd_ptr <= {ADDR_WIDTH{1'b0}}; //读指针复位为0
else if(rd_ptr==FIFO_DEPTH-1)
rd_ptr <= {ADDR_WIDTH{1'b0}};//当计数到最大,读指针恢复为0,若最大值与位宽对应,可不写
else if(rd_en && !empty)
rd_ptr <= rd_ptr + 1'b1; //读使能且FIFO非空,读指针+1
always@(posedge clk or negedge rst_n)
if(!rst_n)
wr_ptr <= {ADDR_WIDTH{1'b0}};//写指针复位为0
else if(wr_ptr==FIFO_DEPTH-1)
wr_ptr <= {ADDR_WIDTH{1'b0}};//当计数到最大,写指针恢复为0,若最大值与位宽对应,可不写
else if(wr_en && !wfull)
wr_ptr <= wr_ptr + 1'b1; //写使能且FIFO非满,写指针+1
4.空满信号
空满信号的判断有两种方式:
a.通过对FIFO中的数据进行计数,若计数值为0,则空;若计数值等于FIFO深度,则满,本文使用这种方式
always@(posedge clk or negedge rst_n)
if(!rst_n)
fifo_cnt <= {(ADDR_WIDTH+1){1'b0}}; //复位将计数值清零
else if(wr_en && rd_en)
fifo_cnt <= fifo_cnt; //当同时进行读写时,计数值不变
else if(wr_en && !full)
fifo_cnt <= fifo_cnt + 1'b1; //当进行写时,计数值+1
else if(rd_en && !empty)
fifo_cnt <= fifo_cnt - 1'b1; //当进行读时,计数值-1
assign full = (fifo_cnt == FIFO_DEPTH); //当计数值为FIFO深度时,表示已满
assign empty= (fifo_cnt == 0); //当计数值为0时,表示已空
b.通过是否“套圈”来判断空满。当写指针写满一圈又和读指针相同时,已经写满。可通过增加读写指针位宽,利用最高位来指示是否"套圈"
reg rd_flag;
reg wr_flag;
always@(posedge clk or negedge rst_n)
if(!rst_n)
rd_flag <= 1'b0; //读标志复位为0
else if(rd_ptr == FIFO_DEPTH-1)
rd_flag = ~rd_flag; //每当读到最大值,读标志翻转
always@(posedge clk or negedge rst_n)
if(!rst_n)
wr_flag <= 1'b0; //写标志复位为0
else if(wr_ptr == FIFO_DEPTH-1)
wr_flag = ~wr_flag; //每当写到最大值,写标志翻转
always@(posedge clk or negedge rst_n)
if(!rst_n)
empty <= 1'b0;
else if(rd_flag==wr_flag && wr_ptr==rd_ptr) //若读写标志相同,且读写指针相同,空
empty <= 1'b1;
else
empty <= 1'b0;
always@(posedge clk or negedge rst_n)
if(!rst_n)
full <= 1'b0;
else if(rd_flag!=wr_flag && wr_ptr==rd_ptr) //若读写标志不同,且读写指针相同,满
full <= 1'b1;
else
full <= 1'b0;
4.FIFO实体(RAM)
本文例化双端口RAM,双端口RAM链接如下:双端口RAM
需要注意的是,对于双端口RAM的读写使能,需要判断空满状态
dual_port_RAM RAM1(
.rst_n(rst_n),
.wr_clk(clk),
.wr_en(wr_en & !full),
.wr_addr(wr_r_ptr),
.wr_data(wdata),
.rd_clk(clk),
.rd_en(rd_en & !empty),
.rd_addr(rd_ptr),
.rd_data(rdata)
);
总结
本文介绍了同步FIFO的基本概念以及参数化的verilog代码实现,对于空满判断,介绍了两种方法。