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

2022-09-21 10:24:27 浏览数 (2)

大家好,又见面了,我是你们的朋友全栈君。

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;

代码语言:javascript复制
// 读操作
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;

代码语言:javascript复制
// 读指针
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对应地址处;

代码语言:javascript复制
//写操作
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;

代码语言:javascript复制
//写指针
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

格雷码指针生成逻辑:

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

代码语言:javascript复制
// 二进制指针转换为格雷指针
assign wp_g = (wp>>1) ^ wp;
assign rp_g = (rp>>1) ^ rp;

格雷码指针同步逻辑:

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

代码语言:javascript复制
// 读时钟域,写地址同步
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

空满检测逻辑:

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

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

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

代码语言:javascript复制
// 空满检测,使用同步后的格雷指针?
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、实现代码

代码语言:javascript复制
`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、仿真验证

测试文件如下:

代码语言:javascript复制
`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实现二进制码与格雷码转换

亚稳态专题

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

0 人点赞