/*****************************************************************************
 * Function: Dual Clock Asynchronous FIFO
 * Copyright: Lambda Project Authors. ALl rights Reserved.
 * License:  MIT (see LICENSE file in Lambda repository)
 *
 * Docs:
 *
 * This is a wrapper for selecting from a set of hardened memory macros.
 *
 * A synthesizable reference model is used when the TYPE is DEFAULT. The
 * synthesizable model does not implement the cfg and test interface and should
 * only be used for basic testing and for synthesizing for FPGA devices.
 * Advanced ASIC development should rely on complete functional models
 * supplied on a per macro basis.
 *
 * Technologoy specific implementations of "la_dpram" would generally include
 * one ore more hardcoded instantiations of RAM modules with a generate
 * statement relying on the "TYPE" to select between the list of modules
 * at build time.
 *
 ****************************************************************************/

module la_asyncfifo #(
    parameter DW    = 32,        // Memory width
    parameter DEPTH = 4,         // FIFO depth
    parameter NS    = 1,         // Number of power supplies
    parameter CTRLW = 1,         // width of asic ctrl interface
    parameter TESTW = 1,         // width of asic teset interface
    parameter CHAOS = 0,         // generates random full logic when set
    parameter TYPE  = "DEFAULT"  // Pass through variable for hard macro
) (  // write port
    input                  wr_clk,
    input                  wr_nreset,
    input      [   DW-1:0] wr_din,        // data to write
    input                  wr_en,         // write fifo
    input                  wr_chaosmode,  // randomly assert fifo full when set
    output reg             wr_full,       // fifo full
    // read port
    input                  rd_clk,
    input                  rd_nreset,
    output     [   DW-1:0] rd_dout,       // output data (next cycle)
    input                  rd_en,         // read fifo
    output reg             rd_empty,      // fifo is empty
    // Power signals
    input                  vss,           // ground signal
    input      [   NS-1:0] vdd,           // supplies
    // Generic interfaces
    input      [CTRLW-1:0] ctrl,          // pass through ASIC control interface
    input      [TESTW-1:0] test           // pass through ASIC test interface
);

    // local params
    // The last part is to support DEPTH of 1
    localparam AW = $clog2(DEPTH) + {31'h0, (DEPTH == 1)};

    // local wires
    reg  [AW:0] wr_grayptr;
    reg  [AW:0] wr_binptr;
    reg  [AW:0] wr_binptr_mem;
    wire [AW:0] wr_grayptr_nxt;
    wire [AW:0] wr_binptr_nxt;
    wire [AW:0] wr_binptr_mem_nxt;
    wire [AW:0] wr_grayptr_sync;
    wire        wr_chaosfull;

    reg  [AW:0] rd_grayptr;
    reg  [AW:0] rd_binptr;
    reg  [AW:0] rd_binptr_mem;
    wire [AW:0] rd_grayptr_nxt;
    wire [AW:0] rd_binptr_nxt;
    wire [AW:0] rd_binptr_mem_nxt;
    wire [AW:0] rd_grayptr_sync;

    genvar i;

    //###########################
    //# WRITE SIDE LOGIC
    //###########################

    always @(posedge wr_clk or negedge wr_nreset)
        if (~wr_nreset) begin
            wr_binptr_mem[AW:0] <= 'b0;
            wr_binptr[AW:0]     <= 'b0;
            wr_grayptr[AW:0]    <= 'b0;
        end else begin
            wr_binptr_mem[AW:0] <= (wr_binptr_mem_nxt[AW:0] == DEPTH) ? 'b0 : wr_binptr_mem_nxt[AW:0];
            wr_binptr[AW:0] <= wr_binptr_nxt[AW:0];
            wr_grayptr[AW:0] <= wr_grayptr_nxt[AW:0];
        end

    // Update binary pointer on write and not full
    assign wr_binptr_mem_nxt[AW:0] = wr_binptr_mem[AW-1:0] + {{(AW-1){1'b0}}, (wr_en && ~wr_full)};
    assign wr_binptr_nxt[AW:0] = wr_binptr[AW:0] + {{AW{1'b0}}, (wr_en && ~wr_full)};

    // Convert binary point to gray pointer for sync
    assign wr_grayptr_nxt[AW:0] = wr_binptr_nxt[AW:0] ^ {1'b0, wr_binptr_nxt[AW:1]};

    // Full comparison (gray pointer based)
    // Amir - add support for fifo DEPTH that is not power of 2
    // Note - the previous logic also had a bug that full was high one entry to soon

    reg [AW:0] rd_binptr_sync;
    wire [AW:0] fifo_used;
    integer j;

    always @(*) begin
        rd_binptr_sync[AW] = rd_grayptr_sync[AW];
        for (j = AW; j > 0; j = j - 1)
        rd_binptr_sync[j-1] = rd_binptr_sync[j] ^ rd_grayptr_sync[j-1];
    end

    assign fifo_used = wr_binptr - rd_binptr_sync;

    always @(posedge wr_clk or negedge wr_nreset)
        if (~wr_nreset) wr_full <= 1'b0;
        else
            wr_full <= (wr_chaosfull & wr_chaosmode) |
                  (fifo_used + {{AW{1'b0}}, (wr_en && ~wr_full)}) == DEPTH;

    // Write --> Read clock synchronizer
    for (i = 0; i < (AW + 1); i = i + 1) begin
        la_drsync wrsync (
            .out(wr_grayptr_sync[i]),
            .clk(rd_clk),
            .nreset(rd_nreset),
            .in(wr_grayptr[i])
        );
    end

    //###########################
    //# READ SIDE LOGIC
    //###########################

    always @(posedge rd_clk or negedge rd_nreset)
        if (~rd_nreset) begin
            rd_binptr_mem[AW:0] <= 'b0;
            rd_binptr[AW:0]     <= 'b0;
            rd_grayptr[AW:0]    <= 'b0;
        end else begin
            rd_binptr_mem[AW:0] <= (rd_binptr_mem_nxt[AW:0] == DEPTH) ? 'b0 : rd_binptr_mem_nxt[AW:0];
            rd_binptr[AW:0] <= rd_binptr_nxt[AW:0];
            rd_grayptr[AW:0] <= rd_grayptr_nxt[AW:0];
        end

    assign rd_binptr_mem_nxt[AW:0] = rd_binptr_mem[AW-1:0] + {{(AW-1){1'b0}}, (rd_en && ~rd_empty)};
    assign rd_binptr_nxt[AW:0] = rd_binptr[AW:0] + {{AW{1'b0}}, (rd_en && ~rd_empty)};
    assign rd_grayptr_nxt[AW:0] = rd_binptr_nxt[AW:0] ^ {1'b0, rd_binptr_nxt[AW:1]};

    // Full comparison (gray pointer based)
    always @(posedge rd_clk or negedge rd_nreset)
        if (~rd_nreset) rd_empty <= 1'b1;
        else rd_empty <= (rd_grayptr_nxt[AW:0] == wr_grayptr_sync[AW:0]);

    // Read --> write clock synchronizer
    for (i = 0; i < (AW + 1); i = i + 1) begin
        la_drsync rdsync (
            .out(rd_grayptr_sync[i]),
            .clk(wr_clk),
            .nreset(wr_nreset),
            .in(rd_grayptr[i])
        );
    end

    //###########################
    //# Dual Port Memory
    //###########################

    reg [DW-1:0] ram[DEPTH-1:0];

    // Write port (FIFO input)
    always @(posedge wr_clk) if (wr_en & ~wr_full) ram[wr_binptr_mem[AW-1:0]] <= wr_din[DW-1:0];

    // Read port (FIFO output)
    assign rd_dout[DW-1:0] = ram[rd_binptr_mem[AW-1:0]];

    //############################
    // Randomly Asserts FIFO full
    //############################

    generate
        if (CHAOS) begin
            // TODO: implement LFSR
            reg chaosreg;
            always @(posedge wr_clk or negedge wr_nreset)
                if (~wr_nreset) chaosreg <= 1'b0;
                else chaosreg <= ~chaosreg;
            assign wr_chaosfull = chaosreg;
        end else begin
            assign wr_chaosfull = 1'b0;
        end

    endgenerate


endmodule
