0. 目录
1. FIFO简介
- FIFO 是一种先进先出的数据缓存器,一般用在隔离两边读写带宽不一致,或者位宽不一样的地方。
- 在 FPGA 设计,使用 FIFO 一般有两个方法,第一个方法是直接调用官方的 FIFO IP,另外一个方法是自己设计 FIFO 控制逻辑。
- FIFO 包括同步 FIFO 和异步 FIFO 两种。
- 同步 FIFO 有一个时钟信号,读和写逻辑全部使用这一个时钟信号。
- 异步 FIFO 有两个时钟信号,读和写逻辑用的各自的读写时钟。
- FIFO 与 RAM 的区别是没有外部读写地址线;缺点是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加 1 完成,不能像RAM那样可以由地址线决定读取或写入某个指定的地址。
- FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。
- FIFO 的宽度:即 FIFO 一次读写操作的数据位。
- FIFO 的深度:指的是 FIFO 可以存储多少个 N 位的数据(如果宽度为 N)。
- 满标志:FIFO 已满或将要满时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出(overflow)。
- 空标志:FIFO 已空或将要空时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出(underflow)。
- 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据(同步FIFO中与写时钟一致)。
- 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据(同步FIFO中与读时钟一致)。
2. 同步FIFO的实现
- 两种实现方式:计数器法和高位扩展法。
- FIFO设计关键:产生可靠的 FIFO 读写指针和生成 FIFO“空”/“满”状态标志。
- FIFO读空的情况:(读指针追上写指针)–读写指针实质为读写地址
① 在复位操作时,当读写指针相等时,表明 FIFO 为空;
② 当读指针读出 FIFO 中最后一 个字后,追赶上了写指针时,表明 FIFO 为空。如下左图。 - FIFO写满的情况:(写指针追上读指针)
① 当读写指针再次相等时,表明 FIFO 为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around) 又追上了读指针。如下右图。
2.1 计数器法
构建一个计数器,该计数器(fifo_cnt)用于指示当前 FIFO 中数据的个数:
- 复位时,该计数器为0,FIFO中的数据个数为0,当wr_en = rd_en=1时,说明又读又写,计数器不变,FIFO中的数据个数无变化。
- 当wr_en =1,rd_en = 0时且 full=0,则 fifo_cnt +1;表示写操作且 FIFO 未满时候,FIFO 中的数据个数增加了 1 。
- 当wr_en =0,rd_en = 1时且 empty=0,则 fifo_cnt -1; 表示读操作且 FIFO 未满时候,FIFO 中的数据个数减少了 1 。
- fifo_cnt =0 的时候,表示 FIFO 空,需要设置 empty=1;fifo_cnt = fifo的深度 的时候,表示 FIFO现在已经满,需要设置 full=1。
2.1.1 计数器实现同步FIFO–Verilog代码
//计算器法:
//wr_en = rd_en = 1 -> cnt不变; (cnt指的是当前FIFO内部的数据个数)
//wr_en =0, rd_en = 1 -> cnt=cnt-1;
//wr_en =1, rd_en = 0 -> cnt=cnt+1.
//cnt = fifo_len, -> full=1;
//cnt = 0, -> empty = 1;
module Sync_FIFO_cnt(
input wr_en,rd_en,clk,reset,
input [7:0] dataIn,
output [7:0] dataOut,
output full,empty
);
parameter fifo_len = 8;
reg [3:0] fifo_cnt; // 当前fifo内部数据的个数
reg [7:0] dataOut_reg;
reg [2:0] addr_wr;
reg [2:0] addr_rd;
reg full_reg,empty_reg;
reg [7:0] RAM [fifo_len-1:0];
// generate addr_write
always @(posedge clk or posedge reset)
if (reset) begin
addr_wr <= 3'd0;
RAM[addr_wr] <= 8'd0;
end
else if (full_reg == 1'd0 && wr_en) begin
addr_wr <= addr_wr + 1'b1;
RAM[addr_wr] <= dataIn;
end
else begin
addr_wr <= addr_wr;
RAM[addr_wr] <= RAM[addr_wr];
end
// generate addr_read
always @(posedge clk or posedge reset)
if (reset) begin
addr_rd <= 3'd0;
dataOut_reg <= 8'd0;
end
else if (empty_reg == 1'd0 && rd_en) begin
addr_rd <= addr_rd + 1'b1;
dataOut_reg <= RAM[addr_rd];
end
else begin
addr_rd <= addr_rd;
dataOut_reg <= dataOut_reg;
end
// count the number of fifo data
always @(posedge clk or posedge reset)
if (reset)
fifo_cnt <= 3'd0;
else if (wr_en == 1'd1 && rd_en == 1'd0) begin
fifo_cnt <= fifo_cnt + 1'd1;
end
else if (wr_en == 1'd0 && rd_en == 1'd1) begin
fifo_cnt <= fifo_cnt - 1'd1;
end
else begin
fifo_cnt <= fifo_cnt;
end
// generate full and empty signal
always @(*)
if (fifo_cnt == (fifo_len)) begin
full_reg <= 1'd1;
empty_reg <= 1'd0;
end
else if (fifo_cnt == 3'd0) begin
full_reg <= 1'd0;
empty_reg <= 1'd1;
end
else begin
full_reg <= 1'd0;
empty_reg <= 1'd0;
end
assign dataOut = dataOut_reg;
assign full = full_reg;
assign empty = empty_reg;
endmodule
2.1.2 Sync_FIFO_cnt_tb
`timescale 1ns/1ns //时间单位/精度
//------------<模块及端口声明>----------------------------------------
module Sync_FIFO_cnt_tb();
//parameter DATA_WIDTH = 8 ; //FIFO位宽
//parameter DATA_DEPTH = 8 ; //FIFO深度
//parameter fifo_depth = 8;
//parameter fifo_depth_bit = 3;
//parameter data_width = 8;
reg clk ;
reg rst_n ;
reg rst;
reg [DATA_WIDTH-1:0] data_in ;
reg rd_en ;
reg wr_en ;
wire [DATA_WIDTH-1:0] data_out;
wire empty ;
wire full ;
wire [$clog2(DATA_DEPTH) : 0] fifo_cnt;
//------------<例化被测试模块>----------------------------------------
Sync_FIFO_cnt
//#(
// .fifo_depth(fifo_depth),
// .fifo_depth_bit(fifo_depth_bit),
// .data_width(data_width)
//)
Sync_FIFO_cnt_u(
.clk (clk ),
.reset (~rst_n ),
.dataIn (data_in ),
.rd_en (rd_en ),
.wr_en (wr_en ),
.dataOut (data_out ),
.empty (empty ),
.full (full )
);
//------------<设置初始测试条件>----------------------------------------
initial begin
clk = 1'b0; //初始时钟为0
rst_n <= 1'b0; //初始复位
data_in <= 'd0;
wr_en <= 1'b0;
rd_en <= 1'b0;
//重复8次写操作,让FIFO写满
repeat(8) begin
@(negedge clk)begin
rst_n <= 1'b1;
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
end
end
//重复8次读操作,让FIFO读空
repeat(8) begin
@(negedge clk)begin
wr_en <= 1'b0;
rd_en <= 1'd1;
end
end
//重复4次写操作,写入4个随机数据
repeat(4) begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
rd_en <= 1'b0;
end
end
//持续同时对FIFO读写,写入数据为随机数据
forever begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
rd_en <= 1'b1;
end
end
end
//------------<设置时钟>----------------------------------------------
always #10 clk = ~clk; //系统时钟周期20ns
//rst = ~ rst_n;
endmodule
测试结果:
2.2 高位扩展法
- 当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,而显然不会让读指针多跑一圈,所以可能出现的情况只能是写指针多跑了一圈,就意味着FIFO被写满了。
- 当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了。
- 此处要考虑FIFO深度不是2^N的情况,因此需要把高低位的判断分开。
2.2.1 位扩展实现同步FIFO–Verilog代码
module Sync_FIFO_addBit
#(
parameter fifo_depth = 8,
parameter fifo_depth_bit = 3,
parameter data_width = 8
)
(
input wr_en,rd_en,clk,reset,
input [data_width-1:0] dataIn,
output [data_width-1:0] dataOut,
output full,empty
);
reg [data_width-1:0] RAM [fifo_depth-1:0];
reg [fifo_depth_bit:0] addr_wr;
reg [fifo_depth_bit:0] addr_rd;
reg [data_width-1:0] dataOut_reg;
reg full_reg,empty_reg;
// generate addr_wr
always @(posedge clk or posedge reset)
if (reset) begin
addr_wr <= 'd0;
RAM[addr_wr] <= 'd0;
end
else if (addr_wr[fifo_depth_bit-1:0] < (fifo_depth - 1) && wr_en==1'd1 && full_reg == 1'd0) begin
addr_wr <= addr_wr + 1'd1;
RAM[addr_wr] <= dataIn;
end
else if (addr_wr[fifo_depth_bit-1:0] == (fifo_depth - 1) && wr_en==1'd1 && full_reg == 1'd0) begin
addr_wr[fifo_depth_bit] <= ~addr_wr[fifo_depth_bit];
addr_wr[fifo_depth_bit-1:0] <= 'd0;
RAM[addr_wr[fifo_depth_bit-1:0]] <= dataIn;
end
else begin
addr_wr <= addr_wr;
RAM[addr_wr] <= RAM[addr_wr];
end
// generate addr_rd
always @(posedge clk or posedge reset)
if (reset) begin
addr_rd <= 'd0;
dataOut_reg <= 'd0;
end
else if (addr_rd[fifo_depth_bit-1:0] < (fifo_depth - 1) && rd_en==1'd1 && empty_reg == 1'd0) begin
addr_rd <= addr_rd + 1'd1;
dataOut_reg <= RAM[addr_rd[fifo_depth_bit-1:0]];
end
else if (addr_rd[fifo_depth_bit-1:0] == (fifo_depth - 1) && rd_en==1'd1 && empty_reg == 1'd0) begin
addr_rd[fifo_depth_bit] <= ~addr_rd[fifo_depth_bit];
addr_rd[fifo_depth_bit-1:0] <= 'd0;
dataOut_reg <= RAM[addr_rd[fifo_depth_bit-1:0]];
end
else begin
addr_rd <= addr_rd;
dataOut_reg <= dataOut_reg;
end
// judge full and empty
always @(*)
if (reset) begin
empty_reg <= 1'd0;
full_reg <= 1'd0;
end
else if (addr_wr[fifo_depth_bit-1:0] == addr_rd[fifo_depth_bit-1:0] && addr_wr[fifo_depth_bit] != addr_rd[fifo_depth_bit]) begin
empty_reg <= 1'd0;
full_reg <= 1'd1;
end
else if (addr_wr == addr_rd) begin
empty_reg <= 1'd1;
full_reg <= 1'd0;
end
else begin
empty_reg <= 1'd0;
full_reg <= 1'd0;
end
assign dataOut = dataOut_reg;
assign full = full_reg;
assign empty = empty_reg;
endmodule
2.2.2 Sync_FIFO_addBit_tb
`timescale 1ns/1ns //时间单位/精度
//------------<模块及端口声明>----------------------------------------
module Sync_FIFO_cnt_tb();
parameter DATA_WIDTH = 8 ; //FIFO位宽
parameter DATA_DEPTH = 8 ; //FIFO深度
parameter fifo_depth = 8;
parameter fifo_depth_bit = 3;
parameter data_width = 8;
reg clk ;
reg rst_n ;
reg rst;
reg [DATA_WIDTH-1:0] data_in ;
reg rd_en ;
reg wr_en ;
wire [DATA_WIDTH-1:0] data_out;
wire empty ;
wire full ;
wire [$clog2(DATA_DEPTH) : 0] fifo_cnt;
//------------<例化被测试模块>----------------------------------------
Sync_FIFO_addBit
#(
.fifo_depth(fifo_depth),
.fifo_depth_bit(fifo_depth_bit),
.data_width(data_width)
)
Sync_FIFO_addBit_u(
.clk (clk ),
.reset (~rst_n ),
.dataIn (data_in ),
.rd_en (rd_en ),
.wr_en (wr_en ),
.dataOut (data_out ),
.empty (empty ),
.full (full )
);
//------------<设置初始测试条件>----------------------------------------
initial begin
clk = 1'b0; //初始时钟为0
rst_n <= 1'b0; //初始复位
data_in <= 'd0;
wr_en <= 1'b0;
rd_en <= 1'b0;
//重复8次写操作,让FIFO写满
repeat(8) begin
@(negedge clk)begin
rst_n <= 1'b1;
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
end
end
//重复8次读操作,让FIFO读空
repeat(8) begin
@(negedge clk)begin
wr_en <= 1'b0;
rd_en <= 1'd1;
end
end
//重复4次写操作,写入4个随机数据
repeat(4) begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
rd_en <= 1'b0;
end
end
//持续同时对FIFO读写,写入数据为随机数据
forever begin
@(negedge clk)begin
wr_en <= 1'b1;
data_in <= $random; //生成8位随机数
rd_en <= 1'b1;
end
end
end
//------------<设置时钟>----------------------------------------------
always #10 clk = ~clk; //系统时钟周期20ns
//rst = ~ rst_n;
endmodule
仿真结果:
- 实现了同步FIFO,下一篇学习异步FIFO。