异步fifo的10个测试关注点_异步FIFO

异步fifo的10个测试关注点_异步FIFO1、异步FIFO简介及其原理FIFO是英文FirstInFirstOut的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据。异步FIFO是指读写时钟不一致,读写时钟是互相独立的。1.1用途用途1:  跨时钟域:异步FIFO读写分别采用相互异步的不同时钟。在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步FIFO是这个问题的一

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

1、异步FIFO简介及其原理

FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据。

异步FIFO 是指读写时钟不一致,读写时钟是互相独立的。

1.1 用途

用途1:
  跨时钟域:异步FIFO读写分别采用相互异步的不同时钟。在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步FIFO是这个问题的一种简便、快捷的解决方案,使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。

用途2:
  位宽变换:对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。

1.2 结构

在这里插入图片描述
由图可见,异步FIFO的核心部件就是一个 Simple Dual Port RAM ;左右两边的长条矩形是地址控制器,负责控制地址自增、将二进制地址转为格雷码以及解格雷码;下面的两对D触发器 sync_r2w 和 sync_w2r 是同步器,负责将写地址同步至读时钟域、将读地址同步至写时钟域。

FIFO的常见参数

FIFO的宽度:即FIFO一次读写操作的数据位;
FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。

2、 FIFO的“空”/“满”检测

FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。
此处切记判断读空和写满时指针的比较方式时不同的,大华面试时就问到了这个问题,一下把我干糊涂了,直接面试凉了。

  • 如上图所示的同步模块synchronize to write clk,其作用是把读时钟域的读指针rd_ptr采集到写时钟(wr_clk)域,然后和写指针wr_ptr进行比较从而产生或撤消写满标志位wr_full;

  • 同步模块synchronize to read clk的作用是把写时钟域的写指针wr_ptr采集到读时钟域,然后和读指针rd_ptr进行比较从而产生或撤消读空标志位rd_empty。

2.1、读写指针工作原理

读指针:总是指向下一个将要被写入的单元,复位时,指向第一个单元(编号为0)。
写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)

FIFO空标志位:

(1)系统复位,读写指针全部清零,读写指针相等时;
(2)数据读出速率大于写入速率,读指针赶上了写指针,FIFO为空,如图所示:
在这里插入图片描述

FIFO 写满标志位的产生

(1)读写指针指向了同一地址,但写指针超前整整一圈,FIFO被写满;
在这里插入图片描述

2.2、 空满区分

为了区分到底是满状态还是空状态,可以采用以下方法:

在指针中添加一个额外的位,当写指针增加并越过最后一个FIFO地址时,就将未用的最高位(MSB)加1,其他位回0.对读指针也进行同样的操作。

此时,对于深度为2^n的FIFO,需要的读写指针位宽为(n+1)位。如对于深度为8的FIFO,需要采用4bit的计数器,0000~1000、1001~1111,MSB作为折回标志位,而低3位作为地址指针。

如果两个指针的MSB不同,其他位相同,说明写指针比读指针多折回了一次;如r_addr=0000,而w_addr = 1000,为满。如果两个指针的MSB相同,其余位相等,则说明两个指针折回的次数相等,说明FIFO为空;

2.3、 二进制FIFO指针的考虑

将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。

(1)二进制如何转化为格雷码

二进制数的最高位保持不变 后续位依次与前一位进行异或运算

assign  gray_code = (bin_code>>1)  ^  bin_code;

在这里插入图片描述

(2) 使用gray码进行对比,如何判断“空”与“满”

使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。

对于“空”的判断依然依据二者完全相等(包括MSB);

对于“满”的判断,如下图,由于gray码除了MSB外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:

(1)wptr和同步过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
(2)wptr与rptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置。
(3)剩下的其余位完全相等。

这里直接给出结论:

  • 判断读空时将写时钟域的写指针同步到读时钟,然后与读时钟域的读指针进行比较,每一位都完全相同才判断为读空;

  • 判断写满时:需要 写时钟域的格雷码wgray_next 和 被同步到写时钟域的读指针wr2_rp 高两位不相同,其余各位完全相同;

