异步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位的改变,可能导致格雷码失去原来的意义。

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

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

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • redis过期key的删除策略[通俗易懂]

    前言在使用redis的过程中,不免会产生过期的key,而这些key过期后并不会实时地马上被删除,当这些key数量累积越来越多,就会占用很多内存,因此在redis底层同时使用了三种策略来删除这些key。第一种策略:被动删除当读/写一个key时,redis首先会检查这个key是否存在,如果存在且已过期,则直接删除这个key并返回nil给客户端。第二种策略:定期删除redis中有一系列的定期任务(serverCron),这些任务每隔一段时间就会运行一次,其中就包含清理过期key的任务,运行频率由配置文件

  • ibm x201 怎么清理内部_维修小技巧 篇五:ThinkPadX201i拆机换散热风扇清灰教程

    ibm x201 怎么清理内部_维修小技巧 篇五:ThinkPadX201i拆机换散热风扇清灰教程维修小技巧篇五:ThinkPadX201i拆机换散热风扇清灰教程2020-11-2521:45:4416点赞14收藏42评论创作立场声明:拆装有风险、拆装有风险、拆装有风险,欢迎收藏,点赞,打赏,关注。(๑ºั╰╯ºั๑)大家好我是况天佑,我们又见面啦。家中使用许久的ThinkPadX201i开机后会自动关机,参考度娘的教程大几率散热风扇问题,散热风扇不正常导致过热而自动关机,速度拼多多下单…

  • windows10系统下vue开发环境搭建

    windows10系统下vue开发环境搭建安装NodeJs下载地址:http://nodejs.cn/download/到官网下载自己系统对应的版本,按照推荐的方式默认安装,这里不再赘述。安装完成后,打卡powershell,执行命令node-v查询一下,检查是否正常安装。如果提示找不到node命令,添加node安装路径到系统环境变量,重启powershell,再试。如果你安装的是旧版本的npm,可以很容易得通过npm命令来升级。sudonpminstallnpm-g#linuxnpminstallnpm-g

    2022年10月20日
  • 支持向量回归(多核函数)「建议收藏」

    支持向量回归(多核函数)「建议收藏」支持向量机之支持向量回归,SVR

  • pycharm 打不开了_pycharm激活成功教程之后打不开

    pycharm 打不开了_pycharm激活成功教程之后打不开pycharm打不开问题总结1:第一步:进入如下路径,找到cmd.exe,右键选择“以管理员身份运行”;第二步:在打开的cmd窗口中,输入netshwinsockreset,按回车键;第三步:重启电脑;第四步:重启后,双击pycharm图标就能打开了!2:你下载的应该是官方版的,然后自己加了网上下载的.jar激活成功教程插件,并添加了这个插件的路径到.vmoptions文件。打不开是因为你修改…

  • python jieba库_Python jieba库的使用说明「建议收藏」

    python jieba库_Python jieba库的使用说明「建议收藏」1、jieba库基本介绍(1)、jieba库概述jieba是优秀的中文分词第三方库-中文文本需要通过分词获得单个的词语-jieba是优秀的中文分词第三方库,需要额外安装-jieba库提供三种分词模式,最简单只需掌握一个函数(2)、jieba分词的原理Jieba分词依靠中文词库-利用一个中文词库,确定汉字之间的关联概率-汉字间概率大的组成词组,形成分词结果-除了分词,用户还可以添加自定义…

发表回复

您的电子邮箱地址不会被公开。

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