我是靠谱客的博主 聪慧冬日,最近开发中收集的这篇文章主要介绍verilog实现异步fifo基于verilog实现异步fifo,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

基于verilog实现异步fifo

百度网盘链接:https://pan.baidu.com/s/1dP3ADbFCHXPId-TYDbpR0Q
提取码:djly
–来自百度网盘超级会员V5的分享

1.功能简述

本文实现的是双端口异步fifo,具有异步读写数据功能。在输入和输出设备各自的时钟驱动下,在使能信号有效时,对数据进行写入和读出,并保证输入设备先写入的数据在读出端先读出。在写入数据将要写满存储空间时,异步FIFO向输入设备反馈将满信号“aful_out_alfull”;在输出设备将要把存储空间的数据读空时,异步FIFO向输出设备反馈将空信号“aem_in_alempty”。当存储空间已经被写满时,异步FIFO向输入设备反馈写满信号“wful_mem_wfull” 并停止写操作;当存储空间已经被读空时,异步FIFO向输出设备反馈**读空信号“rem_mem_rempty”**并停止读操作。结构简图如下图所示:在这里插入图片描述

2.具体结构

异步fifo具体结构框图如下图所示,主要包含中间的双端口RAM存储器和外围的控制电路。通过外围的控制电路,实现在异步时钟下,对先写入存储器的数据先读出。
在这里插入图片描述

3.基本原理

(1)存储空间配置

首先,根据所需要的fifo的大小(位宽与深度),配置一个存储阵列(如下图所示),用来暂存写入的数据。(代码中给出的是一个位宽为32,深度为16的一个存储阵列)。然后进行数据读写操作。每一个存储单元都会对应一个地址(0000~1111),在写数据操作时我们会将输入的数据按照先后顺序写入这个存储阵列的每一个存储单元。在读数据操作时,我们会将各存储空间中的数据按顺序读出。需要注意的是:只有在写使能信号有效存储空间未被写满时,才能够写入数据;只有在读使能信号有效存储空间未被读空时,才能够读出数据。此存储器是个双端口RAM,读写操作在各自的时钟域写完成,可以实现同时读写
在这里插入图片描述

(2)写满与读空信号的产生

在开始写入数据时,我们会按照地址的先后顺序依次写入数据。读写数据的地址指针都是从0000到1111递增,在1111后直接跳到0000再重新向1111递增。当存储空间中有数据时才能读出数据,所以写地址指针总是超前或等于读地址指针。当存储空间被写满时,写数据地址指针已经超前于读数据地址指针一轮(已经把16个存储单元全部写完回到起点),虽然地址是相同的。当存储空间
当存储空间被读空时,说明读数据地址指针已经赶上了写数据地址指针,读写地址指针也是相同的。所以为了区别读写地址指针相同表示的是同一个地址还是写地址超前读地址一轮,我们会将地址指针扩展一位。下图列举了写满和读空的其中五种情况,需要注意事的是,写满和读空不一定是在起始地址(0 0000和1 0000),在任意地址均有可能,只要扩展后的读写地址指针相同或者相差一轮。
在这里插入图片描述

(3)跨时钟域同步与格雷码的引入

在进行写满和读空判断时,我们会比较写数据地址指针和读数据地址指针,但是异步fifo有两个时钟域(读时钟域和写时钟域),读写操作分别在各自的时钟域下进行。所以在比较地址前首先要将地址进行跨时钟域(常见的跨时钟域方法自行补充,这里不再赘述)。在进行写满判断时,首先要将读时钟域下的读地址指针同步到写时钟域,并在写时钟域下对地址进行比较。在进行读空判断时,首先要将写时钟域下的写地址指针同步到读时钟域,并在读时钟域进行比较。这里的跨时钟域用的是两个D触发器完成,因为能够一定程度上避免亚稳态
对于什么是亚稳态?
在异步时序逻辑电路中,在进行跨时钟域同步时,由于不同时钟时钟域的时钟存在相位差,这个相位差是变化的,会导致采样时不能满足建立时间和保持时间,使电路输出处于一个未知的状态(可能是一个中间值或者是一个震荡的值)。所以为了尽可能避免亚稳态的发生,要使相邻两个地址的变化尽可能小,所以地址指针换成了格雷码。格雷码相邻两个数据只相差一位。所以在进行写满判断时,读写地址相差一轮,此时格雷码最高两位相反,其余位相同读空判断仍然是格雷码完全相同。
在这里插入图片描述
两级D触发器的引入对fifo性能的影响:
以写时钟快于读时钟时进行写满判断为例,在进行写满判断时,是将写时钟域下的写地址指针和延时了两个写时钟的读地址指针进行比较,此时实际的读地址是超前于比较的地址,所以写满信号是一个保守值,在将要写满时就反馈一个写满信号,保证在将要写满时就反馈写满信号,避免在写满时继续写入数据。会影响fifo的性能,但不会影响功能。
同样,在读时钟快于写时钟进行读空判断时,在将要读空输出一个读空信号,保证在未被读空时就不再读出数据,而不会发生已经读空时再继续读出数据的情况。(这个说法比较绕)

