module async_fifo #(
    parameter DATA_WIDTH = 8,
    parameter FIFO_DEPTH  = 16
)(
    input wr_clk,
    input wr_rst_n,
    input wr_en,
    input [DATA_WIDTH-1:0] wr_data,
    output full,

    input rd_clk,
    input rd_rst_n,
    input rd_en,
    output [DATA_WIDTH-1:0] rd_data,
    output empty
);

    reg [DATA_WIDTH-1:0] mem [FIFO_DEPTH -1 : 0];
    reg [$clog2(FIFO_DEPTH) : 0] wr_ptr, rd_ptr;
    
    integer i;
    always@(posedge wr_clk or negedge wr_rst_n) begin
        if(!wr_rst_n) begin
            wr_ptr <= 'd0;
            for(i=0;i<FIFO_DEPTH;i=i+1) begin
                    mem[i] <= 'd0;
                end
        end else if(wr_en && !full) begin
            mem[wr_ptr[$clog2(FIFO_DEPTH)-1:0]] <= wr_data;
            wr_ptr <= wr_ptr + 1'b1;
        end else begin
            wr_ptr <= wr_ptr;
        end
    end

    always@(posedge rd_clk or negedge rd_rst_n) begin
        if(!rd_rst_n) begin
            rd_ptr <= 'd0;
        end else if(rd_en && !empty) begin
              rd_ptr <= rd_ptr + 1'b1;
        end else begin
            rd_ptr <= rd_ptr;
        end
    end

    wire [$clog2(FIFO_DEPTH):0] wr_ptr_g , rd_ptr_g;

    assign wr_ptr_g = wr_ptr ^(wr_ptr >>1);
    assign rd_ptr_g = rd_ptr ^(rd_ptr >>1);

    reg [$clog2(FIFO_DEPTH):0] wr_ptr_gr , wr_ptr_grr;
    reg [$clog2(FIFO_DEPTH):0] rd_ptr_gr , rd_ptr_grr;

    always@(posedge rd_clk or negedge rd_rst_n) begin
        if(!rd_rst_n) begin
            wr_ptr_gr <= 0;
            wr_ptr_grr <=0;
        end else begin
            wr_ptr_gr <= wr_ptr_g;
            wr_ptr_grr <= wr_ptr_gr;
        end
    end

    always@(posedge wr_clk or negedge wr_rst_n) begin
        if(!wr_rst_n) begin
            rd_ptr_gr <= 0;
            rd_ptr_grr <=0;
        end else begin
            rd_ptr_gr <= rd_ptr_g;
            rd_ptr_grr <= rd_ptr_gr;
        end
    end
    
    assign rd_data = mem[rd_ptr[$clog2(FIFO_DEPTH)-1:0]];


    assign full = ((wr_ptr_g[$clog2(FIFO_DEPTH)] != 
        rd_ptr_grr[$clog2(FIFO_DEPTH)]) && (wr_ptr_g[$clog2(FIFO_DEPTH)-1] !=
        rd_ptr_grr[$clog2(FIFO_DEPTH)]-1) && (wr_ptr_g[$clog2(FIFO_DEPTH)-2:0] ==
        rd_ptr_grr[$clog2(FIFO_DEPTH)-2 : 0])) ? 1:0;

    assign empty = (rd_ptr_g[$clog2(FIFO_DEPTH) : 0] == 
        wr_ptr_grr[$clog2(FIFO_DEPTH) :0]) ? 1:0;

endmodule
