当前位置:首页 > 新闻资讯 > FPGA之家动态 >

异步fifo_FPGA基础知识极简教程(4)从FIFO设计讲起之异步FIFO篇

时间:2025-02-10      来源:FPGA_UCY 关于我们 0

基础知识不扎实的改进方法_基础知识不扎实的家长评语_FPGA基础知识

目录 参考资料交个朋友 写在前面

一开始是想既然是极简教程,就应该只给出FIFO的概念,没想到还是给出了同步以及异步FIFO的设计,要不然总感觉内容不完整,也好,自己设计的FIFO模块不用去担心因IP核跨平台不通用的缺陷!那我们开始吧。

CSDN-专业IT技术社区-登录​ 正文 同步FIFO回顾

上一篇博客讲了同步FIFO的概念以及同步FIFO的设计问题,并给出了同步FIFO的代码以及VHDL代码,并经过了行为仿真测试,链接如下:

FPGA基础知识极简教程(3)从FIFO设计讲起之同步FIFO篇-CSDN博客​

FPGA基础知识_基础知识不扎实的家长评语_基础知识不扎实的改进方法

$clog2()系统函数使用

这里简单提一下,同步FIFO的代码中用到了一个系统函数$clog2(),这个系统函数的使用方法很简单:

parameter DATA_WIDTH = 8;
parameter DATA_DEPTH = 8;
reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];
reg [$clog2(DATA_DEPTH) - 1 : 0] wr_pointer = 0;
reg [$clog2(DATA_DEPTH) - 1 : 0] rd_pointer = 0;

例如我定义了FIFO缓冲区的深度为 = 8,那么其地址(指针)位宽是多少呢? 这时候就可以使用系统函数$clog2()了,位宽可以表示为:

$clog2(DATA_DEPTH)                  // = 3;

指针就可以定义为:

reg [$clog2(DATA_DEPTH) - 1 : 0] wr_pointer = 0;
reg [$clog2(DATA_DEPTH) - 1 : 0] rd_pointer = 0;

综合属性控制资源使用

还有一点需要提的是,我们都知道在FPGA中FIFO的实现可以使用分布式资源或者BLOCK RAM,那么如何掌控呢? 当使用FIFO缓冲空间较小时,我们选择使用 RAM;当使用FIFO缓冲空间较大时,我们选择使用BLOCK RAM资源;这是一般的选择原则。 我们可以通过在设计代码中加入约束条件来控制,之前有写过

随笔(1)综合属性之&?-CSDN博客​

FPGA基础知识_基础知识不扎实的家长评语_基础知识不扎实的改进方法

就上述同步FIFO而言,我们可以在缓冲区定义时候添加如下约束:

(*ram_style = "distributed"*) reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];
或者:
(*ram_style = "block"*) reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];

为了验证是否有用,我们在中进行验证如下:

当设计中使用BLOCK RAM约束:

(*ram_style = "block"*)reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];

综合后的电路图如下,可见FIFO缓存区使用的资源为BLOCK RAM;

FPGA基础知识_基础知识不扎实的改进方法_基础知识不扎实的家长评语

同时给出资源利用率报告:

基础知识不扎实的改进方法_基础知识不扎实的家长评语_FPGA基础知识

可见存在BLOCK RAM ,由于我仅仅综合了一个同步FIFO,因此这个Block RAM一定是FIFO缓冲区消耗的。

当使用 RAM约束时:

(*ram_style = "distributed"*)reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];

综合后电路图FIFO缓冲区部分:

FPGA基础知识_基础知识不扎实的改进方法_基础知识不扎实的家长评语

资源利用率情况;

基础知识不扎实的家长评语_FPGA基础知识_基础知识不扎实的改进方法

可见 ,并未使用BLOCK RAM,而是使用了LUT RAM,也即分布式RAM。 综上,验证了这条约束的有效性。

异步FIFO设计 FIFO用途回顾

再设计异步FIFO电路之前,有必要说明一下FIFO的用途,上篇博文提到:

FPGA或者ASIC设计内部电路多位数据在不同的时钟域交互,为了数据安全、正确、稳定交互,我们需要设计异步FIFO进行跨时钟域交互。正如之前博客所写:

漫谈时序设计(1)跨时钟域是设计出来的,而非约束出来的!-CSDN博客_跨时钟域时序约束​

FPGA基础知识_基础知识不扎实的家长评语_基础知识不扎实的改进方法

我们在时序分析时候,通常都将跨时钟域路径进行伪路径约束,因此我们必须在设计时候解决跨时钟域数据传输问题,异步FIFO在此起到关键作用。

