⭐️作者简介:
小瑞同学
,主要学习FPGA、信号处理、通信等。
🍎个人主页:小瑞同学的博客主页
🌻个人信条:越努力,越幸运!
⏰日期:2023.12.1
📖文章内容概述:介绍了同步FIFO的概念和工作原理,并使用Verilog代码模拟实现了同步FIFO,最后对其进行仿真。
连载系列:FPGA中FIFO的应用
完整工程已上传至CSDN:下载链接
- 同步FIFO设计
- 异步FIFO设计
- Vivado FIFO IP核的调用
1.同步FIFO简介
1.1 概述
📄FIFO,即First In First Out,是一种常用的存储结构。正如其名,先进入FIFO的数据会先输出。FIFO通常可分为同步FIFO和异步FIFO两类,本篇文章我们先来介绍同步FIFO。
📄同步FIFO是指其读写受同一时钟控制,一般用于数据的缓冲、传输速率匹配等。
FIFO的优点:没有读写地址线,顺序读入数据,顺序读出数据,使用起来简单方便。
FIFO的缺点:不能像RAM或ROM那样读取或写入某个指定的地址。
1.2 主要参数
参数 | 意义 |
---|---|
时钟 | 读写采用同一时钟 |
FIFO的宽度 | FIFO一次读写的数据位宽 |
FIFO的深度 | FIFO中存储的数据个数 |
空标志 | FIFO已空时发出空信号,以阻止读操作继续读取无效数据 |
满标志 | FIFO已满时发出满信号,以阻止写操作继续向FIFO中写数据而导致FIFO溢出 |
2.工作原理
📄同步FIFO实现起来相对简单,关键在于如何产生空满标志信号。
2.1 工作时序
- 在写使能有效且FIFO非满时,对FIFO进行写;写地址由自增产生,将数据写入该地址 ,初始为0。
- 在读使能有效且FIFO非空时,对FIFO进行读;读地址由自增产生,将数据从该地址读出,初始为0。
2.2 空满判断
📄FIFO空满判断主要有计数器法和高位扩展法两种,这里我们先介绍计数器法,下篇文章介绍高位扩展法。
计数器法就是在程序中添加一个计数器data_cnt
:
- 当写使能有效且FIFO非满时data_cnt+1;
- 当读使能有效且FIFO非空时data_cnt-1;
- 当data_cnt==0时,FIFO读空,产生空标志;
- 当data_cnt==FIFO深度时,FIFO写满,产生满标志。
3.verilog代码
了解同步FIFO的工作原理后,代码思路就有了,程序如下:
`timescale 1ns / 1ps
module fifo_syn
#(parameter FIFO_WIDTH=8,
parameter FIFO_DEPTH=16)
(
input clk ,
input rstn ,
input wr_en ,
input rd_en ,
input [FIFO_WIDTH-1:0] din ,
output empty ,
output full ,
output reg [FIFO_WIDTH-1:0] dout
);
reg [$clog2(FIFO_DEPTH)-1:0] wr_addr;
reg [$clog2(FIFO_DEPTH)-1:0] rd_addr;
reg [$clog2(FIFO_DEPTH):0] data_cnt;//由于FIFO赋值和地址自增相差1周期,需多计数1周期
reg [FIFO_WIDTH-1:0] fifo [0:FIFO_DEPTH-1];
assign empty=(data_cnt==0) ? 1'b1 : 1'b0;
assign full=(data_cnt==FIFO_DEPTH) ? 1'b1: 1'b0;
//写FIFO
always@(posedge clk or negedge rstn)begin
if(!rstn)begin
wr_addr<='b0;
end
else if(wr_en&&!full)begin
wr_addr<=wr_addr+1'b1;
fifo[wr_addr]<=din;
end
else begin
wr_addr<=wr_addr;
end
end
//读FIFO
always@(posedge clk or negedge rstn)begin
if(!rstn)begin
rd_addr<='b0;
dout<='b0;
end
else if(rd_en&&!empty)begin
rd_addr<=rd_addr+1'b1;
dout<=fifo[rd_addr];
end
else begin
rd_addr<=rd_addr;
end
end
//数据状态变化
always@(posedge clk or negedge rstn)begin
if(!rstn)begin
data_cnt<='b0;
end
else begin
case({wr_en,rd_en})
2'b10:
if(data_cnt!=FIFO_DEPTH)//空满时计数器不变
data_cnt<=data_cnt+1'b1;
2'b01:
if(data_cnt!=0)
data_cnt<=data_cnt-1'b1;
default:
data_cnt<=data_cnt;
endcase
end
end
endmodule
4.仿真分析
4.1 参考testbench文件
📄仿真中主要是产生读写使能信号和要写入的数据。为方便起见,这里我们编写几个简单的任务来产生读写使能信号,为保证数据稳定,在时钟的下降沿赋值。
{$random}%(2^FIFO_WIDTH)表示产生范围为0—2 ^ FIFO_WIDTH-1的随机数。
有关$random更详细的用法,可参考这篇文章: $random的用法详解
`timescale 1ns / 1ps
module tb();
parameter FIFO_WIDTH=8;
reg clk;
reg rstn;
reg wr_en;
reg rd_en;
reg [FIFO_WIDTH-1:0] din;
wire empty;
wire full;
wire [FIFO_WIDTH-1:0] dout;
initial begin
clk=1'b0;
rstn=1'b0;
wr_en=1'b0;
rd_en=1'b0;
din='b0;
#15;
rstn=1'b1;
repeat(20)begin
wr_only;
end
repeat(20)begin
rd_only;
end
repeat(5)begin
wr_only;
end
repeat(5)begin
wr_rd;
end
end
always #10 clk=~clk;
//只写的任务
task wr_only;
begin
@(negedge clk)begin
wr_en=1'b1;
rd_en=1'b0;
din={$random}%(2^FIFO_WIDTH);//生成0~2^FIFO_WIDTH-1的随机数
end
end
endtask
//只读的任务
task rd_only;
begin
@(negedge clk)begin
rd_en=1'b1;
wr_en=1'b0;
end
end
endtask
//读写的任务
task wr_rd;
begin
@(negedge clk)begin
rd_en=1'b1;
wr_en=1'b1;
din={$random}%(2^FIFO_WIDTH);
end
end
endtask
fifo_syn fifo_syn_u
(
. clk (clk),
. rstn (rstn),
. wr_en (wr_en),
. rd_en (rd_en),
. din (din),
. empty (empty),
. full (full),
. dout (dout)
);
endmodule
4.2 仿真结果
写FIFO:
读FIFO:
同时读写:
❤️如果觉得文章对你有所帮助的话,别忘了点个收藏和赞哦~
❤️更多优质内容可浏览本人主页👇,期待再次与你相遇!
🎉🎉🎉🎉🎉🎉小瑞同学的博客主页🎉🎉🎉🎉🎉🎉