Implemented store queue for speculative st

This commit is contained in:
Florian Zaruba 2017-04-25 18:18:38 +02:00
parent 33a88d8b10
commit 69a6da981b
9 changed files with 193 additions and 9 deletions

View file

@ -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 \

View file

@ -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 ),

View file

@ -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.

View file

@ -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,

View file

@ -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
View file

@ -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 ),

View file

@ -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
View file

@ -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
View 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