diff --git a/doc/cs_registers.rst b/doc/cs_registers.rst index 814d3e8f..8482aa09 100644 --- a/doc/cs_registers.rst +++ b/doc/cs_registers.rst @@ -500,6 +500,11 @@ Other bit fields read as zero. +-------+------+------------------------------------------------------------------+ | Bit# | R/W | Description | +-------+------+------------------------------------------------------------------+ +| 1 | WARL | **data_ind_timing:** Enable (1) or disable (0) data-independent | +| | | timing features. If the core has not been configured with | +| | | security features (SecureIbex parameter == 0), this field will | +| | | always read as zero. | ++-------+------+------------------------------------------------------------------+ | 0 | WARL | **icache_enable:** Enable (1) or disable (0) the instruction | | | | cache. If the instruction cache has not been configured (ICache | | | | parameter == 0), this field will always read as zero. | diff --git a/doc/integration.rst b/doc/integration.rst index 59e6f558..245e64e4 100644 --- a/doc/integration.rst +++ b/doc/integration.rst @@ -23,6 +23,7 @@ Instantiation Template .MultiplierImplementation ( "fast" ), .ICache ( 0 ), .ICacheECC ( 0 ), + .SecureIbex ( 0 ), .DbgTriggerEn ( 0 ), .DmHaltAddr ( 32'h1A110800 ), .DmExceptionAddr ( 32'h1A110808 ) @@ -110,6 +111,9 @@ Parameters | ``ICacheECC`` | bit | 0 | *EXPERIMENTAL* Enable SECDED ECC protection in ICache (if | | | | | ICache == 1) | +------------------------------+-------------+------------+-----------------------------------------------------------------+ +| ``SecureIbex`` | bit | 0 | *EXPERIMENTAL* Enable various additional features targeting | +| | | | secure code execution. | ++------------------------------+-------------+------------+-----------------------------------------------------------------+ | ``DbgTriggerEn`` | bit | 0 | Enable debug trigger support (one trigger only) | +------------------------------+-------------+------------+-----------------------------------------------------------------+ | ``DmHaltAddr`` | int | 0x1A110800 | Address to jump to when entering Debug Mode | diff --git a/dv/cs_registers/tb/tb_cs_registers.sv b/dv/cs_registers/tb/tb_cs_registers.sv index e4279354..4c2b87c7 100644 --- a/dv/cs_registers/tb/tb_cs_registers.sv +++ b/dv/cs_registers/tb/tb_cs_registers.sv @@ -71,6 +71,7 @@ module tb_cs_registers #( logic [31:0] pc_id_i; logic [31:0] pc_wb_i; + logic data_ind_timing_o; logic icache_enable_o; logic csr_save_if_i; diff --git a/ibex_core.core b/ibex_core.core index 9c087577..4461f0c6 100644 --- a/ibex_core.core +++ b/ibex_core.core @@ -100,6 +100,12 @@ parameters: default: false description: "Enables third pipeline stage (EXPERIMENTAL)" + SecureIbex: + datatype: bool + paramtype: vlogparam + default: false + description: "Enables security hardening features (EXPERIMENTAL)" + targets: default: filesets: diff --git a/ibex_core_tracing.core b/ibex_core_tracing.core index 6eac23f7..a0f315fb 100644 --- a/ibex_core_tracing.core +++ b/ibex_core_tracing.core @@ -77,6 +77,12 @@ parameters: default: false description: "Enables third pipeline stage (EXPERIMENTAL) [0/1]" + SecureIbex: + datatype: bool + paramtype: vlogparam + default: false + description: "Enables security hardening features (EXPERIMENTAL) [0/1]" + targets: default: filesets: @@ -100,6 +106,7 @@ targets: - BranchTargetALU - WritebackStage - MultiplierImplementation + - SecureIbex default_tool: verilator toplevel: ibex_core_tracing tools: diff --git a/lint/verilator_waiver.vlt b/lint/verilator_waiver.vlt index f3b1f6e2..ebc0b17c 100644 --- a/lint/verilator_waiver.vlt +++ b/lint/verilator_waiver.vlt @@ -18,6 +18,7 @@ lint_off -rule WIDTH -file "*/rtl/ibex_core_tracing.sv" -match "*'RV32M'*" lint_off -rule WIDTH -file "*/rtl/ibex_core_tracing.sv" -match "*'RV32E'*" lint_off -rule WIDTH -file "*/rtl/ibex_core_tracing.sv" -match "*'BranchTargetALU'*" lint_off -rule WIDTH -file "*/rtl/ibex_core_tracing.sv" -match "*'WritebackStage'*" +lint_off -rule WIDTH -file "*/rtl/ibex_core_tracing.sv" -match "*'SecureIbex'*" // Filename 'ibex_register_file_ff' does not match MODULE name: ibex_register_file // ibex_register_file_ff and ibex_register_file_latch provide two diff --git a/rtl/ibex_core.sv b/rtl/ibex_core.sv index 5d3fe04a..1a85e195 100644 --- a/rtl/ibex_core.sv +++ b/rtl/ibex_core.sv @@ -25,6 +25,7 @@ module ibex_core #( parameter bit ICache = 1'b0, parameter bit ICacheECC = 1'b0, parameter bit DbgTriggerEn = 1'b0, + parameter bit SecureIbex = 1'b0, parameter int unsigned DmHaltAddr = 32'h1A110800, parameter int unsigned DmExceptionAddr = 32'h1A110808 ) ( @@ -99,7 +100,8 @@ module ibex_core #( import ibex_pkg::*; - localparam int unsigned PMP_NUM_CHAN = 2; + localparam int unsigned PMP_NUM_CHAN = 2; + localparam bit DataIndTiming = SecureIbex; // IF/ID signals logic instr_valid_id; @@ -116,6 +118,7 @@ module ibex_core #( logic [31:0] pc_id; // Program counter in ID stage logic [31:0] pc_wb; // Program counter in WB stage + logic data_ind_timing; logic icache_enable; logic icache_inval; @@ -418,6 +421,7 @@ module ibex_core #( .RV32M ( RV32M ), .RV32B ( RV32B ), .BranchTargetALU ( BranchTargetALU ), + .DataIndTiming ( DataIndTiming ), .WritebackStage ( WritebackStage ) ) id_stage_i ( .clk_i ( clk ), @@ -491,6 +495,7 @@ module ibex_core #( .priv_mode_i ( priv_mode_id ), .csr_mstatus_tw_i ( csr_mstatus_tw ), .illegal_csr_insn_i ( illegal_csr_insn_id ), + .data_ind_timing_i ( data_ind_timing ), // LSU .lsu_req_o ( lsu_req ), // to load store unit @@ -767,6 +772,7 @@ module ibex_core #( ibex_cs_registers #( .DbgTriggerEn ( DbgTriggerEn ), + .DataIndTiming ( DataIndTiming ), .ICache ( ICache ), .MHPMCounterNum ( MHPMCounterNum ), .MHPMCounterWidth ( MHPMCounterWidth ), @@ -828,6 +834,7 @@ module ibex_core #( .pc_id_i ( pc_id ), .pc_wb_i ( pc_wb ), + .data_ind_timing_o ( data_ind_timing ), .icache_enable_o ( icache_enable ), .csr_save_if_i ( csr_save_if ), diff --git a/rtl/ibex_core_tracing.sv b/rtl/ibex_core_tracing.sv index cbe63fc4..107298be 100644 --- a/rtl/ibex_core_tracing.sv +++ b/rtl/ibex_core_tracing.sv @@ -21,6 +21,7 @@ module ibex_core_tracing #( parameter bit ICache = 1'b0, parameter bit ICacheECC = 1'b0, parameter bit DbgTriggerEn = 1'b0, + parameter bit SecureIbex = 1'b0, parameter int unsigned DmHaltAddr = 32'h1A110800, parameter int unsigned DmExceptionAddr = 32'h1A110808 ) ( @@ -111,6 +112,7 @@ module ibex_core_tracing #( .ICacheECC ( ICacheECC ), .DbgTriggerEn ( DbgTriggerEn ), .WritebackStage ( WritebackStage ), + .SecureIbex ( SecureIbex ), .DmHaltAddr ( DmHaltAddr ), .DmExceptionAddr ( DmExceptionAddr ) ) u_ibex_core ( diff --git a/rtl/ibex_cs_registers.sv b/rtl/ibex_cs_registers.sv index a6a84ac2..ffba2012 100644 --- a/rtl/ibex_cs_registers.sv +++ b/rtl/ibex_cs_registers.sv @@ -14,6 +14,7 @@ module ibex_cs_registers #( parameter bit DbgTriggerEn = 0, + parameter bit DataIndTiming = 1'b0, parameter bit ICache = 1'b0, parameter int unsigned MHPMCounterNum = 10, parameter int unsigned MHPMCounterWidth = 40, @@ -79,6 +80,7 @@ module ibex_cs_registers #( input logic [31:0] pc_wb_i, // CPU control bits + output logic data_ind_timing_o, output logic icache_enable_o, // Exception save/restore @@ -158,7 +160,8 @@ module ibex_cs_registers #( // CPU control register fields typedef struct packed { - logic [30:0] unused_ctrl; + logic [31:2] unused_ctrl; + logic data_ind_timing; logic icache_enable; } CpuCtrl_t; @@ -1037,6 +1040,34 @@ module ibex_cs_registers #( // Cast register write data assign cpuctrl_wdata = CpuCtrl_t'(csr_wdata_int); + // Generate fixed time execution bit + if (DataIndTiming) begin : gen_dit + logic data_ind_timing_d, data_ind_timing_q; + + assign data_ind_timing_d = (csr_we_int && (csr_addr == CSR_CPUCTRL)) ? + cpuctrl_wdata.data_ind_timing : data_ind_timing_q; + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + data_ind_timing_q <= 1'b0; // disabled on reset + end else begin + data_ind_timing_q <= data_ind_timing_d; + end + end + + assign cpuctrl_rdata.data_ind_timing = data_ind_timing_q; + + end else begin : gen_no_dit + // tieoff for the unused bit + logic unused_dit; + assign unused_dit = cpuctrl_wdata.data_ind_timing; + + // field will always read as zero if not configured + assign cpuctrl_rdata.data_ind_timing = 1'b0; + end + + assign data_ind_timing_o = cpuctrl_rdata.data_ind_timing; + // Generate icache enable bit if (ICache) begin : gen_icache_enable logic icache_enable_d, icache_enable_q; @@ -1064,12 +1095,13 @@ module ibex_cs_registers #( assign cpuctrl_rdata.icache_enable = 1'b0; end - // tieoff for the currently unused bits of cpuctrl - logic [31:1] unused_cpuctrl; - assign unused_cpuctrl = {cpuctrl_wdata[31:1]}; - assign icache_enable_o = cpuctrl_rdata.icache_enable; + // tieoff for the currently unused bits of cpuctrl + logic [31:2] unused_cpuctrl; + assign unused_cpuctrl = {cpuctrl_wdata[31:2]}; + + //////////////// // Assertions // //////////////// diff --git a/rtl/ibex_decoder.sv b/rtl/ibex_decoder.sv index c3c0d9c9..84e89953 100644 --- a/rtl/ibex_decoder.sv +++ b/rtl/ibex_decoder.sv @@ -35,6 +35,7 @@ module ibex_decoder #( output logic ecall_insn_o, // syscall instr encountered output logic wfi_insn_o, // wait for interrupt instr encountered output logic jump_set_o, // jump taken set signal + input logic branch_taken_i, // registered branch decision output logic icache_inval_o, // from IF-ID pipeline register @@ -655,26 +656,24 @@ module ibex_decoder #( endcase if (BranchTargetALU) begin - // With branch target ALU the main ALU evaluates the branch condition and the branch - // target ALU calculates the target (which is controlled in a seperate block below) + bt_a_mux_sel_o = OP_A_CURRPC; + // Not-taken branch will jump to next instruction (used in secure mode) + bt_b_mux_sel_o = branch_taken_i ? IMM_B_B : IMM_B_INCR_PC; + end + + // Without branch target ALU, a branch is a two-stage operation using the Main ALU in both + // stages + if (instr_first_cycle_i) begin + // First evaluate the branch condition alu_op_a_mux_sel_o = OP_A_REG_A; alu_op_b_mux_sel_o = OP_B_REG_B; - bt_a_mux_sel_o = OP_A_CURRPC; - bt_b_mux_sel_o = IMM_B_B; end else begin - // Without branch target ALU, a branch is a two-stage operation using the Main ALU in both - // stages - if (instr_first_cycle_i) begin - // First evaluate the branch condition - alu_op_a_mux_sel_o = OP_A_REG_A; - alu_op_b_mux_sel_o = OP_B_REG_B; - end else begin - // Then calculate jump target - alu_op_a_mux_sel_o = OP_A_CURRPC; - alu_op_b_mux_sel_o = OP_B_IMM; - imm_b_mux_sel_o = IMM_B_B; - alu_operator_o = ALU_ADD; - end + // Then calculate jump target + alu_op_a_mux_sel_o = OP_A_CURRPC; + alu_op_b_mux_sel_o = OP_B_IMM; + // Not-taken branch will jump to next instruction (used in secure mode) + imm_b_mux_sel_o = branch_taken_i ? IMM_B_B : IMM_B_INCR_PC; + alu_operator_o = ALU_ADD; end end diff --git a/rtl/ibex_id_stage.sv b/rtl/ibex_id_stage.sv index 62756cbb..b5112475 100644 --- a/rtl/ibex_id_stage.sv +++ b/rtl/ibex_id_stage.sv @@ -20,6 +20,7 @@ module ibex_id_stage #( parameter bit RV32E = 0, parameter bit RV32M = 1, parameter bit RV32B = 0, + parameter bit DataIndTiming = 1'b0, parameter bit BranchTargetALU = 0, parameter bit WritebackStage = 0 ) ( @@ -95,6 +96,7 @@ module ibex_id_stage #( input ibex_pkg::priv_lvl_e priv_mode_i, input logic csr_mstatus_tw_i, input logic illegal_csr_insn_i, + input logic data_ind_timing_i, // Interface to load store unit output logic lsu_req_o, @@ -183,6 +185,7 @@ module ibex_id_stage #( logic branch_in_dec; logic branch_set, branch_set_d; + logic branch_taken; logic jump_in_dec; logic jump_set; @@ -375,6 +378,7 @@ module ibex_id_stage #( .ecall_insn_o ( ecall_insn_dec ), .wfi_insn_o ( wfi_insn_dec ), .jump_set_o ( jump_set ), + .branch_taken_i ( branch_taken ), .icache_inval_o ( icache_inval_o ), // from IF-ID pipeline register @@ -582,19 +586,16 @@ module ibex_id_stage #( assign multdiv_operand_a_ex_o = rf_rdata_a_fwd; assign multdiv_operand_b_ex_o = rf_rdata_b_fwd; - typedef enum logic { FIRST_CYCLE, MULTI_CYCLE } id_fsm_e; - id_fsm_e id_fsm_q, id_fsm_d; + //////////////////////// + // Branch set control // + //////////////////////// - ///////////////////////////// - // ID-EX Pipeline Register // - ///////////////////////////// - - if (BranchTargetALU) begin : g_branch_set_direct + if (BranchTargetALU && !DataIndTiming) begin : g_branch_set_direct // Branch set fed straight to controller with branch target ALU // (condition pass/fail used same cycle as generated instruction request) assign branch_set = branch_set_d; - end else begin : g_branch_set_flopped - // Branch set flopped without branch target ALU + end else begin : g_branch_set_flop + // Branch set flopped without branch target ALU, or in fixed time execution mode // (condition pass/fail used next cycle where branch target is calculated) logic branch_set_q; @@ -606,7 +607,32 @@ module ibex_id_stage #( end end - assign branch_set = branch_set_q; + // Branches always take two cycles in fixed time execution mode, with or without the branch + // target ALU (to avoid a path from the branch decision into the branch target ALU operand + // muxing). + assign branch_set = (BranchTargetALU && !data_ind_timing_i) ? branch_set_d : branch_set_q; + end + + // Branch condition is calculated in the first cycle and flopped for use in the second cycle + // (only used in fixed time execution mode to determine branch destination). + if (DataIndTiming) begin : g_sec_branch_taken + logic branch_taken_q; + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + branch_taken_q <= 1'b0; + end else begin + branch_taken_q <= branch_decision_i; + end + end + + assign branch_taken = ~data_ind_timing_i | branch_taken_q; + + end else begin : g_nosec_branch_taken + + // Signal unused without fixed time execution mode - only taken branches will trigger branch_set + assign branch_taken = 1'b1; + end // Holding branch_set/jump_set high for more than one cycle may not cause a functional issue but @@ -614,6 +640,13 @@ module ibex_id_stage #( // that this shouldn't ever happen. `ASSERT(NeverDoubleBranch, branch_set |=> ~branch_set) + /////////////// + // ID-EX FSM // + /////////////// + + typedef enum logic { FIRST_CYCLE, MULTI_CYCLE } id_fsm_e; + id_fsm_e id_fsm_q, id_fsm_d; + always_ff @(posedge clk_i or negedge rst_ni) begin : id_pipeline_reg if (!rst_ni) begin id_fsm_q <= FIRST_CYCLE; @@ -622,10 +655,6 @@ module ibex_id_stage #( end end - /////////////// - // ID-EX FSM // - /////////////// - // ID/EX stage can be in two states, FIRST_CYCLE and MULTI_CYCLE. An instruction enters // MULTI_CYCLE if it requires multiple cycles to complete regardless of stalls and other // considerations. An instruction may be held in FIRST_CYCLE if it's unable to begin executing @@ -666,10 +695,13 @@ module ibex_id_stage #( end branch_in_dec: begin // cond branch operation - id_fsm_d = (!BranchTargetALU && branch_decision_i) ? MULTI_CYCLE : FIRST_CYCLE; - stall_branch = ~BranchTargetALU & branch_decision_i; - branch_set_d = branch_decision_i; - perf_branch_o = 1'b1; + // All branches take two cycles in fixed time execution mode, regardless of branch + // condition. + id_fsm_d = (data_ind_timing_i || (!BranchTargetALU && branch_decision_i)) ? + MULTI_CYCLE : FIRST_CYCLE; + stall_branch = (~BranchTargetALU & branch_decision_i) | data_ind_timing_i; + branch_set_d = branch_decision_i | data_ind_timing_i; + perf_branch_o = 1'b1; end jump_in_dec: begin // uncond branch operation