这三条大概讲的都是一个意思,总结起来就是FIFO可以起到数据缓冲或缓存的作用,例如突然数据,我们就需要先将其缓存起来,之后再从FIFO中读出出来进行处理,这样也可以保证数据不会丢失。

引用互联网上其他说法就是:数据写入过快,并且间隔时间长,也就是突发写入。那么通过设置一定深度的FIFO,可以起到数据暂存的功能,且使得后续处理流程平滑。

异步FIFO原理回顾

无论是同步FIFO还是异步FIFO,其大致原理都是一致的,先入先出自然不必多说,关于空满的判断都是通过读写指针之间的关系来判断;还有就是异步FIFO的指针需要进行一定的处理,例如格雷码处理,这样可以减小读指针同步到写指针时钟域,或者写指针同步到读指针时钟域时出现亚稳态的概率,这是因为格雷码每次只有一位变化,这样一位数据在进行跨时钟域传输的时候亚稳态出现的概率会大大减小。同步之后便进行对比,以此来判断FIFO的空满。 那异步FIFO如何判断空满呢? 回答这个问题之前,我想先统一的说明FIFO(同步或者异步)是如何判断空满的?

FPGA基础知识_基础知识不扎实的改进方法_基础知识不扎实的家长评语

起始,读写指针都是0,FIFO一定为空;之后对FIFO进行一系列的读写操作,导致读写指针关系发生了变化,可以分为下面两种情况:

读比写要快,或者说读指针追写指针,如果追上了,也即二者再次相等,则FIFO读空;写比读快,或者说写指针弯道超越追读指针,当写指针再次绕到读指针背后并与读指针重合,也即二者相等时,FIFO写满!

在同步FIFO中,我们使用计数的方式进行判断空满,运用的也是这个原理,写一个数据时,计数值加1,读出一个数据时,计数值减1,如下图:

FPGA基础知识_基础知识不扎实的改进方法_基础知识不扎实的家长评语

我最喜欢用这幅图来分析FIFO,下面一行一行的分析:

第一行:写入1个数据,计数值为1;第二行:写入5个数据,计数值为6;第三行:读出3个数据,计数值为3;第四行:写入3个数据,计数值为6;第五行:写入2个数据,计数值为8,等于FIFO深度,则表示写满;第六行:读出6个数据,计数值为2,表示还剩下两个数据缓存在FIFO中。

如果再接着读2个 数据,则计数值为0,FIFO就被读空了。

好了,我们分析完了同步FIFO是如何判断空满的,下面重点放在异步FIFO的原理上。

我曾写过一篇CDC问题的博客,谈到了异步FIFO的设计:

谈谈跨时钟域传输问题(CDC)-CSDN博客_跨时钟域对脉冲展宽​

FPGA基础知识_基础知识不扎实的家长评语_基础知识不扎实的改进方法

这篇博客中说,同步FIFO可以使用计数方式来判断空满,但是异步FIFO不能,因为写指针和读指针根本不在同一个时钟域,计数器无法处理这样的计数。 那么怎么处理呢? 博客里采用的方法是对读写指针的位宽多添1位,这样可以在读写指针相等时,表示FIFO空,而在写指针和读指针最高位不同,而其他位相等时,也即写指针大于读指针一个FIFO深度的数值,表示FIFO满,这不就是意味着写指针绕了一圈,又追上了读指针了吗? 恰是如此,用来解决不用计数而具体判断FIFO空满的问题。

这只是解决了判断空满的一个问题,也就是确定指针的关系! 那下一个问题就是如何判断? 由于读写指针不在同一个时钟域,二者需要同步到同一个时钟域后进行判断大小。

基础知识不扎实的家长评语_基础知识不扎实的改进方法_FPGA基础知识

具体的操作就是在各自的时钟域内进行读写操作,同时:

判断是否写满时,需要将读指针转换成格雷码形式,再同步到写时钟域,与写指针比较,判断是否写满! 细心的人恐怕能否发现,这里存在的一个小插曲,当读指针转换成格雷码以及同步到写时钟域的过程中,读写指针可能还都在递增,这样的话,等同步后的读指针与写指针相等时(不包括最高位),实际的读指针可能已经变了,这样的话其实还有几个空间没有写满!但这样设计就有问题吗?没有问题!这叫保守设计,可以增加FIFO的安全性。

下面是判断是否写满的示意图:

FPGA基础知识_基础知识不扎实的改进方法_基础知识不扎实的家长评语

上面是写满判断的情况,下面给出读空判断的可能情形分析:

