Verilog FIFO设计-从同步开始说起
0 写在前面
FIFO可根据读写时钟是否为同一时钟域可分为同步FIFO和异步FIFO,本文主要介绍同步FIFO,异步FIFO将在下篇介绍
1 什么是FIFO
FIFO全称 First In First Out
,即先进先出。
FIFO主要用于以为下几个方面:
- 跨时钟域数据传输
- 将数据发送到芯片外之前进行缓冲,如发送到DRAM或SRAM
- 存储数据以备后用
FIFO是异步数据传输时常用的存储器,多bit数据异步传输时,无论是从快时钟域到慢时钟域,还是从慢时钟域到快时钟域,都可以使用FIFO处理。
2 重要参数
FIFO中重要的参数有深度、宽度、空标志、满标志、读时钟、读时针、写时钟和写时针
我看到过一个很形象的比喻:
把FIFO比作汽车进入一个单向行驶的隧道,隧道两端都有一个门进行控制,
FIFO宽度
就是这个隧道单向有几个车道,FIFO的深度
就是一个车道能容纳多少辆车,当隧道内停满车辆时,这就是FIFO的满标志
,当隧道内没有一辆车时,这便是空标志
读时钟:读操作所遵循的时钟,时钟沿到来时读取数据
写时钟:写操作所遵循的时钟,时钟沿到来时写入数据
读指针:指向下一个要读出的地址,读完自动加1
写指针:指向下一个要写入地址,写完自动加1
下面从FIFO接口开始说起,下图适用于任何FIFO的基本接口框图
FIFO可分为读数据一端和写数据一端
wr_en
和rd_en
分别为写/读使能端,就像上面隧道的例子,两个使能端就好像是两边的门,只有门打开的时候才允许车辆进出wr_data
和rd_data
分别是要写入FIFO的数据和要从FIFO中读取的数据fifo_full
和fifo_empty
分别为FIFO的满/空标志位
3 FIFO设计的重要原则
- 任何FIFO都不要向满FIFO中写入数据(写溢出)
- 任何FIFO都不要从空FIFO中读取数据(读溢出)
FIFO设计的核心便是空满的判断,如何判断FIFO是否写满(或读空),这里我们可以利用地址指针,如下图:
每写入一次数据,写地址指针会加1,每读取一次数据,读地址指针会加1
就像上图所示,当读地址指针追上写地址指针,FIFO便是读空状态
同理,当写地址指针再次追上读地址指针,FIFO便是写满状态,就像下图
4 同步FIFO设计
先直接给出Verilog代码
module syn_fifo(clk, rstn, wr_en, rd_en, wr_data, rd_data, fifo_full, fifo_empty);
//参数定义
parameter width = 8;
parameter depth = 8;
parameter addr = 3;
//输入信号
input clk; //时钟信号
input rstn; //下降沿复位
input wr_en; //写入使能
input rd_en; //读取使能
//数据信号
input [width - 1 : 0] wr_data; //写数据
output [width - 1 : 0] rd_data; //读数据
reg [width - 1 : 0] rd_data;
//空满判断信号
output fifo_full;
output fifo_empty;
//定义一个计数器,用于判断空满
reg [$clog2(depth): 0] cnt;
//定义读写地址
reg [depth - 1 : 0] wr_ptr;
reg [depth - 1 : 0] rd_ptr;
//定义一个宽度为为width,深度为depth的fifo
reg [width - 1 : 0] fifo [depth - 1 : 0];
//写地址操作
always @ (posedge clk or negedge rstn) begin
if(!rstn)
wr_ptr <= 0;
else if(wr_en && !fifo_full) //写使能,且fifo未写满
wr_ptr <= wr_ptr + 1;
else
wr_ptr <= wr_ptr;
end
//读地址操作
always @ (posedge clk or negedge rstn) begin
if(!rstn)
rd_ptr <= 0;
else if(rd_en && !fifo_empty) //读使能,且fifo不为空
rd_ptr <= rd_ptr + 1;
else
rd_ptr <= rd_ptr;
end
//写数据
integer i;
always @ (posedge clk or negedge rstn) begin
if(!rstn) begin //复位清空fifo
for(i = 0; i < depth; i = i + 1)
fifo[i] <= 0;
end
else if(wr_en) //写使能时将数据写入fifo
fifo[wr_ptr] <= wr_data;
else //否则保持
fifo[wr_ptr] <= fifo[wr_ptr];
end
//读数据
always @ (posedge clk or negedge rstn) begin
if(!rstn)
rd_data <= 0;
else if (rd_en)
rd_data <= fifo[rd_ptr]; //从fifo中读取数据
else
rd_data <= rd_data;
end
//辅助计数,用于判断空满
always @ (posedge clk or negedge rstn) begin
if(!rstn)
cnt <= 0;
else if (wr_en && !rd_en && !fifo_full) //有效的只写入
cnt <= cnt + 1;
else if (!wr_en && rd_en && !fifo_empty) //有效的只读取
cnt <= cnt - 1;
else
cnt <= cnt;
end
//空满判断
assign fifo_full = (cnt == depth)? 1 : 0;
assign fifo_empty = (cnt == 0) ? 1 : 0;
endmodule
下面是tb测试文件
module syn_fifo_tb;
reg clk, rstn;
reg wr_en, rd_en;
wire fifo_full, fifo_empty;
reg [7 : 0] wr_data;
wire [7 : 0] rd_data;
//生成波形
initial begin
$fsdbDumpfile("wave.fsdb");
$fsdbDumpvars(0, myfifo);
$fsdbDumpon();
end
//例化
syn_fifo myfifo(
.clk(clk),
.rstn(rstn),
.wr_en(wr_en),
.rd_en(rd_en),
.fifo_full(fifo_full),
.fifo_empty(fifo_empty),
.wr_data(wr_data),
.rd_data(rd_data)
);
initial begin
rstn = 1;
wr_en = 0;
rd_en = 0;
repeat(2) @(negedge clk);
rstn = 0;
@(negedge clk);
rstn = 1;
@(negedge clk);
wr_data = {$random}%60;
wr_en = 1;
repeat(2) @ (negedge clk);
wr_data = {$random}%60;
@(negedge clk);
wr_en = 0;
rd_en = 1;
repeat(4) @ (negedge clk);
rd_en = 0;
wr_en = 1;
wr_data = {$random}%60;
repeat(5) @ (negedge clk);
wr_data = {$random}%60;
repeat(2) @ (negedge clk);
wr_en = 0;
rd_en = 1;
repeat(2) @ (negedge clk);
rd_en = 0;
wr_en = 1;
wr_data = {$random}%60;
repeat(3) @ (negedge clk);
wr_en = 0;
#50 $finish;
end
initial begin
clk = 0;
forever #5 clk = ~clk;
end
endmodule
下面是仿真波形
下面开始逐段分析
在初始阶段,我们并未给出wr_data
的数据,也并未向FIFO中写入任何数据,所以此时FIFO一直是空的(fifo_empty
拉高)
随后,我们将wr_data
赋值为8,并让wr_en
有效,在时钟上升沿到来时,开始写入数据,此时fifo_empty
被拉低,因为有数据被写入
数据8被写入两次后,将wr_data
更改为39,且数据39仅被写入一次
拉低wr_en
,拉高rd_en
开始读取数据,此处可以看出,读取数据时,先读取出两个8,随后读取一个39,随后fifo_empty
被拉高,因为此时FIFO中已经没有数据可供读取
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 百万级群聊的设计实践
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程
· .NET 10 首个预览版发布,跨平台开发与性能全面提升
· 《HelloGitHub》第 107 期