mirror of
https://github.com/openhwgroup/cva6.git
synced 2025-04-20 04:07:36 -04:00
Reduce dcache data output width from 64 to 32 bits in cv32a6 configuration Signed-off-by: sjthales <sebastien.jacq@thalesgroup.com>
279 lines
14 KiB
Systemverilog
279 lines
14 KiB
Systemverilog
// 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
|
|
|
|
|
|
module store_buffer import ariane_pkg::*; (
|
|
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)
|
|
output logic store_buffer_empty_o, // there is no store pending in neither the speculative unit or the non-speculative queue
|
|
|
|
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 [riscv::PLEN-1:0] paddr_i, // physical address of store which needs to be placed in the queue
|
|
input riscv::xlen_t data_i, // data which is placed in the queue
|
|
input logic [(riscv::XLEN/8)-1: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 [riscv::PLEN-1:0] address;
|
|
riscv::xlen_t data;
|
|
logic [(riscv::XLEN/8)-1: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;
|
|
|
|
assign store_buffer_empty_o = (speculative_status_cnt_q == 0) & no_st_pending_o;
|
|
// ----------------------------------------
|
|
// 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
|
|
|
|
|
|
|