store_buffer.sv 13.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279
// 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