当判断是否读空时,需要把写指针同步到读时钟域,具体过程是先将写指针转换为格雷码,再同步到读时钟域,之后和读指针比较,如果二者相等,则空标志置位! 还是和第一种情况有同样的插曲,当写指针转换成格雷码以及同步到读时钟域的过程中,写指针和读指针都可能还在递增,这样当二者判断相等的时候,则写指针可能还多写了几个空间,实际上并没有读空。 那问题来了,这样操作就有问题了吗?同样没有问题,这样也保证来了FIFO的安全,防止被读空。

下面给出手绘示意图:

FPGA基础知识_基础知识不扎实的家长评语_基础知识不扎实的改进方法

到此,这一种设计方式的异步FIFO算是讲完了,下面就是设计的问题了。

异步FIFO设计

如果你认真分析了上述异步FIFO的实现方式,那么你会分分钟写出实现代码,我的版本如下:

`timescale 1ns / 1ps
// Engineer: Reborn Lee
// Module Name: asy_fifo
// https://blog.csdn.net/Reborn_Lee
module asy_fifo#(
	parameter DATA_WIDTH = 8,
	parameter DATA_DEPTH = 32
	)(
	input wr_clk,
	input wr_rst,
	input wr_en,
	input [DATA_WIDTH - 1 : 0] wr_data,
	output reg full,
	input rd_clk,
	input rd_rst,
	input rd_en,
	output reg [DATA_WIDTH - 1 : 0] rd_data,
	output reg empty
    );
	// define FIFO buffer 
	reg [DATA_WIDTH - 1 : 0] fifo_buffer[0 : DATA_DEPTH - 1];
	//define the write and read pointer and 
	//pay attention to the size of pointer which should be greater one to normal
	reg [$clog2(DATA_DEPTH) : 0] wr_pointer = 0, rd_pointer = 0; 
	//write data to fifo buffer and wr_pointer control
	always@(posedge wr_clk) begin
		if(wr_rst) begin
			wr_pointer <= 0;
		end
		else if(wr_en) begin
			wr_pointer <= wr_pointer + 1;
			fifo_buffer[wr_pointer] <= wr_data;
		end
	end
	//read data from fifo buffer and rd_pointer control
	always@(posedge rd_clk) begin
		if(rd_rst) begin
			rd_pointer <= 0;
		end
		else if(rd_en) begin
			rd_pointer <= rd_pointer + 1;
			rd_data <= fifo_buffer[rd_pointer];
		end
	end
	//wr_pointer and rd_pointer translate into gray code
	wire [$clog2(DATA_DEPTH) : 0] wr_ptr_g, rd_ptr_g; 
	assign wr_ptr_g = wr_pointer ^ (wr_pointer >>> 1);
	assign rd_ptr_g = rd_pointer ^ (rd_pointer >>> 1);
	//wr_pointer after gray coding synchronize into read clock region
	reg [$clog2(DATA_DEPTH) : 0] wr_ptr_gr, wr_ptr_grr, rd_ptr_gr, rd_ptr_grr; 
	always@(rd_clk) begin
		if(rd_rst) begin
			wr_ptr_gr <= 0;
			wr_ptr_grr <= 0;
		end
		else begin
			wr_ptr_gr <= wr_ptr_g;
			wr_ptr_grr <= wr_ptr_gr;
		end
	end
	//rd_pointer after gray coding synchronize into  write clock region
	always@(wr_clk) begin
		if(wr_rst) begin
			rd_ptr_gr <= 0;
			rd_ptr_grr <= 0;
		end
		else begin
			rd_ptr_gr <= rd_ptr_g;
			rd_ptr_grr <= rd_ptr_gr;
		end
	end
	// judge full or empty
	always@(posedge rd_clk) begin
		if(rd_rst) empty <= 0;
		else if(wr_ptr_grr == rd_ptr_g) begin
			empty <= 1;
		end
		else empty <= 0;
 	end
 	always@(posedge wr_clk) begin
 		if(wr_rst) full <= 0;
 		else if( (rd_ptr_grr[$clog2(DATA_DEPTH) - 1 : 0] == wr_ptr_g[$clog2(DATA_DEPTH) - 1 : 0])
 			&& ( rd_ptr_grr[$clog2(DATA_DEPTH)] != wr_ptr_g[$clog2(DATA_DEPTH)] ) ) begin
 			full <= 1;
 		end
 		else full <= 0;
 	end
endmodule

注意事项

reg [$clog2(DATA_DEPTH) : 0] wr_pointer = 0, rd_pointer = 0; 

always@(posedge rd_clk) begin
  if(rd_rst) empty <= 0;
  else if(wr_ptr_grr == rd_ptr_g) begin
   empty <= 1;
  end
  else empty <= 0;
  end

一定要二者相等的下一个读周期empty信号为1;

always@(posedge wr_clk) begin
   if(wr_rst) full <= 0;
   else if( (rd_ptr_grr[$clog2(DATA_DEPTH) - 1 : 0] == wr_ptr_g[$clog2(DATA_DEPTH) - 1 : 0])
    && ( rd_ptr_grr[$clog2(DATA_DEPTH)] != wr_ptr_g[$clog2(DATA_DEPTH)] ) ) begin
    full <= 1;
   end
   else full <= 0;
  end

//wr_pointer and rd_pointer translate into gray code
 wire [$clog2(DATA_DEPTH) : 0] wr_ptr_g, rd_ptr_g; 
 assign wr_ptr_g = wr_pointer ^ (wr_pointer >>> 1);
 assign rd_ptr_g = rd_pointer ^ (rd_pointer >>> 1);

当然你用时序逻辑也可以哦。

异步FIFO仿真

我们对上述设计进行行为仿真,先给出我的测试文件:

`timescale 1ns/1ps
module asy_fifo_tb;
	parameter DATA_WIDTH = 8;
	parameter DATA_DEPTH = 16;
	reg wr_clk;
	reg wr_rst;
	reg wr_en;
	reg [DATA_WIDTH - 1 : 0] wr_data;
	wire full;
	reg rd_clk;
	reg rd_rst;
	reg rd_en;
	wire [DATA_WIDTH - 1 : 0] rd_data;
	wire empty;
	initial begin
		wr_clk = 0;
		forever begin
			#5 wr_clk = ~wr_clk;
		end
	end
	initial begin
		rd_clk = 0;
		forever begin
			#10 rd_clk = ~rd_clk;
		end
	end
	initial begin
		wr_rst = 1;
		rd_rst = 1;
		wr_en = 0;
		rd_en = 0;
		#30 
		wr_rst = 0;
		rd_rst = 0;
		//write data into fifo buffer
		@(negedge wr_clk) 
		wr_data = $random;
		wr_en = 1;
		repeat(7) begin
			@(negedge wr_clk) 
			wr_data = $random; // write into fifo 8 datas in all;
		end
		// read parts
		@(negedge wr_clk) 
		wr_en = 0;
		@(negedge rd_clk) 
		rd_en = 1;
		repeat(7) begin
			@(negedge rd_clk);  // read empty 
		end 
		@(negedge rd_clk)
		rd_en = 0;
		//write full
		# 150
		@(negedge wr_clk)
		wr_en = 1;
		wr_data = $random;
		repeat(15) begin
		@(negedge wr_clk)
			wr_data = $random;
		end
		@(negedge wr_clk)
		wr_en = 0;
		#50 $finish;
	end
	asy_fifo #(
			.DATA_WIDTH(DATA_WIDTH),
			.DATA_DEPTH(DATA_DEPTH)
		) inst_asy_fifo (
			.wr_clk  (wr_clk),
			.wr_rst  (wr_rst),
			.wr_en   (wr_en),
			.wr_data (wr_data),
			.full    (full),
			.rd_clk  (rd_clk),
			.rd_rst  (rd_rst),
			.rd_en   (rd_en),
			.rd_data (rd_data),
			.empty   (empty)
		);
