实现异步的几种方式_异步怎么实现

实现异步的几种方式_异步怎么实现FIFO根据输入输出时钟是否一致,分为同步FIFO与异步FIFO。本文以异步FIFO与同步FIFO的异同入手,在比较过程中逐步对异步FIFO进行分析,介绍异步FIFO相比于同步FIFO的额外处理,最终实现异步FIFO,并进行了仿真、调试、以及验证

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

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

FIFO根据输入输出时钟是否一致,分为同步FIFO与异步FIFO。同步FIFO中,读写控制信号以及数据均处于同一时钟域,满足STA分析时一般不会出现亚稳态等不稳定情形;而对于异步FIFO,读写相关信号处于不同时钟域,信号的不同步可能会导致亚稳态,导致FIFO工作异常,设计较为复杂;在之前的记录中,我们对同步FIFO的设计进行了分析:

Verilog实现FIFO专题(3-同步FIFO设计)

此处我们不再对同步FIFO进行介绍而直接以异步FIFO与同步FIFO的异同为线索,逐步对异步FIFO进行分析,介绍异步FIFO相比于同步FIFO的额外处理,并进一步实现异步FIFO。

目录

一、异步FIFO与同步FIFO工作流程比较

1、同步FIFO

2、异步FIFO

二、异步FIFO的空满检测

1、同步FIFO的空满检测

2、异步FIFO的空满检测

计数检测空满:

指针比较检测空满:

扩展指针比较检测空满:

格雷码指针比较检测空满:

三、异步FIFO的同步处理

1、同步方式

2、延迟对FIFO设计的影响

结论:

FIFO满检测:

FIFO空检测:

四、异步FIFO设计

1、端口设计

外部端口

内部信号

2、功能描述

3、实现代码

4、仿真验证

五、参考文献


一、异步FIFO与同步FIFO工作流程比较

1、同步FIFO

同步FIFO的读写控制信号以及数据均处于同一时钟域,即:FIFO在同一时钟驱动下进行读写操作,读控制信号有效且FIFO不为空时,输出读指针对应地址的数据,随后读指针加1;写控制信号有效且FIFO不为满时,将输入数据存储到写指针对应地址处,随后写指针加1;

2、异步FIFO

异步FIFO的工作内容与同步FIFO类似:FIFO在时钟驱动下进行读写操作,读控制信号有效且FIFO不为空时,输出读指针对应地址的数据,随后读指针加1;写控制信号有效且FIFO不为满时,将输入数据存储到写指针对应地址处,随后写指针加1;

但是异步FIFO的控制并不像同步FIFO那么简单,因为异步FIFO工作在不同的时钟域,这就带来了一些问题:

(1)如何进行空满检测?还能像同步FIFO中通过计数值来判断吗?

(2)需要同步电路

二、异步FIFO的空满检测

1、同步FIFO的空满检测

同步FIFO的空满检测可以通过计数很简单的实现:

读写逻辑是同一个时钟,因此可以在每次时钟来临时进行判断,如果不执行读写操作/同时读写,则计数值不变;只执行读操作,计数值减1;只执行写操作,计数值加1;

如果计数值为0,则说明FIFO空,只能写不能读(直到写入一次数据,计数值加1,FIFO不再为空才能执行读操作);

如果计数值为FIFO深度,则说明FIFO满,只能读不能写(直到读出一次数据,计数值减1,FIFO不再为满才能执行写操作);

2、异步FIFO的空满检测

计数检测空满:

异步FIFO不能采用同步FIFO这种计数方式来实现空满检测,因为用两个时钟去控制同一个计数器的加剪很明显是不可取的。

如果不明白为什么不能用两个时钟控制同一个计数器,可以查阅:Verilog中always@()语句双边沿触发(语法与综合的差异)

指针比较检测空满:

读写指针指向读写操作面向的FIFO地址空间,因此空满检测的另一个思路是比较读写指针。每次写操作执行,写指针加1;而每次读操作执行,读指针加1,因此:

FIFO空发生在:读指针追上写指针时;

FIFO满发生在:写指针追上读指针时;

但是这种处理仍存在一个问题,就是读写指针相等时,难以区分FIFO是空还是满。

