0

I wrote an asynchronous FIFO in SystemVerilog for clock domain crossing and the read/empty is working. When I try to write until it is full, the full flag goes high, but the write will push on data one more time than it is suppose to (overwrites one spot that has not been read).

This is strange because when I look at the simulation results the state that the write logic is in is full and write is low, but it still writes to the ram. Here is the code and a simple testbench.

`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

dual port ram module:

`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

Simple testbench:

`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

Below are the results. As you can see at the yellow marker, write is low for both the ram and async fifo (wr_en is high from the tb), but mem (signal at the very bottom) is still writen to

testbench results

2
  • Not a full answer, but 1. It's pretty unusual to use "write" in the sensitivity list of a dual-port ram. Did you mean to use "clk" instead? 2. There's probably a glitch on the "write" signal at the time pointed to by your cursor. "write" is assigned in a comb statement so there's probably a glitch where "state_a" changes. Commented Mar 10, 2020 at 19:18
  • @KevinKruse Yes, I removed the write and added the clock to the sensitivity list (posedge clk) but got the same result. Is there a way to fix the state_a change glitch? Is it only a simulation glitch or would something like this run on hw without that glitch? Commented Mar 10, 2020 at 19:56

1 Answer 1

0

There's no guarantee on the ordering of these processes:

    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);

The assign can happen before or after the always, so top_grayA could end up with the value from either before or after the increment.

Aside from that, you used always @* instead of always_comb here, so maybe you intended to create latches. But that generally isn't recommended for FPGAs.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.