概述
同步FIFO
本文参考:
- 同步FIFO的两种Verilog设计方法(计数器法、高位扩展法)
- 01 【Verilog实战】同步FIFO的设计(附源码RTL/TB)
一、概念介绍
1.1 什么是FIFO
FIFO是一种先进先出的数据缓存器,一般用在隔离两边读写带宽不一致
,或者位宽不一样
的地方。在FPGA设计中,使用FIFO一般有两个方法,第一个方法是直接调用官方的FIFO IP,第二个方法是自己设计FIFO控制逻辑。
FIFO包括同步FIFO
和异步FIFO
两种,同步FIFO有一个时钟信号,读和写逻辑全部使用这一个时钟信号;异步FIFO有两个时钟信号,读和写逻辑使用各自的时钟。FIFO 与普通存储器 RAM 的区别是没有外部读写地址线,使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。 FIFO 本质上是由 RAM 加读写控制逻辑构成的一种先进先出的数据缓冲器。
1.2 FIFO常见参数
- FIFO的宽度:即FIFO一次读写操作的数据位
- FIFO的深度:即FIFO可以存储多少个N位数据
- 满标志:FIFO 已满或将要满时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的写操作继续向 FIFO 中写数据而造成溢出
- 空标志:FIFO 已空或将要空时由 FIFO 的状态送出的一个信号,以阻止 FIFO 的读操作继续从 FIFO 中读出数据而造成无效数据的读出
- 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据(同步FIFO中与写时钟一致)
- 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据(同步FIFO中与读时钟一致)
二、同步FIFO设计方法
2.1 数据机制
FIFO是一个顺序数据结构,先存入的数据先取出,可以看成是一个上下都没有封口的羽毛球桶,先放进去的羽毛球也一定会先掉出来。
FIFO数据的写入写出依靠读指针和写指针的移动,写指针RP指向下一个要写入的数据单元,读指针WP指向即将取出的数据单元。如下图所示,可以加一个计数器来标志FIFO的状态,初始化时,读指针和写指针指向同一数据地址,计数器为0。
指针的移动分为三种情况:
- 写使能有效:写地址加一,读地址不变,计数器加一,标志着FIFO里多存入了一个数;
- 读使能有效:读地址加一,写地址不变,计数器减一,标志着FIFO里空出来一个单元;
- 读写使能同时有效:读写地址均加一,计数器不变。
计数器等于0的时候,表示FIFO为空;计数器等于FIFO深度的时候,表示FIFO已满,以此产生空信号和满信号。
1.4 端口描述
二、RTL实现
`timescale 1ns/1ps
module sync_fifo #(
parameter DATA_WIDTH = 32,
parameter DATA_DEPTH = 8,
parameter PTR_WIDTH = $clog2(DATA_DEPTH)
)(
input wire clk_i,
input wire rst_n_i,
// write interface
input wire wr_en_i,
input wire [DATA_WIDTH-1:0] wr_data_i,
// read interface
input wire rd_en_i,
output reg [DATA_WIDTH-1:0] rd_data_o,
//Flags_o
output wire full_o,
output wire empty_o
);
reg [DATA_WIDTH-1:0] FIFO[0:DATA_DEPTH-1];
reg [PTR_WIDTH-1:0] wr_ptr;
reg [PTR_WIDTH-1:0] rd_ptr;
reg [PTR_WIDTH:0] fifo_cnt;
/*---------------------------------------------------
--------------- FIFO counter ---------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
fifo_cnt <= 4'b0;
else
if(rd_en_i==1'b1 && wr_en_i==1'b0 && empty_o==1'b0)
fifo_cnt <= fifo_cnt - 4'b1;
else if(wr_en_i==1'b1 && rd_en_i==1'b0 && full_o==1'b0)
fifo_cnt <= fifo_cnt + 1;
// else if(wr_en_i==1'b1 && rd_en_i==1'b1)
// fifo_cnt <= fifo_cnt;
else
fifo_cnt <= fifo_cnt;
end
/*---------------------------------------------------
------------- generate the flags -----------------
---------------------------------------------------*/
assign full_o = (fifo_cnt==DATA_DEPTH) ? 1'b1 : 1'b0;
assign empty_o = (fifo_cnt==4'b0) ? 1'b1 : 1'b0;
/*---------------------------------------------------
-------------------- read data -------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
rd_ptr <= 3'b0;
else if(rd_en_i==1'b1 && empty_o==1'b0)
rd_ptr <= rd_ptr + 3'b1;
else
rd_ptr <= rd_ptr;
end
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
rd_data_o <= 32'b0;
else
if(rd_en_i==1'b1 && empty_o==1'b0)
rd_data_o <= FIFO[rd_ptr];
end
/*---------------------------------------------------
-------------------- write data ------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
wr_ptr <= 3'b0;
else if(wr_en_i==1'b1 && full_o==1'b0)
wr_ptr <= wr_ptr + 3'b1;
else
wr_ptr <= wr_ptr;
end
integer i;
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
for(i=0;i<DATA_DEPTH;i=i+1)
FIFO[i] <= 32'b0;
else
if(wr_en_i==1'b1 && full_o==1'b0)
FIFO[wr_ptr] <= wr_data_i;
end
endmodule
测试文件:
`timescale 1ns/1ps
module sync_fifo_tb;
reg clk_i;
reg rst_n_i;
// write interface
reg wr_en_i;
reg [31:0] wr_data_i;
// read interface
reg rd_en_i;
wire [31:0] rd_data_o;
//Flags_o
wire full_o;
wire empty_o;
initial begin
clk_i = 0;
rst_n_i = 1;
wr_data_i = 0;
#100 rst_n_i = 0;
#100 rst_n_i = 1;
end
always #50 clk_i = ~clk_i;
always #100 wr_data_i = {wr_data_i+1}%10;
initial begin
wr_en_i = 1;
rd_en_i = 0;
#800;
wr_en_i = 0;
rd_en_i = 1;
#300;
wr_en_i = 1;
rd_en_i = 0;
#1000;
wr_en_i = 0;
rd_en_i = 1;
#1000;
wr_en_i = 1;
rd_en_i = 0;
#300;
wr_en_i = 1;
rd_en_i = 1;
#1000;
$stop;
end
sync_fifo u_sync_fifo(
.clk_i (clk_i ),
.rst_n_i (rst_n_i ),
.wr_en_i (wr_en_i ),
.wr_data_i(wr_data_i),
.rd_en_i (rd_en_i ),
.rd_data_o(rd_data_o),
.full_o (full_o ),
.empty_o (empty_o )
);
endmodule
测试结果:
三、高位扩展法
在之前的方法中,使用了一个计数器判断FIFO中数据的存储,如在深度为8的FIFO中,需要3bit的读写指针来分别指示读写地址3’b000-3’b111这8个地址。若将地址指针扩展1bit,则变成4bit的地址,而地址表示区间则变成了4’b0000-4’b1111。假设不看最高位的话,后面3位的表示区间仍然是3’b000-3’b111,也就意味着最高位可以拿来作为指示位。
- 当最高位不同,且其他位相同,则表示读指针或者写指针多跑了一圈,而显然不会让读指针多跑一圈(多跑一圈读啥?),所以可能出现的情况只能是写指针多跑了一圈,与就意味着FIFO被写满了
- 当最高位相同,且其他位相同,则表示读指针追到了写指针或者写指针追到了读指针,而显然不会让写指针追读指针(这种情况只能是写指针超过读指针一圈),所以可能出现的情况只能是读指针追到了写指针,也就意味着FIFO被读空了
`timescale 1ns/1ps
module sync_fifo2 #(
parameter DATA_WIDTH = 32,
parameter DATA_DEPTH = 8,
parameter PTR_WIDTH = $clog2(DATA_DEPTH)
)(
input wire clk_i,
input wire rst_n_i,
// write interface
input wire wr_en_i,
input wire [DATA_WIDTH-1:0] wr_data_i,
// read interface
input wire rd_en_i,
output reg [DATA_WIDTH-1:0] rd_data_o,
//Flags_o
output wire full_o,
output wire empty_o
);
reg [DATA_WIDTH-1:0] FIFO[0:DATA_DEPTH-1];
reg [PTR_WIDTH:0] wr_ptr;
reg [PTR_WIDTH:0] rd_ptr;
wire wr_ptr_msb;
wire rd_ptr_msb;
wire [PTR_WIDTH-1:0] wr_ptr_true;
wire [PTR_WIDTH-1:0] rd_ptr_true;
/*---------------------------------------------------
------------- generate the flags -----------------
---------------------------------------------------*/
assign full_o = (rd_ptr=={~wr_ptr_msb, wr_ptr_true}) ? 1'b1 : 1'b0;
assign empty_o = (rd_ptr==wr_ptr) ? 1'b1 : 1'b0;
/*---------------------------------------------------
-------------------- read data -------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
rd_ptr <= 3'b0;
else if(rd_en_i==1'b1 && empty_o==1'b0)
rd_ptr <= rd_ptr + 3'b1;
else
rd_ptr <= rd_ptr;
end
assign {rd_ptr_msb, rd_ptr_true} = rd_ptr;
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
rd_data_o <= 32'b0;
else
if(rd_en_i==1'b1 && empty_o==1'b0)
rd_data_o <= FIFO[rd_ptr_true];
end
/*---------------------------------------------------
-------------------- write data ------------------
---------------------------------------------------*/
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
wr_ptr <= 3'b0;
else if(wr_en_i==1'b1 && full_o==1'b0)
wr_ptr <= wr_ptr + 3'b1;
else
wr_ptr <= wr_ptr;
end
assign {wr_ptr_msb, wr_ptr_true} = wr_ptr;
integer i;
always @(posedge clk_i or negedge rst_n_i) begin
if(!rst_n_i)
for(i=0;i<DATA_DEPTH;i=i+1)
FIFO[i] <= 32'b0;
else
if(wr_en_i==1'b1 && full_o==1'b0)
FIFO[wr_ptr_true] <= wr_data_i;
end
endmodule
最后
以上就是俊秀大地为你收集整理的同步FIFO同步FIFO的全部内容,希望文章能够帮你解决同步FIFO同步FIFO所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复