(4)“将空”和“将满”信号的产生

“将空”和“将满”信号产生与“读空”和“写满”信号产生是类似的,就是比较读写地址。不同的是,“将空”与“将满”信号判别标准是读写地址指针的距离小于一个阈值“ALMOST_GAP”,工程中将此阈值参数化了,可以进行修改。在进行读写地址比较时,需要判定读写地址指针的距离,而格雷码不具备这个功能,所以需要转换为二进制代码。“将空”信号是读写地址指针距离小于阈值时为高,“将满”信号的判断则需要分两种情况,一种是地址扩展位不同,此时表示写地址有一个回卷,直接将读写地址除去符号位的部分做差与间隔比较。而扩展位相同时,需要在差值上再加上FIFO深度。

4.各模块的实现

(1)顶层模块

`timescale 1ns / 1ps
module fifo
#(
  parameter DSIZE =32,                                    //数据宽度
  parameter ASIZE = 6,                                    //数据的深度为2^n
  parameter ALMOST_GAP = 3                                //将空与将满判别阈值
 ) 
 (
     output [DSIZE-1:0] mem_out_rdata,                    //读出的数据
     
     input  [DSIZE-1:0] in_mem_wdata,                     //写入的数据
     input              winc, wclk, wrst_n,               //读写使能、时钟、复位信号
     input              rinc, rclk, rrst_n
 );

  wire                    wful_mem_wfull;                 //写满和读空标志位
  wire                    rem_mem_rempty;
  wire                    aem_out_alempty;
  wire                    aful_in_alfull;   
  wire   [ASIZE-1:0] wful_mem_waddr, rem_mem_raddr;       //读写二进制地址                      
  wire   [ASIZE:0]   wful_wtor_wgad, rem_rtow_rgad, rtow_wful_wsad, wtor_rem_rsad;           //格雷码读写地址指针及时钟同步后得到的格雷码地址指针
  
  // 将读地址格雷码指针同步到写写时钟域
   rtowsync  u_rtowsync
   (
    .rtow_wful_wsad       (rtow_wful_wsad ),
    .rem_rtow_rgad        (rem_rtow_rgad  ),                          
    .wclk                 (wclk           ), 
    .wrst_n               (wrst_n         )  
   );

  // 将写地址格雷码指针同步到读时钟域
   wtorsync  u_wtorsync 
   (
    .wtor_rem_rsad        (wtor_rem_rsad), 
    .wful_wtor_wgad       (wful_wtor_wgad),                          
    .rclk                 (rclk),
    .rrst_n               (rrst_n)
   );

  //存储器的配置
  fifomem 
  #(DSIZE, ASIZE)
  u_fifomem                        
  (
    .mem_out_rdata        (mem_out_rdata), 
    .in_mem_wdata         (in_mem_wdata),                           
    .wful_mem_waddr       (wful_mem_waddr),
    .rem_mem_raddr        (rem_mem_raddr),
    .wrst_n               (wrst_n),                           
    .wclken               (winc),
    .wful_mem_wfull       (wful_mem_wfull),                           
    .wclk                 (wclk),
    .rrst_n               (rrst_n),
    .rclken               (rinc), 
    .rem_mem_rempty       (rem_mem_rempty), 
    .rclk                 (rclk)
  );

  //读空判断
  rem
  #(ASIZE)    
  u_rem                          
  (
    .rem_mem_rempty       (rem_mem_rempty),                          
    .rem_mem_raddr        (rem_mem_raddr),                          
    .rem_rtow_rgad        (rem_rtow_rgad),
    .wtor_rem_rsad        (wtor_rem_rsad),                          
    .rinc                 (rinc),
    .rclk                 (rclk),                          
    .rrst_n               (rrst_n)
  );

  //写满判断
  wful 
  #(ASIZE)    
  u_wful                         
  (
    .wful_mem_wfull       (wful_mem_wfull),
    .wful_mem_waddr       (wful_mem_waddr),  
    .wful_wtor_wgad       (wful_wtor_wgad),
    .rtow_wful_wsad       (rtow_wful_wsad),    
    .winc                 (winc),
    .wclk                 (wclk),        
    .wrst_n               (wrst_n)
  );

  //将空标志位
  aem
  #(ASIZE,ALMOST_GAP)
  u_aem
  (
    .aem_out_alempty      (aem_out_alempty),
    .rem_rtow_rgad        (rem_rtow_rgad),
    .wtor_rem_rsad        (wtor_rem_rsad),
    .rinc                 (rinc),
    .rclk                 (rclk),
    .rrst_n               (rrst_n)
  );

  //位将满标志
  aful
  #(ASIZE,ALMOST_GAP)
  u_aful
  (
    .aful_in_alfull       (aful_in_alfull),
    .wful_wtor_wgad       (wful_wtor_wgad),
    .rtow_wful_wsad       (rtow_wful_wsad),
    .winc                 (winc),
    .wclk                 (wclk),
    .wrst_n               (wrst_n)
  );
  endmodule

(2)存储空间配置模块

//配置存储器
module fifomem
#(
    parameter  DATASIZE = 32,                                  
    parameter  ADDRSIZE = 6                         
) // Number of mem address bits
(
    output reg  [DATASIZE-1:0]      mem_out_rdata,                             //读出的数据
    input       [DATASIZE-1:0]      in_mem_wdata,                              //写入的数据
    input       [ADDRSIZE-1:0]      wful_mem_waddr, rem_mem_raddr,             //读写的地址
    input                           wrst_n,wclken, wful_mem_wfull, wclk,       //写使能,写满和写时钟
    input                           rrst_n,rclken, rem_mem_rempty, rclk 
);

    //定义一个存储空间
    localparam DEPTH = 1<<ADDRSIZE;                     //获取数据深度          
    reg [DATASIZE-1:0] mem [0:DEPTH-1];                 //定义数据存储器:位宽为[DATASIZE-1:0],个数为[0:DEPTH-1] 

    //写数据操作
    always @(posedge wclk or negedge wrst_n)begin       //当写使能有效且还未写满时将数据写入实体存储器中
        if(!wrst_n)
            mem[wful_mem_waddr] <=32'bx;
        else if (wclken && !wful_mem_wfull)
            mem[wful_mem_waddr] <= in_mem_wdata;
    end
    //读数据操作
    always @(posedge rclk or negedge rrst_n ) begin
        if(!rrst_n)
            mem_out_rdata<=32'bx;
        else if(rclken && !rem_mem_rempty)
            mem_out_rdata <= mem[rem_mem_raddr];
    end
    endmodule

(3)读指针同步到写时钟域

//将读指针同步到写时钟域
module rtowsync
#(
    parameter ADDRSIZE = 6
)
(
    output reg [ADDRSIZE:0] rtow_wful_wsad,                  //读同步到写
    input      [ADDRSIZE:0] rem_rtow_rgad,                   //输入读地址格雷码指针
    input                   wclk, wrst_n
);
  reg [ADDRSIZE:0] wq1_rptr;
 
  always @(posedge wclk or negedge wrst_n)   
      if (!wrst_n) begin
          wq1_rptr <= 0;          
          rtow_wful_wsad <= 0;
      end           
      else begin        
          wq1_rptr<= rem_rtow_rgad;
          rtow_wful_wsad<=wq1_rptr;
      end          
  endmodule

(4)写地址指针同步到读时钟域

//写地址指针同步到读时钟域
module wtorsync
#(parameter ADDRSIZE = 6)
(
    output reg      [ADDRSIZE:0]    wtor_rem_rsad,      //写同步到读
    input           [ADDRSIZE:0]    wful_wtor_wgad,     //写指针作为输入
    input                           rclk, rrst_n        //写时钟和写复位
);
 
  reg [ADDRSIZE:0] rq1_wptr;
 
  always @(posedge rclk or negedge rrst_n)   
      if (!rrst_n)begin
          rq1_wptr <= 0;
          wtor_rem_rsad <= 0;
      end 
      else begin
          rq1_wptr <= wful_wtor_wgad;
          wtor_rem_rsad <= rq1_wptr;
      end
endmodule

(5)读空判断模块

//读空判断,判断地址指针的格雷码
/*
    关于代码的几点理解与注意事项:
        读空判断是读写地址一致,而不止有00_000、10_000两个状态
        
*/
module rem
#(
    parameter ADDRSIZE = 4
)
(
    output reg                          rem_mem_rempty      ,               //输出的读空标志信号
    output          [ADDRSIZE-1:0]      rem_mem_raddr       ,               //二进制形式的读地址
    output reg      [ADDRSIZE  :0]      rem_rtow_rgad       ,               //格雷码形式的读指针
    input           [ADDRSIZE  :0]      wtor_rem_rsad       ,               //写指针与读时钟同步信号
    input                               rinc,rclk,rrst_n                    //读请求信号,写时钟,写复位
);
    reg  [ADDRSIZE:0] rbin;                                     //二进制读地址指针                                
    wire [ADDRSIZE:0] rgraynext, rbinnext;                      //次态的二进制指针和格雷码指针

    //两种码制地址指针同步:将二进制的读指针与格雷码进制的读指针同步
    always @(posedge rclk or negedge rrst_n) 
        if (!rrst_n) begin
            rbin <= 0;
            rem_rtow_rgad <= 0;            
        end  
        else begin        
            rbin <= rbinnext;                      //直接作为存储实体的地址
            rem_rtow_rgad <= rgraynext;            //输出到 sync_r2w.v模块,被同步到 wrclk 时钟域
        end

    // Memory read-address pointer (okay to use binary to address memory)
    assign rem_mem_raddr     = rbin[ADDRSIZE-1:0];              //直接作为存储实体的地址,比如连接到RAM存储实体的读地址端。
    assign rbinnext  = rbin + (rinc & ~rem_mem_rempty);         //没有读空且有读请求的时候读指针加1
    assign rgraynext = (rbinnext>>1) ^ rbinnext;                //将二进制的读指针转为格雷码


    // FIFO empty when the next rem_rtow_rgad == synchronized wptr or on reset 
    assign rempty_val = (rgraynext == wtor_rem_rsad);                //当读指针等于同步后的写指针,则为空
    //读空标志位与读时钟同步
    always @(posedge rclk or negedge rrst_n) 
        if (!rrst_n)
            rem_mem_rempty <= 1'b1; 
        else     
            rem_mem_rempty <= rempty_val;
 
endmodule

(6)写满判断模块

//写满判断,判断格雷码地址指针
/*
    关于代码的几点理解与注意事项:
        写满标志为读写格雷码最高两位相反,低位相同
*/
module wful
#(
    parameter ADDRSIZE = 6
) 
(
    output reg                          wful_mem_wfull          ,        //写满标志位
    output          [ADDRSIZE-1:0]      wful_mem_waddr          ,        //二进制写指针
    output reg      [ADDRSIZE  :0]      wful_wtor_wgad          ,        //格雷码读指针
    input           [ADDRSIZE  :0]      rtow_wful_wsad          ,        //读指针同步到写时钟信号
    input                               winc,wclk,wrst_n                 //写请求,写时钟,写复位
);
    reg  [ADDRSIZE:0] wbin;
    wire [ADDRSIZE:0] wgraynext, wbinnext;

    //两种码制地址指针同步:将二进制的读指针与格雷码进制的读指针同步
    always @(posedge wclk or negedge wrst_n)   
        if (!wrst_n)
            {wbin, wful_wtor_wgad} <= 0;   
        else         
            {wbin, wful_wtor_wgad} <= {wbinnext, wgraynext};

    // Memory write-address pointer (okay to use binary to address memory) 
    assign wful_mem_waddr        = wbin[ADDRSIZE-1:0];                 //直接作为存储实体的地址,比如连接到RAM存储实体的读地址端。
    assign wbinnext   = wbin + (winc & ~wful_mem_wfull);               //写请求有效且未写满时,写地址加一
    assign wgraynext = (wbinnext>>1) ^ wbinnext;              //二进制代码转换为格雷码
    //判断是否写满
    assign wfull_val = (wgraynext=={~rtow_wful_wsad[ADDRSIZE:ADDRSIZE-1],rtow_wful_wsad[ADDRSIZE-2:0]}); 

    //写满信号同步到时钟
    always @(posedge wclk or negedge wrst_n)
        if (!wrst_n)
            wful_mem_wfull  <= 1'b0;   
        else     
            wful_mem_wfull  <= wfull_val;
    
  endmodule

(7)“将空”信号产生模块

//将空信号的产生
module aem
#(
    parameter ADDRSIZE = 6,
    parameter ALMOST_GAP = 3 
)
(
    output reg                      aem_out_alempty          ,       //输出的读空信号
    input       [ADDRSIZE:0]        rem_rtow_rgad            ,       //格雷码形式的读指针
    input       [ADDRSIZE:0]        wtor_rem_rsad            ,       //写同步到读的格雷码指针
    input                           rinc,rclk,rrst_n    
);
    //将格雷码转换为二进制代码
    wire [ADDRSIZE  :0] r_bin;                          //读的二进制地址指针
    wire [ADDRSIZE  :0] wtor_bin;                       //写转换到读时钟域二进制地址指针
    wire almost_empty_val;

    //将格雷码地址指针转换为二进制地址指针
    assign r_bin[ADDRSIZE] =  rem_rtow_rgad[ADDRSIZE];
    assign wtor_bin[ADDRSIZE] =  wtor_rem_rsad[ADDRSIZE];
    generate 
    genvar i;
        for (i=0;i<ADDRSIZE;i=i+1) begin:g2b
            assign r_bin[i] = rem_rtow_rgad[i]^r_bin[i+1];
            assign wtor_bin[i] = wtor_rem_rsad[i]^wtor_bin[i+1]; 
        end
    endgenerate

    //输出将空信号
    assign almost_empty_val = ((wtor_bin-r_bin)<=ALMOST_GAP);

    always@(posedge rclk or negedge rrst_n)
        if(!rrst_n)
            aem_out_alempty<=1'b1;
        else
            aem_out_alempty<=almost_empty_val;
endmodule

(8)“将满”信号产生模块

module aful
#(
    parameter ADDRSIZE = 6,
    parameter ALMOST_GAP = 3
)
(
    output reg                      aful_in_alfull ,
    input       [ADDRSIZE:0]        wful_wtor_wgad            ,       //格雷码形式读地址指针
    input       [ADDRSIZE:0]        rtow_wful_wsad        ,       //读同步到写格雷码地址指针    
    input                           winc,wclk,wrst_n
);
    //将格雷码转换为二进制代码
    wire [ADDRSIZE  :0] w_bin;                          //读的二进制地址指针
    wire [ADDRSIZE  :0] rtow_bin;                       //写转换到读时钟域二进制地址指针
    wire almost_full_val;
    wire [ADDRSIZE  :0] wgap_reg;
    localparam DEPTH = 1<<ADDRSIZE;
    
    //将格雷码地址指针转换为二进制地址指针
    assign w_bin[ADDRSIZE] =  wful_wtor_wgad[ADDRSIZE];
    assign rtow_bin[ADDRSIZE] =  rtow_wful_wsad[ADDRSIZE];
    generate 
    genvar i;
        for (i=0;i<ADDRSIZE;i=i+1) begin:g2b
            assign w_bin[i] = wful_wtor_wgad[i]^w_bin[i+1];
            assign rtow_bin[i] = rtow_wful_wsad[i]^rtow_bin[i+1]; 
        end
    endgenerate

    //输出将满信号
    //assign almost_full_val = (rtow_bin==w_bin+7'd64);
    assign wgap_reg = (w_bin[ADDRSIZE]^rtow_bin[ADDRSIZE])?rtow_bin[ADDRSIZE-1:0]-w_bin[ADDRSIZE-1:0]:DEPTH+rtow_bin-w_bin;
    assign almost_full_val = (wgap_reg<=ALMOST_GAP);

    always@(posedge wclk or negedge wrst_n)
        if(!wrst_n)
            aful_in_alfull <= 1'b0;
        else
            aful_in_alfull <= almost_full_val;
endmodule

(9)测试模块

`timescale 1ns / 1ps
//
// Company: 
// Engineer: 
// 
// Create Date: 2022/04/01 15:43:04
// Design Name: 
// Module Name: fifo_tb
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module fifo_tb();
    reg  [31:0]     in_mem_wdata        ;
    reg             winc, wclk, wrst_n  ; 
    reg             rinc, rclk, rrst_n  ;
    wire [31:0]     mem_out_rdata       ;  
    wire            wfull               ;  
    wire            rempty              ;  
    localparam CYCLE = 20;
    localparam CYCLE1 = 40;

    fifo
    u_fifo (
                   .mem_out_rdata(mem_out_rdata),  
                   .wfull(wfull),  
                   .rempty(rempty),  
                   .in_mem_wdata (in_mem_wdata),  
                   .winc  (winc), 
                   .wclk  (wclk), 
                   .wrst_n(wrst_n), 
                   .rinc(rinc), 
                   .rclk(rclk), 
                   .rrst_n(rrst_n)
     );
    
    //读写时钟的产生
    //时钟周期,单位为ns,可在此修改时钟周期。
        //生成本地时钟50M

        //写时钟远大于读时钟
        initial begin
            wclk = 0;
            forever
            #(CYCLE/2)
            wclk=~wclk;
        end
        initial begin
            rclk = 0;
            forever
            #(CYCLE1/2)
            rclk=~rclk;
        end

        // //读时钟远大于写时钟
        //     initial begin
        //         wclk = 0;
        //         forever
        //         #(CYCLE1/2)
        //         wclk=~wclk;
        //     end
        //     initial begin
        //         rclk = 0;
        //         forever
        //         #(CYCLE/2)
        //         rclk=~rclk;
        //     end
    //复位信号
        initial begin
            wrst_n = 1;
            #2;
            wrst_n = 0;
            #(CYCLE*3);
            wrst_n = 1;
        end
        
         initial begin
            rrst_n = 1;
            #2;
            rrst_n = 0;
            #(CYCLE*3);
            rrst_n = 1;
        end

    //复位信号测试
        // initial begin
        //     #10_000;
        //     wrst_n = 0;
        //     #10_060;
        //     wrst_n = 1;
        // end
        
        // initial begin
        //     rrst_n = 1;
        //     #10_000;
        //     rrst_n = 0;
        //     #10_060;
        //     rrst_n = 1;
        // end

    //读写使能信号产生
        initial begin
            winc=0;
            #5 winc=1;
        end 

        initial begin
           rinc=0;
            #5 rinc=1;
        end

        //读写使能信号测试
        // initial begin
        //         #10_000;
        //         winc=0;
        //         #11_000; 
        //         winc=1;
        //     end 

        // initial begin
        //     #10_000
        //     rinc=0;
        //     #11_000
        //     rinc=1;
        //     end

    //   #5 out_mem_rinc =1;
    //             always  @(posedge wclk or negedge wrst_n)begin
    //                 if(wrst_n==1'b0)begin
    //                     winc <= 0;
    //                     rinc <= 0;
    //                 end
    //                 else begin
    //                     winc <= $random;
    //                     rinc <= $random;
    //                 end
    //             end
    
    //             always  @(posedge rclk or negedge rrst_n)begin
    //                 if(rrst_n==1'b0)begin                  
    //                     rinc <= 0;
    //                 end
    //                 else begin                
    //                     rinc <= $random;
    //                 end
    //             end
    // always@(*)begin
    //   if(winc == 1)
    //     in_mem_wdata= $random ;
    //   else
    //     in_mem_wdata = 0;
    // end  
    
    //随机数的生成,产生写入的随机数
    always #30 in_mem_wdata= $random ;
