mirror of
https://github.com/openhwgroup/cva6.git
synced 2025-04-22 05:07:21 -04:00
Implemented store queue for speculative st
This commit is contained in:
parent
33a88d8b10
commit
69a6da981b
9 changed files with 193 additions and 9 deletions
2
Makefile
2
Makefile
|
@ -15,7 +15,7 @@ agents = tb/agents/fu_if/fu_if.sv tb/agents/fu_if/fu_if_agent_pkg.sv \
|
|||
interfaces = include/debug_if.svh include/mem_if.svh tb/agents/fifo_if/fifo_if.sv
|
||||
# this list contains the standalone components
|
||||
src = alu.sv tb/sequences/alu_sequence_pkg.sv tb/env/alu_env_pkg.sv tb/test/alu_lib_pkg.sv tb/alu_tb.sv \
|
||||
tb/scoreboard_tb.sv ptw.sv tlb.sv \
|
||||
tb/scoreboard_tb.sv ptw.sv tlb.sv store_queue.sv \
|
||||
if_stage.sv compressed_decoder.sv fetch_fifo.sv commit_stage.sv prefetch_buffer.sv \
|
||||
mmu.sv lsu.sv fifo.sv tb/fifo_tb.sv mem_arbiter.sv \
|
||||
scoreboard.sv issue_read_operands.sv decoder.sv id_stage.sv util/cluster_clock_gating.sv regfile.sv ex_stage.sv ariane.sv \
|
||||
|
|
|
@ -164,6 +164,7 @@ module ariane
|
|||
ex_stage ex_stage_i (
|
||||
.clk_i ( clk_i ),
|
||||
.rst_ni ( rst_n ),
|
||||
.flush_i ( 1'b0 ),
|
||||
.operator_i ( operator_o ),
|
||||
.operand_a_i ( operand_a_o ),
|
||||
.operand_b_i ( operand_b_o ),
|
||||
|
|
|
@ -298,3 +298,16 @@ We need to be careful when altering some of the register. Some of those register
|
|||
| asid_i | Input | ASID (address space identifier) |
|
||||
| flush_tlb_i | Input | Flush TLB |
|
||||
| lsu_ready_wb_i | Input | LSU is ready to accept new data |
|
||||
|
||||
|
||||
## LSU
|
||||
|
||||
### Memory Arbiter
|
||||
|
||||
The memory arbiter's purpose is to arbitrate the memory accesses coming/going from/to the PTW, store queue and load requests. On a flush it needs to wait for all previous transactions to return. We currently do not have another way to squash load and stores that already went into the memory hierarchy.
|
||||
|
||||
### Store Queue
|
||||
|
||||
The store queue keeps track of all stores. It has two entries: One is for already committed instructions and one is for outstanding instructions. On a flush only the instruction which has the already committed instruction saved persists its data. But on a flush it can't request further to the memory since this could potentially stall us indefinitely because of the property of the memory arbiter (see above).
|
||||
|
||||
The store queue works with physical addresses only. At the time when they are committed the translation is correct. Furthermore the store queue directly outputs the address and value of the instruction it is going to commit since any subsequent store also needs to check for the address.
|
||||
|
|
|
@ -24,6 +24,7 @@ module ex_stage #(
|
|||
)(
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i,
|
||||
|
||||
input fu_op operator_i,
|
||||
input logic [63:0] operand_a_i,
|
||||
|
|
6
fifo.sv
6
fifo.sv
|
@ -23,6 +23,7 @@ module fifo #(
|
|||
)(
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic flush_i, // flush the queue
|
||||
// status flags
|
||||
output logic full_o, // queue is full
|
||||
output logic empty_o, // queue is empty
|
||||
|
@ -80,6 +81,11 @@ module fifo #(
|
|||
write_pointer_q <= '{default: 0};
|
||||
status_cnt_q <= '{default: 0};
|
||||
mem_q <= '{default: 0};
|
||||
end else if (flush_i) begin
|
||||
read_pointer_q <= '{default: 0};
|
||||
write_pointer_q <= '{default: 0};
|
||||
status_cnt_q <= '{default: 0};
|
||||
mem_q <= '{default: 0};
|
||||
end else begin
|
||||
read_pointer_q <= read_pointer_n;
|
||||
write_pointer_q <= write_pointer_n;
|
||||
|
|
6
lsu.sv
6
lsu.sv
|
@ -23,6 +23,7 @@ module lsu #(
|
|||
)(
|
||||
input logic clk_i,
|
||||
input logic rst_ni,
|
||||
input logic flush_i,
|
||||
|
||||
input fu_op operator_i,
|
||||
input logic [63:0] operand_a_i,
|
||||
|
@ -106,7 +107,9 @@ module lsu #(
|
|||
.data_be_i ( data_be_i ),
|
||||
.data_gnt_o ( data_gnt_o ),
|
||||
.data_rvalid_o( data_rvalid_o ),
|
||||
.data_rdata_o ( data_rdata_o )
|
||||
.data_rdata_o ( data_rdata_o ),
|
||||
.flush_ready_o( ), // TODO: connect, wait for flush to be valid
|
||||
.*
|
||||
);
|
||||
|
||||
// connecting PTW to D$ (aka mem arbiter)
|
||||
|
@ -124,7 +127,6 @@ module lsu #(
|
|||
// -------------------
|
||||
logic lsu_req_i;
|
||||
logic [63:0] lsu_vaddr_i;
|
||||
logic lsu_ready_wb_i;
|
||||
|
||||
mmu #(
|
||||
.INSTR_TLB_ENTRIES ( 16 ),
|
||||
|
|
|
@ -22,8 +22,9 @@ module mem_arbiter #(
|
|||
parameter int NR_PORTS = 3
|
||||
)
|
||||
(
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
output logic flush_ready_o, // the flush is ready, e.g.: all transaction returned
|
||||
// slave port
|
||||
output logic [63:0] address_o,
|
||||
output logic [63:0] data_wdata_o,
|
||||
|
@ -51,12 +52,17 @@ module mem_arbiter #(
|
|||
logic [NR_PORTS-1:0] data_o;
|
||||
logic pop_i;
|
||||
|
||||
// essentially wait for the queue to be empty
|
||||
assign flush_ready_o = empty_o;
|
||||
|
||||
fifo #(
|
||||
.dtype ( logic [NR_PORTS-1:0] ),
|
||||
.DEPTH ( 4 )
|
||||
) fifo_i (
|
||||
.clk_i ( clk_i ),
|
||||
.rst_ni ( rst_ni ),
|
||||
// the flush is accomplished implicitly by waiting for the flush ready signal
|
||||
.flush_i ( 1'b0 ),
|
||||
.full_o ( full_o ),
|
||||
.empty_o ( empty_o ),
|
||||
.data_i ( data_i ),
|
||||
|
|
6
mmu.sv
6
mmu.sv
|
@ -52,7 +52,6 @@ module mmu #(
|
|||
input logic [37:0] pd_ppn_i,
|
||||
input logic [ASID_WIDTH-1:0] asid_i,
|
||||
input logic flush_tlb_i,
|
||||
input logic lsu_ready_wb_i,
|
||||
// Memory interfaces
|
||||
// Instruction memory interface
|
||||
mem_if.Slave instr_if,
|
||||
|
@ -73,7 +72,6 @@ module mmu #(
|
|||
logic walking_instr;
|
||||
logic ptw_error;
|
||||
|
||||
logic flush_i;
|
||||
logic update_is_2M;
|
||||
logic update_is_1G;
|
||||
logic [26:0] update_vpn;
|
||||
|
@ -95,7 +93,7 @@ module mmu #(
|
|||
) itlb_i (
|
||||
.clk_i ( clk_i ),
|
||||
.rst_ni ( rst_ni ),
|
||||
.flush_i ( flush_i ),
|
||||
.flush_i ( flush_tlb_i ),
|
||||
.update_is_2M_i ( update_is_2M ),
|
||||
.update_is_1G_i ( update_is_1G ),
|
||||
.update_vpn_i ( update_vpn ),
|
||||
|
@ -118,7 +116,7 @@ module mmu #(
|
|||
(
|
||||
.clk_i ( clk_i ),
|
||||
.rst_ni ( rst_ni ),
|
||||
.flush_i ( ),
|
||||
.flush_i ( flush_tlb_i ),
|
||||
.ptw_active_o ( ptw_active ),
|
||||
.walking_instr_o ( walking_instr ),
|
||||
.ptw_error_o ( ptw_error ),
|
||||
|
|
157
store_queue.sv
Executable file
157
store_queue.sv
Executable file
|
@ -0,0 +1,157 @@
|
|||
// Author: Florian Zaruba, ETH Zurich
|
||||
// Date: 25.04.2017
|
||||
// Description: Store queue persists store requests and pushes them to memory
|
||||
//
|
||||
// Copyright (C) 2017 ETH Zurich, University of Bologna
|
||||
// All rights reserved.
|
||||
//
|
||||
// This code is under development and not yet released to the public.
|
||||
// Until it is released, the code is under the copyright of ETH Zurich and
|
||||
// the University of Bologna, and may contain confidential and/or unpublished
|
||||
// work. Any reuse/redistribution is strictly forbidden without written
|
||||
// permission from ETH Zurich.
|
||||
//
|
||||
// Bug fixes and contributions will eventually be released under the
|
||||
// SolderPad open hardware license in the context of the PULP platform
|
||||
// (http://www.pulp-platform.org), under the copyright of ETH Zurich and the
|
||||
// University of Bologna.
|
||||
//
|
||||
|
||||
module store_queue (
|
||||
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 [63:0] paddr_o, // physical address of the valid store
|
||||
output logic [63:0] data_o, // data at the given address
|
||||
output logic valid_o, // committed data is valid
|
||||
output logic [7:0] be_o, // byte enable set
|
||||
|
||||
input logic commmit_i, // commit the instruction which was placed there most recently
|
||||
|
||||
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
|
||||
// it is easier to have an unconditional commit
|
||||
input logic valid_i, // this is a valid store
|
||||
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
|
||||
|
||||
// D$ interface
|
||||
output logic [63:0] address_o,
|
||||
output logic [63:0] data_wdata_o,
|
||||
output logic data_req_o,
|
||||
output logic data_we_o,
|
||||
output logic [7:0] data_be_o,
|
||||
input logic data_gnt_i,
|
||||
input logic data_rvalid_i,
|
||||
input logic [63:0] data_rdata_i
|
||||
);
|
||||
// the store queue has two parts:
|
||||
// 1. Speculative queue
|
||||
// 2. Commit queue which is non-speculative, e.g.: the store will definitely happen.
|
||||
// For simplicity reasons we just keep those two elements and not one real queue
|
||||
// should it turn out that this bottlenecks we can still increase the capacity here
|
||||
// potentially at the cost of increased area.
|
||||
|
||||
enum { IDLE, WAIT_RVALID } CS, NS;
|
||||
|
||||
struct packed {
|
||||
logic [63:0] address;
|
||||
logic [63:0] data;
|
||||
logic [7:0] be;
|
||||
logic valid; // entry is valid
|
||||
} commit_queue_n, commit_queue_q, store_queue_n, store_queue_q;
|
||||
|
||||
// we can directly output the commit entry since we just have one element in this "queue"
|
||||
assign paddr_o = commit_queue_q.address;
|
||||
assign data_o = commit_queue_q.data;
|
||||
assign be_o = commit_queue_q.be;
|
||||
assign valid_o = commit_queue_q.valid;
|
||||
|
||||
// memory interface
|
||||
always_comb begin : store_if
|
||||
// those signals can directly be output to the memory if
|
||||
address_o = commit_queue_q.address;
|
||||
data_wdata_o = commit_queue_q.data;
|
||||
data_be_o = commit_queue_q.be;
|
||||
data_we_o = 1'b1; // we will always write in the store queue
|
||||
data_req_o = 1'b0;
|
||||
NS = CS;
|
||||
|
||||
if (~flush_i) begin
|
||||
case (CS)
|
||||
// we can accept a new request if we were idle
|
||||
IDLE: begin
|
||||
if (commit_queue_q.valid) begin
|
||||
data_req_o = 1'b1;
|
||||
if (data_gnt_i) begin// advance to the next state if we received the grant
|
||||
NS = WAIT_RVALID;
|
||||
// we can evict it from the commit buffer
|
||||
commit_queue_n.valid = 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
WAIT_RVALID: begin
|
||||
|
||||
if (data_rvalid_i) begin
|
||||
// if we got the rvalid we can already perform a new store
|
||||
if (commit_queue_q.valid)
|
||||
data_req_o = 1'b1;
|
||||
else // if do not need to perform another
|
||||
NS = IDLE;
|
||||
end else // wait here for the rvalid
|
||||
NS = WAIT_RVALID;
|
||||
end
|
||||
|
||||
default:;
|
||||
endcase
|
||||
end
|
||||
|
||||
// shift the store request from the speculative buffer
|
||||
// to the non-speculative
|
||||
if (commmit_i) begin
|
||||
commit_queue_n = store_queue_q;
|
||||
end
|
||||
end
|
||||
|
||||
// LSU interface
|
||||
always_comb begin : lsu_if
|
||||
// if there is no commit pending and the uncommitted queue is empty as well we can accept the request
|
||||
automatic logic ready = ~commit_queue_q.valid & ~store_queue_q.valid;
|
||||
ready_o = ready;
|
||||
store_queue_n = store_queue_q;
|
||||
// we are ready to accept a new entry and the input data is valid
|
||||
if (ready & valid_i & ~commmit_i) begin
|
||||
store_queue_n.address = paddr_i;
|
||||
store_queue_n.data = data_i;
|
||||
store_queue_n.be = be_i;
|
||||
store_queue_n.valid = 1'b1;
|
||||
end
|
||||
// invalidate this result
|
||||
// as it is moved to the non-speculative queue
|
||||
if (~valid_i & commmit_i) begin
|
||||
store_queue_n.valid = 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
// registers
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin : proc_
|
||||
if(~rst_ni) begin
|
||||
commit_queue_q <= '{default: 0};
|
||||
store_queue_q <= '{default: 0};
|
||||
CS <= IDLE;
|
||||
end else if (flush_i) begin // just empty the speculative queue
|
||||
commit_queue_q <= commit_queue_n;
|
||||
store_queue_q <= '{default: 0};
|
||||
CS <= NS;
|
||||
end else begin
|
||||
commit_queue_q <= commit_queue_n;
|
||||
store_queue_q <= store_queue_n;
|
||||
CS <= NS;
|
||||
end
|
||||
end
|
||||
endmodule
|
Loading…
Add table
Add a link
Reference in a new issue