同步和异步fifo设计

同步fifo设计

信号列表

  • clk:fifo操作时钟

  • wr_en:fifo写使能,在wr_en有效的情况下,上升沿采集wdata数据。

  • wdata:fifo写数据。

  • full:fifo满有效。高电平时,代表当前wdata并未写入fifo。

  • rd_en:fifo读使能。在rd_en有效,empty无效时,数据同步送出fifo。

  • empty:fifo空有效。高电平时,代表当前rdata无效。

要点

使用两个地址寄存器,分别为raddr和waddr,假设fifo深度为8,则地址位置从000->111,只需要让raddr和waddr是4个位宽,并且让对fifo读写操作后,raddr和waddr地址加1即可。

当raddr[3:0]=waddr[3:0],则代表fifo是空的。

当raddr[3]!=waddr[3] raddr[2:0]=waddr[2:0],则代表fifo是满的。

verilog

module fifo_syn  #(
    parameter wa = 3,
    parameter wd = 4)
(
    input rst_n,
    input clk,
    input wr_en,
    input [wd-1:0] wdata,
    output full,
    input rd_en,
    output [wd-1:0] rdata,
    output empty
);
    //syn [wa-1:0] raddr ^  [wa-1:0] raddr 
    reg [wa:0] raddr; 
    reg [wa:0] waddr;
     wire condition_1 = &(~(raddr[wa-1:0]^waddr[wa-1:0]));
    wire condition_2 =  &(~(raddr[wa]^waddr[wa]));
    assign full = ~condition_2&condition_1;
    assign empty = condition_1&condition_2;

    parameter deep  = (1<<wa)-1;
    reg [wd-1:0] fifo_mem[deep:0] ;

    always @(posedge clk) begin
        if(!rst_n)
            waddr <= {wa{1'b0}};
        else if(wr_en&&!full) begin
            fifo_mem[waddr[wa-1:0]] <= wdata;
            waddr <= waddr + 1'b1;
        end
    end
assign  rdata = (!empty&&rd_en)?fifo_mem[raddr[wa-1:0]]:{wd{1'b0}};
    always @(posedge clk) begin
        if(!rst_n)
            raddr <= {wa{1'b0}};
        else if(rd_en&&!empty) begin
            raddr <= raddr + 1'b1;
        end
    end
endmodule
`timescale 1ns / 1ps
module tb();
reg clk;
reg rst_n;
reg wr_en,rd_en;
wire empty,full;
reg [3:0] wdata;
wire [3:0] rdata;
fifo_syn #(
    .wa(3),
    .wd(4)
) fifo_syn_inst(
    .rst_n (rst_n),
    .clk (clk),
    .wr_en(wr_en),
    .wdata(wdata),
    .full(full),
    .rd_en(rd_en),
    .rdata(rdata),
    .empty(empty)
);

always #10 clk=~clk;

initial begin
 clk = 0;
 rd_en = 0;
 rst_n = 0;
 #100 
 rst_n = 1;
end
    always @(posedge clk) begin
        if(!rst_n)begin
            wr_en <= 1'b0;
        end else begin
            wr_en <= 1'b1;
            wdata <= $random%16;
        end
    end

     always @(posedge clk) begin
         if(!rst_n)begin
             rd_en <= 1'b0;
         end else begin
             rd_en <= 1'b1;
         end
     end


endmodule

异步fifo设计

信号列表

  • wclk:写fifo的时钟

  • wr_en:fifo写使能,在wr_en有效的情况下,上升沿采集wdata数据。

  • wdata:fifo写数据。

  • full:fifo满有效。高电平时,代表当前wdata并未写入fifo。

  • rclk:读fifo的时钟。

  • rd_en:fifo读使能。在rd_en有效,empty无效时,数据同步送出fifo。

  • rdata_valid:高电平时,代表当前rdata有效。

  • empty:fifo空有效。

要点

raddr和waddr位宽和前面同步fifo设计一致,不过由于异步时钟域的问题,需要使用双同步触发器将其中一个时钟域同步到另一个时钟域中,同时为了避免同步使用多bit信号,需要对raddr和waddr进行格雷码编码。

假设fifo深度为8,则地址位置从000->111,只需要让raddr和waddr是4个位宽,并且让对fifo读写操作后,raddr和waddr地址加1即可。由于使用了格雷码编码。观察4bit格雷码编码表格:

二进制格雷码
00000000
00010001
00100011
00110010
01000110
01010111
01100101
01110100
10001100
10011101
10101111
10111110
11001010
11011011
11101001
11111000

可以得到 当raddr[3]=waddr[3],raddr[2]=waddr[2],raddr[1:0]=waddr[1:0],则代表fifo是空的。

当raddr[3]!=waddr[3],raddr[2]!=waddr[2],raddr[1:0]=waddr[1:0],则代表fifo是满的。

  • 二进制码转换成二进制格雷码

    • 保留二进制码的最高位作为格雷码的最高位,而次高位格雷码为二进制码的高位与次高位相异或,同理其余各位的格雷码为当前位的二进制于,前1位的二进制异或。
  • 同步方式

    • 双同步触发器
  • 空信号

    • 将写时钟域写地址寄存器,同步到读时钟域,与读时钟域读地址进行对比产生空信号
  • 满信号

    • 将读时钟域读地址寄存器,同步到写时钟域,与写时钟域写地址进行对比产生满信号
  • 同步过程中出错

    • 假设格雷码写指针从000->001,将写指针同步到读时钟域同步出错,出错的结果只可能是000->000,因为相邻位的格雷码每次只有一位变化,这个出错结果实际上也就是写指针没有跳变而是保持不变。从而可能让空标志产生,但是不会空读。同样当读地址同步出错,从而可能让满标志产生,同样不会满写。
  • 写快读慢

    • 满标志
      • 读地址同步到写时钟域,因为读慢写快,所以不会有读地址遗漏,同步消耗时钟周期,所以同步后的读地址滞后(小于等于)当前读地址,所以可能满标志会提前产生,此时满并非真满。
    • 空标志
      • 写地址同步到读时钟域,因为读慢写快,所以同步时会有写地址遗漏,但是漏掉的地址会对FIFO的空标志产生影响吗?实际不会,同步到读时钟的写地址要小于等于实际写地址。这样在判断空标志时会出现不是真正空的情况,但是遗漏的地址没有对FIFO的逻辑操作产生影响。
  • 写慢读快

    • 满标志
      • 读地址同步到写时钟域,因为写慢读快,所以同步时会有读地址遗漏,但是漏掉的地址会对FIFO的满标志产生影响吗?实际不会,同步到写时钟的读地址要小于等于实际读地址。这样在判断满标志时会出现不是真正满的情况,但是遗漏的地址没有对FIFO的逻辑操作产生影响。
    • 空标志
      • 写地址同步到读时钟域,因为写慢读快,所以不会有写地址遗漏,同步消耗时钟周期,所以同步后的写地址滞后(小于等于)当前写地址,所以可能空标志会提前产生,此时空并非真空。

因此无论哪种情况多不会出现错误。

module fifo_asy  #(
    parameter wa = 3,
    parameter wd = 4)
(
    input rst_n,
    input wclk,
    input wr_en,
    input [wd-1:0] wdata,
    output full,
    input rd_en,
    input rclk,
    output reg [wd-1:0]  rdata,
    output reg rdata_valid,
    output empty
);
//syn [wa-1:0] raddr &  [wa-1:0] raddr 
    reg [wa:0] raddr; 
    reg [wa:0] waddr;

//gray    
    wire [wa:0] gray_waddr = (waddr>>1)^waddr;
    wire [wa:0] gray_raddr = (raddr>>1)^raddr;

// full rclk->wclk 
    reg [wa:0] gray_raddr_r1,gray_raddr_r2;
    always @(posedge wclk) begin
        if(!rst_n)begin
            gray_raddr_r1 <= {wa{1'b0}};
            gray_raddr_r2 <= {wa{1'b0}};
        end else begin
            gray_raddr_r1 <= gray_raddr;
            gray_raddr_r2 <= gray_raddr_r1;
        end
    end

    wire full_con1 =  &(~(gray_waddr[wa-2:0]^gray_raddr_r1[wa-2:0]));
    wire full_con2 =  &(~(gray_waddr[wa:wa-1]^gray_raddr_r1[wa:wa-1]));
    assign full = full_con1&~full_con2;

// empty wclk->rclk 
    reg [wa:0] gray_waddr_r1,gray_waddr_r2;
    always @(posedge rclk) begin
        if(!rst_n)begin
            gray_waddr_r1 <= {wa{1'b0}};
            gray_waddr_r2 <= {wa{1'b0}};
        end else begin
            gray_waddr_r1 <= gray_waddr;
            gray_waddr_r2 <= gray_waddr_r1;
        end
    end
    wire empty_con1 =  &(~(gray_waddr_r2[wa-2:0]^gray_raddr[wa-2:0]));
    wire empty_con2 =  &(~(gray_waddr_r2[wa:wa-1]^gray_raddr[wa:wa-1]));
    assign empty = empty_con1&empty_con2;

    parameter deep  = (1<<wa)-1;
    reg [wd-1:0] fifo_mem[deep:0] ;

    always @(posedge wclk) begin
        if(!rst_n)
            waddr <= {wa{1'b0}};
        else if(wr_en&&!full) begin
            fifo_mem[waddr[wa-1:0]] <= wdata;
            waddr                   <= waddr + 1'b1;
        end
    end

   // assign  rdata = (!empty&&rd_en)?fifo_mem[raddr[wa-1:0]]:{wd{1'b0}};

    always @(posedge rclk) begin
        if(!rst_n)begin
            raddr <= {wa{1'b0}};
            rdata <= {wd{1'b0}};
            rdata_valid <= 1'b0;
        end else if(rd_en&&!empty) begin
            raddr <= raddr + 1'b1;
            rdata <= fifo_mem[raddr[wa-1:0]];
            rdata_valid <= 1'b1;
        end else begin
              rdata         <= {wd{1'b0}};
                rdata_valid <= 1'b0;
        end
    end
endmodule

这里有两个变化:

  • rdata数据将不在使用assign,而是在读时钟域寄存器输出,这样才能保证在读出来的时候被其他模块稳定读入。
  • 由于寄存了rdata,所以增加一个rdata_valid信号来同步指示rdata,高电平时代表rdata数据有效。
`timescale 1ns / 1ps
module tb();
reg wclk;
reg rclk;
reg rst_n;
reg wr_en,rd_en;
wire empty,full;
wire rdata_valid;
reg [3:0] wdata;
wire [3:0] rdata;
fifo_asy #(
    .wa(3),
    .wd(4)
) fifo_syn_inst(
    .rst_n (rst_n),
    .wclk (wclk),
    .wr_en(wr_en),
    .wdata(wdata),
    .full(full),
    .rd_en(rd_en),
    .rclk (rclk),
    .rdata(rdata),
     .rdata_valid(rdata_valid),
    .empty(empty)
);

always #10 wclk=~wclk;
always #10 rclk=~rclk;

initial begin
 wclk = 0;
 rclk = 0;
 rd_en = 0;
 rst_n = 0;
 #100 
 rst_n = 1;
end
    always @(posedge wclk) begin
        if(!rst_n)begin
            wr_en <= 1'b0;
            wdata <= 1'b0;
        end else begin
            if(!full)begin
                wr_en <= 1'b1;
                wdata <= wdata + 1'b1;
            end
        end
    end

     always @(posedge rclk) begin
         if(!rst_n)begin
            rd_en <= 1'b0;
         end else begin
            rd_en <= 1'b1;
         end
     end
endmodule

写快读慢仿真

延时修改如下:

always #10 wclk=~wclk;
always #20 rclk=~rclk;

在这里插入图片描述

写慢读快仿真

延时修改如下:

always #20 wclk=~wclk;
always #10 rclk=~rclk;

在这里插入图片描述