endmodule

5.仿真结果

(1)写时钟频率大于读时钟频率

a.初始化结果

写时钟大于读时钟仿真时,使用的写时钟是50MHZ,读时钟是25MHZ。仿真结果
如下图所示。首先会将使能信号拉高让读写操作有效,其次将读写复位信号先拉低后重新拉高,初始化读写操作。读写地址复位后为初始地址,在使能信号拉高后正常读写时递增。写入的数据由系统函数$random间隔30个系统时钟更新一个随机数。
在这里插入图片描述

b.地址指针跨时钟域同步

写同步到读时钟域的写地址格雷码指针“wtor_rem_rsad”相较于同步前的写地址格雷
码指针慢了两个读时钟周期。即同步模块会在第一个读时钟上升沿进行采样,地址会在第二个读时钟完成同步。同理,读同步到写时钟域的读格雷码地址指针“rtow_wful_wsad”相较于同步前的读地址格雷码指针慢了两个写时钟周期。
写地址指针同步到读时钟域:

在这里插入图片描述
读地址指针同步到写时钟域:
在这里插入图片描述

c.读写数据

在初始化以后,在复位信号无效且使能信号有效时,在写时钟沿到来时刻开始按照地
址依次写入数据,如下图所示。在这里插入图片描述
在读出数据时,见下图,会将写入的数据(见第一行),按照时间先后顺序,无丢失的读出,只是由于读写时钟不是同一个时钟,所以读出数据慢于写入数据。
写入的数据
在这里插入图片描述
读出的数据
在这里插入图片描述