endmodule

仿真波形为:

基础知识不扎实的改进方法_基础知识不扎实的家长评语_FPGA基础知识

基础知识不扎实的改进方法_基础知识不扎实的家长评语_FPGA基础知识

仿真通过,且功能符合预期。

由于本博客写的时候有点长,几乎一天了,所以就到这里吧!不得不说的是,异步FIFO的实现方式肯定不只有这一种,还有很多其他实现方式,各位可以自行尝试。 后面如果有更多有关FIFO的有趣知识或者心得体会,我会继续补充!

参考资料谈谈跨时钟域传输问题(CDC)-CSDN博客_跨时钟域对脉冲展宽​

FPGA基础知识_基础知识不扎实的家长评语_基础知识不扎实的改进方法

調試成功的簡單異步FIFO--實現+ - IT閱讀​ 参考资料3​异步FIFO 代码注释分析​

基础知识不扎实的改进方法_FPGA基础知识_基础知识不扎实的家长评语

参考资料5​ 参考资料6​ the basic FIFO right​ 参考资料8​ 交个朋友 FPGA/IC技术交流-CSDN博客​

FPGA基础知识_基础知识不扎实的家长评语_基础知识不扎实的改进方法


注明:本内容来源网络,不用于商业使用,禁止转载,如有侵权,请来信到邮箱:429562386ⓐqq.com 或联系本站客服处理,感谢配合!

用户登陆

    未注册用户登录后会自动为您创建账号

提交留言