// Copyright 2018 ETH Zurich and University of Bologna.
// Copyright and related rights are licensed under the Solderpad Hardware
// License, Version 0.51 (the "License"); you may not use this file except in
// compliance with the License.  You may obtain a copy of the License at
// http://solderpad.org/licenses/SHL-0.51. Unless required by applicable law
// or agreed to in writing, software, hardware and materials distributed under
// this License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
// CONDITIONS OF ANY KIND, either express or implied. See the License for the
// specific language governing permissions and limitations under the License.
//
// Author: Florian Zaruba, ETH Zurich
// Date: 25.04.2017
// Description: Store queue persists store requests and pushes them to memory
//              if they are no longer speculative

import ariane_pkg::*;

module store_buffer (
    input logic          clk_i,           // Clock
    input logic          rst_ni,          // Asynchronous reset active low
    input logic          flush_i,         // if we flush we need to pause the transactions on the memory
                                          // otherwise we will run in a deadlock with the memory arbiter
    output logic         no_st_pending_o, // non-speculative queue is empty (e.g.: everything is committed to the memory hierarchy)

    input  logic [11:0]  page_offset_i,         // check for the page offset (the last 12 bit if the current load matches them)
    output logic         page_offset_matches_o, // the above input page offset matches -> let the store buffer drain

    input  logic         commit_i,        // commit the instruction which was placed there most recently
    output logic         commit_ready_o,  // commit queue is ready to accept another commit request
    output logic         ready_o,         // the store queue is ready to accept a new request
                                          // it is only ready if it can unconditionally commit the instruction, e.g.:
                                          // the commit buffer needs to be empty
    input  logic         valid_i,         // this is a valid store
    input  logic         valid_without_flush_i, // just tell if the address is valid which we are current putting and do not take any further action

    input  logic [63:0]  paddr_i,         // physical address of store which needs to be placed in the queue
    input  logic [63:0]  data_i,          // data which is placed in the queue
    input  logic [7:0]   be_i,            // byte enable in
    input  logic [1:0]   data_size_i,     // type of request we are making (e.g.: bytes to write)

    // D$ interface
    input  dcache_req_o_t req_port_i,
    output dcache_req_i_t req_port_o
);

    // the store queue has two parts:
    // 1. Speculative queue
    // 2. Commit queue which is non-speculative, e.g.: the store will definitely happen.
    struct packed {
        logic [63:0] address;
        logic [63:0] data;
        logic [7:0]  be;
        logic [1:0]  data_size;
        logic        valid;     // this entry is valid, we need this for checking if the address offset matches
    } speculative_queue_n [DEPTH_SPEC-1:0], speculative_queue_q [DEPTH_SPEC-1:0],
      commit_queue_n [DEPTH_COMMIT-1:0],    commit_queue_q [DEPTH_COMMIT-1:0];

    // keep a status count for both buffers
    logic [$clog2(DEPTH_SPEC):0] speculative_status_cnt_n, speculative_status_cnt_q;
    logic [$clog2(DEPTH_COMMIT):0] commit_status_cnt_n, commit_status_cnt_q;
    // Speculative queue
    logic [$clog2(DEPTH_SPEC)-1:0] speculative_read_pointer_n,  speculative_read_pointer_q;
    logic [$clog2(DEPTH_SPEC)-1:0] speculative_write_pointer_n, speculative_write_pointer_q;
    // Commit Queue
    logic [$clog2(DEPTH_COMMIT)-1:0] commit_read_pointer_n,  commit_read_pointer_q;
    logic [$clog2(DEPTH_COMMIT)-1:0] commit_write_pointer_n, commit_write_pointer_q;


    // ----------------------------------------
    // Speculative Queue - Core Interface
    // ----------------------------------------
    always_comb begin : core_if
        automatic logic [DEPTH_SPEC:0] speculative_status_cnt;
        speculative_status_cnt = speculative_status_cnt_q;

        // we are ready if the speculative and the commit queue have a space left
        ready_o = (speculative_status_cnt_q < (DEPTH_SPEC - 1)) || commit_i;
        // default assignments
        speculative_status_cnt_n    = speculative_status_cnt_q;
        speculative_read_pointer_n  = speculative_read_pointer_q;
        speculative_write_pointer_n = speculative_write_pointer_q;
        speculative_queue_n         = speculative_queue_q;
        // LSU interface
        // we are ready to accept a new entry and the input data is valid
        if (valid_i) begin
            speculative_queue_n[speculative_write_pointer_q].address   = paddr_i;
            speculative_queue_n[speculative_write_pointer_q].data      = data_i;
            speculative_queue_n[speculative_write_pointer_q].be        = be_i;
            speculative_queue_n[speculative_write_pointer_q].data_size = data_size_i;
            speculative_queue_n[speculative_write_pointer_q].valid   = 1'b1;
            // advance the write pointer
            speculative_write_pointer_n = speculative_write_pointer_q + 1'b1;
            speculative_status_cnt++;
        end

        // evict the current entry out of this queue, the commit queue will thankfully take it and commit it
        // to the memory hierarchy
        if (commit_i) begin
            // invalidate
            speculative_queue_n[speculative_read_pointer_q].valid = 1'b0;
            // advance the read pointer
            speculative_read_pointer_n = speculative_read_pointer_q + 1'b1;
            speculative_status_cnt--;
        end

        speculative_status_cnt_n = speculative_status_cnt;

        // when we flush evict the speculative stores
        if (flush_i) begin
            // reset all valid flags
            for (int unsigned i = 0; i < DEPTH_SPEC; i++)
                speculative_queue_n[i].valid = 1'b0;

            speculative_write_pointer_n = speculative_read_pointer_q;
            // also reset the status count
            speculative_status_cnt_n = 'b0;
        end
    end

    // ----------------------------------------
    // Commit Queue - Memory Interface
    // ----------------------------------------

    // we will never kill a request in the store buffer since we already know that the translation is valid
    // e.g.: a kill request will only be necessary if we are not sure if the requested memory address will result in a TLB fault
    assign req_port_o.kill_req  = 1'b0;
    assign req_port_o.data_we   = 1'b1; // we will always write in the store queue
    assign req_port_o.tag_valid = 1'b0;

    // those signals can directly be output to the memory
    assign req_port_o.address_index = commit_queue_q[commit_read_pointer_q].address[ariane_pkg::DCACHE_INDEX_WIDTH-1:0];
    // if we got a new request we already saved the tag from the previous cycle
    assign req_port_o.address_tag   = commit_queue_q[commit_read_pointer_q].address[ariane_pkg::DCACHE_TAG_WIDTH     +
                                                                                    ariane_pkg::DCACHE_INDEX_WIDTH-1 :
                                                                                    ariane_pkg::DCACHE_INDEX_WIDTH];
    assign req_port_o.data_wdata    = commit_queue_q[commit_read_pointer_q].data;
    assign req_port_o.data_be       = commit_queue_q[commit_read_pointer_q].be;
    assign req_port_o.data_size     = commit_queue_q[commit_read_pointer_q].data_size;

    always_comb begin : store_if
        automatic logic [DEPTH_COMMIT:0] commit_status_cnt;
        commit_status_cnt = commit_status_cnt_q;

        commit_ready_o = (commit_status_cnt_q < DEPTH_COMMIT);
        // no store is pending if we don't have any element in the commit queue e.g.: it is empty
        no_st_pending_o         = (commit_status_cnt_q == 0);
        // default assignments
        commit_read_pointer_n   = commit_read_pointer_q;
        commit_write_pointer_n  = commit_write_pointer_q;

        commit_queue_n = commit_queue_q;

        req_port_o.data_req     = 1'b0;

        // there should be no commit when we are flushing
        // if the entry in the commit queue is valid and not speculative anymore we can issue this instruction
        if (commit_queue_q[commit_read_pointer_q].valid) begin
            req_port_o.data_req = 1'b1;
            if (req_port_i.data_gnt) begin
                // we can evict it from the commit buffer
                commit_queue_n[commit_read_pointer_q].valid = 1'b0;
                // advance the read_pointer
                commit_read_pointer_n = commit_read_pointer_q + 1'b1;
                commit_status_cnt--;
            end
        end
        // we ignore the rvalid signal for now as we assume that the store
        // happened if we got a grant

        // shift the store request from the speculative buffer to the non-speculative
        if (commit_i) begin
            commit_queue_n[commit_write_pointer_q] = speculative_queue_q[speculative_read_pointer_q];
            commit_write_pointer_n = commit_write_pointer_n + 1'b1;
            commit_status_cnt++;
        end

        commit_status_cnt_n     = commit_status_cnt;
    end

    // ------------------
    // Address Checker
    // ------------------
    // The load should return the data stored by the most recent store to the
    // same physical address.  The most direct way to implement this is to
    // maintain physical addresses in the store buffer.

    // Of course, there are other micro-architectural techniques to accomplish
    // the same thing: you can interlock and wait for the store buffer to
    // drain if the load VA matches any store VA modulo the page size (i.e.
    // bits 11:0).  As a special case, it is correct to bypass if the full VA
    // matches, and no younger stores' VAs match in bits 11:0.
    //
    // checks if the requested load is in the store buffer
    // page offsets are virtually and physically the same
    always_comb begin : address_checker
        page_offset_matches_o = 1'b0;

        // check if the LSBs are identical and the entry is valid
        for (int unsigned i = 0; i < DEPTH_COMMIT; i++) begin
            // Check if the page offset matches and whether the entry is valid, for the commit queue
            if ((page_offset_i[11:3] == commit_queue_q[i].address[11:3]) && commit_queue_q[i].valid) begin
                page_offset_matches_o = 1'b1;
                break;
            end
        end

        for (int unsigned i = 0; i < DEPTH_SPEC; i++) begin
            // do the same for the speculative queue
            if ((page_offset_i[11:3] == speculative_queue_q[i].address[11:3]) && speculative_queue_q[i].valid) begin
                page_offset_matches_o = 1'b1;
                break;
            end
        end
        // or it matches with the entry we are currently putting into the queue
        if ((page_offset_i[11:3] == paddr_i[11:3]) && valid_without_flush_i) begin
            page_offset_matches_o = 1'b1;
        end
    end


    // registers
    always_ff @(posedge clk_i or negedge rst_ni) begin : p_spec
        if (~rst_ni) begin
            speculative_queue_q         <= '{default: 0};
            speculative_read_pointer_q  <= '0;
            speculative_write_pointer_q <= '0;
            speculative_status_cnt_q    <= '0;
        end else begin
            speculative_queue_q         <= speculative_queue_n;
            speculative_read_pointer_q  <= speculative_read_pointer_n;
            speculative_write_pointer_q <= speculative_write_pointer_n;
            speculative_status_cnt_q    <= speculative_status_cnt_n;
        end
     end

    // registers
    always_ff @(posedge clk_i or negedge rst_ni) begin : p_commit
        if (~rst_ni) begin
            commit_queue_q              <= '{default: 0};
            commit_read_pointer_q       <= '0;
            commit_write_pointer_q      <= '0;
            commit_status_cnt_q         <= '0;
        end else begin
            commit_queue_q              <= commit_queue_n;
            commit_read_pointer_q       <= commit_read_pointer_n;
            commit_write_pointer_q      <= commit_write_pointer_n;
            commit_status_cnt_q         <= commit_status_cnt_n;
        end
     end

///////////////////////////////////////////////////////
// assertions
///////////////////////////////////////////////////////

    //pragma translate_off
    `ifndef VERILATOR
    // assert that commit is never set when we are flushing this would be counter intuitive
    // as flush and commit is decided in the same stage
    commit_and_flush: assert property (
        @(posedge clk_i) rst_ni && flush_i |-> !commit_i)
        else $error ("[Commit Queue] You are trying to commit and flush in the same cycle");

    speculative_buffer_overflow: assert property (
        @(posedge clk_i) rst_ni && (speculative_status_cnt_q == DEPTH_SPEC) |-> !valid_i)
        else $error ("[Speculative Queue] You are trying to push new data although the buffer is not ready");

    speculative_buffer_underflow: assert property (
        @(posedge clk_i) rst_ni && (speculative_status_cnt_q == 0) |-> !commit_i)
        else $error ("[Speculative Queue] You are committing although there are no stores to commit");

    commit_buffer_overflow: assert property (
        @(posedge clk_i) rst_ni && (commit_status_cnt_q == DEPTH_COMMIT) |-> !commit_i)
        else $error("[Commit Queue] You are trying to commit a store although the buffer is full");
    `endif
    //pragma translate_on
endmodule