1 assign full = (wr_addr_gray == { 
   ~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同 
2 assign empty = ( rd_addr_gray == wr_addr_gray_d2 );

在这里插入图片描述

3、代码:

代码思想

二进制指针转换为格雷码,跨时钟后进行空满判断。读空是写时钟域下的写指针同步到读时钟域下与读指针进行比较,完全一样即为读空。写满是读时钟域的读指针同步到写时钟域与写指针比较,高两位不同低位相同即为写满。

//异步FIFO代码,关键是判断空满信号
module asyn_fifo
	#(
		parameter data_width = 16,
		parameter data_depth = 8,
		parameter ram_depth  = 256
	)
	(
		input 							rst_n,
		
		//写的四个接口 
		input 							wr_clk,
		input 							wr_en,
		input 		[data_width-1:0]	data_in,
		output							full,
		
		//读的四个接口 
		input 							rd_clk,
		input 							rd_en,
		output	reg [data_width-1:0]	data_out,
		output	 						empty
);

	reg 	[data_depth-1:0]	wr_adr;
	reg 	[data_depth-1:0]	rd_adr;
			
	reg 	[data_depth:0]		wr_adr_ptr;
	reg 	[data_depth:0]		rd_adr_ptr;
				
	wire 	[data_depth:0]		wr_adr_gray;
	reg 	[data_depth:0]		wr_adr_gray1;
	reg 	[data_depth:0]		wr_adr_gray2;
	wire 	[data_depth:0]		rd_adr_gray;
	reg 	[data_depth:0]		rd_adr_gray1;
	reg 	[data_depth:0]		rd_adr_gray2;
	
	//dual port ram - write and read,定义读写地址
	assign wr_adr = wr_adr_ptr[data_depth-1:0];
	assign rd_adr = rd_adr_ptr[data_depth-1:0];
	
	//定义存储空间FIFO
	integer i;
	reg [data_width-1:0] ram_fifo [ram_depth-1:0];
	
	always@(posedge wr_clk or negedge rst_n)begin
		if(!rst_n)begin
			for(i=0;i<ram_depth;i=i+1)
				ram_fifo[i] <= 'd0;
		end
		else if(wr_en && (~full))
			ram_fifo[wr_adr] <= data_in;
		else
			ram_fifo[wr_adr] <= ram_fifo[wr_adr];
	end
	
	always@(posedge rd_clk or negedge rst_n)begin
		if(!rst_n)
			data_out <= 'd0;
		else if(rd_en && (~empty))
			data_out <= ram_fifo[rd_adr];
		else
			data_out <= 'd0;
	end
	
	
	//wr_adr_ptr ++ and rd_adr_ptr ++
	always@(posedge wr_clk or negedge rst_n)begin
		if(!rst_n)
			wr_adr_ptr <= 'd0;
		else if(wr_en && (~full))
			wr_adr_ptr <= wr_adr_ptr + 1'b1;
		else
			wr_adr_ptr <= wr_adr_ptr;
	end
	
	always@(posedge rd_clk or negedge rst_n)begin
		if(!rst_n)
			rd_adr_ptr <= 'd0;
		else if(rd_en && (~empty))
			rd_adr_ptr <= rd_adr_ptr + 1'b1;
		else
			rd_adr_ptr <= rd_adr_ptr;
	end
	
	
	//binary to gray 二进制转格雷码
	assign wr_adr_gray = (wr_adr_ptr >> 1) ^ wr_adr_ptr;
	assign rd_adr_gray = (rd_adr_ptr >> 1) ^ rd_adr_ptr;
	
	
	//gray cdc compare 格雷码跨时钟比较指针-------------------------------重点
	always@(posedge wr_clk or negedge rst_n)begin
		if(!rst_n)begin
			rd_adr_gray1 <= 'd0;
			rd_adr_gray2 <= 'd0;
		end
		else begin
			rd_adr_gray1 <= rd_adr_gray;
			rd_adr_gray2 <= rd_adr_gray1;
		end
	end
	
	always@(posedge rd_clk or negedge rst_n)begin
		if(!rst_n)begin
			wr_adr_gray1 <= 'd0;
			wr_adr_gray2 <= 'd0;
		end
		else begin
			wr_adr_gray1 <= wr_adr_gray;
			wr_adr_gray2 <= wr_adr_gray1;
		end
	end
	
	assign empty = (rd_adr_gray == wr_adr_gray2)?1'b1:1'b0;
	assign full  = (wr_adr_gray[data_depth:data_depth-1] = (~(rd_adr_gray2[data_depth:data_depth-1]))) && (wr_adr_gray[data_depth-2:0] == rd_adr_gray2[data_depth-2:0]);
	

endmodule

仿真测试代码

`timescale 1ns / 1ps

module asyn_fifo_tb;
    
    reg 						      rst_n;
								
	reg 						      wr_clk;
	reg 						      wr_en;
	reg 	      [15:0]	          data_in;
	wire						      full;
				
	reg 						      rd_clk;
	reg 						      rd_en;
	wire	      [15:0]	          data_out;
	wire	 					      empty;

    asyn_fifo asyn_fifo_inst
	(
		  .rst_n      (rst_n),
								
		  .wr_clk     (wr_clk),
		  .wr_en      (wr_en),
		  .data_in    (data_in),
		  .full       (full),
				
		  .rd_clk     (rd_clk),
		  .rd_en      (rd_en),
		  .data_out   (data_out),
		  .empty      (empty)
);
    
    initial wr_clk = 0;
    always#10 wr_clk = ~wr_clk;
    
    initial rd_clk = 0;
    always#30 rd_clk = ~rd_clk;
    
    always@(posedge wr_clk or negedge rst_n)begin
        if(!rst_n)
            data_in <= 'd0;
        else if(wr_en)
            data_in <= data_in + 1'b1;
        else
            data_in <= data_in;
    end
    
    initial begin
        rst_n = 0;
        wr_en = 0;
        rd_en = 0;
        #200;
        rst_n = 1;
        wr_en = 1;
        #20000;
        wr_en = 0;
        rd_en = 1;
        #20000;
        rd_en = 0;
        $stop; 
    end
    
endmodule


在这里插入图片描述

可见读数据对写数据实现了多比特跨时钟域处理,这是异步FIFO的一个常用用途。

4、重要补充

关于异步FIFO的关键技术,有两个,一个是格雷码减小亚稳态,另一个是指针信号跨异步时钟域的传递。

我在自己写异步FIFO的时候也很疑惑,地址指针在同步化的时候,肯定会产生至少两个周期的延迟,如果是从快时钟域到慢时钟域,快时域的地址指针并不能都被慢时域的时钟捕获,同步后的指针比起实际的指针延迟会更大。如果以此来产生fifo_empty和fifo_full 信号会非常不准器。

查找资料和仿真后发现,数字电路的世界真的很神奇,还有很多的东西需要去学习。非常巧妙,FIFO中的一个潜在的条件是write_ptr总是大于或者等于read_ptr;分为两种情况,写快读慢和写慢读快。

  • 1.在写时钟大于读时钟时,产生fifo_empty信号,需要将write_ptr同步到读时钟域,写指针会有延时,可能比实际的写地址要小,如果不满足fifo_empty的产生条件,没问题。如果满足fifo_empty的触发条件,说明此时同步后的write_ptr = read_ptr,即实际的write_ptr >= read_ptr,最坏的情况就是write_ptr > read_ptr,像这种FIFO非空而产生空标志信号的情况称为“虚空”,但是也并不影响FIFO的功能。

  • 2.在写时钟大于读时钟时,产生fifo_full信号,需要将read_ptr同步到写时钟域,读指针会有延时,可能比实际的读地址要小,如果不满足fifo_full的产生条件,没问题。如果满足fifo_full的触发条件,说明此时同步后的read_ptr == write_ptr – fifo_depth,即实际的read_ptr >= read_ptr – fifo_depth,最坏的情况就是read_ptr > read_ptr – fifo_depth,像这种FIFO非满而产生满标志信号的情况称为“虚满”,但是也并不影响FIFO的功能。
    写慢读快的情况也同上,并没有大的差异,不再分析。

关于格雷码减小亚稳态,如果读写时钟差距过大,从快时钟域同步到慢时钟域的信号,时钟捕获的相邻两个数据变化并不是只有一个bit位的改变,可能导致格雷码失去原来的意义。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/170188.html原文链接:https://javaforall.net

(0)
上一篇 2022年8月13日 下午4:00
下一篇 2022年8月13日 下午4:00


相关推荐

  • SpringBoot 微信第三方登陆详解

    SpringBoot 微信第三方登陆详解第一步这篇文章只讲三方登陆主要实现步骤 不讲理论这是详细理论拿到微信拉取二维码的接口地址建一个网页设一个跳转 a 标签接口地址 https open weixin com connect qrconnect appid APPID amp redirect uri REDIRECT URI amp response type code amp scope SCOPE amp state STATE wechat redirect 开发平台创建完成后 获取到 appid 和 appsecre

    2026年3月19日
    2
  • 【大模型入门必看】AI大模型知识点大梳理:一篇文章带你全面掌握核心概念与技术!

    【大模型入门必看】AI大模型知识点大梳理:一篇文章带你全面掌握核心概念与技术!

    2026年3月12日
    3
  • vue中v-solt插槽的使用

    vue中v-solt插槽的使用具名插槽 slot 与 v solt 插槽使用插槽分成两步 第一步在组件元素内 为其它元素设置插槽名称 通过 slot 属性设置 第二步在组件模板中 通过 slot 组件 使用这些元素 通过 name 属性指令插槽名称如果没有设置 name 属性 默认会使用剩余的元素 使用插槽的时候 默认会引入 slot 属性所在的元素 不想引入该元素 我们可以使用 template 模板元素 使用 template 模板元素的时候 在新版本中 建议用 v slot 指令代替 slot 属性此时为插槽定义名称的语法是冒号语

    2026年3月18日
    1
  • HtmlAgilityPack 总结(一)

    HtmlAgilityPack 总结(一)一个解析html的C#类库HtmlAgilityPack,今天终于有时间整理一下,并把Demo分享一下。HtmlAgilityPack是一个基于.Net的、第三方免费开源的微型类库,主要用于在服务器端解析html文档(在B/S结构的程序中客户端可以用Javascript、jquery解析html)。截止到本文发表时,HtmlAgilityPack的最新版本为1.4.6。下载地址:ht

    2022年7月15日
    14
  • 查看linux内核版本号的方法_查看系统型号命令

    查看linux内核版本号的方法_查看系统型号命令小编给大家分享一下关于查看Linux系统版本、内核版本、查看Linux的IP地址以及Terminal终端最常用到的几个命令符:一、查看Linux系统版本(cat/etc/issue或者lsb_release-a)二、查看Linux内核版本命令(cat/proc/version或者uname-a)三、查看Linux的IP地址:四、常用命令:1、tab键命令补全2、cle…

    2022年10月10日
    4
  • 【组网】NAT类型为Udpblocked的解决方法

    【组网】NAT类型为Udpblocked的解决方法气死我了前段时间测了下NAT类型,发现是Udpblocked;从路由器检查到网关,发现电脑直连网关拨号也是Udpblocked;折磨了好几天,百思不得其解,但是用网好像也没什么异常,反倒是反复设置桥接成功把vlan搞乱了;今天临时试了下在公司测了下NAT类型,好家伙公司也是受阻;最后发现原来是测试工具自带的地址已经挂了。换个地址就好了也就是说我家里其实可能一直啥事没有,我一直在跟空气斗智斗勇有一说一默认的地址用了好多年了,怎么突然就歇逼了,百思不得其解…

    2025年5月27日
    8

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号