扩展指针比较检测空满:

如上分析,直接比较读写指针时存在一个特例:读写指针相等时,难以区分FIFO是空还是满。

因此,有人提出,将指针进行高位扩展。即指针加宽一位,当写指针超出FIFO深度时,这额外的一位就会改变。FIFO的深度决定了指针扩展前(即除了最高位的其余位)的宽度,而这扩展的一位与FIFO深度无关,是为了标志指针多转了一圈,因此:

当读写指针完全相同时,FIFO空;

当读写指针高位不同,其余位完全相同时,FIFO满;

经过指针扩展,可以明确的进行空满检测。但是这种处理仍然不够,因为异步FIFO读写时钟相互独立,分属不同时钟域,相邻二进制地址位变化时,不止一个地址位都要变化,这样指针在进行同步过程中很容易出错,比如写指针在从0111到1000跳变时4位同时改变,这样读时钟在进行写指针同步后得到的写指针可能是0000-1111的某个值,一共有2^4个可能的情况,而这些都是不可控制的,你并不能确定会出现哪个值,那出错的概率非常大。

格雷码指针比较检测空满:

如上分析,直接比较扩展读写指针时可能因多位改变导致错误。因此,进一步采用gray码形式的指针,利用格雷码每次只变化一位的特性,降低同步发生时错误的概率。如图,为一个深度为8FIFO的格雷码指针(绿色框中):

实现异步的几种方式_异步怎么实现

0-7为真实的FIFO地址,而8-15是指针多转一圈以后的地址(8-0,9-1…)。应注意,此时指针按照格雷码方式进行编码,不能再用二级制指针的比较方式来判断空满。比如:位置6(0101)和位置9(1101),除最高位外其余位均相等。但是很明显,位置9实际对应位置1处的存储空间。

因此,格雷码指针下的空满检测条件为:

当最高位和次高位均相同,其余位相同:FIFO空

当最高位和次高位均相反,其余位相同:FIFO满

因此,最终的空满检测方式为:将二进制指针转换为格雷码,用于另一时钟域接收,随后按照检测条件进行检测。

二进制指针转换为格雷码的详情见:Verilog实现二进制码与格雷码转换  此处不再展开。

三、异步FIFO的同步处理

1、同步方式

判断FIFO空满状态时,需要在读FIFO时获取写时钟域的写指针,与读指针比较来判断FIFO是否为空;需要在写FIFO时获取读时钟域的读指针,与写指针比较来判断FIFO是否为满;

也就是说,判断空满状态时牵扯到跨时钟域问题,需要进行同步;

采用两级寄存器打两拍的方式进行同步,具体实现见:亚稳态专题

2、延迟对FIFO设计的影响

异步FIFO通过比较读写指针进行满空判断,但是读写指针属于不同的时钟域,所以在比较之前需要先将读写指针进行同步处理,将读写指针同步后再进行比较,判断FIFO空满状态。但是因为在同步指针时需要时间(如延迟两拍同步),而在这个同步的时间内有可能还会写入/读出新的数据,因此同步后的指针一定是小于或者等于当前实际的读/写指针,那么此时判断FIFO满空状态时是否会出错?是否会导致错误?

结论:

先说结论:异步逻辑进行同步时,不可避免需要额外的时钟开销,这会导致满空趋于保守,但是保守并不等于错误,这么写会稍微有性能损失,但是不会出错。

FIFO满检测:

FIFO满检测发生在写时钟域,将读指针同步到写时钟域后再和写指针比较,进行FIFO满状态判断。因为同步时间的存在,同步后的读指针一定是小于或者等于当前真正的读指针(同步时间内可能出现了读操作,导致读指针增加),所以此时判断FIFO为满不一定是真满,这样更保守(即:写指针=同步读指针<=真实读指针),这样可以保证FIFO的特性:FIFO空之后不能继续读取。

FIFO空检测:

FIFO空检测发生在读时钟域,将写指针同步到读时钟域后再和读指针比较,进行FIFO空状态判断。因为同步时间的存在,同步后的写指针一定是小于或者等于当前真正的写指针(同步时间内可能出现了写操作,导致写指针增加),所以此时判断FIFO为空不一定是真空,这样更保守(即:读指针=同步写指针<=真实写指针),这样可以保证FIFO的特性:FIFO满之后不能继续写入。

