异步FIFO SystemVerilog

问题描述 投票:0回答:1

我在SystemVerilog中编写了一个异步FIFO,用于时钟域穿越,并且读/空操作正常。当我尝试写入直到其写满时,full标志变高,但是写操作将推入数据的时间比预期的要多(覆盖一个尚未读取的点)。

这很奇怪,因为当我查看仿真结果时,写入逻辑处于已满状态,而写入则处于低状态,但仍会写入RAM。这是代码和一个简单的测试台。

`timescale 1ns / 1ps

module fifo_async
    #(parameter DATA_WIDTH = 8,
      parameter ADDR_WIDTH = 4,
      parameter MEM_SIZE   = 2**ADDR_WIDTH)
     (input logic clka,
      input logic clkb,
      input logic reset,
      input logic rd_en,
      input logic wr_en,
      input logic [DATA_WIDTH-1:0] wrdata,
      output logic [DATA_WIDTH-1:0] rdata,
      output logic empty,
      output logic full
    );

    // ram inputs //
    logic write;
    logic read;
    logic rd;
    logic [ADDR_WIDTH-1:0] waddr;
    logic [ADDR_WIDTH-1:0] raddr;
    logic [DATA_WIDTH-1:0] wdata;

    // top pointer in clock domain A and B //
    logic [ADDR_WIDTH-1:0] top_ptrA;
    logic [ADDR_WIDTH-1:0] top_grayA;
    logic [ADDR_WIDTH-1:0] top_grayA_tmp;
    logic [ADDR_WIDTH-1:0] top_grayB;

    // bottom pointer in clock domain B and A //
    logic [ADDR_WIDTH-1:0] bot_ptrB;
    logic [ADDR_WIDTH-1:0] bot_grayB;
    logic [ADDR_WIDTH-1:0] bot_grayB_tmp;
    logic [ADDR_WIDTH-1:0] bot_grayA;

    // used for full flag logic 
    logic [ADDR_WIDTH-1:0] top_ptr_minus_one;
    logic [ADDR_WIDTH-1:0] top_ptr_minus_one_grayA ;

    // dual port ram //
    dual_port_ram_async #(.DATA_WIDTH(DATA_WIDTH),.ADDR_WIDTH(ADDR_WIDTH))
    async_ram (.clk(clka),.write, .read(rd), .raddr, .waddr, .wdata,.rdata);

    // converts binary to gray code //
    function automatic [ADDR_WIDTH-1:0] gray(input [ADDR_WIDTH-1:0] ptr);
       gray = (ptr >> 1) ^ ptr;
    endfunction

    //====================================================================//
    //===================== CLOCK A DOMAIN (WRITE) =======================//
    //====================================================================//
    typedef enum logic [1:0] {RESETA=2'b00,IDLEA = 2'b01, WRITEA=2'b11,FULL = 2'b10} states_a;

    states_a state_a, next_a;

    // Current State Logic -- sequential logic //
    always_ff @(posedge clka) begin
        if (reset) begin
            state_a <= RESETA;
            end
        else
            state_a <= next_a;
    end

    // next state logic //
    always_comb begin
        unique case(state_a)
        RESETA:
            next_a = IDLEA;
        IDLEA:
            if (wr_en) next_a = WRITEA;
            else next_a = IDLEA;
        WRITEA:
            if (top_grayA == bot_grayA) next_a = FULL;
            else if (!write) next_a = IDLEA;
            else next_a = WRITEA;
        FULL:
            if (top_ptr_minus_one_grayA != bot_grayA) next_a = IDLEA;  
            else next_a = FULL;  
        endcase
    end

     // moore outputs logic //
    always_comb begin
        unique case(state_a)
        RESETA:
        begin
            write = 0;
            full = 0;
            wdata = '0;
            end
        IDLEA:
        begin
            write = 1'b0;
            full  = 1'b0;
        end
        WRITEA:
        begin
            write = wr_en;
            wdata = wrdata;
        end
        FULL:
        begin
            write = 1'b0;   
            full  = 1'b1;
        end
        endcase
    end

    // datapath //
    always @(*) begin
        if (reset) begin
            top_ptr_minus_one = 0;
            top_ptrA = 0;
            waddr = 0;
        end
        else if (write && !full) begin
            top_ptr_minus_one = top_ptrA;
            top_ptrA = top_ptrA + 1;
            waddr = top_ptrA;
        end      
    end

    // convert top_ptrA to graycode //
    assign top_grayA = gray(top_ptrA);
    assign top_ptr_minus_one_grayA = gray(top_ptr_minus_one);

    // Double flop top_grayA to clock domain B //
    always_ff @(posedge clkb) begin
        top_grayA_tmp <= top_grayA;
        top_grayB <= top_grayA_tmp;
    end


    //====================================================================//
    //======================== CLOCK B DOMAIN (READ) ======================//
    //====================================================================//
    typedef enum logic [1:0] {RESETB=2'b00,IDLEB = 2'b01, READB=2'b11,EMPTY = 2'b10} states_b;

    states_b state_b, next_b;

    // current state logic //
    always_ff @(posedge clkb) begin
        if (reset)
            state_b <= RESETB;
        else
            state_b <= next_b;
    end


    //next state logic //
    always_comb begin
        unique case(state_b)
        RESETB:
            next_b = EMPTY;
        EMPTY:
            if (top_grayB != bot_grayB) next_b = IDLEB;
        READB:
            if (top_grayB == bot_grayB) next_b = EMPTY;
            else next_b = IDLEB;
        IDLEB:
            if (rd_en) next_b = READB;
        endcase;   
    end

    // moore outpur logic //
    always_comb begin
        unique case(state_b)
        RESETB:
            begin
            read  = 1'b0;
            empty = 1'b0;
            end
        EMPTY:
        begin
            read  = 1'b0;
            empty = 1'b1;
        end
        READB:
        begin
            read  = 1'b1;
            empty = 1'b0;
        end
        IDLEB:
        begin
            read  = 1'b0;
            empty = 1'b0;
        end
        endcase    
    end

    // Datapath //
    always_comb begin
        if (reset) begin
            bot_ptrB = 0;
            raddr = 0;
        end
        else if (read && !empty) begin
        bot_ptrB = bot_ptrB + 1;
        raddr = bot_ptrB;
        end
    end

    // convert bot_ptrB to gray code //
    assign bot_grayB = gray(bot_ptrB);

    // double flop to cross bot_grayB to clock domain A
    always_ff @(posedge clka) begin
        bot_grayB_tmp <= bot_grayB;
        bot_grayA <= bot_grayB_tmp;
        rd <= read;
    end    

endmodule

双端口内存模块:

`timescale 1ns / 1ps

