// FSM for Skid-Buffer

// This FSM assumes the AXI meaning and behaviour of valid/ready handshake
// signals: when both are high, data transfers at the end of the clock cycle.
// It is an error to raise ready when not able to accept data (thus losing the
// incoming data), or to raise valid when not able to send data (thus
// duplicating previously sent data). These error situations are not handled.

// This FSM manages the valid/ready transactions for a sending master
// interface and a receiving slave interface (AXI terminology). 
// The associated data buffers are in the related datapath module.
// The datapath is expected to be one data output register, fed from either
// the data input, or a buffered copy of data input.

// We want to keep the state inside this module, so that the associated
// datapath module does not have to know anything about the current state or
// its encoding.  Output enough control signals to have the datapath steer
// logic as necessary and manage the data buffers themselves.

`default_nettype none

module skid_buffer_fsm
// No parameters
(
    input   wire    clock,

    // Slave interface
    input   wire    s_valid,
    output  reg     s_ready,

    // Master Interface
    output  reg     m_valid,
    input   wire    m_ready,

    // Control to Datapath
    output  reg     data_out_wren,
    output  reg     data_buffer_wren,
    output  reg     use_buffered_data    
);

// --------------------------------------------------------------------------
// Have the initial output values match what they would be for an empty
// datapath.

    initial begin
        s_ready             = 1'b1; // empty at start, so accept data
        m_valid             = 1'b0;
        data_out_wren       = 1'b1; // empty at start, so accept data
        data_buffer_wren    = 1'b0;
        use_buffered_data   = 1'b0;
    end

// --------------------------------------------------------------------------
// Lets describe the possible states of the datapath, and initialize it.

// This describes a binary state encoding, but the CAD tool can re-encode and
// re-number the state encoding. Usually this is beneficial, but if the
// states+inputs fit in a single LUT, forcing binary encoding reduces area.
// See what works best (i.e.: reaches the highest speed) for your given FPGA.

    localparam STATE_BITS = 2;

    localparam [STATE_BITS-1:0] EMPTY = 'd0; // Output and buffer registers empty
    localparam [STATE_BITS-1:0] BUSY  = 'd1; // Output register holds data
    localparam [STATE_BITS-1:0] FULL  = 'd2; // Both output and buffer registers hold data
    // There is no case where only the buffer register would hold data.

    // No handling of erroneous and unreachable state 3.
    // We could check and raise an error flag.

    reg [STATE_BITS-1:0] state      = EMPTY;
    reg [STATE_BITS-1:0] state_next = EMPTY;

// --------------------------------------------------------------------------
// Compute the allowable output read/valid handshake signals based on the
// datapath state. (use state_next so we can have a nice registered output).

// This tiny bit of code is critical since it implies the fundamental
// operating assumptions of a skid buffer: that one interface cannot have its
// current state depend on the current state of the other interface, as that
// would be a combinational path between both interfaces.

// Without this code, we would have to handle many more cases in the upcoming
// datapath transformation and state transition code. If something below is not
// clear, refer back to this code.

    // The slave interface is ready to accept data whenever the datapath is not full.
    // The master interface has data to give whenever the datapath is not empty.
    always @(posedge clock) begin
        s_ready <= (state_next != FULL);
        m_valid <= (state_next != EMPTY); 
    end

// --------------------------------------------------------------------------
// Let's enumerate the possible interface states which transform the contents
// of the datapath. Here, this means the slave interface inserting and the
// master interface removing data items from the datapath.

    reg insert = 1'b0;
    reg remove = 1'b0;

    always @(*) begin
        insert = (s_valid == 1'b1) && (s_ready == 1'b1);
        remove = (m_valid == 1'b1) && (m_ready == 1'b1);
    end

// --------------------------------------------------------------------------
// Let's describe the possible transformations to the datapath, and in which state
// they can happen.

    reg load    = 1'b0; // Empty datapath inserts data into output register.
    reg flow    = 1'b0; // New inserted data into output register as the old data is removed.
    reg fill    = 1'b0; // New inserted data into buffer register. Data not removed from output register.
    reg flush   = 1'b0; // Move data from buffer register into output register. Remove old data. No new data inserted.
    reg unload  = 1'b0; // Remove data from output register, leaving the datapath empty.

    always @(*) begin
        load    = (state == EMPTY) && (insert == 1'b1);
        flow    = (state == BUSY)  && (insert == 1'b1) && (remove == 1'b1);
        fill    = (state == BUSY)  && (insert == 1'b1) && (remove == 1'b0);
        flush   = (state == FULL)  && (insert == 1'b0) && (remove == 1'b1);
        unload  = (state == BUSY)  && (insert == 1'b0) && (remove == 1'b1);
    end

// --------------------------------------------------------------------------
// Lets calculate the next state after each datapath transformation.

    always @(*) begin
        state_next = (load   == 1'b1) ? BUSY  : state;
        state_next = (flow   == 1'b1) ? BUSY  : state_next;
        state_next = (fill   == 1'b1) ? FULL  : state_next;
        state_next = (flush  == 1'b1) ? BUSY  : state_next;
        state_next = (unload == 1'b1) ? EMPTY : state_next;
    end

    always @(posedge clock) begin
        state <= state_next;
    end

// --------------------------------------------------------------------------
// Finally, from the datapath transformations, compute the necessary control
// signals. These are not registered here, as they end at registers in the
// datapath.

    always @(*) begin
        data_out_wren     = (load  == 1'b1) || (flow == 1'b1) || (flush == 1'b1);
        data_buffer_wren  = (fill  == 1'b1);
        use_buffered_data = (flush == 1'b1);
    end

endmodule