// 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 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, // 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, input logic [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, // 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, // 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 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, // write-back port input branchpredict_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] wb_valid_i // data in is valid ); 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]; 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; // the issue queue is full don't issue any new instructions assign issue_full = (issue_cnt_q == NR_ENTRIES-1); 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; 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 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 (wb_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; 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; 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 && wb_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 = wb_valid_i[j]; break; end if (mem_q[trans_id_i[j]].sbe.rd == rs2_i && wb_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 = wb_valid_i[j]; break; end if (mem_q[trans_id_i[j]].sbe.rd == rs3_i && wb_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 = wb_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; 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 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."); 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) wb_valid_i[i] && wb_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 endmodule