module dual_port_ram_async

    #(parameter DATA_WIDTH = 8,
      parameter ADDR_WIDTH = 3,
      parameter MEM_SIZE   = 2**ADDR_WIDTH
    )(
    input logic clk,
    input logic write,
    input logic read,
    input logic  [ADDR_WIDTH-1:0] raddr,
    input logic  [ADDR_WIDTH-1:0] waddr,
    input logic  [DATA_WIDTH-1:0] wdata,
    output logic [DATA_WIDTH-1:0] rdata
    );

    bit [DATA_WIDTH-1:0] mem [0:MEM_SIZE-1];

    always @(write) begin
        if (write) begin mem[waddr] = wdata; end
        else begin mem[waddr] = mem[waddr]; end
    end

    assign rdata = (read)?mem[raddr]:'z;

endmodule

简单的测试台:

`timescale 1ns / 1ps
module fifo_async_tb;

    localparam DATA_WIDTH = 8;
    localparam ADDR_WIDTH = 8;   

    localparam Ta = 10;
    localparam Tb = 20;

    //inputs
    logic clka = 0;
    logic clkb = 0;
    logic reset;

    logic rd_en;
    logic wr_en;
    logic [DATA_WIDTH-1:0] wrdata;

    // outputs
    logic [DATA_WIDTH-1:0] rdata;
    logic empty;
    logic full;

    fifo_async fifo(.*);

    always #(Ta/2) clka = ~clka;
    always #(Tb/2) clkb = ~clkb;

    initial begin

    rd_en = 0;
    wr_en = 0;
    wrdata = '0;
    reset = 1;
    #40
    reset = 0;
    #15

    #10
    rd_en = 1;
    #50;   
    rd_en = 0;
        // start test
    repeat(18) begin
    wr_en = 1;
    wrdata++;
    #(Ta/2);
    wr_en = 0;
    #(Ta/2);
    end 

    $stop;
    end

endmodule

下面是结果。正如您在黄色标记处看到的那样,ram和异步fifo的write都较低(wr_en在tb处较高),但mem(最底部的信号)仍被写入

testbench results

fpga system-verilog state-machine
1个回答
0
投票

无法保证这些过程的顺序:

    always @(*) begin
        if (reset) begin
            top_ptr_minus_one = 0;
            top_ptrA = 0;
            waddr = 0;
        end
        else if (write && !full) begin
            top_ptr_minus_one = top_ptrA;
            top_ptrA = top_ptrA + 1;
            waddr = top_ptrA;
        end      
    end

    assign top_grayA = gray(top_ptrA);

assign可以在always之前或之后发生,因此top_grayA可能以递增之前或之后的值结束。

[除此之外,您在这里使用always @*而不是always_comb,所以也许您打算创建锁存器。但这通常不建议用于FPGA。

© www.soinside.com 2019 - 2024. All rights reserved.