mirror of
https://github.com/openhwgroup/cva6.git
synced 2025-04-19 03:44:46 -04:00
Scoreboard refactoring for better timing
This commit is contained in:
parent
2f0d26d997
commit
1c4940f4cd
7 changed files with 364 additions and 294 deletions
|
@ -16,7 +16,7 @@ git submodule update --init --recursive
|
|||
|
||||
# copy ariane specific config
|
||||
cp config/default.config config/default.config.bak
|
||||
cp $ROOT/ci/default.config config/default.config
|
||||
cp $ROOT/ci/float.config config/default.config
|
||||
git checkout ./output/Makefile
|
||||
git apply $ROOT/ci/torture_make.patch
|
||||
|
||||
|
|
|
@ -38,13 +38,13 @@ package ariane_pkg;
|
|||
int BTBEntries;
|
||||
int BHTEntries;
|
||||
// PMAs
|
||||
int NrNonIdempotentRules; // Number of non idempotent rules
|
||||
int unsigned NrNonIdempotentRules; // Number of non idempotent rules
|
||||
logic [NrMaxRules-1:0][63:0] NonIdempotentAddrBase; // base which needs to match
|
||||
logic [NrMaxRules-1:0][63:0] NonIdempotentLength; // bit mask which bits to consider when matching the rule
|
||||
int NrExecuteRegionRules; // Number of regions which have execute property
|
||||
int unsigned NrExecuteRegionRules; // Number of regions which have execute property
|
||||
logic [NrMaxRules-1:0][63:0] ExecuteRegionAddrBase; // base which needs to match
|
||||
logic [NrMaxRules-1:0][63:0] ExecuteRegionLength; // bit mask which bits to consider when matching the rule
|
||||
int NrCachedRegionRules; // Number of regions which have cached property
|
||||
int unsigned NrCachedRegionRules; // Number of regions which have cached property
|
||||
logic [NrMaxRules-1:0][63:0] CachedRegionAddrBase; // base which needs to match
|
||||
logic [NrMaxRules-1:0][63:0] CachedRegionLength; // bit mask which bits to consider when matching the rule
|
||||
// cache config
|
||||
|
@ -92,7 +92,7 @@ package ariane_pkg;
|
|||
endfunction
|
||||
|
||||
function automatic logic range_check(logic[63:0] base, logic[63:0] len, logic[63:0] address);
|
||||
// if len is a power of two, and base is properly aligned, this chack can be simplified
|
||||
// if len is a power of two, and base is properly aligned, this check could be simplified
|
||||
return (address >= base) && (address < (base+len));
|
||||
endfunction : range_check
|
||||
|
||||
|
|
|
@ -299,12 +299,12 @@ package riscv;
|
|||
localparam int unsigned IRQ_S_EXT = 9;
|
||||
localparam int unsigned IRQ_M_EXT = 11;
|
||||
|
||||
localparam logic [63:0] MIP_SSIP = (1 << IRQ_S_SOFT);
|
||||
localparam logic [63:0] MIP_MSIP = (1 << IRQ_M_SOFT);
|
||||
localparam logic [63:0] MIP_STIP = (1 << IRQ_S_TIMER);
|
||||
localparam logic [63:0] MIP_MTIP = (1 << IRQ_M_TIMER);
|
||||
localparam logic [63:0] MIP_SEIP = (1 << IRQ_S_EXT);
|
||||
localparam logic [63:0] MIP_MEIP = (1 << IRQ_M_EXT);
|
||||
localparam logic [63:0] MIP_SSIP = 1 << IRQ_S_SOFT;
|
||||
localparam logic [63:0] MIP_MSIP = 1 << IRQ_M_SOFT;
|
||||
localparam logic [63:0] MIP_STIP = 1 << IRQ_S_TIMER;
|
||||
localparam logic [63:0] MIP_MTIP = 1 << IRQ_M_TIMER;
|
||||
localparam logic [63:0] MIP_SEIP = 1 << IRQ_S_EXT;
|
||||
localparam logic [63:0] MIP_MEIP = 1 << IRQ_M_EXT;
|
||||
|
||||
localparam logic [63:0] S_SW_INTERRUPT = (1 << 63) | IRQ_S_SOFT;
|
||||
localparam logic [63:0] M_SW_INTERRUPT = (1 << 63) | IRQ_M_SOFT;
|
||||
|
|
|
@ -32,8 +32,9 @@ module fpu_wrap (
|
|||
output exception_t fpu_exception_o
|
||||
);
|
||||
|
||||
// this is a workaround
|
||||
// otherwise compilation might issue an error if FLEN=0
|
||||
// this is a workaround
|
||||
// otherwise compilation might issue an error if FLEN=0
|
||||
enum logic {READY, STALL} state_q, state_d;
|
||||
if (FP_PRESENT) begin : fpu_gen
|
||||
logic [FLEN-1:0] operand_a_i;
|
||||
logic [FLEN-1:0] operand_b_i;
|
||||
|
@ -94,7 +95,6 @@ module fpu_wrap (
|
|||
logic [4:0] fpu_status;
|
||||
|
||||
// FSM to handle protocol inversion
|
||||
enum logic {READY, STALL} state_q, state_d;
|
||||
logic hold_inputs;
|
||||
logic use_hold;
|
||||
|
||||
|
|
|
@ -37,8 +37,8 @@ module issue_read_operands #(
|
|||
input logic [FLEN-1:0] rs3_i,
|
||||
input logic rs3_valid_i,
|
||||
// get clobber input
|
||||
input fu_t [2**REG_ADDR_SIZE:0] rd_clobber_gpr_i,
|
||||
input fu_t [2**REG_ADDR_SIZE:0] rd_clobber_fpr_i,
|
||||
input fu_t [2**REG_ADDR_SIZE-1:0] rd_clobber_gpr_i,
|
||||
input fu_t [2**REG_ADDR_SIZE-1:0] rd_clobber_fpr_i,
|
||||
// To FU, just single issue for now
|
||||
output fu_data_t fu_data_o,
|
||||
output logic [63:0] pc_o,
|
||||
|
|
|
@ -74,8 +74,8 @@ module issue_stage #(
|
|||
// ---------------------------------------------------
|
||||
// Scoreboard (SB) <-> Issue and Read Operands (IRO)
|
||||
// ---------------------------------------------------
|
||||
fu_t [2**REG_ADDR_SIZE:0] rd_clobber_gpr_sb_iro;
|
||||
fu_t [2**REG_ADDR_SIZE:0] rd_clobber_fpr_sb_iro;
|
||||
fu_t [2**REG_ADDR_SIZE-1:0] rd_clobber_gpr_sb_iro;
|
||||
fu_t [2**REG_ADDR_SIZE-1:0] rd_clobber_fpr_sb_iro;
|
||||
|
||||
logic [REG_ADDR_SIZE-1:0] rs1_iro_sb;
|
||||
logic [63:0] rs1_sb_iro;
|
||||
|
|
|
@ -12,307 +12,377 @@
|
|||
// Date: 08.04.2017
|
||||
// Description: Scoreboard - keeps track of all decoded, issued and committed instructions
|
||||
|
||||
import ariane_pkg::*;
|
||||
|
||||
module scoreboard #(
|
||||
parameter int unsigned NR_ENTRIES = 8,
|
||||
parameter int unsigned NR_WB_PORTS = 1,
|
||||
parameter int unsigned NR_COMMIT_PORTS = 2
|
||||
)(
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
output logic sb_full_o,
|
||||
input logic flush_unissued_instr_i, // flush only un-issued instructions
|
||||
input logic flush_i, // flush whole scoreboard
|
||||
input logic unresolved_branch_i, // we have an unresolved branch
|
||||
// list of clobbered registers to issue stage
|
||||
output fu_t [2**REG_ADDR_SIZE:0] rd_clobber_gpr_o,
|
||||
output fu_t [2**REG_ADDR_SIZE:0] rd_clobber_fpr_o,
|
||||
parameter int unsigned NR_ENTRIES = 8, // must be a power of 2
|
||||
parameter int unsigned NR_WB_PORTS = 1,
|
||||
parameter int unsigned NR_COMMIT_PORTS = 2
|
||||
) (
|
||||
input logic clk_i, // Clock
|
||||
input logic rst_ni, // Asynchronous reset active low
|
||||
output logic sb_full_o,
|
||||
input logic flush_unissued_instr_i, // flush only un-issued instructions
|
||||
input logic flush_i, // flush whole scoreboard
|
||||
input logic unresolved_branch_i, // we have an unresolved branch
|
||||
// list of clobbered registers to issue stage
|
||||
output ariane_pkg::fu_t [2**ariane_pkg::REG_ADDR_SIZE-1:0] rd_clobber_gpr_o,
|
||||
output ariane_pkg::fu_t [2**ariane_pkg::REG_ADDR_SIZE-1:0] rd_clobber_fpr_o,
|
||||
|
||||
// regfile like interface to operand read stage
|
||||
input logic [REG_ADDR_SIZE-1:0] rs1_i,
|
||||
output logic [63:0] rs1_o,
|
||||
output logic rs1_valid_o,
|
||||
// regfile like interface to operand read stage
|
||||
input logic [ariane_pkg::REG_ADDR_SIZE-1:0] rs1_i,
|
||||
output logic [63:0] rs1_o,
|
||||
output logic rs1_valid_o,
|
||||
|
||||
input logic [REG_ADDR_SIZE-1:0] rs2_i,
|
||||
output logic [63:0] rs2_o,
|
||||
output logic rs2_valid_o,
|
||||
input logic [ariane_pkg::REG_ADDR_SIZE-1:0] rs2_i,
|
||||
output logic [63:0] rs2_o,
|
||||
output logic rs2_valid_o,
|
||||
|
||||
input logic [REG_ADDR_SIZE-1:0] rs3_i,
|
||||
output logic [FLEN-1:0] rs3_o,
|
||||
output logic rs3_valid_o,
|
||||
input logic [ariane_pkg::REG_ADDR_SIZE-1:0] rs3_i,
|
||||
output logic [ariane_pkg::FLEN-1:0] rs3_o,
|
||||
output logic rs3_valid_o,
|
||||
|
||||
// advertise instruction to commit stage, if commit_ack_i is asserted advance the commit pointer
|
||||
output scoreboard_entry_t [NR_COMMIT_PORTS-1:0] commit_instr_o,
|
||||
input logic [NR_COMMIT_PORTS-1:0] commit_ack_i,
|
||||
// advertise instruction to commit stage, if commit_ack_i is asserted advance the commit pointer
|
||||
output ariane_pkg::scoreboard_entry_t [NR_COMMIT_PORTS-1:0] commit_instr_o,
|
||||
input logic [NR_COMMIT_PORTS-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
|
||||
input scoreboard_entry_t decoded_instr_i,
|
||||
input logic decoded_instr_valid_i,
|
||||
output logic decoded_instr_ack_o,
|
||||
// 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
|
||||
input ariane_pkg::scoreboard_entry_t decoded_instr_i,
|
||||
input logic decoded_instr_valid_i,
|
||||
output logic decoded_instr_ack_o,
|
||||
|
||||
// instruction to issue logic, if issue_instr_valid and issue_ready is asserted, advance the issue pointer
|
||||
output scoreboard_entry_t issue_instr_o,
|
||||
output logic issue_instr_valid_o,
|
||||
input logic issue_ack_i,
|
||||
// instruction to issue logic, if issue_instr_valid and issue_ready is asserted, advance the issue pointer
|
||||
output ariane_pkg::scoreboard_entry_t issue_instr_o,
|
||||
output logic issue_instr_valid_o,
|
||||
input logic issue_ack_i,
|
||||
|
||||
// write-back port
|
||||
input bp_resolve_t resolved_branch_i,
|
||||
input logic [NR_WB_PORTS-1:0][TRANS_ID_BITS-1:0] trans_id_i, // transaction ID at which to write the result back
|
||||
input logic [NR_WB_PORTS-1:0][63:0] wbdata_i, // write data in
|
||||
input exception_t [NR_WB_PORTS-1:0] ex_i, // exception from a functional unit (e.g.: ld/st exception)
|
||||
input logic [NR_WB_PORTS-1:0] wt_valid_i // data in is valid
|
||||
// write-back port
|
||||
input ariane_pkg::bp_resolve_t resolved_branch_i,
|
||||
input logic [NR_WB_PORTS-1:0][ariane_pkg::TRANS_ID_BITS-1:0] trans_id_i, // transaction ID at which to write the result back
|
||||
input logic [NR_WB_PORTS-1:0][63:0] wbdata_i, // write data in
|
||||
input ariane_pkg::exception_t [NR_WB_PORTS-1:0] ex_i, // exception from a functional unit (e.g.: ld/st exception)
|
||||
input logic [NR_WB_PORTS-1:0] wt_valid_i // data in is valid
|
||||
);
|
||||
localparam int unsigned BITS_ENTRIES = $clog2(NR_ENTRIES);
|
||||
localparam int unsigned BITS_ENTRIES = $clog2(NR_ENTRIES);
|
||||
|
||||
// this is the FIFO struct of the issue queue
|
||||
struct packed {
|
||||
logic issued; // this bit indicates whether we issued this instruction e.g.: if it is valid
|
||||
scoreboard_entry_t sbe; // this is the score board entry we will send to ex
|
||||
} mem_q [NR_ENTRIES-1:0], mem_n [NR_ENTRIES-1:0];
|
||||
// this is the FIFO struct of the issue queue
|
||||
struct packed {
|
||||
logic issued; // this bit indicates whether we issued this instruction e.g.: if it is valid
|
||||
logic is_rd_fpr_flag; // redundant meta info, added for speed
|
||||
ariane_pkg::scoreboard_entry_t sbe; // this is the score board entry we will send to ex
|
||||
} mem_q [NR_ENTRIES-1:0], mem_n [NR_ENTRIES-1:0];
|
||||
|
||||
logic [BITS_ENTRIES-1:0] issue_cnt_n, issue_cnt_q;
|
||||
logic [BITS_ENTRIES-1:0] issue_pointer_n, issue_pointer_q;
|
||||
logic [BITS_ENTRIES-1:0] commit_pointer_n, commit_pointer_q;
|
||||
logic issue_full;
|
||||
logic issue_full, issue_en;
|
||||
logic [BITS_ENTRIES-1:0] issue_cnt_n, issue_cnt_q;
|
||||
logic [BITS_ENTRIES-1:0] issue_pointer_n, issue_pointer_q;
|
||||
logic [NR_COMMIT_PORTS-1:0][BITS_ENTRIES-1:0] commit_pointer_n, commit_pointer_q;
|
||||
logic [$clog2(NR_COMMIT_PORTS):0] num_commit;
|
||||
|
||||
// the issue queue is full don't issue any new instructions
|
||||
assign issue_full = (issue_cnt_q == NR_ENTRIES-1);
|
||||
// the issue queue is full don't issue any new instructions
|
||||
// works since aligned to power of 2
|
||||
assign issue_full = &issue_cnt_q;
|
||||
|
||||
assign sb_full_o = issue_full;
|
||||
assign sb_full_o = issue_full;
|
||||
|
||||
// output commit instruction directly
|
||||
always_comb begin : commit_ports
|
||||
for (logic [BITS_ENTRIES-1:0] i = 0; i < NR_COMMIT_PORTS; i++)
|
||||
commit_instr_o[i] = mem_q[commit_pointer_q + i].sbe;
|
||||
// output commit instruction directly
|
||||
always_comb begin : commit_ports
|
||||
for (int unsigned i = 0; i < NR_COMMIT_PORTS; i++)
|
||||
commit_instr_o[i] = mem_q[commit_pointer_q[i]].sbe;
|
||||
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;
|
||||
// make sure we assign the correct trans ID
|
||||
issue_instr_o.trans_id = issue_pointer_q;
|
||||
// we are ready if we are not full and don't have any unresolved branches, but it can be
|
||||
// the case that we have an unresolved branch which is cleared in that cycle (resolved_branch_i == 1)
|
||||
issue_instr_valid_o = decoded_instr_valid_i & ~unresolved_branch_i & ~issue_full;
|
||||
decoded_instr_ack_o = issue_ack_i & ~issue_full;
|
||||
end
|
||||
|
||||
// maintain a FIFO with issued instructions
|
||||
// keep track of all issued instructions
|
||||
always_comb begin : issue_fifo
|
||||
// default assignment
|
||||
mem_n = mem_q;
|
||||
issue_en = 1'b0;
|
||||
|
||||
// if we got a acknowledge from the issue stage, put this scoreboard entry in the queue
|
||||
if (decoded_instr_valid_i && decoded_instr_ack_o && !flush_unissued_instr_i) begin
|
||||
// the decoded instruction we put in there is valid (1st bit)
|
||||
// increase the issue counter and advance issue pointer
|
||||
issue_en = 1'b1;
|
||||
mem_n[issue_pointer_q] = {1'b1, // valid bit
|
||||
ariane_pkg::is_rd_fpr(decoded_instr_i.op), // whether rd goes to the fpr
|
||||
decoded_instr_i // decoded instruction record
|
||||
};
|
||||
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;
|
||||
// make sure we assign the correct trans ID
|
||||
issue_instr_o.trans_id = issue_pointer_q;
|
||||
// we are ready if we are not full and don't have any unresolved branches, but it can be
|
||||
// the case that we have an unresolved branch which is cleared in that cycle (resolved_branch_i == 1)
|
||||
issue_instr_valid_o = decoded_instr_valid_i && !unresolved_branch_i && !issue_full;
|
||||
decoded_instr_ack_o = issue_ack_i && !issue_full;
|
||||
// ------------
|
||||
// Write Back
|
||||
// ------------
|
||||
for (int unsigned i = 0; i < NR_WB_PORTS; 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
|
||||
mem_n[trans_id_i[i]].sbe.valid = 1'b1;
|
||||
mem_n[trans_id_i[i]].sbe.result = wbdata_i[i];
|
||||
// save the target address of a branch (needed for debug in commit stage)
|
||||
mem_n[trans_id_i[i]].sbe.bp.predict_address = resolved_branch_i.target_address;
|
||||
// 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 (mem_q[trans_id_i[i]].sbe.fu inside {ariane_pkg::FPU, ariane_pkg::FPU_VEC})
|
||||
mem_n[trans_id_i[i]].sbe.ex.cause = ex_i[i].cause;
|
||||
end
|
||||
end
|
||||
|
||||
// maintain a FIFO with issued instructions
|
||||
// keep track of all issued instructions
|
||||
always_comb begin : issue_fifo
|
||||
automatic logic [BITS_ENTRIES-1:0] issue_cnt;
|
||||
automatic logic [BITS_ENTRIES-1:0] commit_pointer;
|
||||
|
||||
commit_pointer = commit_pointer_q;
|
||||
issue_cnt = issue_cnt_q;
|
||||
|
||||
// default assignment
|
||||
mem_n = mem_q;
|
||||
issue_pointer_n = issue_pointer_q;
|
||||
|
||||
// if we got a acknowledge from the issue stage, put this scoreboard entry in the queue
|
||||
if (decoded_instr_valid_i && decoded_instr_ack_o && !flush_unissued_instr_i) begin
|
||||
// the decoded instruction we put in there is valid (1st bit)
|
||||
// increase the issue counter
|
||||
issue_cnt++;
|
||||
mem_n[issue_pointer_q] = {1'b1, decoded_instr_i};
|
||||
// advance issue pointer
|
||||
issue_pointer_n = issue_pointer_q + 1'b1;
|
||||
end
|
||||
|
||||
// ------------
|
||||
// Write Back
|
||||
// ------------
|
||||
for (int unsigned i = 0; i < NR_WB_PORTS; 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_n[trans_id_i[i]].issued) begin
|
||||
mem_n[trans_id_i[i]].sbe.valid = 1'b1;
|
||||
mem_n[trans_id_i[i]].sbe.result = wbdata_i[i];
|
||||
// save the target address of a branch (needed for debug in commit stage)
|
||||
mem_n[trans_id_i[i]].sbe.bp.predict_address = resolved_branch_i.target_address;
|
||||
// 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 (mem_n[trans_id_i[i]].sbe.fu inside {FPU, FPU_VEC})
|
||||
mem_n[trans_id_i[i]].sbe.ex.cause = ex_i[i].cause;
|
||||
end
|
||||
end
|
||||
|
||||
// ------------
|
||||
// Commit Port
|
||||
// ------------
|
||||
// we've got an acknowledge from commit
|
||||
for (logic [BITS_ENTRIES-1:0] i = 0; i < NR_COMMIT_PORTS; i++) begin
|
||||
if (commit_ack_i[i]) begin
|
||||
// decrease the issue counter
|
||||
issue_cnt--;
|
||||
// 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].sbe.valid = 1'b0;
|
||||
// advance commit pointer
|
||||
commit_pointer++;
|
||||
end
|
||||
end
|
||||
|
||||
// ------
|
||||
// Flush
|
||||
// ------
|
||||
if (flush_i) begin
|
||||
for (int unsigned i = 0; i < NR_ENTRIES; i++) begin
|
||||
// set all valid flags for all entries to zero
|
||||
mem_n[i].issued = 1'b0;
|
||||
mem_n[i].sbe.valid = 1'b0;
|
||||
mem_n[i].sbe.ex.valid = 1'b0;
|
||||
// set the pointer and counter back to zero
|
||||
issue_cnt = '0;
|
||||
issue_pointer_n = '0;
|
||||
commit_pointer = '0;
|
||||
end
|
||||
end
|
||||
|
||||
// update issue counter
|
||||
issue_cnt_n = issue_cnt;
|
||||
// update commit potiner
|
||||
commit_pointer_n = commit_pointer;
|
||||
// ------------
|
||||
// Commit Port
|
||||
// ------------
|
||||
// we've got an acknowledge from commit
|
||||
for (logic [BITS_ENTRIES-1:0] i = 0; i < NR_COMMIT_PORTS; 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]].sbe.valid = 1'b0;
|
||||
end
|
||||
end
|
||||
|
||||
// -------------------
|
||||
// RD clobber process
|
||||
// -------------------
|
||||
// rd_clobber output: output currently clobbered destination registers
|
||||
always_comb begin : clobber_output
|
||||
rd_clobber_gpr_o = '{default: NONE};
|
||||
rd_clobber_fpr_o = '{default: NONE};
|
||||
// check for all valid entries and set the clobber register accordingly
|
||||
for (int unsigned i = 0; i < NR_ENTRIES; i++) begin
|
||||
if (mem_q[i].issued) begin
|
||||
// output the functional unit which is going to clobber this register
|
||||
if (is_rd_fpr(mem_q[i].sbe.op))
|
||||
rd_clobber_fpr_o[mem_q[i].sbe.rd] = mem_q[i].sbe.fu;
|
||||
else
|
||||
rd_clobber_gpr_o[mem_q[i].sbe.rd] = mem_q[i].sbe.fu;
|
||||
end
|
||||
end
|
||||
// the gpr zero register is always free
|
||||
rd_clobber_gpr_o[0] = NONE;
|
||||
// ------
|
||||
// Flush
|
||||
// ------
|
||||
if (flush_i) begin
|
||||
for (int unsigned i = 0; i < NR_ENTRIES; i++) begin
|
||||
// set all valid flags for all entries to zero
|
||||
mem_n[i].issued = 1'b0;
|
||||
mem_n[i].sbe.valid = 1'b0;
|
||||
mem_n[i].sbe.ex.valid = 1'b0;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// FIFO counter updates
|
||||
popcount #(
|
||||
.INPUT_WIDTH(NR_COMMIT_PORTS)
|
||||
) i_popcount (
|
||||
.data_i(commit_ack_i),
|
||||
.popcount_o(num_commit)
|
||||
);
|
||||
|
||||
assign issue_cnt_n = (flush_i) ? '0 : issue_cnt_q - num_commit + issue_en;
|
||||
assign commit_pointer_n[0] = (flush_i) ? '0 : commit_pointer_q[0] + num_commit;
|
||||
assign issue_pointer_n = (flush_i) ? '0 : issue_pointer_q + issue_en;
|
||||
|
||||
// precompute offsets for commit slots
|
||||
for (genvar k=1; k < NR_COMMIT_PORTS; k++) begin : gen_cnt_incr
|
||||
assign commit_pointer_n[k] = (flush_i) ? '0 : commit_pointer_n[0] + unsigned'(k);
|
||||
end
|
||||
|
||||
// -------------------
|
||||
// RD clobber process
|
||||
// -------------------
|
||||
// rd_clobber output: output currently clobbered destination registers
|
||||
logic [2**ariane_pkg::REG_ADDR_SIZE-1:0][NR_ENTRIES:0] gpr_clobber_vld;
|
||||
logic [2**ariane_pkg::REG_ADDR_SIZE-1:0][NR_ENTRIES:0] fpr_clobber_vld;
|
||||
ariane_pkg::fu_t [NR_ENTRIES:0] clobber_fu;
|
||||
|
||||
always_comb begin : clobber_assign
|
||||
gpr_clobber_vld = '0;
|
||||
fpr_clobber_vld = '0;
|
||||
|
||||
// default (highest entry hast lowest prio in arbiter tree below)
|
||||
clobber_fu[NR_ENTRIES] = ariane_pkg::NONE;
|
||||
for (int unsigned i = 0; i < 2**ariane_pkg::REG_ADDR_SIZE; i++) begin
|
||||
gpr_clobber_vld[i][NR_ENTRIES] = 1'b1;
|
||||
fpr_clobber_vld[i][NR_ENTRIES] = 1'b1;
|
||||
end
|
||||
|
||||
// ----------------------------------
|
||||
// Read Operands (a.k.a forwarding)
|
||||
// ----------------------------------
|
||||
// read operand interface: same logic as register file
|
||||
always_comb begin : read_operands
|
||||
rs1_o = 64'b0;
|
||||
rs2_o = 64'b0;
|
||||
rs3_o = '0;
|
||||
rs1_valid_o = 1'b0;
|
||||
rs2_valid_o = 1'b0;
|
||||
rs3_valid_o = 1'b0;
|
||||
|
||||
for (int unsigned i = 0; i < NR_ENTRIES; i++) begin
|
||||
// only consider this entry if it is valid
|
||||
if (mem_q[i].issued) begin
|
||||
// look at the appropriate fields and look whether there was an
|
||||
// instruction that wrote the rd field before, first for RS1 and then for RS2, then for RS3
|
||||
// we check the type of the stored result register file against issued register file
|
||||
if ((mem_q[i].sbe.rd == rs1_i) && (is_rd_fpr(mem_q[i].sbe.op) == is_rs1_fpr(issue_instr_o.op))) begin
|
||||
rs1_o = mem_q[i].sbe.result;
|
||||
rs1_valid_o = mem_q[i].sbe.valid;
|
||||
end else if ((mem_q[i].sbe.rd == rs2_i) && (is_rd_fpr(mem_q[i].sbe.op) == is_rs2_fpr(issue_instr_o.op))) begin
|
||||
rs2_o = mem_q[i].sbe.result;
|
||||
rs2_valid_o = mem_q[i].sbe.valid;
|
||||
end else if ((mem_q[i].sbe.rd == rs3_i) && (is_rd_fpr(mem_q[i].sbe.op) == is_imm_fpr(issue_instr_o.op))) begin
|
||||
rs3_o = mem_q[i].sbe.result;
|
||||
rs3_valid_o = mem_q[i].sbe.valid;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
// -----------
|
||||
// Forwarding
|
||||
// -----------
|
||||
// provide a direct combinational path from WB a.k.a forwarding
|
||||
// make sure that we are not forwarding a result that got an exception
|
||||
for (int unsigned j = 0; j < NR_WB_PORTS; j++) begin
|
||||
if (mem_q[trans_id_i[j]].sbe.rd == rs1_i && wt_valid_i[j] && ~ex_i[j].valid
|
||||
&& (is_rd_fpr(mem_q[trans_id_i[j]].sbe.op) == is_rs1_fpr(issue_instr_o.op))) begin
|
||||
rs1_o = wbdata_i[j];
|
||||
rs1_valid_o = wt_valid_i[j];
|
||||
break;
|
||||
end
|
||||
if (mem_q[trans_id_i[j]].sbe.rd == rs2_i && wt_valid_i[j] && ~ex_i[j].valid
|
||||
&& (is_rd_fpr(mem_q[trans_id_i[j]].sbe.op) == is_rs2_fpr(issue_instr_o.op))) begin
|
||||
rs2_o = wbdata_i[j];
|
||||
rs2_valid_o = wt_valid_i[j];
|
||||
break;
|
||||
end
|
||||
if (mem_q[trans_id_i[j]].sbe.rd == rs3_i && wt_valid_i[j] && ~ex_i[j].valid
|
||||
&& (is_rd_fpr(mem_q[trans_id_i[j]].sbe.op) == is_imm_fpr(issue_instr_o.op))) begin
|
||||
rs3_o = wbdata_i[j];
|
||||
rs3_valid_o = wt_valid_i[j];
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
// make sure we didn't read the zero register
|
||||
if (rs1_i == '0 && ~is_rs1_fpr(issue_instr_o.op)) // only GPR reg0 is 0
|
||||
rs1_valid_o = 1'b0;
|
||||
if (rs2_i == '0 && ~is_rs2_fpr(issue_instr_o.op)) // only GPR reg0 is 0
|
||||
rs2_valid_o = 1'b0;
|
||||
// check for all valid entries and set the clobber accordingly
|
||||
for (int unsigned i = 0; i < NR_ENTRIES; i++) begin
|
||||
gpr_clobber_vld[mem_q[i].sbe.rd][i] = mem_q[i].issued & ~mem_q[i].is_rd_fpr_flag;
|
||||
fpr_clobber_vld[mem_q[i].sbe.rd][i] = mem_q[i].issued & mem_q[i].is_rd_fpr_flag;
|
||||
clobber_fu[i] = mem_q[i].sbe.fu;
|
||||
end
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin
|
||||
if(~rst_ni) begin
|
||||
mem_q <= '{default: 0};
|
||||
issue_cnt_q <= '0;
|
||||
commit_pointer_q <= '0;
|
||||
issue_pointer_q <= '0;
|
||||
end else begin
|
||||
issue_cnt_q <= issue_cnt_n;
|
||||
issue_pointer_q <= issue_pointer_n;
|
||||
mem_q <= mem_n;
|
||||
commit_pointer_q <= commit_pointer_n;
|
||||
end
|
||||
// GPR[0] is always free
|
||||
gpr_clobber_vld[0] = '0;
|
||||
end
|
||||
|
||||
for (genvar k = 0; k < 2**ariane_pkg::REG_ADDR_SIZE; k++) begin : gen_sel_clobbers
|
||||
// get fu that is going to clobber this register (there should be only one)
|
||||
rr_arb_tree #(
|
||||
.NumIn(NR_ENTRIES+1),
|
||||
.DataType(ariane_pkg::fu_t),
|
||||
.ExtPrio(1'b1),
|
||||
.AxiVldRdy(1'b1)
|
||||
) i_sel_gpr_clobbers (
|
||||
.clk_i ( clk_i ),
|
||||
.rst_ni ( rst_ni ),
|
||||
.flush_i ( 1'b0 ),
|
||||
.rr_i ( '0 ),
|
||||
.req_i ( gpr_clobber_vld[k] ),
|
||||
.gnt_o ( ),
|
||||
.data_i ( clobber_fu ),
|
||||
.gnt_i ( 1'b1 ),
|
||||
.req_o ( ),
|
||||
.data_o ( rd_clobber_gpr_o[k] ),
|
||||
.idx_o ( )
|
||||
);
|
||||
rr_arb_tree #(
|
||||
.NumIn(NR_ENTRIES+1),
|
||||
.DataType(ariane_pkg::fu_t),
|
||||
.ExtPrio(1'b1),
|
||||
.AxiVldRdy(1'b1)
|
||||
) i_sel_fpr_clobbers (
|
||||
.clk_i ( clk_i ),
|
||||
.rst_ni ( rst_ni ),
|
||||
.flush_i ( 1'b0 ),
|
||||
.rr_i ( '0 ),
|
||||
.req_i ( fpr_clobber_vld[k] ),
|
||||
.gnt_o ( ),
|
||||
.data_i ( clobber_fu ),
|
||||
.gnt_i ( 1'b1 ),
|
||||
.req_o ( ),
|
||||
.data_o ( rd_clobber_fpr_o[k] ),
|
||||
.idx_o ( )
|
||||
);
|
||||
end
|
||||
|
||||
// ----------------------------------
|
||||
// Read Operands (a.k.a forwarding)
|
||||
// ----------------------------------
|
||||
// read operand interface: same logic as register file
|
||||
logic [NR_ENTRIES+NR_WB_PORTS-1:0] rs1_fwd_req, rs2_fwd_req, rs3_fwd_req;
|
||||
logic [NR_ENTRIES+NR_WB_PORTS-1:0][63:0] rs_data;
|
||||
logic rs1_valid, rs2_valid;
|
||||
|
||||
// WB ports have higher prio than entries
|
||||
for (genvar k = 0; unsigned'(k) < NR_WB_PORTS; k++) begin : gen_rs_wb
|
||||
assign rs1_fwd_req[k] = (mem_q[trans_id_i[k]].sbe.rd == rs1_i) & wt_valid_i[k] & (~ex_i[k].valid) & (mem_q[trans_id_i[k]].is_rd_fpr_flag == ariane_pkg::is_rs1_fpr(issue_instr_o.op));
|
||||
assign rs2_fwd_req[k] = (mem_q[trans_id_i[k]].sbe.rd == rs2_i) & wt_valid_i[k] & (~ex_i[k].valid) & (mem_q[trans_id_i[k]].is_rd_fpr_flag == ariane_pkg::is_rs2_fpr(issue_instr_o.op));
|
||||
assign rs3_fwd_req[k] = (mem_q[trans_id_i[k]].sbe.rd == rs3_i) & wt_valid_i[k] & (~ex_i[k].valid) & (mem_q[trans_id_i[k]].is_rd_fpr_flag == ariane_pkg::is_imm_fpr(issue_instr_o.op));
|
||||
assign rs_data[k] = wbdata_i[k];
|
||||
end
|
||||
for (genvar k = 0; unsigned'(k) < NR_ENTRIES; k++) begin : gen_rs_entries
|
||||
assign rs1_fwd_req[k+NR_WB_PORTS] = (mem_q[k].sbe.rd == rs1_i) & mem_q[k].issued & mem_q[k].sbe.valid & (mem_q[k].is_rd_fpr_flag == ariane_pkg::is_rs1_fpr(issue_instr_o.op));
|
||||
assign rs2_fwd_req[k+NR_WB_PORTS] = (mem_q[k].sbe.rd == rs2_i) & mem_q[k].issued & mem_q[k].sbe.valid & (mem_q[k].is_rd_fpr_flag == ariane_pkg::is_rs2_fpr(issue_instr_o.op));
|
||||
assign rs3_fwd_req[k+NR_WB_PORTS] = (mem_q[k].sbe.rd == rs3_i) & mem_q[k].issued & mem_q[k].sbe.valid & (mem_q[k].is_rd_fpr_flag == ariane_pkg::is_imm_fpr(issue_instr_o.op));
|
||||
assign rs_data[k+NR_WB_PORTS] = mem_q[k].sbe.result;
|
||||
end
|
||||
|
||||
// check whether we are accessing GPR[0], rs3 is only used with the FPR!
|
||||
assign rs1_valid_o = rs1_valid & ((|rs1_i) | ariane_pkg::is_rs1_fpr(issue_instr_o.op));
|
||||
assign rs2_valid_o = rs2_valid & ((|rs2_i) | ariane_pkg::is_rs2_fpr(issue_instr_o.op));
|
||||
|
||||
// use fixed prio here
|
||||
// this implicitly gives higher prio to WB ports
|
||||
rr_arb_tree #(
|
||||
.NumIn(NR_ENTRIES+NR_WB_PORTS),
|
||||
.DataWidth(64),
|
||||
.ExtPrio(1'b1),
|
||||
.AxiVldRdy(1'b1)
|
||||
) i_sel_rs1 (
|
||||
.clk_i ( clk_i ),
|
||||
.rst_ni ( rst_ni ),
|
||||
.flush_i ( 1'b0 ),
|
||||
.rr_i ( '0 ),
|
||||
.req_i ( rs1_fwd_req ),
|
||||
.gnt_o ( ),
|
||||
.data_i ( rs_data ),
|
||||
.gnt_i ( 1'b1 ),
|
||||
.req_o ( rs1_valid ),
|
||||
.data_o ( rs1_o ),
|
||||
.idx_o ( )
|
||||
);
|
||||
|
||||
rr_arb_tree #(
|
||||
.NumIn(NR_ENTRIES+NR_WB_PORTS),
|
||||
.DataWidth(64),
|
||||
.ExtPrio(1'b1),
|
||||
.AxiVldRdy(1'b1)
|
||||
) i_sel_rs2 (
|
||||
.clk_i ( clk_i ),
|
||||
.rst_ni ( rst_ni ),
|
||||
.flush_i ( 1'b0 ),
|
||||
.rr_i ( '0 ),
|
||||
.req_i ( rs2_fwd_req ),
|
||||
.gnt_o ( ),
|
||||
.data_i ( rs_data ),
|
||||
.gnt_i ( 1'b1 ),
|
||||
.req_o ( rs2_valid ),
|
||||
.data_o ( rs2_o ),
|
||||
.idx_o ( )
|
||||
);
|
||||
|
||||
rr_arb_tree #(
|
||||
.NumIn(NR_ENTRIES+NR_WB_PORTS),
|
||||
.DataWidth(64),
|
||||
.ExtPrio(1'b1),
|
||||
.AxiVldRdy(1'b1)
|
||||
) i_sel_rs3 (
|
||||
.clk_i ( clk_i ),
|
||||
.rst_ni ( rst_ni ),
|
||||
.flush_i ( 1'b0 ),
|
||||
.rr_i ( '0 ),
|
||||
.req_i ( rs3_fwd_req ),
|
||||
.gnt_o ( ),
|
||||
.data_i ( rs_data ),
|
||||
.gnt_i ( 1'b1 ),
|
||||
.req_o ( rs3_valid_o ),
|
||||
.data_o ( rs3_o ),
|
||||
.idx_o ( )
|
||||
);
|
||||
|
||||
// sequential process
|
||||
always_ff @(posedge clk_i or negedge rst_ni) begin : regs
|
||||
if(!rst_ni) begin
|
||||
mem_q <= '{default: 0};
|
||||
issue_cnt_q <= '0;
|
||||
commit_pointer_q <= '0;
|
||||
issue_pointer_q <= '0;
|
||||
end else begin
|
||||
issue_cnt_q <= issue_cnt_n;
|
||||
issue_pointer_q <= issue_pointer_n;
|
||||
mem_q <= mem_n;
|
||||
commit_pointer_q <= commit_pointer_n;
|
||||
end
|
||||
end
|
||||
|
||||
//pragma translate_off
|
||||
`ifndef VERILATOR
|
||||
initial begin
|
||||
assert (NR_ENTRIES == 2**BITS_ENTRIES) else $fatal("Scoreboard size needs to be a power of two.");
|
||||
//pragma translate_off
|
||||
`ifndef VERILATOR
|
||||
initial begin
|
||||
assert (NR_ENTRIES == 2**BITS_ENTRIES) else $fatal("Scoreboard size needs to be a power of two.");
|
||||
end
|
||||
|
||||
// assert that zero is never set
|
||||
assert property (
|
||||
@(posedge clk_i) disable iff (!rst_ni) (rd_clobber_gpr_o[0] == ariane_pkg::NONE))
|
||||
else $fatal (1,"RD 0 should not bet set");
|
||||
// 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");
|
||||
|
||||
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");
|
||||
|
||||
// assert that we never give an issue ack signal if the instruction is not valid
|
||||
assert property (
|
||||
@(posedge clk_i) disable iff (!rst_ni) issue_ack_i |-> issue_instr_valid_o)
|
||||
else $fatal (1,"Issue acknowledged but instruction is not valid");
|
||||
|
||||
// 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 < NR_WB_PORTS; i++) begin
|
||||
for (genvar j = 0; j < NR_WB_PORTS; 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
|
||||
|
||||
// assert that zero is never set
|
||||
assert property (
|
||||
@(posedge clk_i) rst_ni |-> (rd_clobber_gpr_o[0] == NONE))
|
||||
else $error ("RD 0 should not bet set");
|
||||
// assert that we never acknowledge a commit if the instruction is not valid
|
||||
assert property (
|
||||
@(posedge clk_i) (rst_ni && commit_ack_i[0] |-> commit_instr_o[0].valid))
|
||||
else $error ("Commit acknowledged but instruction is not valid");
|
||||
|
||||
assert property (
|
||||
@(posedge clk_i) (rst_ni && commit_ack_i[1] |-> commit_instr_o[1].valid))
|
||||
else $error ("Commit acknowledged but instruction is not valid");
|
||||
|
||||
// assert that we never give an issue ack signal if the instruction is not valid
|
||||
assert property (
|
||||
@(posedge clk_i) (rst_ni && issue_ack_i |-> issue_instr_valid_o))
|
||||
else $error ("Issue acknowledged but instruction is not valid");
|
||||
|
||||
// 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 < NR_WB_PORTS; i++) begin
|
||||
for (genvar j = 0; j < NR_WB_PORTS; j++) begin
|
||||
assert property (
|
||||
@(posedge clk_i) wt_valid_i[i] && wt_valid_i[j] && (i != j) |-> (trans_id_i[i] != trans_id_i[j]))
|
||||
else $error ("Two or more functional units are retiring instructions with the same transaction id!");
|
||||
end
|
||||
end
|
||||
`endif
|
||||
//pragma translate_on
|
||||
end
|
||||
`endif
|
||||
//pragma translate_on
|
||||
endmodule
|
||||
|
|
Loading…
Add table
Reference in a new issue