Verilog 编写同步FIFO和异步 FIFO

Verilog 编写同步/异步 FIFO

同步FIFO

Verilog代码:

module Sync_fifo(
    input clk_i,
    (* direct_reset = "true" *) input rst_i,//同步复位
    input [7:0]  	   wr_data_i,
    input        	   wr_en_i,
    
    input        	   rd_en_i,
    output reg [7:0]   rd_data_o,
    	   
    output reg         fifo_full_o,
    output reg         fifo_empty_o
    );
    integer i;
    reg [7:0] data_store[15:0];//reg数组存储
    reg [3:0] rd_ptr,wr_ptr;//读写指针
    
    always @(posedge clk_i)begin:write_data
        if(rst_i)begin
            wr_ptr <= 4'b0;
            for(i=0;i<=15;i=i+1)
                data_store[i] <= 8'b0;
        end
        else if(fifo_full_o)//fifo为满时,不进行写操作,写指针不变化
            wr_ptr <= wr_ptr;
        else if(wr_en_i)begin
            data_store[wr_ptr] <= wr_data_i;//写数据
            wr_ptr <= wr_ptr + 4'd1;
        end
        else begin
            wr_ptr <= wr_ptr;
        end
    end
    
    always @(posedge clk_i)begin:read_data
        if(rst_i)begin
            rd_ptr <= 4'b0;
            rd_data_o <= 8'b0;
        end
        else if(fifo_empty_o)begin//fifo为空时,输出信号置0
            rd_ptr <= rd_ptr;
            rd_data_o <= 8'b0;
        end
        else if(rd_en_i)begin
            rd_ptr <= rd_ptr + 4'd1;
            rd_data_o <= data_store[rd_ptr];//读数据
        end
        else begin
            rd_ptr <= rd_ptr;
            rd_data_o <= 8'b0;
        end
    end
    
    always @(posedge clk_i)begin:empty_gene
        if(rst_i)
            fifo_empty_o <= 1'b1;
        else if(wr_en_i & rd_en_i)//既读也写,empty保持不变
            fifo_empty_o <= fifo_empty_o;
        else if(wr_en_i)//只写不读,empty拉低
            fifo_empty_o <= 1'b0;
        else if(rd_en_i)//只读不写,在读写指针相等时full拉高
            fifo_empty_o <= (wr_ptr == rd_ptr+4'd1)? 1'b1:fifo_empty_o;
    end
    
    always @(posedge clk_i)begin:full_gene
        if(rst_i)
            fifo_full_o <= 1'b0;
        else if(wr_en_i & rd_en_i)//既读也写,full保持不变
            fifo_full_o <= fifo_full_o;
        else if(rd_en_i)//只读不写,full拉低
            fifo_full_o <= 1'b0;
        else if(wr_en_i)//只写不读,在读写指针相等时full拉高
            fifo_full_o <= (rd_ptr == wr_ptr+4'd1)? 1'b1:fifo_full_o;
    end
endmodule

异步FIFO

参照Xilinx fifo-generator修改端口,在同步FIFO的代码基础上进行修改(当然是大改了)

读写时钟不同带来的几个问题:

1. 复位问题
1.1 同步还是异步?

​ 在CSDN上看了一些异步FIFO的代码,大部分都采用了异步复位,这种方式对于数据路径影响小,但是会产生撤销复位信号时的亚稳态问题,同时会引入毛刺,除非使用复位同步器(异步复位,同步释放)和毛刺过滤器(过滤1ns之内的毛刺)。

`timescale 1ns / 1ps
module arstn_sync(//Async rstn signal synchronizer & burrs filter
    input clk_i,
    input arstn_i,
    (* max_fanout = 50 *) output reg arstn_sync_o //reduce fanout of single register
    );
    wire #(1) arstn_dly = arstn_i;//filter burrs within 1ns
    wire arstn = arstn_i | arstn_dly;
    
    reg arstn_sync0;
    always @(posedge clk_i or negedge arstn)begin
        if(~arstn)begin:async_reset
            arstn_sync0  <= 1'b0;
            arstn_sync_o <= 1'b0;
        end
        else begin:sync_release
            arstn_sync0  <= 1'b1;
            arstn_sync_o <= arstn_sync0;
        end
    end
endmodule

​ 笔者日常使用的都是同步复位,时序问题少,时钟边沿可以过滤毛刺,但是综合工具无法区分复位信号和数据信号,可以使用对应的综合命令[1]将复位信号接入触发器的复位引脚,如同步FIFO代码中的

	(* direct_reset = "true" *) input rst_i,

​ 本文设计的异步FIFO采用同步复位

1.2 同步复位与那个时钟同步?

​ FIFO的本质是存储器,读操作对存储的数据不会产生影响,而写操作会。换句话说,对于FIFO,写是被动的,读是主动的,因此(外部输入的)复位信号需要和写操作时钟保持同步。

​ 在 Xilinx IP 核 fifo-generator 的手册(pg057)中也提到 UltraScale 架构的 FPGA 只能使用同步复位引脚,复位信号必须与写时钟同步[2]

UltraScale architecture-based built-in FIFO supports only the synchronous reset (srst). The reset must always be synchronous to write clock (clk/wr_clk). (page.136)

2. 空/满信号产生问题
2.1 亚稳态

​ 在同步FIFO的设计中,full和empty信号的产生都需要比较读指针和写指针,而在异步条件下,两个指针分属不同的时钟域,直接进行比较的话,数据变化与时钟跳变沿过于接近会违背触发器的建立(Setup)或者保持(Hold)时间,产生亚稳态,使电路进入不稳定的状态,产生错误的输出,除非两个时钟能够保持一定的频率和相位关系,让数据变化与时钟跳变沿的时间间隔足够大。

​ 跨时钟域传输的数据可以视为异步输入,可以使用多级同步器减少亚稳态的发生概率。

module multi_synchronizer(
    input clk_i,
    input async_i,
    output  sync_o
    );
    parameter STEP_NUM = 2;//multi-step synchronizer
    (* shreg_extract = "no" *) reg [STEP_NUM-1:0] sync_data;//synthesis cmd: no shift register
    assign sync_o = sync_data[STEP_NUM-1];
    integer i;
    always @(posedge clk_i)begin
        sync_data[0] <= async_i;
        for(i=1;i<STEP_NUM;i=i+1)
            sync_data[i]<= sync_data[i-1];
    end
endmodule

​ 在大多数多时钟域的设计中,使用两级同步电路就足以避免亚稳态的出现了[3]。因此,异步FIFO使用两级同步器将读写指针及读写使能信号传输至另一时钟域下。

​ 两级同步器会给指针数据带来两个时钟周期的延迟,由full和empty产生的逻辑来看,两个信号都可以按时拉高,而拉低会延后两个时钟周期,延迟的时间内FIFO本可以工作但被阻止了,存在一定的浪费,但是不会导致读出或者写入发生错误。

2.2 二进制指针跳变

​ 多级同步器并不能完全避免亚稳态的出现,而使用二进制指针,寄存器在向高位进位时,会有较多的位产生跳变,进入亚稳态时将会产生大量错误,因此在读写时钟域之间传递指针数据需要减少相邻两个指针间的跳变位数,理论上最低的跳变位数为1。

​ 格雷码完美地符合了这一要求,介绍和二进制数转换方法可见参考资料[4]。

表. 3位二进制码、格雷码、十进制数对应关系
二进制码格雷码十进制数
0000000
0010011
0100112
0110103
1001104
1011115
1101016
1111007

​ n位二进制码 b i n i bin_i bini 转换为n位格雷码 g r a y i gray_i grayi 的公式及对应的Verilog代码[3]如下:

b i n n − 1 = g r a y n − 1 b i n i = g r a y i ⊕ b i n i + 1 bin_{n-1} = gray_{n-1}\\bin_i = gray_i \oplus bin_{i+1} binn1=grayn1bini=grayibini+1

module gray2bin #(parameter WIDTH = 4)(
    input [WIDTH-1:0] gray_i,
    output reg [WIDTH-1:0] bin_o
    );
    integer i;
    always @(gray_i)
        for(i=0;i<WIDTH;i=i+1)
            bin_o[i] <= ^(gray_i>>i);
endmodule

​ n位格雷码 g r a y i gray_i grayi 转换为n位二进制码 b i n i bin_i bini 的公式及对应的Verilog代码如下:
g r a y n − 1 = b i n n − 1 g r a y i = b i n i ⊕ b i n i + 1 gray_{n-1} = bin_{n-1}\\gray_i = bin_i \oplus bin_{i+1} grayn1=binn1grayi=binibini+1

module bin2gray #(parameter WIDTH = 4)(
    input [WIDTH-1:0] bin_i,
    output reg [WIDTH-1:0] gray_o
	);
    always @(bin_i)
        gray_o <= (bin_i>>1)^bin_i;
endmodule
2.3 基于格雷码的空/满信号产生

​ 2.2节已经指出跨时钟域传递读写指针的时候需要使用格雷码编码,如果使用同步FIFO中的空/满信号产生方式,则需要将格雷码转换为二进制码,比较麻烦,可以直接使用格雷码生成空/满信号。

​ 当读写指针相等时,FIFO必定处于全空或者全满的状态,需要额外的信号区分两种状态。做一个比喻:读指针和写指针就像在同一个操场绕圈跑步,如果写指针跑得很快,把读指针“套圈”了,那么继续写就会将原有的数据覆盖,所以要产生满信号阻止进一步的写入;如果读指针跑得很快,把写指针跑过的路程都追上了,那么继续读就会读出已经读过的数据,所以要产生空信号阻止进一步的读出。

​ 写指针应始终不落后于读指针,同时最多领先读指针一个FIFO长度。在计数器里设置一位记录“套圈”与否即可分辨出读写指针相等时对应的空/满状态。N位格雷码第2N个编码为 {1,(N-1){0}} ,当计数器计数至此的时候可将记录变量翻转,用于对比。(类似于二进制数里的进位,将此位作为最高位和N位格雷码组合,姑且称为N+1位扩展格雷码

​ 在写时钟域,读扩展格雷码指针经过两级同步器同步后,与写扩展格雷码指针作对比,当二者最高位不同低N位全部相同时,full信号拉高;在读时钟域,当同步后的读写扩展格雷码最高位和低N位全部相同时,empty拉高。

​ 进一步地,RAM也可以直接使用格雷码读写,无需进行二进制码转换,不影响正常工作。

​ 基于输出寄存器的带进位信号格雷码计数器Verilog代码如下:

module gray_counter #(parameter WIDTH = 4)(//WIDTH >= 3
    input clk_i,
    input rst_i,
    input cnt_en_i,
    output reg             carry_bit_o,
    output reg [WIDTH-1:0] gray_cnt_o
    );
    reg [WIDTH-2:0] gray_cnt_r;//record last cnt code
    always @(posedge clk_i)begin
        if(rst_i)begin
            gray_cnt_o[0]          <= 1'b0;
            gray_cnt_o[1]          <= 1'b0;
            gray_cnt_o[WIDTH-1]    <= 1'b0;
            gray_cnt_r             <= {(WIDTH-1){1'b0}};
            carry_bit_o            <= 1'b0;
        end
        else if(cnt_en_i)begin
            //individual assignment of the lowest 2 bits and highest bit
            //when the lowest bit hold its value, flip it
            gray_cnt_o[0]          <= (~(gray_cnt_r[0]^gray_cnt_o[0]))? ~gray_cnt_o[0]:gray_cnt_o[0];
            //when the second lowest bit doesn't hold its value and lowest bit = 1, flip it  
            gray_cnt_o[1]          <= (~(gray_cnt_r[1]^gray_cnt_o[1]) & gray_cnt_o[0])? ~gray_cnt_o[1]:gray_cnt_o[1];
            //when highest bit equals the second highest bit, and other bits are 0, flip it
            gray_cnt_o[WIDTH-1]    <= (gray_cnt_o[WIDTH-1]^gray_cnt_o[WIDTH-2]) & ~(|gray_cnt_o[WIDTH-3:0])? ~gray_cnt_o[WIDTH-1]:gray_cnt_o[WIDTH-1];
            gray_cnt_r             <= gray_cnt_o[WIDTH-2:0];
            //when cnt code equals 100...00, carry_bit = 1 to indicate count end
            carry_bit_o            <= (gray_cnt_o[WIDTH-1]&~(|gray_cnt_o[WIDTH-2:0]))? ~carry_bit_o:carry_bit_o;
        end
    end
    genvar i;
    generate for(i=2;i<WIDTH-1;i=i+1)begin
        always @(posedge clk_i)begin
            if(rst_i)
                gray_cnt_o[i]    <= 1'b0;
            else if(cnt_en_i)
                if(~(gray_cnt_r[i]^gray_cnt_o[i]) & gray_cnt_o[i-1] & ~(|gray_cnt_o[i-2:0]))
                //when i-th bit hold its value, and other bits constitute 100...00
                //flip i-th bit, i = 2 ~ WIDTH-2
                    gray_cnt_o[i] <= ~gray_cnt_o[i];
        end
    end
    endgenerate
endmodule

解决上述问题之后,搭建异步FIFO,Verilog代码如下:

module Async_fifo #(parameter ADDR_WIDTH = 4,DATA_WIDTH = 8)(
    (* direct_reset = "true" *) input rst_i,
    
    input                   clk_wr_i,
    input                   wr_en_i,
    input [DATA_WIDTH-1:0]  wr_data_i,
    
    input                       clk_rd_i,
    input                       rd_en_i,
    output reg [DATA_WIDTH-1:0] rd_data_o,
    
    output reg          fifo_full_o,
    output reg          fifo_empty_o
    );
    integer i;
    reg [DATA_WIDTH-1:0] data_store[({ADDR_WIDTH{1'b1}}):0];//register group to store data 
    reg [ADDR_WIDTH:0] wr_ptr_r,wr_sync_ptr;//extended gray code, clk_rd_i domain
    reg [ADDR_WIDTH:0] rd_ptr_r,rd_sync_ptr;//extended gray code, clk_wr_i domain
    wire [ADDR_WIDTH-1:0] wr_ptr,rd_ptr;//write/read pointer
    wire carry_bit_wr,carry_bit_rd;
    wire full_flag,empty_flag;
    gray_counter #(.WIDTH(ADDR_WIDTH)) inst_gray_counter_wr(
        .clk_i(clk_wr_i),
        .rst_i(rst_i),
        .cnt_en_i(wr_en_i & ~full_flag),
        .carry_bit_o(carry_bit_wr),
        .gray_cnt_o(wr_ptr)
    );//gray code counter, when cnt_en_i = 1, gary_cnt_o become to next code
    gray_counter #(.WIDTH(ADDR_WIDTH)) inst_gray_counter_rd(
        .clk_i(clk_rd_i),
        .rst_i(rst_i),
        .cnt_en_i(rd_en_i & ~empty_flag),
        .carry_bit_o(carry_bit_rd),
        .gray_cnt_o(rd_ptr)
    );
    always @(posedge clk_rd_i)begin:rd_clk_sync
        wr_ptr_r        <= rst_i? {(ADDR_WIDTH+1){1'b0}}:{carry_bit_wr,wr_ptr};
        wr_sync_ptr     <= rst_i? {(ADDR_WIDTH+1){1'b0}}:wr_ptr_r;
    end//synchroniazer based on 2-step FF
    always @(posedge clk_wr_i)begin:wr_clk_sync
        rd_ptr_r        <= rst_i? {(ADDR_WIDTH+1){1'b0}}:{carry_bit_rd,rd_ptr};
        rd_sync_ptr     <= rst_i? {(ADDR_WIDTH+1){1'b0}}:rd_ptr_r;
    end
    always @(posedge clk_wr_i)begin:wr_data
        if(rst_i)
            for(i=0;i<={ADDR_WIDTH{1'b1}};i=i+1)
                data_store[i] <= {DATA_WIDTH{1'b0}};
        else if(~full_flag & wr_en_i)//write enabled and fifo not full
            data_store[wr_ptr] <= wr_data_i;
    end
    always @(posedge clk_rd_i)begin:rd_data
        if(rst_i)
            rd_data_o <= {DATA_WIDTH{1'b0}};
        else if(empty_flag)//when fifo is empty, read 0
            rd_data_o <= {DATA_WIDTH{1'b0}};
        else if(rd_en_i)//read enabled
            rd_data_o <= data_store[rd_ptr];
        else
            rd_data_o <= {DATA_WIDTH{1'b0}};
    end
    assign full_flag = (rd_sync_ptr[ADDR_WIDTH]^carry_bit_wr)&(rd_sync_ptr[ADDR_WIDTH-1:0] == wr_ptr);
    //when extended bit differs and other bits equal, full = 1
    always @(posedge clk_wr_i)begin:full_gene
         fifo_full_o <= rst_i? 1'b0:full_flag;
    end
    assign empty_flag = (wr_sync_ptr[ADDR_WIDTH:0] == {carry_bit_rd,rd_ptr});
    //when extended bit and other bits equal, empty = 1
    always @(posedge clk_rd_i)begin:empty_gene
        fifo_empty_o <= rst_i? 1'b0:empty_flag;
    end
endmodule

写时钟周期为6.6ns,读时钟周期为4ns,仿真测试效果如下:

图1-1 数据能够按写入顺序读出,若干写操作后empty拉低,读完所有数据empty拉高,读出数据变0
图1-2 写满所有存储空间后full拉高,阻止后续写操作(数据“31”未写入),若干读操作后full拉低

**写时钟周期为4ns,读时钟周期为6.6ns,**仿真测试效果如下:

图2-1 数据能够按写入顺序读出,若干写操作后empty拉低,读使能拉低后读出数据变0
图2-2 写满所有存储空间后full拉高,阻止后续写操作(数据“36”未写入)
图2-3 读使能后继续读出剩余数据,若干读操作后full拉低,读完所有数据empty拉高,读出数据变0

参考文献:

[1]Xilinx: Vivado Design Suite User Guide: Synthesis (UG901)

[2]Xilinx: pg057-fifo-generator

[3](印)阿罗拉. 硬件架构的艺术:数字电路的设计方法与技术[M]. 北京:机械工业出版社, 2014.

[4]CSDN: 格雷码与二进制的转换

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值