// Copyright 2021 Silicon Labs, Inc.
//
// This file, and derivatives thereof are licensed under the
// Solderpad License, Version 2.0 (the "License").
//
// Use of this file means you agree to the terms and conditions
// of the license and are in full compliance with the License.
//
// You may obtain a copy of the License at:
//
//     https://solderpad.org/licenses/SHL-2.0/
//
// Unless required by applicable law or agreed to in writing, software
// and hardware implementations thereof distributed under the License
// is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
// OF ANY KIND, EITHER EXPRESSED OR IMPLIED.
//
// See the License for the specific language governing permissions and
// limitations under the License.

////////////////////////////////////////////////////////////////////////////////
// Engineer:       Halfdan Bechmann  -  halfdan.bechmann@silabs.com           //
//                                                                            //
// Design Name:    Write Buffer                                               //
// Project Name:   CV32E40S                                                   //
// Language:       SystemVerilog                                              //
//                                                                            //
// Description:    Single word write buffer                                   //
//                                                                            //
////////////////////////////////////////////////////////////////////////////////

module cv32e40s_write_buffer import cv32e40s_pkg::*;
#(
  parameter int          PMA_NUM_REGIONS = 0,
  parameter pma_cfg_t    PMA_CFG[PMA_NUM_REGIONS-1:0] = '{default:PMA_R_DEFAULT}
)
  (
   // clock and reset
   input logic           clk,
   input logic           rst_n,

   // inputs
   input  logic          valid_i,
   input  obi_data_req_t trans_i,
   input  logic          ready_i,

   // outputs
   output logic          valid_o,
   output obi_data_req_t trans_o,
   output logic          ready_o
  );

  // local signals
  write_buffer_state_e        state, next_state;
  obi_data_req_t              trans_q;
  logic                       push;
  logic                       bufferable;

  assign bufferable = trans_i.memtype[0];

  ///////////////////////////////////////////
  // FSM
  ///////////////////////////////////////////
  always_ff @(posedge clk, negedge rst_n) begin
    if(!rst_n) begin
      state <= WBUF_EMPTY;
    end else begin
      state <= next_state;
    end
  end

  always_comb begin
    next_state = state;
    push       = 1'b0;

    case(state)
      WBUF_EMPTY: begin // buffer is empty
        if(bufferable && valid_i && !ready_i) begin
          next_state = WBUF_FULL;
          push       = 1'b1; // push incoming data into the buffer
        end
      end
      WBUF_FULL: begin // buffer is full
        if(ready_i) begin
          if (bufferable && valid_i) begin
            next_state = WBUF_FULL;
            push       = 1'b1; // push incoming data into the buffer
          end else begin
            next_state = WBUF_EMPTY;
          end
        end
      end

      default:;
    endcase // case (state)
  end

  ///////////////////////////////////////////
  // Buffer
  ///////////////////////////////////////////
  always_ff @(posedge clk, negedge rst_n) begin
    if(!rst_n) begin
      trans_q <= obi_data_req_t'{we: 1'b1, default: '0};
    end else if (push) begin
      trans_q <= trans_i;
    end
  end

  ///////////////////////////////////////////
  // Outputs
  ///////////////////////////////////////////
  assign ready_o = (state == WBUF_FULL) ?
                   (bufferable && ready_i) : // A downstream ready will free up the buffer for a new bufferable transfer
                   (bufferable || ready_i);  // Ready for bufferable transfer, accept any transfer if downstream is ready
  assign valid_o = (state == WBUF_FULL) || valid_i;
  assign trans_o = (state == WBUF_FULL) ? trans_q : trans_i;

endmodule
