diff --git a/rtl/ibex_register_file_common.sv b/rtl/ibex_register_file_common.sv index 08c47af6..3f8aa927 100644 --- a/rtl/ibex_register_file_common.sv +++ b/rtl/ibex_register_file_common.sv @@ -11,7 +11,7 @@ module ibex_register_file_common #( parameter bit FPGA = 0, parameter int unsigned AddrWidth = 5, - parameter int unsigned NumWords = 5, + parameter int unsigned NumWords = 2 ** AddrWidth, parameter int unsigned DataWidth = 32, parameter bit WrenCheck = 0, parameter bit RdataMuxCheck = 0 @@ -81,6 +81,10 @@ module ibex_register_file_common #( .err_o (oh_we_err) ); end else begin : gen_no_wren_check + if (FPGA == 0) begin : gen_unused_we0_strobe + logic unused_strobe; + assign unused_strobe = we_onehot_a[0]; // this is never read from in this case + end assign oh_we_err = 1'b0; end diff --git a/rtl/ibex_register_file_ff.sv b/rtl/ibex_register_file_ff.sv index 53c89e2d..01be7c17 100644 --- a/rtl/ibex_register_file_ff.sv +++ b/rtl/ibex_register_file_ff.sv @@ -11,231 +11,147 @@ * targeting FPGA synthesis or Verilator simulation. */ module ibex_register_file_ff #( - parameter bit RV32E = 0, - parameter int unsigned DataWidth = 32, - parameter bit DummyInstructions = 0, - parameter bit WrenCheck = 0, - parameter bit RdataMuxCheck = 0, - parameter logic [DataWidth-1:0] WordZeroVal = '0 + parameter bit RV32E = 0, + parameter int unsigned DataWidth = 32, + parameter bit DummyInstructions = 0, + parameter bit WrenCheck = 0, + parameter bit RdataMuxCheck = 0, + parameter logic [DataWidth-1:0] WordZeroVal = '0 ) ( - // Clock and Reset - input logic clk_i, - input logic rst_ni, + // Clock and Reset + input logic clk_i, + input logic rst_ni, - input logic test_en_i, - input logic dummy_instr_id_i, - input logic dummy_instr_wb_i, + input logic test_en_i, + input logic dummy_instr_id_i, + input logic dummy_instr_wb_i, - //Read port R1 - input logic [4:0] raddr_a_i, - output logic [DataWidth-1:0] rdata_a_o, + //Read port R1 + input logic [ 4:0] raddr_a_i, + output logic [DataWidth-1:0] rdata_a_o, - //Read port R2 - input logic [4:0] raddr_b_i, - output logic [DataWidth-1:0] rdata_b_o, + //Read port R2 + input logic [ 4:0] raddr_b_i, + output logic [DataWidth-1:0] rdata_b_o, - // Write port W1 - input logic [4:0] waddr_a_i, - input logic [DataWidth-1:0] wdata_a_i, - input logic we_a_i, + // Write port W1 + input logic [ 4:0] waddr_a_i, + input logic [DataWidth-1:0] wdata_a_i, + input logic we_a_i, - // This indicates whether spurious WE or non-one-hot encoded raddr are detected. - output logic err_o + // This indicates whether spurious WE or non-one-hot encoded raddr are detected. + output logic err_o ); - localparam int unsigned ADDR_WIDTH = RV32E ? 4 : 5; - localparam int unsigned NUM_WORDS = 2**ADDR_WIDTH; + localparam int unsigned ADDR_WIDTH = RV32E ? 4 : 5; + localparam int unsigned NUM_WORDS = 2 ** ADDR_WIDTH; - logic [DataWidth-1:0] rf_reg [NUM_WORDS]; - logic [NUM_WORDS-1:0] we_a_dec; + logic [DataWidth-1:0] rf_reg[NUM_WORDS]; - logic oh_raddr_a_err, oh_raddr_b_err, oh_we_err; + // Encode we_a/raddr_a/raddr_b into one-hot encoded signals + logic [NUM_WORDS-1:0] raddr_onehot_a, raddr_onehot_b, we_onehot_a; - always_comb begin : we_a_decoder - for (int unsigned i = 0; i < NUM_WORDS; i++) begin - we_a_dec[i] = (waddr_a_i == 5'(i)) ? we_a_i : 1'b0; - end - end + // One-hot encoding error signals + logic oh_raddr_a_err, oh_raddr_b_err, oh_we_err; - // SEC_CM: DATA_REG_SW.GLITCH_DETECT - // This checks for spurious WE strobes on the regfile. - if (WrenCheck) begin : gen_wren_check - // Buffer the decoded write enable bits so that the checker - // is not optimized into the address decoding logic. - logic [NUM_WORDS-1:0] we_a_dec_buf; - prim_buf #( - .Width(NUM_WORDS) - ) u_prim_buf ( - .in_i(we_a_dec), - .out_o(we_a_dec_buf) + // Common security functionality + ibex_register_file_common #( + .AddrWidth(ADDR_WIDTH), + .NumWords(NUM_WORDS), + .DataWidth(DataWidth), + .WrenCheck(WrenCheck), + .RdataMuxCheck(RdataMuxCheck) + ) security_module ( + .clk_i, + .rst_ni, + .raddr_a_i, + .raddr_onehot_a, + .oh_raddr_a_err, + .raddr_b_i, + .raddr_onehot_b, + .oh_raddr_b_err, + .waddr_a_i, + .we_a_i, + .we_onehot_a, + .oh_we_err, + .err_o ); - prim_onehot_check #( - .AddrWidth(ADDR_WIDTH), - .AddrCheck(1), - .EnableCheck(1) - ) u_prim_onehot_check ( - .clk_i, - .rst_ni, - .oh_i(we_a_dec_buf), - .addr_i(waddr_a_i), - .en_i(we_a_i), - .err_o(oh_we_err) - ); - end else begin : gen_no_wren_check - logic unused_strobe; - assign unused_strobe = we_a_dec[0]; // this is never read from in this case - assign oh_we_err = 1'b0; - end + // No flops for R0 as it's hard-wired to 0 + for (genvar i = 1; i < NUM_WORDS; i++) begin : g_rf_flops + logic [DataWidth-1:0] rf_reg_q; - // No flops for R0 as it's hard-wired to 0 - for (genvar i = 1; i < NUM_WORDS; i++) begin : g_rf_flops - logic [DataWidth-1:0] rf_reg_q; + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + rf_reg_q <= WordZeroVal; + end else if (we_onehot_a[i]) begin + rf_reg_q <= wdata_a_i; + end + end - always_ff @(posedge clk_i or negedge rst_ni) begin - if (!rst_ni) begin - rf_reg_q <= WordZeroVal; - end else if (we_a_dec[i]) begin - rf_reg_q <= wdata_a_i; - end + assign rf_reg[i] = rf_reg_q; end - assign rf_reg[i] = rf_reg_q; - end + // With dummy instructions enabled, R0 behaves as a real register but will always return 0 for + // real instructions. + if (DummyInstructions) begin : g_dummy_r0 + // SEC_CM: CTRL_FLOW.UNPREDICTABLE + logic we_r0_dummy; + logic [DataWidth-1:0] rf_r0_q; - // With dummy instructions enabled, R0 behaves as a real register but will always return 0 for - // real instructions. - if (DummyInstructions) begin : g_dummy_r0 - // SEC_CM: CTRL_FLOW.UNPREDICTABLE - logic we_r0_dummy; - logic [DataWidth-1:0] rf_r0_q; + // Write enable for dummy R0 register (waddr_a_i will always be 0 for dummy instructions) + assign we_r0_dummy = we_a_i & dummy_instr_wb_i; - // Write enable for dummy R0 register (waddr_a_i will always be 0 for dummy instructions) - assign we_r0_dummy = we_a_i & dummy_instr_wb_i; + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + rf_r0_q <= WordZeroVal; + end else if (we_r0_dummy) begin + rf_r0_q <= wdata_a_i; + end + end - always_ff @(posedge clk_i or negedge rst_ni) begin - if (!rst_ni) begin - rf_r0_q <= WordZeroVal; - end else if (we_r0_dummy) begin - rf_r0_q <= wdata_a_i; - end + // Output the dummy data for dummy instructions, otherwise R0 reads as zero + assign rf_reg[0] = dummy_instr_id_i ? rf_r0_q : WordZeroVal; + + end else begin : g_normal_r0 + logic unused_dummy_instr; + assign unused_dummy_instr = dummy_instr_id_i ^ dummy_instr_wb_i; + + // R0 is nil + assign rf_reg[0] = WordZeroVal; end - // Output the dummy data for dummy instructions, otherwise R0 reads as zero - assign rf_reg[0] = dummy_instr_id_i ? rf_r0_q : WordZeroVal; + if (RdataMuxCheck) begin : gen_rdata_mux_check + // MUX register to rdata_a/b_o according to raddr_a/b_onehot. + prim_onehot_mux #( + .Width (DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_a_mux ( + .clk_i, + .rst_ni, + .in_i (rf_reg), + .sel_i(raddr_onehot_a), + .out_o(rdata_a_o) + ); - end else begin : g_normal_r0 - logic unused_dummy_instr; - assign unused_dummy_instr = dummy_instr_id_i ^ dummy_instr_wb_i; + prim_onehot_mux #( + .Width (DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_b_mux ( + .clk_i, + .rst_ni, + .in_i (rf_reg), + .sel_i(raddr_onehot_b), + .out_o(rdata_b_o) + ); + end else begin : gen_no_rdata_mux_check + assign rdata_a_o = rf_reg[raddr_a_i]; + assign rdata_b_o = rf_reg[raddr_b_i]; + end - // R0 is nil - assign rf_reg[0] = WordZeroVal; - end - - if (RdataMuxCheck) begin : gen_rdata_mux_check - // Encode raddr_a/b into one-hot encoded signals. - logic [NUM_WORDS-1:0] raddr_onehot_a, raddr_onehot_b; - logic [NUM_WORDS-1:0] raddr_onehot_a_buf, raddr_onehot_b_buf; - prim_onehot_enc #( - .OneHotWidth(NUM_WORDS) - ) u_prim_onehot_enc_raddr_a ( - .in_i (raddr_a_i), - .en_i (1'b1), - .out_o (raddr_onehot_a) - ); - - prim_onehot_enc #( - .OneHotWidth(NUM_WORDS) - ) u_prim_onehot_enc_raddr_b ( - .in_i (raddr_b_i), - .en_i (1'b1), - .out_o (raddr_onehot_b) - ); - - // Buffer the one-hot encoded signals so that the checkers - // are not optimized. - prim_buf #( - .Width(NUM_WORDS) - ) u_prim_buf_raddr_a ( - .in_i (raddr_onehot_a), - .out_o(raddr_onehot_a_buf) - ); - - prim_buf #( - .Width(NUM_WORDS) - ) u_prim_buf_raddr_b ( - .in_i (raddr_onehot_b), - .out_o(raddr_onehot_b_buf) - ); - - // SEC_CM: DATA_REG_SW.GLITCH_DETECT - // Check the one-hot encoded signals for glitches. - prim_onehot_check #( - .AddrWidth(ADDR_WIDTH), - .OneHotWidth(NUM_WORDS), - .AddrCheck(1), - // When AddrCheck=1 also EnableCheck needs to be 1. - .EnableCheck(1) - ) u_prim_onehot_check_raddr_a ( - .clk_i, - .rst_ni, - .oh_i (raddr_onehot_a_buf), - .addr_i (raddr_a_i), - // Set enable=1 as address is always valid. - .en_i (1'b1), - .err_o (oh_raddr_a_err) - ); - - prim_onehot_check #( - .AddrWidth(ADDR_WIDTH), - .OneHotWidth(NUM_WORDS), - .AddrCheck(1), - // When AddrCheck=1 also EnableCheck needs to be 1. - .EnableCheck(1) - ) u_prim_onehot_check_raddr_b ( - .clk_i, - .rst_ni, - .oh_i (raddr_onehot_b_buf), - .addr_i (raddr_b_i), - // Set enable=1 as address is always valid. - .en_i (1'b1), - .err_o (oh_raddr_b_err) - ); - - // MUX register to rdata_a/b_o according to raddr_a/b_onehot. - prim_onehot_mux #( - .Width(DataWidth), - .Inputs(NUM_WORDS) - ) u_rdata_a_mux ( - .clk_i, - .rst_ni, - .in_i (rf_reg), - .sel_i (raddr_onehot_a), - .out_o (rdata_a_o) - ); - - prim_onehot_mux #( - .Width(DataWidth), - .Inputs(NUM_WORDS) - ) u_rdata_b_mux ( - .clk_i, - .rst_ni, - .in_i (rf_reg), - .sel_i (raddr_onehot_b), - .out_o (rdata_b_o) - ); - end else begin : gen_no_rdata_mux_check - assign rdata_a_o = rf_reg[raddr_a_i]; - assign rdata_b_o = rf_reg[raddr_b_i]; - assign oh_raddr_a_err = 1'b0; - assign oh_raddr_b_err = 1'b0; - end - - assign err_o = oh_raddr_a_err || oh_raddr_b_err || oh_we_err; - - // Signal not used in FF register file - logic unused_test_en; - assign unused_test_en = test_en_i; + // Signal not used in FF register file + logic unused_test_en; + assign unused_test_en = test_en_i; endmodule diff --git a/rtl/ibex_register_file_fpga.sv b/rtl/ibex_register_file_fpga.sv index 65698d1c..3082179a 100644 --- a/rtl/ibex_register_file_fpga.sv +++ b/rtl/ibex_register_file_fpga.sv @@ -12,190 +12,135 @@ * FPGA architectures, it will produce RAM32M primitives. Other vendors have not yet been tested. */ module ibex_register_file_fpga #( - parameter bit RV32E = 0, - parameter int unsigned DataWidth = 32, - parameter bit DummyInstructions = 0, - parameter bit WrenCheck = 0, - parameter bit RdataMuxCheck = 0, - parameter logic [DataWidth-1:0] WordZeroVal = '0 + parameter bit RV32E = 0, + parameter int unsigned DataWidth = 32, + parameter bit DummyInstructions = 0, + parameter bit WrenCheck = 0, + parameter bit RdataMuxCheck = 0, + parameter logic [DataWidth-1:0] WordZeroVal = '0 ) ( - // Clock and Reset - input logic clk_i, - input logic rst_ni, + // Clock and Reset + input logic clk_i, + input logic rst_ni, - input logic test_en_i, - input logic dummy_instr_id_i, - input logic dummy_instr_wb_i, + input logic test_en_i, + input logic dummy_instr_id_i, + input logic dummy_instr_wb_i, - //Read port R1 - input logic [ 4:0] raddr_a_i, - output logic [DataWidth-1:0] rdata_a_o, - //Read port R2 - input logic [ 4:0] raddr_b_i, - output logic [DataWidth-1:0] rdata_b_o, - // Write port W1 - input logic [ 4:0] waddr_a_i, - input logic [DataWidth-1:0] wdata_a_i, - input logic we_a_i, + //Read port R1 + input logic [ 4:0] raddr_a_i, + output logic [DataWidth-1:0] rdata_a_o, + //Read port R2 + input logic [ 4:0] raddr_b_i, + output logic [DataWidth-1:0] rdata_b_o, + // Write port W1 + input logic [ 4:0] waddr_a_i, + input logic [DataWidth-1:0] wdata_a_i, + input logic we_a_i, - // This indicates whether spurious WE or non-one-hot encoded raddr are detected. - output logic err_o + // This indicates whether spurious WE or non-one-hot encoded raddr are detected. + output logic err_o ); - localparam int ADDR_WIDTH = RV32E ? 4 : 5; - localparam int NUM_WORDS = 2 ** ADDR_WIDTH; + localparam int ADDR_WIDTH = RV32E ? 4 : 5; + localparam int NUM_WORDS = 2 ** ADDR_WIDTH; - logic [DataWidth-1:0] mem[NUM_WORDS]; - logic we; // write enable if writing to any register other than R0 + logic [DataWidth-1:0] mem[NUM_WORDS]; + logic we; // write enable if writing to any register other than R0 - logic [DataWidth-1:0] mem_o_a, mem_o_b; + // Encode we_a/raddr_a/raddr_b into one-hot encoded signals + logic [NUM_WORDS-1:0] raddr_onehot_a, raddr_onehot_b, we_onehot_a; - // WE strobe and one-hot encoded raddr alert. - logic oh_raddr_a_err, oh_raddr_b_err, oh_we_err; - assign err_o = oh_raddr_a_err || oh_raddr_b_err || oh_we_err; + // WE strobe and one-hot encoded raddr alert. + logic oh_raddr_a_err, oh_raddr_b_err, oh_we_err; - if (RdataMuxCheck) begin : gen_rdata_mux_check - // Encode raddr_a/b into one-hot encoded signals. - logic [NUM_WORDS-1:0] raddr_onehot_a, raddr_onehot_b; - logic [NUM_WORDS-1:0] raddr_onehot_a_buf, raddr_onehot_b_buf; - prim_onehot_enc #( - .OneHotWidth(NUM_WORDS) - ) u_prim_onehot_enc_raddr_a ( - .in_i (raddr_a_i), - .en_i (1'b1), - .out_o (raddr_onehot_a) + logic [DataWidth-1:0] mem_o_a, mem_o_b; + + // Common security functionality + ibex_register_file_common #( + .FPGA(1), + .AddrWidth(ADDR_WIDTH), + .NumWords(NUM_WORDS), + .DataWidth(DataWidth), + .WrenCheck(WrenCheck), + .RdataMuxCheck(RdataMuxCheck) + ) security_module ( + .clk_i, + .rst_ni, + .raddr_a_i, + .raddr_onehot_a, + .oh_raddr_a_err, + .raddr_b_i, + .raddr_onehot_b, + .oh_raddr_b_err, + .waddr_a_i, + .we_a_i, + .we_onehot_a, + .oh_we_err, + .err_o ); - prim_onehot_enc #( - .OneHotWidth(NUM_WORDS) - ) u_prim_onehot_enc_raddr_b ( - .in_i (raddr_b_i), - .en_i (1'b1), - .out_o (raddr_onehot_b) - ); + if (RdataMuxCheck) begin : gen_rdata_mux_check + // MUX register to rdata_a/b_o according to raddr_a/b_onehot. + prim_onehot_mux #( + .Width (DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_a_mux ( + .clk_i, + .rst_ni, + .in_i (mem), + .sel_i(raddr_onehot_a), + .out_o(mem_o_a) + ); - // Buffer the one-hot encoded signals so that the checkers - // are not optimized. - prim_buf #( - .Width(NUM_WORDS) - ) u_prim_buf_raddr_a ( - .in_i (raddr_onehot_a), - .out_o(raddr_onehot_a_buf) - ); + prim_onehot_mux #( + .Width (DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_b_mux ( + .clk_i, + .rst_ni, + .in_i (mem), + .sel_i(raddr_onehot_b), + .out_o(mem_o_b) + ); - prim_buf #( - .Width(NUM_WORDS) - ) u_prim_buf_raddr_b ( - .in_i (raddr_onehot_b), - .out_o(raddr_onehot_b_buf) - ); - - // SEC_CM: DATA_REG_SW.GLITCH_DETECT - // Check the one-hot encoded signals for glitches. - prim_onehot_check #( - .AddrWidth(ADDR_WIDTH), - .OneHotWidth(NUM_WORDS), - .AddrCheck(1), - // When AddrCheck=1 also EnableCheck needs to be 1. - .EnableCheck(1) - ) u_prim_onehot_check_raddr_a ( - .clk_i, - .rst_ni, - .oh_i (raddr_onehot_a_buf), - .addr_i (raddr_a_i), - // Set enable=1 as address is always valid. - .en_i (1'b1), - .err_o (oh_raddr_a_err) - ); - - prim_onehot_check #( - .AddrWidth(ADDR_WIDTH), - .OneHotWidth(NUM_WORDS), - .AddrCheck(1), - // When AddrCheck=1 also EnableCheck needs to be 1. - .EnableCheck(1) - ) u_prim_onehot_check_raddr_b ( - .clk_i, - .rst_ni, - .oh_i (raddr_onehot_b_buf), - .addr_i (raddr_b_i), - // Set enable=1 as address is always valid. - .en_i (1'b1), - .err_o (oh_raddr_b_err) - ); - - // MUX register to rdata_a/b_o according to raddr_a/b_onehot. - prim_onehot_mux #( - .Width(DataWidth), - .Inputs(NUM_WORDS) - ) u_rdata_a_mux ( - .clk_i, - .rst_ni, - .in_i (mem), - .sel_i (raddr_onehot_a), - .out_o (mem_o_a) - ); - - prim_onehot_mux #( - .Width(DataWidth), - .Inputs(NUM_WORDS) - ) u_rdata_b_mux ( - .clk_i, - .rst_ni, - .in_i (mem), - .sel_i (raddr_onehot_b), - .out_o (mem_o_b) - ); - - assign rdata_a_o = (raddr_a_i == '0) ? WordZeroVal : mem_o_a; - assign rdata_b_o = (raddr_b_i == '0) ? WordZeroVal : mem_o_b; - end else begin : gen_no_rdata_mux_check - assign rdata_a_o = (raddr_a_i == '0) ? WordZeroVal : mem[raddr_a_i]; - assign rdata_b_o = (raddr_b_i == '0) ? WordZeroVal : mem[raddr_b_i]; - - assign oh_raddr_a_err = 1'b0; - assign oh_raddr_b_err = 1'b0; - end - - // we select - assign we = (waddr_a_i == '0) ? 1'b0 : we_a_i; - - // SEC_CM: DATA_REG_SW.GLITCH_DETECT - // This checks for spurious WE strobes on the regfile. - if (WrenCheck) begin : gen_wren_check - // Since the FPGA uses a memory macro, there is only one write-enable strobe to check. - assign oh_we_err = we && !we_a_i; - end else begin : gen_no_wren_check - assign oh_we_err = 1'b0; - end - - // Note that the SystemVerilog LRM requires variables on the LHS of assignments within - // "always_ff" to not be written to by any other process. However, to enable the initialization - // of the inferred RAM32M primitives with non-zero values, below "initial" procedure is needed. - // Therefore, we use "always" instead of the generally preferred "always_ff" for the synchronous - // write procedure. - always @(posedge clk_i) begin : sync_write - if (we == 1'b1) begin - mem[waddr_a_i] <= wdata_a_i; + assign rdata_a_o = (raddr_a_i == '0) ? WordZeroVal : mem_o_a; + assign rdata_b_o = (raddr_b_i == '0) ? WordZeroVal : mem_o_b; + end else begin : gen_no_rdata_mux_check + assign rdata_a_o = (raddr_a_i == '0) ? WordZeroVal : mem[raddr_a_i]; + assign rdata_b_o = (raddr_b_i == '0) ? WordZeroVal : mem[raddr_b_i]; end - end : sync_write - // Make sure we initialize the BRAM with the correct register reset value. - initial begin - for (int k = 0; k < NUM_WORDS; k++) begin - mem[k] = WordZeroVal; + // we select + assign we = (waddr_a_i == '0) ? 1'b0 : we_onehot_a[0]; + + // Note that the SystemVerilog LRM requires variables on the LHS of assignments within + // "always_ff" to not be written to by any other process. However, to enable the initialization + // of the inferred RAM32M primitives with non-zero values, below "initial" procedure is needed. + // Therefore, we use "always" instead of the generally preferred "always_ff" for the synchronous + // write procedure. + always @(posedge clk_i) begin : sync_write + if (we == 1'b1) begin + mem[waddr_a_i] <= wdata_a_i; + end + end : sync_write + + // Make sure we initialize the BRAM with the correct register reset value. + initial begin + for (int k = 0; k < NUM_WORDS; k++) begin + mem[k] = WordZeroVal; + end end - end - // Reset not used in this register file version - logic unused_rst_ni; - assign unused_rst_ni = rst_ni; + // Reset not used in this register file version + logic unused_rst_ni; + assign unused_rst_ni = rst_ni; - // Dummy instruction changes not relevant for FPGA implementation - logic unused_dummy_instr; - assign unused_dummy_instr = dummy_instr_id_i ^ dummy_instr_wb_i; - // Test enable signal not used in FPGA implementation - logic unused_test_en; - assign unused_test_en = test_en_i; + // Dummy instruction changes not relevant for FPGA implementation + logic unused_dummy_instr; + assign unused_dummy_instr = dummy_instr_id_i ^ dummy_instr_wb_i; + // Test enable signal not used in FPGA implementation + logic unused_test_en; + assign unused_test_en = test_en_i; endmodule diff --git a/rtl/ibex_register_file_latch.sv b/rtl/ibex_register_file_latch.sv index 375da414..8bbbfc86 100644 --- a/rtl/ibex_register_file_latch.sv +++ b/rtl/ibex_register_file_latch.sv @@ -12,289 +12,199 @@ * register file when targeting ASIC synthesis or event-based simulators. */ module ibex_register_file_latch #( - parameter bit RV32E = 0, - parameter int unsigned DataWidth = 32, - parameter bit DummyInstructions = 0, - parameter bit WrenCheck = 0, - parameter bit RdataMuxCheck = 0, - parameter logic [DataWidth-1:0] WordZeroVal = '0 + parameter bit RV32E = 0, + parameter int unsigned DataWidth = 32, + parameter bit DummyInstructions = 0, + parameter bit WrenCheck = 0, + parameter bit RdataMuxCheck = 0, + parameter logic [DataWidth-1:0] WordZeroVal = '0 ) ( - // Clock and Reset - input logic clk_i, - input logic rst_ni, + // Clock and Reset + input logic clk_i, + input logic rst_ni, - input logic test_en_i, - input logic dummy_instr_id_i, - input logic dummy_instr_wb_i, + input logic test_en_i, + input logic dummy_instr_id_i, + input logic dummy_instr_wb_i, - //Read port R1 - input logic [4:0] raddr_a_i, - output logic [DataWidth-1:0] rdata_a_o, + //Read port R1 + input logic [ 4:0] raddr_a_i, + output logic [DataWidth-1:0] rdata_a_o, - //Read port R2 - input logic [4:0] raddr_b_i, - output logic [DataWidth-1:0] rdata_b_o, + //Read port R2 + input logic [ 4:0] raddr_b_i, + output logic [DataWidth-1:0] rdata_b_o, - // Write port W1 - input logic [4:0] waddr_a_i, - input logic [DataWidth-1:0] wdata_a_i, - input logic we_a_i, + // Write port W1 + input logic [ 4:0] waddr_a_i, + input logic [DataWidth-1:0] wdata_a_i, + input logic we_a_i, - // This indicates whether spurious WE or non-one-hot encoded raddr are detected. - output logic err_o + // This indicates whether spurious WE or non-one-hot encoded raddr are detected. + output logic err_o ); - localparam int unsigned ADDR_WIDTH = RV32E ? 4 : 5; - localparam int unsigned NUM_WORDS = 2**ADDR_WIDTH; + localparam int unsigned ADDR_WIDTH = RV32E ? 4 : 5; + localparam int unsigned NUM_WORDS = 2 ** ADDR_WIDTH; - logic [DataWidth-1:0] mem[NUM_WORDS]; + logic [DataWidth-1:0] mem[NUM_WORDS]; - logic [NUM_WORDS-1:0] waddr_onehot_a; + logic [NUM_WORDS-1:0] waddr_onehot_a; - logic oh_raddr_a_err, oh_raddr_b_err, oh_we_err; + // One-hot encoding error signals + logic oh_raddr_a_err, oh_raddr_b_err, oh_we_err; - logic [NUM_WORDS-1:1] mem_clocks; - logic [DataWidth-1:0] wdata_a_q; + logic [NUM_WORDS-1:1] mem_clocks; + logic [DataWidth-1:0] wdata_a_q; - // internal addresses - logic [ADDR_WIDTH-1:0] raddr_a_int, raddr_b_int, waddr_a_int; + // internal addresses + logic [ADDR_WIDTH-1:0] raddr_a_int, raddr_b_int, waddr_a_int; + logic [NUM_WORDS-1:0] raddr_onehot_a, raddr_onehot_b, we_onehot_a; - assign raddr_a_int = raddr_a_i[ADDR_WIDTH-1:0]; - assign raddr_b_int = raddr_b_i[ADDR_WIDTH-1:0]; - assign waddr_a_int = waddr_a_i[ADDR_WIDTH-1:0]; + assign raddr_a_int = raddr_a_i[ADDR_WIDTH-1:0]; + assign raddr_b_int = raddr_b_i[ADDR_WIDTH-1:0]; + assign waddr_a_int = waddr_a_i[ADDR_WIDTH-1:0]; - logic clk_int; + logic clk_int; - assign err_o = oh_raddr_a_err || oh_raddr_b_err || oh_we_err; - - ////////// - // READ // - ////////// - if (RdataMuxCheck) begin : gen_rdata_mux_check - // Encode raddr_a/b into one-hot encoded signals. - logic [NUM_WORDS-1:0] raddr_onehot_a, raddr_onehot_b; - logic [NUM_WORDS-1:0] raddr_onehot_a_buf, raddr_onehot_b_buf; - prim_onehot_enc #( - .OneHotWidth(NUM_WORDS) - ) u_prim_onehot_enc_raddr_a ( - .in_i (raddr_a_int), - .en_i (1'b1), - .out_o (raddr_onehot_a) + // Common security functionality + ibex_register_file_common #( + .AddrWidth(ADDR_WIDTH), + .NumWords(NUM_WORDS), + .DataWidth(DataWidth), + .WrenCheck(WrenCheck), + .RdataMuxCheck(RdataMuxCheck) + ) security_module ( + .clk_i, + .rst_ni, + .raddr_a_i(raddr_a_int), + .raddr_onehot_a, + .oh_raddr_a_err, + .raddr_b_i(raddr_b_int), + .raddr_onehot_b, + .oh_raddr_b_err, + .waddr_a_i(waddr_a_int), + .we_a_i, + .we_onehot_a, + .oh_we_err, + .err_o ); - prim_onehot_enc #( - .OneHotWidth(NUM_WORDS) - ) u_prim_onehot_enc_raddr_b ( - .in_i (raddr_b_int), - .en_i (1'b1), - .out_o (raddr_onehot_b) - ); + ////////// + // READ // + ////////// + if (RdataMuxCheck) begin : gen_rdata_mux_check + // MUX register to rdata_a/b_o according to raddr_a/b_onehot. + prim_onehot_mux #( + .Width (DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_a_mux ( + .clk_i, + .rst_ni, + .in_i (mem), + .sel_i(raddr_onehot_a), + .out_o(rdata_a_o) + ); - // Buffer the one-hot encoded signals so that the checkers - // are not optimized. - prim_buf #( - .Width(NUM_WORDS) - ) u_prim_buf_raddr_a ( - .in_i(raddr_onehot_a), - .out_o(raddr_onehot_a_buf) - ); - - prim_buf #( - .Width(NUM_WORDS) - ) u_prim_buf_raddr_b ( - .in_i(raddr_onehot_b), - .out_o(raddr_onehot_b_buf) - ); - - // SEC_CM: DATA_REG_SW.GLITCH_DETECT - // Check the one-hot encoded signals for glitches. - prim_onehot_check #( - .AddrWidth(ADDR_WIDTH), - .OneHotWidth(NUM_WORDS), - .AddrCheck(1), - // When AddrCheck=1 also EnableCheck needs to be 1. - .EnableCheck(1) - ) u_prim_onehot_check_raddr_a ( - .clk_i, - .rst_ni, - .oh_i (raddr_onehot_a_buf), - .addr_i (raddr_a_int), - // Set enable=1 as address is always valid. - .en_i (1'b1), - .err_o (oh_raddr_a_err) - ); - - prim_onehot_check #( - .AddrWidth(ADDR_WIDTH), - .OneHotWidth(NUM_WORDS), - .AddrCheck(1), - // When AddrCheck=1 also EnableCheck needs to be 1. - .EnableCheck(1) - ) u_prim_onehot_check_raddr_b ( - .clk_i, - .rst_ni, - .oh_i (raddr_onehot_b_buf), - .addr_i (raddr_b_int), - // Set enable=1 as address is always valid. - .en_i (1'b1), - .err_o (oh_raddr_b_err) - ); - - // MUX register to rdata_a/b_o according to raddr_a/b_onehot. - prim_onehot_mux #( - .Width(DataWidth), - .Inputs(NUM_WORDS) - ) u_rdata_a_mux ( - .clk_i, - .rst_ni, - .in_i (mem), - .sel_i (raddr_onehot_a), - .out_o (rdata_a_o) - ); - - prim_onehot_mux #( - .Width(DataWidth), - .Inputs(NUM_WORDS) - ) u_rdata_b_mux ( - .clk_i, - .rst_ni, - .in_i (mem), - .sel_i (raddr_onehot_b), - .out_o (rdata_b_o) - ); - end else begin : gen_no_rdata_mux_check - assign rdata_a_o = mem[raddr_a_int]; - assign rdata_b_o = mem[raddr_b_int]; - assign oh_raddr_a_err = 1'b0; - assign oh_raddr_b_err = 1'b0; - end - - /////////// - // WRITE // - /////////// - // Global clock gating - prim_clock_gating cg_we_global ( - .clk_i ( clk_i ), - .en_i ( we_a_i ), - .test_en_i ( test_en_i ), - .clk_o ( clk_int ) - ); - - // Sample input data - // Use clk_int here, since otherwise we don't want to write anything anyway. - always_ff @(posedge clk_int or negedge rst_ni) begin : sample_wdata - if (!rst_ni) begin - wdata_a_q <= WordZeroVal; - end else begin - if (we_a_i) begin - wdata_a_q <= wdata_a_i; - end - end - end - - // Write address decoding - always_comb begin : wad - for (int i = 0; i < NUM_WORDS; i++) begin : wad_word_iter - if (we_a_i && (waddr_a_int == 5'(i))) begin - waddr_onehot_a[i] = 1'b1; - end else begin - waddr_onehot_a[i] = 1'b0; - end - end - end - - // SEC_CM: DATA_REG_SW.GLITCH_DETECT - // This checks for spurious WE strobes on the regfile. - if (WrenCheck) begin : gen_wren_check - // Buffer the decoded write enable bits so that the checker - // is not optimized into the address decoding logic. - logic [NUM_WORDS-1:0] waddr_onehot_a_buf; - prim_buf #( - .Width(NUM_WORDS) - ) u_prim_buf ( - .in_i(waddr_onehot_a), - .out_o(waddr_onehot_a_buf) - ); - - prim_onehot_check #( - .AddrWidth(ADDR_WIDTH), - .AddrCheck(1), - .EnableCheck(1) - ) u_prim_onehot_check ( - .clk_i, - .rst_ni, - .oh_i(waddr_onehot_a_buf), - .addr_i(waddr_a_i), - .en_i(we_a_i), - .err_o(oh_we_err) - ); - end else begin : gen_no_wren_check - logic unused_strobe; - assign unused_strobe = waddr_onehot_a[0]; // this is never read from in this case - assign oh_we_err = 1'b0; - end - - // Individual clock gating (if integrated clock-gating cells are available) - for (genvar x = 1; x < NUM_WORDS; x++) begin : gen_cg_word_iter - prim_clock_gating cg_i ( - .clk_i ( clk_int ), - .en_i ( waddr_onehot_a[x] ), - .test_en_i ( test_en_i ), - .clk_o ( mem_clocks[x] ) - ); - end - - // Actual write operation: - // Generate the sequential process for the NUM_WORDS words of the memory. - // The process is synchronized with the clocks mem_clocks[i], i = 1, ..., NUM_WORDS-1. - for (genvar i = 1; i < NUM_WORDS; i++) begin : g_rf_latches - always_latch begin - if (mem_clocks[i]) begin - mem[i] = wdata_a_q; - end - end - end - - // With dummy instructions enabled, R0 behaves as a real register but will always return 0 for - // real instructions. - if (DummyInstructions) begin : g_dummy_r0 - // SEC_CM: CTRL_FLOW.UNPREDICTABLE - logic we_r0_dummy; - logic r0_clock; - logic [DataWidth-1:0] mem_r0; - - // Write enable for dummy R0 register (waddr_a_i will always be 0 for dummy instructions) - assign we_r0_dummy = we_a_i & dummy_instr_wb_i; - - // R0 clock gate - prim_clock_gating cg_i ( - .clk_i ( clk_int ), - .en_i ( we_r0_dummy ), - .test_en_i ( test_en_i ), - .clk_o ( r0_clock ) - ); - - always_latch begin : latch_wdata - if (r0_clock) begin - mem_r0 = wdata_a_q; - end + prim_onehot_mux #( + .Width (DataWidth), + .Inputs(NUM_WORDS) + ) u_rdata_b_mux ( + .clk_i, + .rst_ni, + .in_i (mem), + .sel_i(raddr_onehot_b), + .out_o(rdata_b_o) + ); + end else begin : gen_no_rdata_mux_check + assign rdata_a_o = mem[raddr_a_int]; + assign rdata_b_o = mem[raddr_b_int]; end - // Output the dummy data for dummy instructions, otherwise R0 reads as zero - assign mem[0] = dummy_instr_id_i ? mem_r0 : WordZeroVal; + /////////// + // WRITE // + /////////// + // Global clock gating + prim_clock_gating cg_we_global ( + .clk_i (clk_i), + .en_i (we_a_i), + .test_en_i(test_en_i), + .clk_o (clk_int) + ); - end else begin : g_normal_r0 - logic unused_dummy_instr; - assign unused_dummy_instr = dummy_instr_id_i ^ dummy_instr_wb_i; + // Sample input data + // Use clk_int here, since otherwise we don't want to write anything anyway. + always_ff @(posedge clk_int or negedge rst_ni) begin : sample_wdata + if (!rst_ni) begin + wdata_a_q <= WordZeroVal; + end else begin + if (we_a_i) begin + wdata_a_q <= wdata_a_i; + end + end + end - assign mem[0] = WordZeroVal; - end + // Individual clock gating (if integrated clock-gating cells are available) + for (genvar x = 1; x < NUM_WORDS; x++) begin : gen_cg_word_iter + prim_clock_gating cg_i ( + .clk_i (clk_int), + .en_i (we_onehot_a[x]), + .test_en_i(test_en_i), + .clk_o (mem_clocks[x]) + ); + end + + // Actual write operation: + // Generate the sequential process for the NUM_WORDS words of the memory. + // The process is synchronized with the clocks mem_clocks[i], i = 1, ..., NUM_WORDS-1. + for (genvar i = 1; i < NUM_WORDS; i++) begin : g_rf_latches + always_latch begin + if (mem_clocks[i]) begin + mem[i] = wdata_a_q; + end + end + end + + // With dummy instructions enabled, R0 behaves as a real register but will always return 0 for + // real instructions. + if (DummyInstructions) begin : g_dummy_r0 + // SEC_CM: CTRL_FLOW.UNPREDICTABLE + logic we_r0_dummy; + logic r0_clock; + logic [DataWidth-1:0] mem_r0; + + // Write enable for dummy R0 register (waddr_a_i will always be 0 for dummy instructions) + assign we_r0_dummy = we_a_i & dummy_instr_wb_i; + + // R0 clock gate + prim_clock_gating cg_i ( + .clk_i (clk_int), + .en_i (we_r0_dummy), + .test_en_i(test_en_i), + .clk_o (r0_clock) + ); + + always_latch begin : latch_wdata + if (r0_clock) begin + mem_r0 = wdata_a_q; + end + end + + // Output the dummy data for dummy instructions, otherwise R0 reads as zero + assign mem[0] = dummy_instr_id_i ? mem_r0 : WordZeroVal; + + end else begin : g_normal_r0 + logic unused_dummy_instr; + assign unused_dummy_instr = dummy_instr_id_i ^ dummy_instr_wb_i; + + assign mem[0] = WordZeroVal; + end `ifdef VERILATOR - initial begin - $display("Latch-based register file not supported for Verilator simulation"); - $fatal; - end + initial begin + $display("Latch-based register file not supported for Verilator simulation"); + $fatal; + end `endif endmodule