// ----------------------------------------------------------------------
// Copyright (c) 2015, The Regents of the University of California All
// rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// 
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
// 
//     * Redistributions in binary form must reproduce the above
//       copyright notice, this list of conditions and the following
//       disclaimer in the documentation and/or other materials provided
//       with the distribution.
// 
//     * Neither the name of The Regents of the University of California
//       nor the names of its contributors may be used to endorse or
//       promote products derived from this software without specific
//       prior written permission.
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL REGENTS OF THE
// UNIVERSITY OF CALIFORNIA BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
// OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
// TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
// USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
// DAMAGE.
// ----------------------------------------------------------------------
//----------------------------------------------------------------------------
// Filename:			fifo.v
// Version:				1.00
// Verilog Standard:	Verilog-2001
// Description: Standard 0-delay fifo implementation. Takes WR_DATA on WR_READY
// and WR_VALID. RD_DATA is read on RD_READY and RD_VALID
// Author:				Dustin Richmond (@darichmond)
//-----------------------------------------------------------------------------
`timescale 1ns/1ns
`include "functions.vh"
module fifo 
    #(
      parameter C_WIDTH = 32,   // Data bus width
      parameter C_DEPTH = 1024, // Depth of the FIFO
      parameter C_DELAY = 2
      )
    (
     input                CLK, // Clock
     input                RST, // Sync reset, active high

     input [C_WIDTH-1:0]  WR_DATA, // Write data input
     input                WR_VALID, // Write enable, high active
     output               WR_READY, // ~Full condition

     output [C_WIDTH-1:0] RD_DATA, // Read data output
     input                RD_READY, // Read enable, high active
     output               RD_VALID // ~Empty condition
     );

    // Local parameters
    localparam C_POW2_DEPTH = 2**clog2(C_DEPTH);
    localparam C_DEPTH_WIDTH = clog2s(C_POW2_DEPTH);

    wire [C_DELAY:0]      wDelayTaps;
    wire                  wDelayWrEn;
    wire                  wWrEn;
    wire                  wRdEn;
    wire                  wRdRdy;
    
    wire                  wRdEnInternal;
    wire                  wRdEnExternal;
    wire                  wEmptyNow;
    wire                  wEmptyNext;

    wire                  wOutputEmpty;
    
    wire                  wFullNow;
    wire                  wFullNext;
    
    reg                   rValid;
    reg [C_DEPTH_WIDTH:0] rWrPtr,_rWrPtr;
    reg [C_DEPTH_WIDTH:0] rWrPtrPlus1, _rWrPtrPlus1;
    reg [C_DEPTH_WIDTH:0] rRdPtr,_rRdPtr;
    reg [C_DEPTH_WIDTH:0] rRdPtrPlus1,_rRdPtrPlus1;
    reg                   rFull,_rFull;
    reg                   rEmpty,_rEmpty;

    assign wRdEnInternal = ~wEmptyNow & ~rValid; // Read enable to propogate data to the BRAM output
    assign wRdEnExternal = RD_READY & !rEmpty;   // Read enable to change data on the output
    assign wRdEn = wRdEnInternal | wRdEnExternal;
    assign wRdRdy = RD_READY & rValid; // Read Data already on the output bus

    assign wWrEn = WR_VALID & !rFull;

    assign wEmptyNow = (rRdPtr == rWrPtr); 
    assign wEmptyNext = (wRdEn & ~wWrEn & (rWrPtr == rRdPtrPlus1));

    assign wFullNow = (rRdPtr[C_DEPTH_WIDTH-1:0] == rWrPtr[C_DEPTH_WIDTH-1:0]) & 
                      (rWrPtr[C_DEPTH_WIDTH] != rRdPtr[C_DEPTH_WIDTH]);
    assign wFullNext = wWrEn & ~wRdEn & (rWrPtrPlus1[C_DEPTH_WIDTH-1:0] == rRdPtr[C_DEPTH_WIDTH-1:0]) & 
                       (rWrPtrPlus1[C_DEPTH_WIDTH] != rRdPtr[C_DEPTH_WIDTH]);

    // Calculate empty
    assign RD_VALID = rValid;

    always @ (posedge CLK) begin
        rEmpty <= #1 (RST ? 1'd1 : _rEmpty);
    end

    always @ (*) begin
        _rEmpty = (wEmptyNow & ~wWrEn) | wEmptyNext;
    end

    always @(posedge CLK) begin
        if(RST) begin
            rValid <= #1 0;
        end else if(wRdEn | wRdRdy) begin
            rValid <= #1 ~(wEmptyNow);
        end
    end

    // Write pointer logic.
    always @ (posedge CLK) begin
        if (RST) begin
            rWrPtr <= #1 0;
            rWrPtrPlus1 <= #1 1;
        end else begin
            rWrPtr <= #1 _rWrPtr;
            rWrPtrPlus1 <= #1 _rWrPtrPlus1;
        end
    end

    always @ (*) begin
        if (wWrEn) begin
            _rWrPtr = rWrPtrPlus1;
            _rWrPtrPlus1 = rWrPtrPlus1 + 1'd1;
        end else begin
            _rWrPtr = rWrPtr;
            _rWrPtrPlus1 = rWrPtrPlus1;
        end
    end

    // Read pointer logic.
    always @ (posedge CLK) begin
        if (RST) begin
            rRdPtr <= #1 0;
            rRdPtrPlus1 <= #1 1;
        end else begin
            rRdPtr <= #1 _rRdPtr;
            rRdPtrPlus1 <= #1 _rRdPtrPlus1;
        end
    end

    always @ (*) begin
        if (wRdEn) begin
            _rRdPtr = rRdPtrPlus1;
            _rRdPtrPlus1 = rRdPtrPlus1 + 1'd1;
        end else begin
            _rRdPtr = rRdPtr;
            _rRdPtrPlus1 = rRdPtrPlus1;
        end
    end


    // Calculate full
    assign WR_READY = ~rFull;

    always @ (posedge CLK) begin
        rFull <= #1 (RST ? 1'd0 : _rFull);
    end

    always @ (*) begin
        _rFull = wFullNow | wFullNext;
    end

    // Memory block (synthesis attributes applied to this module will
    // determine the memory option).
    scsdpram
        #(
          .C_WIDTH(C_WIDTH), 
          .C_DEPTH(C_POW2_DEPTH)
          /*AUTOINSTPARAM*/) 
    mem 
        (
         .WR1_EN                        (wWrEn),
         .WR1_ADDR                      (rWrPtr[C_DEPTH_WIDTH-1:0]),
         .WR1_DATA                      (WR_DATA),

         .RD1_EN                        (wRdEn),
         .RD1_ADDR                      (rRdPtr[C_DEPTH_WIDTH-1:0]),
         .RD1_DATA                      (RD_DATA),
         /*AUTOINST*/
         // Inputs
         .CLK                           (CLK));

    shiftreg
        #(
          // Parameters
          .C_DEPTH          (C_DELAY),
          .C_WIDTH          (1'b1)
          /*AUTOINSTPARAM*/)
    shiftreg_wr_delay_inst
        (
         // Outputs
         .RD_DATA                       (wDelayTaps),
         // Inputs
         .RST_IN                        (RST),
         .WR_DATA                       (wWrEn),
         /*AUTOINST*/
         // Inputs
         .CLK                           (CLK));


endmodule

