diff --git a/Bender.yml b/Bender.yml index 751b49ac45..aaec229052 100644 --- a/Bender.yml +++ b/Bender.yml @@ -249,6 +249,8 @@ sources: - core/axi_shim.sv - core/acc_dispatcher.sv - core/cva6_rvfi_probes.sv + - core/fifo_dp_v3.sv + - core/ctr_unit.sv # What is "frontend"? - core/frontend/btb.sv diff --git a/core/commit_stage.sv b/core/commit_stage.sv index 8d86eb1946..865a10752d 100644 --- a/core/commit_stage.sv +++ b/core/commit_stage.sv @@ -83,7 +83,13 @@ module commit_stage // Flush TLBs and pipeline - CONTROLLER output logic hfence_vvma_o, // Flush TLBs and pipeline - CONTROLLER - output logic hfence_gvma_o + output logic hfence_gvma_o, + // Control transfer records PC - CTR_UNIT + output riscv::xlen_t [CVA6Cfg.NrCommitPorts-1:0] ctr_source_o, + // Control transfer records type - CTR_UNIT + output riscv::ctr_type_t [CVA6Cfg.NrCommitPorts-1:0] ctr_type_o, + // Control transfer records valid - CTR_UNIT + output logic [CVA6Cfg.NrCommitPorts-1:0] ctr_valid_o ); // ila_0 i_ila_commit ( diff --git a/core/ctr_unit.sv b/core/ctr_unit.sv new file mode 100644 index 0000000000..4b4ac2a8d6 --- /dev/null +++ b/core/ctr_unit.sv @@ -0,0 +1,131 @@ +// Copyright 2024 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: Emanuele Parisi, University of Bologna +// Description: Control Transfer Records unit. + + +module ctr_unit + import ariane_pkg::*; +#( + parameter config_pkg::cva6_cfg_t CVA6Cfg = config_pkg::cva6_cfg_empty +) ( + // Subsystem clock - SUBSYSTEM + input logic clk_i, + // Asynchronous reset active low - SUBSYSTEM + input logic rst_ni, + // Input commit ports + input ctr_commit_port_t ctr_commit_port_1_i, + input ctr_commit_port_t ctr_commit_port_2_i, + // Control Transfer Records source register - CTR_UNIT + output riscv::ctrsource_rv_t emitter_source_o, + // Control Transfer Records target register - CTR_UNIT + output riscv::ctrtarget_rv_t emitter_target_o, + // Control Transfer Records data register - CTR_UNIT + output riscv::ctr_type_t emitter_data_o, + // C2ontrol Transfer Records instr register - CTR_UNIT + output logic [31:0] emitter_instr_o, + // Privilege execution level - CTR_UNIT + output riscv::priv_lvl_t priv_lvl_o +); + + // Temporary storage for control transfers with unknown target. + riscv::xlen_t pending_source_d, pending_source_q; + riscv::ctr_type_t pending_type_d, pending_type_q; + logic [31:0] pending_instr_d, pending_instr_q; + riscv::priv_lvl_t pending_priv_lvl_d, pending_priv_lvl_q; + logic pending_valid_d, pending_valid_q; + + ctr_commit_port_t ctr_sbe_entry_out; + + localparam int ReqFifoWidth = $bits(ctr_commit_port_t) ; + logic fifo_empty, fifo_full; + + assign priv_lvl_o = pending_priv_lvl_q; + + // Dual port fifo to serialize CVA6 commit ports + fifo_dp_v3 #( + .FALL_THROUGH ( 1'b1 ), + .DATA_WIDTH ( ReqFifoWidth ), + .DEPTH ( 4 ) + ) dual_port_fifo ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .flush_i ( 1'b0 ), + .testmode_i ( 1'b0 ), + .full_o ( fifo_full ), + .empty_o ( fifo_empty ), + .usage_o ( ), + .data_port_0_i ( ctr_commit_port_1_i ), + .push_port_0_i ( ~fifo_full && ctr_commit_port_1_i.valid ), + .data_port_1_i ( ctr_commit_port_2_i ), + .push_port_1_i ( ~fifo_full && ctr_commit_port_2_i.valid ), + .data_o ( ctr_sbe_entry_out ), + .pop_i ( ~fifo_empty ) + ); + + always_comb begin + // By default, we don't have any control transfer pending. + pending_source_d = 'b0; + pending_type_d = riscv::CTR_TYPE_NONE; + pending_valid_d = 'b0; + pending_instr_d = 'b0; + pending_priv_lvl_d = riscv::PRIV_LVL_M; + if (~(ctr_commit_port_1_i.valid || ctr_commit_port_2_i.valid)) begin + // If no instructions are retired in the current cycle, keep the old values. + pending_source_d = pending_source_q; + pending_type_d = pending_type_q; + pending_valid_d = pending_valid_q; + pending_instr_d = pending_instr_q; + pending_priv_lvl_d = pending_priv_lvl_q; + end else begin + // Record the most recent control transfer with unknown target address. + pending_source_d = ctr_sbe_entry_out.ctr_source; + pending_type_d = ctr_sbe_entry_out.ctr_type; + pending_valid_d = (ctr_commit_port_1_i.valid || ctr_commit_port_2_i.valid); + pending_instr_d = ctr_sbe_entry_out.ctr_instr; + pending_priv_lvl_d = ctr_sbe_entry_out.priv_lvl; + end + end + + always_comb begin + emitter_source_o = 'b0; + emitter_target_o = 'b0; + emitter_data_o = riscv::CTR_TYPE_NONE; + emitter_instr_o = 'b0; + if (pending_valid_q && (ctr_commit_port_1_i.valid || ctr_commit_port_2_i.valid)) begin + emitter_source_o.pc = pending_source_q[riscv::XLEN-1:1]; + emitter_source_o.v = pending_valid_q; + // The MISP bit is unimplemented. + emitter_target_o.pc = ctr_sbe_entry_out.ctr_source[riscv::XLEN-1:1]; + emitter_target_o.misp = 'b0; + // Cycle counting is unimplemented. + emitter_data_o = pending_type_q; + emitter_instr_o = pending_instr_q; + end + end + + always_ff @(posedge clk_i, negedge rst_ni) begin + if (~rst_ni) begin + pending_source_q <= 'b0; + pending_type_q <= riscv::CTR_TYPE_NONE; + pending_instr_q <= 'b0; + pending_valid_q <= 'b0; + pending_priv_lvl_q <= riscv::PRIV_LVL_M; + end else begin + pending_source_q <= pending_source_d; + pending_type_q <= pending_type_d; + pending_priv_lvl_q <= pending_priv_lvl_d; + pending_instr_q <= pending_instr_d; + pending_valid_q <= pending_valid_d; + end + end + +endmodule diff --git a/core/cva6.sv b/core/cva6.sv index 86d49fb399..201deafa98 100644 --- a/core/cva6.sv +++ b/core/cva6.sv @@ -152,7 +152,17 @@ module cva6 // noc request, can be AXI or OpenPiton - SUBSYSTEM output noc_req_t noc_req_o, // noc response, can be AXI or OpenPiton - SUBSYSTEM - input noc_resp_t noc_resp_i + input noc_resp_t noc_resp_i, + // Control Transfer Records source register - CTR_UNIT + output riscv::ctrsource_rv_t emitter_source_o, + // Control Transfer Records target register - CTR_UNIT + output riscv::ctrtarget_rv_t emitter_target_o, + // Control Transfer Records data register - CTR_UNIT + output riscv::ctr_type_t emitter_data_o, + // Control Transfer Records instr register - CTR_UNIT + output logic [31:0] emitter_instr_o, + // Privilege execution level - CTR_UNIT + output riscv::priv_lvl_t priv_lvl_o ); // ------------------------------------------ @@ -449,6 +459,9 @@ module cva6 logic [riscv::XLEN-1:0] mtopi; logic [riscv::XLEN-1:0] stopi; logic [riscv::XLEN-1:0] vstopi; + riscv::xlen_t [CVA6Cfg.NrCommitPorts-1:0] ctr_source_commit_ctr; + riscv::ctr_type_t [CVA6Cfg.NrCommitPorts-1:0] ctr_type_commit_ctr; + logic [CVA6Cfg.NrCommitPorts-1:0] ctr_valid_commit_ctr; // ---------------------------- // Performance Counters <-> * // ---------------------------- @@ -527,6 +540,12 @@ module cva6 logic inval_valid; logic inval_ready; + // CTR signals + logic [CVA6Cfg.NrCommitPorts-1:0] ctr_valid; + logic [CVA6Cfg.NrCommitPorts-1:0] [31:0] ctr_instr; + riscv::ctrsource_rv_t [CVA6Cfg.NrCommitPorts-1:0] ctr_source; + riscv::ctr_type_t [CVA6Cfg.NrCommitPorts-1:0] ctr_type; + always_ff @(posedge clk_i or negedge rst_ni) begin if (~rst_ni) begin rst_uarch_n <= 1'b0; @@ -921,6 +940,9 @@ module cva6 .hfence_gvma_o (hfence_gvma_commit_controller), .fence_t_o (fence_t_commit_controller), .flush_commit_o (flush_commit), + .ctr_source_o (ctr_source_commit_ctr), + .ctr_type_o (ctr_type_commit_ctr), + .ctr_valid_o (ctr_valid_commit_ctr), .* ); @@ -1615,4 +1637,49 @@ module cva6 end //IsRVFI + // ------------------------ + // Control Transfer Records + // ------------------------ + // Handle data interface to be the control transfer records unit + + for(genvar i=0;i reserved for vector encoding if (instr.itype.funct3 != 3'b0) illegal_instr = 1'b1; end @@ -1364,6 +1386,13 @@ module decoder imm_select = JIMM; instruction_o.rd[4:0] = instr.utype.rd; is_control_flow_instr_o = 1'b1; + if (instr.utype.rd inside {5'd1, 5'd5}) begin + control_transfer_type = riscv::CTR_TYPE_DIRCALL; + end else if (instr.utype.rd == 5'd0) begin + control_transfer_type = riscv::CTR_TYPE_DIRJMP; + end else begin + control_transfer_type = riscv::CTR_TYPE_DIRLJMP; + end end riscv::OpcodeAuipc: begin @@ -1685,4 +1714,20 @@ module decoder instruction_o.ex.cause = riscv::DEBUG_REQUEST; end end + + // ------------------------ + // Control Transfer Records + // ------------------------ + always_comb begin : control_transfer_records + instruction_o.cftype = control_transfer_type; + if (instruction_o.ex.valid) begin + if (instruction_o.ex.cause[riscv::XLEN-1]) begin + instruction_o.cftype = riscv::CTR_TYPE_INTR; + end + else begin + instruction_o.cftype = riscv::CTR_TYPE_EXC; + end + end + end + endmodule diff --git a/core/fifo_dp_v3.sv b/core/fifo_dp_v3.sv new file mode 100644 index 0000000000..bb72e352ba --- /dev/null +++ b/core/fifo_dp_v3.sv @@ -0,0 +1,187 @@ +// 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. + +module fifo_dp_v3 #( + parameter bit FALL_THROUGH = 1'b1, // fifo is in fall-through mode + parameter int unsigned DATA_WIDTH = 32, // default data width if the fifo is of type logic + parameter int unsigned DEPTH = 8, // depth can be arbitrary from 0 to 2**32 + parameter type dtype = logic [DATA_WIDTH-1:0], + // DO NOT OVERWRITE THIS PARAMETER + parameter int unsigned ADDR_DEPTH = (DEPTH > 1) ? $clog2(DEPTH) : 1 +)( + input logic clk_i, // Clock + input logic rst_ni, // Asynchronous reset active low + input logic flush_i, // flush the queue + input logic testmode_i, // test_mode to bypass clock gating + // status flags + output logic full_o, // queue is full + output logic empty_o, // queue is empty + output logic [ADDR_DEPTH-1:0] usage_o, // fill pointer + // as long as the queue is not full we can push new data + input dtype data_port_0_i, // data to push into the queue (first port) + input logic push_port_0_i, // data is valid and can be pushed to the queue (first port) + input dtype data_port_1_i, // data to push into the queue (second port) + input logic push_port_1_i, // data is valid and can be pushed to the queue (second port) + // as long as the queue is not empty we can pop new elements + output dtype data_o, // output data + input logic pop_i // pop head from queue +); + // local parameter + // FIFO depth - handle the case of pass-through, synthesizer will do constant propagation + localparam int unsigned FifoDepth = (DEPTH > 0) ? DEPTH : 1; + // clock gating control + logic gate_clock; + // pointer to the read and write section of the queue + logic [ADDR_DEPTH - 1:0] read_pointer_n, read_pointer_q, write_pointer_n, write_pointer_q; + // keep a counter to keep track of the current queue status + // this integer will be truncated by the synthesis tool + logic [ADDR_DEPTH:0] status_cnt_n, status_cnt_q; + // actual memory + dtype [FifoDepth - 1:0] mem_n, mem_q; + + // Available space in the FIFO + logic [ADDR_DEPTH:0] space_available; + // Handle passes and pushes + logic [ADDR_DEPTH:0] temp_space_available; + + assign usage_o = status_cnt_q[ADDR_DEPTH-1:0]; + assign space_available = FifoDepth - status_cnt_q; + assign full_o = (space_available == 0); + assign empty_o = (status_cnt_q == 0) & ~(FALL_THROUGH & (push_port_0_i || push_port_1_i)); + + // read and write queue logic + always_comb begin : read_write_comb + // default assignment + read_pointer_n = read_pointer_q; + write_pointer_n = write_pointer_q; + status_cnt_n = status_cnt_q; + mem_n = mem_q; + gate_clock = 1'b1; + data_o = mem_q[read_pointer_q]; + + temp_space_available = space_available; + + // Default data_o assignment in case of pass-through + data_o = mem_q[read_pointer_q]; + + // Pass-through mode handling + if (FALL_THROUGH && (status_cnt_q == 0)) begin + // Both push_port_0_i and push_port_1_i are asserted + if (push_port_0_i && push_port_1_i) begin + // Pass through data_port_0_i + data_o = data_port_0_i; + // Store data_port_1_i in the FIFO + if (temp_space_available >= 1) begin + mem_n[write_pointer_n] = data_port_1_i; + gate_clock = 1'b0; + write_pointer_n = write_pointer_n + 1; + if (write_pointer_n == FifoDepth[ADDR_DEPTH-1:0]) + write_pointer_n = '0; + status_cnt_n = status_cnt_n + 1; + temp_space_available = temp_space_available - 1; + end + end + // Only push_port_0_i is asserted + else if (push_port_0_i) begin + data_o = data_port_0_i; + end + // Only push_port_1_i is asserted + else if (push_port_1_i) begin + data_o = data_port_1_i; + end + else begin + // No pushes; output remains unchanged + data_o = mem_q[read_pointer_q]; + end + + // Handle pop in pass-through mode + if (pop_i && status_cnt_q > 0) begin + if (read_pointer_q == FifoDepth[ADDR_DEPTH-1:0] - 1) + read_pointer_n = '0; + else + read_pointer_n = read_pointer_q + 1; + status_cnt_n = status_cnt_n - 1; + end + end + else begin + // Normal FIFO operation + // Process first push port + if (push_port_0_i && (temp_space_available >= 1)) begin + mem_n[write_pointer_n] = data_port_0_i; + gate_clock = 1'b0; + write_pointer_n = write_pointer_n + 1; + if (write_pointer_n == FifoDepth[ADDR_DEPTH-1:0]) + write_pointer_n = '0; + status_cnt_n = status_cnt_n + 1; + temp_space_available = temp_space_available - 1; + end + + // Process second push port + if (push_port_1_i && (temp_space_available >= 1)) begin + mem_n[write_pointer_n] = data_port_1_i; + gate_clock = 1'b0; + write_pointer_n = write_pointer_n + 1; + if (write_pointer_n == FifoDepth[ADDR_DEPTH-1:0]) + write_pointer_n = '0; + status_cnt_n = status_cnt_n + 1; + temp_space_available = temp_space_available - 1; + end + + // Handle pop logic + if (pop_i && ~empty_o) begin + if (read_pointer_q == FifoDepth[ADDR_DEPTH-1:0] - 1) + read_pointer_n = '0; + else + read_pointer_n = read_pointer_q + 1; + status_cnt_n = status_cnt_n - 1; + end + end + end + + // sequential process + always_ff @(posedge clk_i or negedge rst_ni) begin + if(~rst_ni) begin + read_pointer_q <= '0; + write_pointer_q <= '0; + status_cnt_q <= '0; + mem_q <= '0; + end else begin + if (flush_i) begin + read_pointer_q <= '0; + write_pointer_q <= '0; + status_cnt_q <= '0; + mem_q <= '0; + end else begin + read_pointer_q <= read_pointer_n; + write_pointer_q <= write_pointer_n; + status_cnt_q <= status_cnt_n; + if (!gate_clock) + mem_q <= mem_n; + end + end + end + +`ifndef SYNTHESIS +`ifndef COMMON_CELLS_ASSERTS_OFF + initial begin + assert (DEPTH >= 0) else $error("DEPTH must be greater than or equal to 0."); + end + + full_write : assert property( + @(posedge clk_i) disable iff (~rst_ni) (full_o |-> (~push_port_0_i && ~push_port_1_i))) + else $fatal (1, "Trying to push new data although the FIFO is full."); + + empty_read : assert property( + @(posedge clk_i) disable iff (~rst_ni) (empty_o |-> ~pop_i)) + else $fatal (1, "Trying to pop data although the FIFO is empty."); +`endif +`endif + +endmodule // fifo_v3 diff --git a/core/include/ariane_pkg.sv b/core/include/ariane_pkg.sv index 6fd594fd26..bdf99ef616 100644 --- a/core/include/ariane_pkg.sv +++ b/core/include/ariane_pkg.sv @@ -759,8 +759,26 @@ package ariane_pkg; logic is_compressed; // signals a compressed instructions, we need this information at the commit stage if // we want jump accordingly e.g.: +4, +2 logic vfp; // is this a vector floating-point instruction? + riscv::ctr_type_t cftype; // 4-bit control transfer type, encoded according to the RISC-V Control Transfer Records extension. + logic [31:0] opcode; } scoreboard_entry_t; + // --------------- + // Ctr strucutres + // --------------- + typedef struct packed { + riscv::xlen_t ctr_source; + riscv::ctr_type_t ctr_type; + logic [31:0] ctr_instr; + riscv::priv_lvl_t priv_lvl; + logic valid; + } ctr_commit_port_t; + + typedef struct packed { + ctr_commit_port_t port_1; + ctr_commit_port_t port_2; + } ctr_scoreboard_t ; + // --------------- // MMU instanciation // --------------- diff --git a/core/include/riscv_pkg.sv b/core/include/riscv_pkg.sv index 541c888f1f..aec558b5b9 100644 --- a/core/include/riscv_pkg.sv +++ b/core/include/riscv_pkg.sv @@ -884,6 +884,44 @@ package riscv; priv_lvl_t prv; } dcsr_t; + // ------------------------ + // Control Transfer Records + // ------------------------ + typedef struct packed { + logic [XLEN-1:1] pc; + logic v; + } ctrsource_rv_t; + + typedef struct packed { + logic [XLEN-1:1] pc; + logic misp; + } ctrtarget_rv_t; + + typedef enum logic[3:0] { + CTR_TYPE_NONE = 4'd0, + CTR_TYPE_EXC = 4'd1, + CTR_TYPE_INTR = 4'd2, + CTR_TYPE_TRET = 4'd3, + CTR_TYPE_NTBR = 4'd4, + CTR_TYPE_TKBR = 4'd5, + CTR_TYPE_INDCALL = 4'd8, + CTR_TYPE_DIRCALL = 4'd9, + CTR_TYPE_INDJMP = 4'd10, + CTR_TYPE_DIRJMP = 4'd11, + CTR_TYPE_CORSWAP = 4'd12, + CTR_TYPE_RET = 4'd13, + CTR_TYPE_INDLJMP = 4'd14, + CTR_TYPE_DIRLJMP = 4'd15 + } ctr_type_t; + + typedef struct packed { + logic [63:32] wpri1; + logic [31:16] cc; + logic ccv; + logic [14:4] wpri0; + ctr_type_t cftype; + } ctrdata_rv_t; + // Instruction Generation *incomplete* function automatic logic [31:0] jal(logic [4:0] rd, logic [20:0] imm); // OpCode Jal diff --git a/core/issue_stage.sv b/core/issue_stage.sv index 29d12219a1..d3e3cfe233 100644 --- a/core/issue_stage.sv +++ b/core/issue_stage.sv @@ -156,7 +156,7 @@ module issue_stage assign issue_instr_o = issue_instr_sb_iro; assign issue_instr_hs_o = issue_instr_valid_sb_iro & issue_ack_iro_sb; - + // --------------------------------------------------------- // 2. Manage instructions in a scoreboard // --------------------------------------------------------- diff --git a/core/scoreboard.sv b/core/scoreboard.sv index 568a4aa7ef..62066b3a15 100644 --- a/core/scoreboard.sv +++ b/core/scoreboard.sv @@ -197,6 +197,17 @@ module scoreboard #( 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 + // write the control transfer type in case of branch taken/not taken, or in case any of there + // functional units in the execute stage generated an exception + if (ex_i[i].valid) begin + mem_n[trans_id_i[i]].sbe.cftype = riscv::CTR_TYPE_EXC; + end else if (resolved_branch_i.valid && resolved_branch_i.cf_type == ariane_pkg::Branch) begin + if (resolved_branch_i.is_taken) begin + mem_n[trans_id_i[i]].sbe.cftype = riscv::CTR_TYPE_TKBR; + end else begin + mem_n[trans_id_i[i]].sbe.cftype = riscv::CTR_TYPE_NTBR; + end + end end end