四、异步FIFO设计

1、端口设计

外部端口

1、读时钟信号clk_r,作为异步FIFO的读驱动信号

2、写时钟信号clk_w,作为异步FIFO的写驱动信号

3、异步复位信号rst_n

// 写FIFO相关

4、数据输入信号din[DW-1:0],作为FIFO数据写入端,DW数据为位宽

5、写使能we

// 读FIFO相关

6、数据输出信号dout[DW-1:0],作为FIFO数据输出端

7、读使能re

// 标志相关

8、FIFO满标志full,FIFO满时不能再写入数据

9、FIFO空标志empty,FIFO空时不能再读出数据

内部信号

1、读指针wp,作为读地址(FIFO读写只能顺序进行,不能外部设置,因此为内部信号)

2、格雷码读指针wp_g,供写时钟域同步后判断FIFO满使用

3、写指针rp,作为写地址

4、格雷码写指针wp_g,供读时钟域同步后判断FIFO空使用

5、[DW-1:0]ram[0:Depth-1],数据存储空间,Depth为FIFO深度

2、功能描述

读逻辑:

clk_r来临,re有效,并且FIFO非空(empty=0)时,将读指针rp对应地址处的数据读出至dout;

// 读操作
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        dout <= {DW{1'bz}};
    else if(!empty & re)
        dout <= ram[rp[AW-1:0]];
    else
        dout <= dout;
end

读指针逻辑

clk_r来临,re有效,并且FIFO非空(empty=0)时,进行一次读操作,rp加1(即顺序读出),并进行格雷码转换,生成对应rp_g;

// 读指针
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        rp <= {AW{1'b0}};
    else if(!empty & re)
        rp <= rp+1'b1;
    else
        rp <= rp;
end

写逻辑:

clk_w来临,we有效,并且FIFO不满(full=0)时,将din写入写指针wp对应地址处;

//写操作
always@(posedge clk_w)
begin
    if(!full & we)
        ram[wp[AW-1:0]] <= din;
    else
        ram[wp[AW-1:0]] <= ram[wp[AW-1:0]];
end

写指针逻辑

clk_w来临,we有效,并且FIFO不满(full=0)时,进行一次写操作,wp加1(即顺序存储),并进行格雷码转换,生成对应wp_g;

//写指针
always@(posedge clk_w or negedge rst_n)
begin
    if(!rst_n)
        wp <= {AW{1'b0}};
    else if(!full & we)
        wp <= wp+1'b1;
    else
        wp <= wp;
end

格雷码指针生成逻辑:

利用二进制与格雷码的转换关系,将二进制指针转换为格雷码指针,用于另一个时钟域的同步接收;

// 二进制指针转换为格雷指针
assign wp_g = (wp>>1) ^ wp;
assign rp_g = (rp>>1) ^ rp;

格雷码指针同步逻辑:

读时钟域,同步写地址,用于空逻辑判断;写时钟域,同步读地址,用于满逻辑判断;

// 读时钟域,写地址同步
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        begin
            wp_m <= {AW{1'b0}};
            wp_s <= {AW{1'b0}};       
        end
    else
        begin
            wp_m <= wp_g;
            wp_s <= wp_m;    
        end       
end
// 写时钟域,读地址同步
always@(posedge clk_w or negedge rst_n)
begin
    if(!rst_n)
        begin
            rp_m <= {AW{1'b0}};
            rp_s <= {AW{1'b0}};       
        end
    else
        begin
            rp_m <= rp_g;
            rp_s <= rp_m;    
        end       
end

空满检测逻辑:

根据格雷码指针判断逻辑,进行空满检测,应注意:

读时钟域,同步写地址。读格雷码指针同步后写格雷码指针比较,用于空逻辑判断;

写时钟域,同步读地址,写格雷码指针同步后读格雷码指针比较,用于满逻辑判断;

// 空满检测,使用同步后的格雷指针?
assign empty = (wp_s == rp_g)?1'b1:1'b0;// 空检测,使用同步后的写格雷指针
assign full = ( {~wp_g[AW:AW-1] , wp_g[AW-2:0]} == {rp_s[AW:AW-1] , rp_s[AW-2:0]} )?1'b1:1'b0;  // 满检测,使用同步后的读格雷指针

3、实现代码

`timescale 1ns / 1ps
//
// Company: 
// Engineer: guoliang CLL
// 
// Create Date: 2020/03/24 20:51:06
// Design Name: 
// Module Name: afifo
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module afifo
#(parameter DW = 8,AW = 4)//默认数据宽度8,FIFO深度16
(
    input clk_r,
    input clk_w,
    input rst_n,
    input we,
    input re,
    input [DW-1:0]din,
    output reg [DW-1:0]dout,
    output empty,
    output full
    );
// internal signal
parameter Depth = 1 << AW;//depth of FIFO 
reg [DW-1:0]ram[0:Depth-1];
reg [AW:0]wp;  //point
reg [AW:0]rp;
wire [AW:0]wp_g;//Gray point
wire [AW:0]rp_g;
reg [AW:0]wp_m;//mid_point for syn
reg [AW:0]rp_m;
reg [AW:0]wp_s;//point after syn
reg [AW:0]rp_s;
// FIFO declaration
// 二进制指针转换为格雷指针
assign wp_g = (wp>>1) ^ wp;
assign rp_g = (rp>>1) ^ rp;
// 空满检测,使用同步后的格雷指针?
assign empty = (wp_s == rp_g)?1'b1:1'b0;// 空检测,使用同步后的写格雷指针
assign full = ( {~wp_g[AW:AW-1] , wp_g[AW-2:0]} == {rp_s[AW:AW-1] , rp_s[AW-2:0]} )?1'b1:1'b0;  // 满检测,使用同步后的读格雷指针
// 读指针
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        rp <= {AW{1'b0}};
    else if(!empty & re)
        rp <= rp+1'b1;
    else
        rp <= rp;
end
//写指针
always@(posedge clk_w or negedge rst_n)
begin
    if(!rst_n)
        wp <= {AW{1'b0}};
    else if(!full & we)
        wp <= wp+1'b1;
    else
        wp <= wp;
end
// 读操作
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        dout <= {DW{1'bz}};
    else if(!empty & re)
        dout <= ram[rp[AW-1:0]];
    else
        dout <= dout;
end
//写操作
always@(posedge clk_w)
begin
    if(!full & we)
        ram[wp[AW-1:0]] <= din;
    else
        ram[wp[AW-1:0]] <= ram[wp[AW-1:0]];
end
// 读时钟域,写地址同步
always@(posedge clk_r or negedge rst_n)
begin
    if(!rst_n)
        begin
            wp_m <= {AW{1'b0}};
            wp_s <= {AW{1'b0}};       
        end
    else
        begin
            wp_m <= wp_g;
            wp_s <= wp_m;    
        end       
end
// 写时钟域,读地址同步
always@(posedge clk_w or negedge rst_n)
begin
    if(!rst_n)
        begin
            rp_m <= {AW{1'b0}};
            rp_s <= {AW{1'b0}};       
        end
    else
        begin
            rp_m <= rp_g;
            rp_s <= rp_m;    
        end       
end
endmodule

4、仿真验证

测试文件如下:

`timescale 1ns / 1ps
//
// Company: 
// Engineer: CLL guoliang
// 
// Create Date: 2020/03/24 21:22:45
// Design Name: 
// Module Name: afifo_tsb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module afifo_tsb(

    );
// port declaration
reg clk_r;
reg clk_w;
reg rst_n;
reg we;
reg re;
reg [7:0]din;
wire  [7:0]dout;
wire empty;
wire full;
//clk
initial
begin
    clk_r = 1'b0;
    forever #25 clk_r = ~clk_r;
end
initial
begin
    clk_w = 1'b0;
    forever #10 clk_w = ~clk_w;
end
// 
initial
begin
    rst_n = 1'b1;
    din = 1'b0;
    re = 1'b0;
    we = 1'b0;
    #50 rst_n = 1'b0;
    #50 rst_n = 1'b1;
    // only write
    we = 1'b1;
    repeat(20) #20 din = din+1'b1;
    // only read 
    we = 1'b0;
    re = 1'b1;
    repeat(20) #50; 
    // read and write
//    we = 1'b1;
//    re = 1'b1;
//    din = 1'b0;
//    repeat(20) #20 din = din+1'b1;     
end
// inst
afifo inst2(
    .clk_r(clk_r),
    .clk_w(clk_w),
    .rst_n(rst_n),
    .we(we),
    .re(re),
    .din(din),
    .dout(dout),
    .empty(empty),
    .full(full)
);
endmodule

仿真结果如下:

篇幅有限,仿真结果不再展开分析。提醒自己,应注意仿真测试是很必要的,通过功能仿真能暴露出设计上的不足、缺陷、以及实现过程中因粗心等导致的其余问题;

因此,如何设计测试文件也具有重要意义。测试文件容易编写,但是如何使得测试文件能全面的对设计进行检测,高效准确的对设计进行测试,无疑是一门学问;

我只简单记录一下,我调试时关注的部分

1、写逻辑

数据能否在写时钟驱动下,顺序写入FIFO中对应地址;FIFO满时,是否停止写入;

2、读逻辑

能否在读时钟驱动下,顺序读出FIFO中对应数据;FIFO空时,是否停止读出;

3、满判断

设计能否在写时钟驱动下,同步读指针,并且在适当位置产生满标志;

3、空判断

设计能否在读时钟驱动下,同步写指针,并且在适当位置产生空标志;

实现异步的几种方式_异步怎么实现

RTL电路如下:

实现异步的几种方式_异步怎么实现

五、参考文献

Verilog实现FIFO专题(3-同步FIFO设计)

异步FIFO的设计

Verilog中always@()语句双边沿触发(语法与综合的差异)

Verilog实现二进制码与格雷码转换

亚稳态专题

 

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

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

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

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

(0)


相关推荐

  • Linux命令-fsync[通俗易懂]

    Linux命令-fsync[通俗易懂]对fsync命令只做简单介绍,不做详细原理解释。通过man命令查看fsync()函数有以下共识:1、**fsync(2)**函数是属于系统核心函数;2、unix系统下函数标注不同数值的含义1)、用户在shell环境可以使用的指令或可执行文件;2)、系统核心提供的可调用的函数与工具;3)、常用函数、函数库,大部分是C的函数库;4)、设备驱动程序,通常在/dev下;5)、配置文件或某…

  • siblings() 获得匹配集合中每个元素的同胞

    siblings() 获得匹配集合中每个元素的同胞

    2021年10月18日
  • 关于Cocos2d-x 3.0正式版 粒子问题在IOS上正常显示,在Android下有问题的解决方式

    关于Cocos2d-x 3.0正式版 粒子问题在IOS上正常显示,在Android下有问题的解决方式

  • nextSibling的兼容问题「建议收藏」

    nextSibling的兼容问题「建议收藏」这个有两个兼容性,innerText不是所有浏览器都兼容的,要用innerHTML 然后就是,对于节点关系,ie有事会将期间的空格当成一个文本节点,但火狐就不会,因此你的nextSibling很可能在ie下取到的是一个文本节点,换在火狐下就是另外一个。本文转自:http://ailantian.bokee.com/6418694.html原文如下:网

  • kettle 性能优化_kettle过滤记录

    kettle 性能优化_kettle过滤记录性能调优在整个工程中是非常重要的,也是非常有必要的。但有的时候我们往往都不知道如何对性能进行调优。其实性能调优主要分两个方面:一方面是硬件调优,一方面是软件调优。本章主要是介绍Kettle的性能优化及效率提升。……

  • linux启动ftp命令_linux安装ftp命令

    linux启动ftp命令_linux安装ftp命令ftp服务器在网上较为常见,Linuxftp命令的功能是用命令的方式来控制在本地机和远程机之间传送文件下面由学习啦小编为大家整理了linux下开启ftp命令的相关知识,希望对大家有所帮助!linux下启动FTP命令的方式一般linux都有vsftpd吧,启动命令是servicevsftpdstart,你要限制匿名登录的话,修改它的配置文件/etc/vsftpd/vsftpd.conf,把an…

发表回复

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

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