cva6/core/scoreboard.sv
Côme 43edcd467e
document issue stage (#2598)
* Fill docs/design/design-manual/source/cva6_issue_stage.adoc
* Add variables to docs/design/design-manual/source/design.adoc
* Update port doc comments in core/issue_stage.sv, core/issue_read_operands.sv and core/scoreboard.sv
2024-11-12 20:28:25 +01:00

356 lines
15 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: 08.04.2017
// Description: Scoreboard - keeps track of all decoded, issued and committed instructions
module scoreboard #(
parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty,
parameter type bp_resolve_t = logic,
parameter type exception_t = logic,
parameter type scoreboard_entry_t = logic,
parameter type forwarding_t = logic,
parameter type writeback_t = logic,
parameter type rs3_len_t = logic
) (
// Subsystem Clock - SUBSYSTEM
input logic clk_i,
// Asynchronous reset active low - SUBSYSTEM
input logic rst_ni,
// Is scoreboard full - PERF_COUNTERS
output logic sb_full_o,
// Prevent from issuing - CONTROLLER
input logic flush_unissued_instr_i,
// Flush whole scoreboard - CONTROLLER
input logic flush_i,
// Writeback Handling of CVXIF
// TO_BE_COMPLETED - ISSUE_READ_OPERANDS
input logic x_transaction_accepted_i,
// TO_BE_COMPLETED - ISSUE_READ_OPERANDS
input logic x_issue_writeback_i,
// TO_BE_COMPLETED - ISSUE_READ_OPERANDS
input logic [CVA6Cfg.TRANS_ID_BITS-1:0] x_id_i,
// advertise instruction to commit stage, if commit_ack_i is asserted advance the commit pointer
// Instructions to commit - COMMIT_STAGE
output scoreboard_entry_t [CVA6Cfg.NrCommitPorts-1:0] commit_instr_o,
// Instruction is cancelled - COMMIT_STAGE
output logic [CVA6Cfg.NrCommitPorts-1:0] commit_drop_o,
// Commit acknowledge - COMMIT_STAGE
input logic [CVA6Cfg.NrCommitPorts-1:0] commit_ack_i,
// instruction to put on top of scoreboard e.g.: top pointer
// we can always put this instruction to the top unless we signal with asserted full_o
// Handshake's data with decode stage - ID_STAGE
input scoreboard_entry_t [CVA6Cfg.NrIssuePorts-1:0] decoded_instr_i,
// instruction value - ID_STAGE
input logic [CVA6Cfg.NrIssuePorts-1:0][31:0] orig_instr_i,
// Handshake's valid with decode stage - ID_STAGE
input logic [CVA6Cfg.NrIssuePorts-1:0] decoded_instr_valid_i,
// Handshake's acknowlege with decode stage - ID_STAGE
output logic [CVA6Cfg.NrIssuePorts-1:0] decoded_instr_ack_o,
// instruction to issue logic, if issue_instr_valid and issue_ready is asserted, advance the issue pointer
// Entry about the instruction to issue - ISSUE_READ_OPERANDS
output scoreboard_entry_t [CVA6Cfg.NrIssuePorts-1:0] issue_instr_o,
// Instruction to issue - ISSUE_READ_OPERANDS
output logic [CVA6Cfg.NrIssuePorts-1:0][31:0] orig_instr_o,
// Is there an instruction to issue - ISSUE_READ_OPERANDS
output logic [CVA6Cfg.NrIssuePorts-1:0] issue_instr_valid_o,
// Issue stage acknowledge - ISSUE_READ_OPERANDS
input logic [CVA6Cfg.NrIssuePorts-1:0] issue_ack_i,
// Forwarding - ISSUE_READ_OPERANDS
output forwarding_t fwd_o,
// Result from branch unit - EX_STAGE
input bp_resolve_t resolved_branch_i,
// Transaction ID at which to write the result back - EX_STAGE
input logic [CVA6Cfg.NrWbPorts-1:0][CVA6Cfg.TRANS_ID_BITS-1:0] trans_id_i,
// Results to write back - EX_STAGE
input logic [CVA6Cfg.NrWbPorts-1:0][CVA6Cfg.XLEN-1:0] wbdata_i,
// Exception from a functional unit (e.g.: ld/st exception) - EX_STAGE
input exception_t [CVA6Cfg.NrWbPorts-1:0] ex_i,
// Indicates valid results - EX_STAGE
input logic [CVA6Cfg.NrWbPorts-1:0] wt_valid_i,
// Cvxif we for writeback - EX_STAGE
input logic x_we_i,
// CVXIF destination register - ISSUE_STAGE
input logic [4:0] x_rd_i,
// Issue pointer - RVFI
output logic [ CVA6Cfg.NrIssuePorts-1:0][CVA6Cfg.TRANS_ID_BITS-1:0] rvfi_issue_pointer_o,
// Commit pointer - RVFI
output logic [CVA6Cfg.NrCommitPorts-1:0][CVA6Cfg.TRANS_ID_BITS-1:0] rvfi_commit_pointer_o
);
// this is the FIFO struct of the issue queue
typedef struct packed {
logic issued; // this bit indicates whether we issued this instruction e.g.: if it is valid
logic cancelled; // this instruction was cancelled (speculative scoreboard)
logic is_rd_fpr_flag; // redundant meta info, added for speed
scoreboard_entry_t sbe; // this is the score board entry we will send to ex
} sb_mem_t;
sb_mem_t [CVA6Cfg.NR_SB_ENTRIES-1:0] mem_q, mem_n;
logic [CVA6Cfg.NR_SB_ENTRIES-1:0] still_issued;
logic [CVA6Cfg.NrIssuePorts-1:0] issue_full;
logic [1:0][CVA6Cfg.NR_SB_ENTRIES/2-1:0] issued_instrs_even_odd;
logic bmiss;
logic [CVA6Cfg.TRANS_ID_BITS-1:0] after_flu_wb;
logic [CVA6Cfg.NR_SB_ENTRIES-1:0] speculative_instrs;
logic [CVA6Cfg.NrIssuePorts-1:0] num_issue;
logic [CVA6Cfg.TRANS_ID_BITS-1:0] issue_pointer_n, issue_pointer_q;
logic [CVA6Cfg.NrIssuePorts:0][CVA6Cfg.TRANS_ID_BITS-1:0] issue_pointer;
logic [CVA6Cfg.NrCommitPorts-1:0][CVA6Cfg.TRANS_ID_BITS-1:0] commit_pointer_n, commit_pointer_q;
logic [$clog2(CVA6Cfg.NrCommitPorts):0] num_commit;
for (genvar i = 0; i < CVA6Cfg.NR_SB_ENTRIES; i++) begin
assign still_issued[i] = mem_q[i].issued & ~mem_q[i].cancelled;
end
for (genvar i = 0; i < CVA6Cfg.NR_SB_ENTRIES; i++) begin
assign issued_instrs_even_odd[i%2][i/2] = mem_q[i].issued;
end
// the issue queue is full don't issue any new instructions
assign issue_full[0] = &issued_instrs_even_odd[0] && &issued_instrs_even_odd[1];
if (CVA6Cfg.SuperscalarEn) begin : assign_issue_full
// Need two slots available to issue two instructions.
// They are next to each other so one must be even and one odd
assign issue_full[1] = &issued_instrs_even_odd[0] || &issued_instrs_even_odd[1];
end
assign sb_full_o = issue_full[0];
// output commit instruction directly
always_comb begin : commit_ports
for (int unsigned i = 0; i < CVA6Cfg.NrCommitPorts; i++) begin
commit_instr_o[i] = mem_q[commit_pointer_q[i]].sbe;
commit_instr_o[i].trans_id = commit_pointer_q[i];
commit_drop_o[i] = mem_q[commit_pointer_q[i]].cancelled;
end
end
assign issue_pointer[0] = issue_pointer_q;
for (genvar i = 0; i < CVA6Cfg.NrIssuePorts; i++) begin
assign issue_pointer[i+1] = issue_pointer[i] + 'd1;
end
// an instruction is ready for issue if we have place in the issue FIFO and it the decoder says it is valid
always_comb begin
issue_instr_o = decoded_instr_i;
orig_instr_o = orig_instr_i;
for (int unsigned i = 0; i < CVA6Cfg.NrIssuePorts; i++) begin
// make sure we assign the correct trans ID
issue_instr_o[i].trans_id = issue_pointer[i];
issue_instr_valid_o[i] = decoded_instr_valid_i[i] & ~issue_full[i];
decoded_instr_ack_o[i] = issue_ack_i[i] & ~issue_full[i];
end
end
// maintain a FIFO with issued instructions
// keep track of all issued instructions
always_comb begin : issue_fifo
// default assignment
mem_n = mem_q;
num_issue = '0;
// if we got a acknowledge from the issue stage, put this scoreboard entry in the queue
for (int unsigned i = 0; i < CVA6Cfg.NrIssuePorts; i++) begin
if (decoded_instr_valid_i[i] && decoded_instr_ack_o[i] && !flush_unissued_instr_i) begin
// the decoded instruction we put in there is valid (1st bit)
// increase the issue counter and advance issue pointer
num_issue += 'd1;
mem_n[issue_pointer[i]] = '{
issued: 1'b1,
cancelled: 1'b0,
is_rd_fpr_flag: CVA6Cfg.FpPresent && ariane_pkg::is_rd_fpr(decoded_instr_i[i].op),
sbe: decoded_instr_i[i]
};
end
end
// ------------
// FU NONE
// ------------
for (int unsigned i = 0; i < CVA6Cfg.NR_SB_ENTRIES; i++) begin
// The FU is NONE -> this instruction is valid immediately
if (mem_q[i].sbe.fu == ariane_pkg::NONE && mem_q[i].issued) mem_n[i].sbe.valid = 1'b1;
end
// ------------
// Write Back
// ------------
for (int unsigned i = 0; i < CVA6Cfg.NrWbPorts; i++) begin
// check if this instruction was issued (e.g.: it could happen after a flush that there is still
// something in the pipeline e.g. an incomplete memory operation)
if (wt_valid_i[i] && mem_q[trans_id_i[i]].issued) begin
if (mem_q[trans_id_i[i]].sbe.is_double_rd_macro_instr && mem_q[trans_id_i[i]].sbe.is_macro_instr) begin
if (mem_q[trans_id_i[i]].sbe.is_last_macro_instr) begin
mem_n[trans_id_i[i]].sbe.valid = 1'b1;
mem_n[8'(trans_id_i[i])-1].sbe.valid = 1'b1;
end else begin
mem_n[trans_id_i[i]].sbe.valid = 1'b0;
end
end else begin
mem_n[trans_id_i[i]].sbe.valid = 1'b1;
end
mem_n[trans_id_i[i]].sbe.result = wbdata_i[i];
// save the target address of a branch (needed for debug in commit stage)
if (CVA6Cfg.DebugEn) begin
mem_n[trans_id_i[i]].sbe.bp.predict_address = resolved_branch_i.target_address;
end
if (mem_n[trans_id_i[i]].sbe.fu == ariane_pkg::CVXIF) begin
if (x_we_i) mem_n[trans_id_i[i]].sbe.rd = x_rd_i;
else mem_n[trans_id_i[i]].sbe.rd = 5'b0;
end
// write the exception back if it is valid
if (ex_i[i].valid) mem_n[trans_id_i[i]].sbe.ex = ex_i[i];
// write the fflags back from the FPU (exception valid is never set), leave tval intact
else if(CVA6Cfg.FpPresent && (mem_q[trans_id_i[i]].sbe.fu == ariane_pkg::FPU || mem_q[trans_id_i[i]].sbe.fu == ariane_pkg::FPU_VEC)) begin
mem_n[trans_id_i[i]].sbe.ex.cause = ex_i[i].cause;
end
end
end
// ------------
// Cancel
// ------------
if (CVA6Cfg.SpeculativeSb) begin
if (bmiss) begin
if (after_flu_wb != issue_pointer[0]) begin
mem_n[after_flu_wb].cancelled = 1'b1;
end
end
end
// ------------
// Commit Port
// ------------
// we've got an acknowledge from commit
for (int i = 0; i < CVA6Cfg.NrCommitPorts; i++) begin
if (commit_ack_i[i]) begin
// this instruction is no longer in issue e.g.: it is considered finished
mem_n[commit_pointer_q[i]].issued = 1'b0;
mem_n[commit_pointer_q[i]].cancelled = 1'b0;
mem_n[commit_pointer_q[i]].sbe.valid = 1'b0;
end
end
// ------
// Flush
// ------
if (flush_i) begin
for (int unsigned i = 0; i < CVA6Cfg.NR_SB_ENTRIES; i++) begin
// set all valid flags for all entries to zero
mem_n[i].issued = 1'b0;
mem_n[i].cancelled = 1'b0;
mem_n[i].sbe.valid = 1'b0;
mem_n[i].sbe.ex.valid = 1'b0;
end
end
end
assign bmiss = resolved_branch_i.valid && resolved_branch_i.is_mispredict;
assign after_flu_wb = trans_id_i[ariane_pkg::FLU_WB] + 'd1;
// FIFO counter updates
if (CVA6Cfg.NrCommitPorts == 2) begin : gen_commit_ports
assign num_commit = commit_ack_i[1] + commit_ack_i[0];
end else begin : gen_one_commit_port
assign num_commit = commit_ack_i[0];
end
assign commit_pointer_n[0] = (flush_i) ? '0 : commit_pointer_q[0] + num_commit;
always_comb begin : assign_issue_pointer_n
issue_pointer_n = issue_pointer[num_issue];
if (flush_i) issue_pointer_n = '0;
end
// precompute offsets for commit slots
for (genvar k = 1; k < CVA6Cfg.NrCommitPorts; k++) begin : gen_cnt_incr
assign commit_pointer_n[k] = (flush_i) ? '0 : commit_pointer_n[0] + unsigned'(k);
end
// Forwarding logic
writeback_t [CVA6Cfg.NrWbPorts-1:0] wb;
for (genvar i = 0; i < CVA6Cfg.NrWbPorts; i++) begin
assign wb[i].valid = wt_valid_i[i];
assign wb[i].data = wbdata_i[i];
assign wb[i].ex_valid = ex_i[i].valid;
assign wb[i].trans_id = trans_id_i[i];
end
assign fwd_o.still_issued = still_issued;
assign fwd_o.issue_pointer = issue_pointer;
assign fwd_o.wb = wb;
for (genvar i = 0; i < CVA6Cfg.NR_SB_ENTRIES; i++) begin
assign fwd_o.sbe[i] = mem_q[i].sbe;
end
// sequential process
always_ff @(posedge clk_i or negedge rst_ni) begin : regs
if (!rst_ni) begin
mem_q <= '{default: sb_mem_t'(0)};
commit_pointer_q <= '0;
issue_pointer_q <= '0;
end else begin
issue_pointer_q <= issue_pointer_n;
mem_q <= mem_n;
mem_q[x_id_i].sbe.rd <= (x_transaction_accepted_i && ~x_issue_writeback_i) ? 5'b0 : mem_n[x_id_i].sbe.rd;
commit_pointer_q <= commit_pointer_n;
end
end
//RVFI
assign rvfi_issue_pointer_o = issue_pointer[CVA6Cfg.NrIssuePorts-1:0];
assign rvfi_commit_pointer_o = commit_pointer_q;
//pragma translate_off
initial begin
assert (CVA6Cfg.NR_SB_ENTRIES == 2 ** CVA6Cfg.TRANS_ID_BITS)
else $fatal(1, "Scoreboard size needs to be a power of two.");
end
// assert that we never acknowledge a commit if the instruction is not valid
assert property (
@(posedge clk_i) disable iff (!rst_ni) commit_ack_i[0] |-> commit_instr_o[0].valid)
else $fatal(1, "Commit acknowledged but instruction is not valid");
if (CVA6Cfg.NrCommitPorts == 2) begin : gen_two_commit_ports
assert property (
@(posedge clk_i) disable iff (!rst_ni) commit_ack_i[1] |-> commit_instr_o[1].valid)
else $fatal(1, "Commit acknowledged but instruction is not valid");
end
// assert that we never give an issue ack signal if the instruction is not valid
for (genvar i = 0; i < CVA6Cfg.NrIssuePorts; i++) begin
assert property (
@(posedge clk_i) disable iff (!rst_ni) issue_ack_i[i] |-> issue_instr_valid_o[i])
else $fatal(1, "Issue acknowledged but instruction is not valid");
end
// there should never be more than one instruction writing the same destination register (except x0)
// check that no functional unit is retiring with the same transaction id
for (genvar i = 0; i < CVA6Cfg.NrWbPorts; i++) begin
for (genvar j = 0; j < CVA6Cfg.NrWbPorts; j++) begin
assert property (
@(posedge clk_i) disable iff (!rst_ni) wt_valid_i[i] && wt_valid_i[j] && (i != j) |-> (trans_id_i[i] != trans_id_i[j]))
else
$fatal(
1,
"Two or more functional units are retiring instructions with the same transaction id!"
);
end
end
//pragma translate_on
endmodule