d.“空”和“满”信号

初始化后存储空间内还没有数据,所以会将输出的读空信号“rem_mem_rempty”拉高,而且读空信号会在开始写入数据后(写地址不为0)延迟两个同步时钟后才会拉低。
“读空”信号
在这里插入图片描述
写满信号如下图所示,从开始存储器中由空逐渐写如数据,因为写时钟快于读数据时钟,当写入的数据写满了存储空间后,需要每读出一个数据后才能写入一个数据,所以异步FIFO一直处于“写满”和“未写满”状态,所以写满标志信号“wful_mem_wfull”输出在1和0之间振荡。
“写满”信号
在这里插入图片描述

d.“将空”和“将满”信号

读空信号是读写二进制地址相聚小于阈值时,输出为高,验证时设置的阈值是3。同读空信号一样,在初始化后在没有写入数据前,存储器中一直没有数据,所以将满信号一直输出为高。此处有个问题是,将空信号设置的阈值是3,而读空信号“rem_mem_rempty”拉低后一个读时钟周期,将空信号“aem_out_alempty”为什么就拉低了?这是因为写时钟频率大于读时钟,在读复位信号失效后,第一个读时钟上升沿采集到的写地址是2(小于阈值),第二个读时钟上升沿采集到的写地址是4(大于阈值),所以前一个读时钟将读空标标志位拉低以后,后一个读时钟就把将空信号拉低了。
“将空”信号
在这里插入图片描述
“将满”信号
将满信号“aful_in_alfull”同写满信号“wful_mem_wfull”一样,在开始写入数据时一直为低,因为写时钟快于读时钟,所以在将写满后(读写地址小于阈值3),将满信号一直为高。
在这里插入图片描述

(2)读时钟频率大于写时钟频率

读时钟大于写时钟,使用的写时钟是25MHZ,读时钟是50MHZ。仿真结果如下图所
示。首先会将使能信号拉高让读写操作有效,其次将读写复位信号先拉低后重新拉高,初始化读写操作。读写地址复位后为初始地址,在使能信号拉高后正常读写时递增。写入的数据由系统函数$random间隔30个系统时钟更新一个随机数。
可以看出写操作有效时写入的数据会在读操作有效时按顺序被读出。因为初始化后存储空间中没有数据,所以读空指示信号“rem_mem_rempty”输出为高,在正常读写后,因为写慢于读,所以每写入一个数据都会被立即读出,所以存储器一直在有有效数据和空状态间变换,所以读空信号一直在0和1间变换。也正因为如此,存储空间中的数据一直小于将空阈值,所以将空信号一直为高电平。在这里插入图片描述

最后

以上就是聪慧冬日为你收集整理的verilog实现异步fifo基于verilog实现异步fifo的全部内容,希望文章能够帮你解决verilog实现异步fifo基于verilog实现异步fifo所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(55)

评论列表共有 0 条评论

立即
投稿
返回
顶部