diff --git a/doc/cs_registers.rst b/doc/cs_registers.rst index 3530d423..a108e3cf 100644 --- a/doc/cs_registers.rst +++ b/doc/cs_registers.rst @@ -66,7 +66,9 @@ Ibex implements all the Control and Status Registers (CSRs) listed in the follow +---------+--------------------+--------+-----------------------------------------------+ | 0x7B3 | ``dscratch1`` | RW | Debug Scratch Register 1 | +---------+--------------------+--------+-----------------------------------------------+ -| 0x7C0 | ``cpuctrl`` | RW | CPU Control Register (Custom CSR) | +| 0x7C0 | ``cpuctrl`` | WARL | CPU Control Register (Custom CSR) | ++---------+--------------------+--------+-----------------------------------------------+ +| 0x7C1 | ``secureseed`` | WARL | Security feature random seed (Custom CSR) | +---------+--------------------+--------+-----------------------------------------------+ | 0xB00 | ``mcycle`` | RW | Machine Cycle Counter | +---------+--------------------+--------+-----------------------------------------------+ @@ -500,6 +502,16 @@ Other bit fields read as zero. +-------+------+------------------------------------------------------------------+ | Bit# | R/W | Description | +-------+------+------------------------------------------------------------------+ +| 5:3 | WARL | **dummy_instr_mask:** Mask to control frequency of dummy | +| | | instruction insertion. If the core has not been configured with | +| | | security features (SecureIbex parameter == 0), this field will | +| | | always read as zero (see :ref:`security`). | ++-------+------+------------------------------------------------------------------+ +| 2 | WARL | **dummy_instr_en:** Enable (1) or disable (0) dummy instruction | +| | | insertion features. If the core has not been configured with | +| | | security features (SecureIbex parameter == 0), this field will | +| | | always read as zero (see :ref:`security`). | ++-------+------+------------------------------------------------------------------+ | 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 | @@ -510,6 +522,20 @@ Other bit fields read as zero. | | | parameter == 0), this field will always read as zero. | +-------+------+------------------------------------------------------------------+ +Security Feature Seed Register (secureseed) +------------------------------------------- + +CSR Address: ``0x7C1`` + +Reset Value: ``0x0000_0000`` + +Accessible in Machine Mode only. + +Custom CSR to allow re-seeding of security-related pseudo-random number generators. +A write to this register will update the seeding of pseudo-random number generators inside the design. +This allows software to improve the randomness, and therefore security, of certain features by periodically reading from a true random number generator peripheral. +Seed values are not actually stored in a register and so reads to this register will always return zero. + Time Registers (time(h)) ------------------------ diff --git a/doc/index.rst b/doc/index.rst index c9350db8..d7796726 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -19,6 +19,7 @@ Ibex User Manual performance_counters exception_interrupts pmp + security debug tracer rvfi diff --git a/doc/security.rst b/doc/security.rst new file mode 100644 index 00000000..c21dddd8 --- /dev/null +++ b/doc/security.rst @@ -0,0 +1,46 @@ +.. _security: + +Security Features +================= + +Ibex implements a set of extra features (when the SecureIbex parameter is set) to support security-critical applications. +All features are runtime configurable via bits in the **cpuctrl** custom CSR. + +Data Independent Timing +----------------------- + +When enabled (via the **data_ind_timing** control bit in the **cpuctrl** register), execution times and power consumption of all instructions shall be independent of input data. +This makes it more difficult for an external observer to infer secret data by observing power lines or exploiting timing side-channels. + +In Ibex, most instructions already execute independent of their input operands. +When data-independent timing is enabled: +* Branches execute identically regardless of their taken/not-taken status +* Early completion of multiplication by zero/one is removed +* Early completion of divide by zero is removed + +Dummy Instruction Insertion +--------------------------- + +When enabled (via the **dummy_instr_en** control bit in the **cpuctrl** register), dummy instructions will be inserted at random intervals into the execution pipeline. +The dummy instructions have no functional impact on processor state, but add some difficult-to-predict timing and power disruption to executed code. +This disruption makes it more difficult for an attacker to infer what is being executed, and also makes it more difficult to execute precisely timed fault injection attacks. + +The frequency of injected instructions can be tuned via the **dummy_instr_mask** bits in the **cpuctrl** register. + ++----------------------+----------------------------------------------------------+ +| **dummy_instr_mask** | Interval | ++----------------------+----------------------------------------------------------+ +| 000 | Dummy instruction every 0 - 4 real instructions | ++----------------------+----------------------------------------------------------+ +| 001 | Dummy instruction every 0 - 8 real instructions | ++----------------------+----------------------------------------------------------+ +| 011 | Dummy instruction every 0 - 16 real instructions | ++----------------------+----------------------------------------------------------+ +| 111 | Dummy instruction every 0 - 32 real instructions | ++----------------------+----------------------------------------------------------+ + +Other values of **dummy_instr_mask** are legal, but will have a less predictable impact. + +The interval between instruction insertion is randomized in the core using an LFSR. +Sofware can periodically re-seed this LFSR with true random numbers (if available) via the **secureseed** CSR. +This will make the insertion interval of dummy instructions much harder for an attacker to predict. diff --git a/dv/uvm/core_ibex/ibex_dv.f b/dv/uvm/core_ibex/ibex_dv.f index 87fcca74..1ebc257f 100644 --- a/dv/uvm/core_ibex/ibex_dv.f +++ b/dv/uvm/core_ibex/ibex_dv.f @@ -15,6 +15,7 @@ ${PRJ_DIR}/ibex/shared/rtl/prim_clock_gating.sv +incdir+${PRJ_DIR}/ibex/rtl ${PRJ_DIR}/ibex/shared/rtl/prim_assert.sv ${PRJ_DIR}/ibex/shared/rtl/prim_generic_ram_1p.sv +${PRJ_DIR}/ibex/shared/rtl/prim_lfsr.sv ${PRJ_DIR}/ibex/shared/rtl/prim_secded_28_22_enc.sv ${PRJ_DIR}/ibex/shared/rtl/prim_secded_28_22_dec.sv ${PRJ_DIR}/ibex/shared/rtl/prim_secded_72_64_enc.sv @@ -28,6 +29,7 @@ ${PRJ_DIR}/ibex/rtl/ibex_controller.sv ${PRJ_DIR}/ibex/rtl/ibex_cs_registers.sv ${PRJ_DIR}/ibex/rtl/ibex_counters.sv ${PRJ_DIR}/ibex/rtl/ibex_decoder.sv +${PRJ_DIR}/ibex/rtl/ibex_dummy_instr.sv ${PRJ_DIR}/ibex/rtl/ibex_ex_block.sv ${PRJ_DIR}/ibex/rtl/ibex_wb_stage.sv ${PRJ_DIR}/ibex/rtl/ibex_id_stage.sv diff --git a/ibex_core.core b/ibex_core.core index 703618a8..7dc70487 100644 --- a/ibex_core.core +++ b/ibex_core.core @@ -9,6 +9,7 @@ filesets: files_rtl: depend: - lowrisc:prim:assert + - lowrisc:prim:lfsr - lowrisc:ibex:ibex_pkg - lowrisc:ibex:ibex_icache files: @@ -28,6 +29,7 @@ filesets: - rtl/ibex_prefetch_buffer.sv - rtl/ibex_pmp.sv - rtl/ibex_wb_stage.sv + - rtl/ibex_dummy_instr.sv # XXX: Figure out the best way to switch these two implementations # dynamically on the target. # - rtl/ibex_register_file_latch.sv # ASIC diff --git a/rtl/ibex_core.sv b/rtl/ibex_core.sv index 56cd25a1..6379928e 100644 --- a/rtl/ibex_core.sv +++ b/rtl/ibex_core.sv @@ -104,10 +104,12 @@ module ibex_core #( import ibex_pkg::*; - localparam int unsigned PMP_NUM_CHAN = 2; - localparam bit DataIndTiming = SecureIbex; + localparam int unsigned PMP_NUM_CHAN = 2; + localparam bit DataIndTiming = SecureIbex; + localparam bit DummyInstructions = SecureIbex; // IF/ID signals + logic dummy_instr_id; logic instr_valid_id; logic instr_new_id; logic [31:0] instr_rdata_id; // Instruction sampled inside IF stage @@ -126,6 +128,10 @@ module ibex_core #( logic imd_val_we_ex; logic data_ind_timing; + logic dummy_instr_en; + logic [2:0] dummy_instr_mask; + logic dummy_instr_seed_en; + logic [31:0] dummy_instr_seed; logic icache_enable; logic icache_inval; @@ -364,10 +370,11 @@ module ibex_core #( ////////////// ibex_if_stage #( - .DmHaltAddr ( DmHaltAddr ), - .DmExceptionAddr ( DmExceptionAddr ), - .ICache ( ICache ), - .ICacheECC ( ICacheECC ) + .DmHaltAddr ( DmHaltAddr ), + .DmExceptionAddr ( DmExceptionAddr ), + .DummyInstructions ( DummyInstructions ), + .ICache ( ICache ), + .ICacheECC ( ICacheECC ) ) if_stage_i ( .clk_i ( clk ), .rst_ni ( rst_ni ), @@ -394,6 +401,7 @@ module ibex_core #( .instr_fetch_err_o ( instr_fetch_err ), .instr_fetch_err_plus2_o ( instr_fetch_err_plus2 ), .illegal_c_insn_id_o ( illegal_c_insn_id ), + .dummy_instr_id_o ( dummy_instr_id ), .pc_if_o ( pc_if ), .pc_id_o ( pc_id ), @@ -403,6 +411,10 @@ module ibex_core #( .pc_mux_i ( pc_mux_id ), .exc_pc_mux_i ( exc_pc_mux_id ), .exc_cause ( exc_cause ), + .dummy_instr_en_i ( dummy_instr_en ), + .dummy_instr_mask_i ( dummy_instr_mask ), + .dummy_instr_seed_en_i ( dummy_instr_seed_en ), + .dummy_instr_seed_i ( dummy_instr_seed ), .icache_enable_i ( icache_enable ), .icache_inval_i ( icache_inval ), @@ -721,24 +733,26 @@ module ibex_core #( ); ibex_register_file #( - .RV32E(RV32E), - .DataWidth(32) + .RV32E (RV32E), + .DataWidth (32), + .DummyInstructions (DummyInstructions) ) register_file_i ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), - .test_en_i ( test_en_i ), + .test_en_i ( test_en_i ), + .dummy_instr_id_i ( dummy_instr_id ), // Read port a - .raddr_a_i ( rf_raddr_a ), - .rdata_a_o ( rf_rdata_a ), + .raddr_a_i ( rf_raddr_a ), + .rdata_a_o ( rf_rdata_a ), // Read port b - .raddr_b_i ( rf_raddr_b ), - .rdata_b_o ( rf_rdata_b ), + .raddr_b_i ( rf_raddr_b ), + .rdata_b_o ( rf_rdata_b ), // write port - .waddr_a_i ( rf_waddr_wb ), - .wdata_a_i ( rf_wdata_wb ), - .we_a_i ( rf_we_wb ) + .waddr_a_i ( rf_waddr_wb ), + .wdata_a_i ( rf_wdata_wb ), + .we_a_i ( rf_we_wb ) ); // Explict INC_ASSERT block to avoid unused signal lint warnings were asserts are not included @@ -802,16 +816,17 @@ module ibex_core #( assign csr_addr = csr_num_e'(csr_access ? alu_operand_b_ex[11:0] : 12'b0); ibex_cs_registers #( - .DbgTriggerEn ( DbgTriggerEn ), - .DataIndTiming ( DataIndTiming ), - .ICache ( ICache ), - .MHPMCounterNum ( MHPMCounterNum ), - .MHPMCounterWidth ( MHPMCounterWidth ), - .PMPEnable ( PMPEnable ), - .PMPGranularity ( PMPGranularity ), - .PMPNumRegions ( PMPNumRegions ), - .RV32E ( RV32E ), - .RV32M ( RV32M ) + .DbgTriggerEn ( DbgTriggerEn ), + .DataIndTiming ( DataIndTiming ), + .DummyInstructions ( DummyInstructions ), + .ICache ( ICache ), + .MHPMCounterNum ( MHPMCounterNum ), + .MHPMCounterWidth ( MHPMCounterWidth ), + .PMPEnable ( PMPEnable ), + .PMPGranularity ( PMPGranularity ), + .PMPNumRegions ( PMPNumRegions ), + .RV32E ( RV32E ), + .RV32M ( RV32M ) ) cs_registers_i ( .clk_i ( clk ), .rst_ni ( rst_ni ), @@ -866,6 +881,10 @@ module ibex_core #( .pc_wb_i ( pc_wb ), .data_ind_timing_o ( data_ind_timing ), + .dummy_instr_en_o ( dummy_instr_en ), + .dummy_instr_mask_o ( dummy_instr_mask ), + .dummy_instr_seed_en_o ( dummy_instr_seed_en ), + .dummy_instr_seed_o ( dummy_instr_seed ), .icache_enable_o ( icache_enable ), .csr_save_if_i ( csr_save_if ), @@ -1010,7 +1029,8 @@ module ibex_core #( // awaiting instruction retirement and RF Write data/Mem read data whilst instruction is in WB // So first stage becomes valid when instruction leaves ID/EX stage and remains valid until // instruction leaves WB - assign rvfi_stage_valid_d[0] = instr_id_done | (rvfi_stage_valid[0] & ~instr_done_wb); + assign rvfi_stage_valid_d[0] = (instr_id_done & ~dummy_instr_id) | + (rvfi_stage_valid[0] & ~instr_done_wb); // Second stage is output stage so simple valid cycle after instruction leaves WB (and so has // retired) assign rvfi_stage_valid_d[1] = instr_done_wb; @@ -1030,7 +1050,7 @@ module ibex_core #( end else begin // Without writeback stage first RVFI stage is output stage so simply valid the cycle after // instruction leaves ID/EX (and so has retired) - assign rvfi_stage_valid_d[0] = instr_id_done; + assign rvfi_stage_valid_d[0] = instr_id_done & ~dummy_instr_id; // Without writeback stage signal new instr_new_wb when instruction enters ID/EX to correctly // setup register write signals assign rvfi_instr_new_wb = instr_new_id; diff --git a/rtl/ibex_cs_registers.sv b/rtl/ibex_cs_registers.sv index 6fd9205e..79101081 100644 --- a/rtl/ibex_cs_registers.sv +++ b/rtl/ibex_cs_registers.sv @@ -13,16 +13,17 @@ `include "prim_assert.sv" 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, - parameter bit PMPEnable = 0, - parameter int unsigned PMPGranularity = 0, - parameter int unsigned PMPNumRegions = 4, - parameter bit RV32E = 0, - parameter bit RV32M = 0 + parameter bit DbgTriggerEn = 0, + parameter bit DataIndTiming = 1'b0, + parameter bit DummyInstructions = 1'b0, + parameter bit ICache = 1'b0, + parameter int unsigned MHPMCounterNum = 10, + parameter int unsigned MHPMCounterWidth = 40, + parameter bit PMPEnable = 0, + parameter int unsigned PMPGranularity = 0, + parameter int unsigned PMPNumRegions = 4, + parameter bit RV32E = 0, + parameter bit RV32M = 0 ) ( // Clock and Reset input logic clk_i, @@ -81,6 +82,10 @@ module ibex_cs_registers #( // CPU control bits output logic data_ind_timing_o, + output logic dummy_instr_en_o, + output logic [2:0] dummy_instr_mask_o, + output logic dummy_instr_seed_en_o, + output logic [31:0] dummy_instr_seed_o, output logic icache_enable_o, // Exception save/restore @@ -160,7 +165,9 @@ module ibex_cs_registers #( // CPU control register fields typedef struct packed { - logic [31:2] unused_ctrl; + logic [31:6] unused_ctrl; + logic [2:0] dummy_instr_mask; + logic dummy_instr_en; logic data_ind_timing; logic icache_enable; } CpuCtrl_t; @@ -420,6 +427,11 @@ module ibex_cs_registers #( csr_rdata_int = {cpuctrl_rdata}; end + // Custom CSR for LFSR re-seeding (cannot be read) + CSR_SECURESEED: begin + csr_rdata_int = '0; + end + default: begin illegal_csr = 1'b1; end @@ -1068,6 +1080,50 @@ module ibex_cs_registers #( assign data_ind_timing_o = cpuctrl_rdata.data_ind_timing; + // Generate dummy instruction signals + if (DummyInstructions) begin : gen_dummy + logic dummy_instr_en_d, dummy_instr_en_q; + logic [2:0] dummy_instr_mask_d, dummy_instr_mask_q; + + assign dummy_instr_en_d = (csr_we_int && (csr_addr == CSR_CPUCTRL)) ? + cpuctrl_wdata.dummy_instr_en : dummy_instr_en_q; + + assign dummy_instr_mask_d = (csr_we_int && (csr_addr == CSR_CPUCTRL)) ? + cpuctrl_wdata.dummy_instr_mask : dummy_instr_mask_q; + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + dummy_instr_en_q <= 1'b0; // disabled on reset + dummy_instr_mask_q <= 3'b000; + end else begin + dummy_instr_en_q <= dummy_instr_en_d; + dummy_instr_mask_q <= dummy_instr_mask_d; + end + end + + assign cpuctrl_rdata.dummy_instr_en = dummy_instr_en_q; + assign cpuctrl_rdata.dummy_instr_mask = dummy_instr_mask_q; + // Signal a write to the seed register + assign dummy_instr_seed_en_o = csr_we_int && (csr_addr == CSR_SECURESEED); + assign dummy_instr_seed_o = csr_wdata_int; + + end else begin : gen_no_dummy + // tieoff for the unused bit + logic unused_dummy_en; + logic [2:0] unused_dummy_mask; + assign unused_dummy_en = cpuctrl_wdata.dummy_instr_en; + assign unused_dummy_mask = cpuctrl_wdata.dummy_instr_mask; + + // field will always read as zero if not configured + assign cpuctrl_rdata.dummy_instr_en = 1'b0; + assign cpuctrl_rdata.dummy_instr_mask = 3'b000; + assign dummy_instr_seed_en_o = 1'b0; + assign dummy_instr_seed_o = '0; + end + + assign dummy_instr_en_o = cpuctrl_rdata.dummy_instr_en; + assign dummy_instr_mask_o = cpuctrl_rdata.dummy_instr_mask; + // Generate icache enable bit if (ICache) begin : gen_icache_enable logic icache_enable_d, icache_enable_q; @@ -1098,8 +1154,8 @@ module ibex_cs_registers #( 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]}; + logic [31:6] unused_cpuctrl; + assign unused_cpuctrl = {cpuctrl_wdata[31:6]}; //////////////// diff --git a/rtl/ibex_dummy_instr.sv b/rtl/ibex_dummy_instr.sv new file mode 100644 index 00000000..5d606a3f --- /dev/null +++ b/rtl/ibex_dummy_instr.sv @@ -0,0 +1,132 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 + +/** + * Dummy instruction module + * + * Provides pseudo-randomly inserted fake instructions for secure code obfuscation + */ + +module ibex_dummy_instr ( + // Clock and reset + input logic clk_i, + input logic rst_ni, + + // Interface to CSRs + input logic dummy_instr_en_i, + input logic [2:0] dummy_instr_mask_i, + input logic dummy_instr_seed_en_i, + input logic [31:0] dummy_instr_seed_i, + + // Interface to IF stage + input logic fetch_valid_i, + input logic id_in_ready_i, + output logic insert_dummy_instr_o, + output logic [31:0] dummy_instr_data_o +); + + localparam int unsigned TIMEOUT_CNT_W = 5; + localparam int unsigned OP_W = 5; + + typedef enum logic [1:0] { + DUMMY_ADD = 2'b00, + DUMMY_MUL = 2'b01, + DUMMY_DIV = 2'b10, + DUMMY_AND = 2'b11 + } dummy_instr_e; + + typedef struct packed { + dummy_instr_e instr_type; + logic [OP_W-1:0] op_b; + logic [OP_W-1:0] op_a; + logic [TIMEOUT_CNT_W-1:0] cnt; + } lfsr_data_t; + localparam int unsigned LFSR_OUT_W = $bits(lfsr_data_t); + + lfsr_data_t lfsr_data; + logic [TIMEOUT_CNT_W-1:0] dummy_cnt_incr, dummy_cnt_threshold; + logic [TIMEOUT_CNT_W-1:0] dummy_cnt_d, dummy_cnt_q; + logic dummy_cnt_en; + logic lfsr_en; + logic [LFSR_OUT_W-1:0] lfsr_state; + logic insert_dummy_instr; + logic [6:0] dummy_set; + logic [2:0] dummy_opcode; + logic [31:0] dummy_instr; + + // Shift the LFSR every time we insert an instruction + assign lfsr_en = insert_dummy_instr & id_in_ready_i; + + prim_lfsr #( + .LfsrDw ( 32 ), + .StateOutDw ( LFSR_OUT_W ) + ) lfsr_i ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .seed_en_i ( dummy_instr_seed_en_i ), + .seed_i ( dummy_instr_seed_i ), + .lfsr_en_i ( lfsr_en ), + .entropy_i ( '0 ), + .state_o ( lfsr_state ) + ); + + // Extract fields from LFSR + assign lfsr_data = lfsr_data_t'(lfsr_state); + + // Set count threshold for inserting a new instruction. This is the pseudo-random value from the + // LFSR with a mask applied (based on CSR config data) to shorten the period if required. + assign dummy_cnt_threshold = lfsr_data.cnt & {dummy_instr_mask_i,{TIMEOUT_CNT_W-3{1'b1}}}; + assign dummy_cnt_incr = dummy_cnt_q + {{TIMEOUT_CNT_W-1{1'b0}},1'b1}; + // Clear the counter everytime a new instruction is inserted + assign dummy_cnt_d = insert_dummy_instr ? '0 : dummy_cnt_incr; + // Increment the counter for each executed instruction while dummy instuctions are + // enabled. + assign dummy_cnt_en = dummy_instr_en_i & id_in_ready_i & + (fetch_valid_i | insert_dummy_instr); + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + dummy_cnt_q <= '0; + end else if (dummy_cnt_en) begin + dummy_cnt_q <= dummy_cnt_d; + end + end + + // Insert a dummy instruction each time the counter hits the threshold + assign insert_dummy_instr = dummy_instr_en_i & (dummy_cnt_q == dummy_cnt_threshold); + + // Encode instruction + always_comb begin + unique case (lfsr_data.instr_type) + DUMMY_ADD : begin + dummy_set = 7'b0000000; + dummy_opcode = 3'b000; + end + DUMMY_MUL : begin + dummy_set = 7'b0000001; + dummy_opcode = 3'b000; + end + DUMMY_DIV : begin + dummy_set = 7'b0000001; + dummy_opcode = 3'b100; + end + DUMMY_AND : begin + dummy_set = 7'b0000000; + dummy_opcode = 3'b111; + end + default : begin + dummy_set = 7'b0000000; + dummy_opcode = 3'b000; + end + endcase + end + + // SET RS2 RS1 OP RD + assign dummy_instr = {dummy_set,lfsr_data.op_b,lfsr_data.op_a,dummy_opcode,5'h00,7'h33}; + + // Assign outputs + assign insert_dummy_instr_o = insert_dummy_instr; + assign dummy_instr_data_o = dummy_instr; + +endmodule diff --git a/rtl/ibex_if_stage.sv b/rtl/ibex_if_stage.sv index fa7a76a5..9dba640e 100644 --- a/rtl/ibex_if_stage.sv +++ b/rtl/ibex_if_stage.sv @@ -13,10 +13,11 @@ `include "prim_assert.sv" module ibex_if_stage #( - parameter int unsigned DmHaltAddr = 32'h1A110800, - parameter int unsigned DmExceptionAddr = 32'h1A110808, - parameter bit ICache = 1'b0, - parameter bit ICacheECC = 1'b0 + parameter int unsigned DmHaltAddr = 32'h1A110800, + parameter int unsigned DmExceptionAddr = 32'h1A110808, + parameter bit DummyInstructions = 1'b0, + parameter bit ICache = 1'b0, + parameter bit ICacheECC = 1'b0 ) ( input logic clk_i, input logic rst_ni, @@ -48,6 +49,7 @@ module ibex_if_stage #( output logic instr_fetch_err_plus2_o, // bus error misaligned output logic illegal_c_insn_id_o, // compressed decoder thinks this // is an invalid instr + output logic dummy_instr_id_o, // Instruction is a dummy output logic [31:0] pc_if_o, output logic [31:0] pc_id_o, @@ -58,6 +60,10 @@ module ibex_if_stage #( input ibex_pkg::exc_pc_sel_e exc_pc_mux_i, // selects ISR address input ibex_pkg::exc_cause_e exc_cause, // selects ISR address for // vectorized interrupt lines + input logic dummy_instr_en_i, + input logic [2:0] dummy_instr_mask_i, + input logic dummy_instr_seed_en_i, + input logic [31:0] dummy_instr_seed_i, input logic icache_enable_i, input logic icache_inval_i, @@ -103,6 +109,14 @@ module ibex_if_stage #( logic if_id_pipe_reg_we; // IF-ID pipeline reg write enable + // Dummy instruction signals + logic fetch_valid_out; + logic stall_dummy_instr; + logic [31:0] instr_out; + logic instr_is_compressed_out; + logic illegal_c_instr_out; + logic instr_err_out; + logic [7:0] unused_boot_addr; logic [7:0] unused_csr_mtvec; @@ -206,7 +220,7 @@ module ibex_if_stage #( end assign branch_req = pc_set_i; - assign fetch_ready = id_in_ready_i; + assign fetch_ready = id_in_ready_i & ~stall_dummy_instr; assign pc_if_o = fetch_addr; assign if_busy_o = prefetch_busy; @@ -218,24 +232,82 @@ module ibex_if_stage #( // to ease timing closure logic [31:0] instr_decompressed; logic illegal_c_insn; - logic instr_is_compressed_int; + logic instr_is_compressed; ibex_compressed_decoder compressed_decoder_i ( - .clk_i ( clk_i ), - .rst_ni ( rst_ni ), - .valid_i ( fetch_valid ), - .instr_i ( fetch_rdata ), - .instr_o ( instr_decompressed ), - .is_compressed_o ( instr_is_compressed_int ), - .illegal_instr_o ( illegal_c_insn ) + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .valid_i ( fetch_valid ), + .instr_i ( fetch_rdata ), + .instr_o ( instr_decompressed ), + .is_compressed_o ( instr_is_compressed ), + .illegal_instr_o ( illegal_c_insn ) ); + // Dummy instruction insertion + if (DummyInstructions) begin : gen_dummy_instr + logic insert_dummy_instr; + logic [31:0] dummy_instr_data; + + ibex_dummy_instr dummy_instr_i ( + .clk_i ( clk_i ), + .rst_ni ( rst_ni ), + .dummy_instr_en_i ( dummy_instr_en_i ), + .dummy_instr_mask_i ( dummy_instr_mask_i ), + .dummy_instr_seed_en_i ( dummy_instr_seed_en_i ), + .dummy_instr_seed_i ( dummy_instr_seed_i ), + .fetch_valid_i ( fetch_valid ), + .id_in_ready_i ( id_in_ready_i ), + .insert_dummy_instr_o ( insert_dummy_instr ), + .dummy_instr_data_o ( dummy_instr_data ) + ); + + // Mux between actual instructions and dummy instructions + assign fetch_valid_out = insert_dummy_instr | fetch_valid; + assign instr_out = insert_dummy_instr ? dummy_instr_data : instr_decompressed; + assign instr_is_compressed_out = insert_dummy_instr ? 1'b0 : instr_is_compressed; + assign illegal_c_instr_out = insert_dummy_instr ? 1'b0 : illegal_c_insn; + assign instr_err_out = insert_dummy_instr ? 1'b0 : fetch_err; + + // Stall the IF stage if we insert a dummy instruction. The dummy will execute between whatever + // is currently in the ID stage and whatever is valid from the prefetch buffer this cycle. The + // PC of the dummy instruction will match whatever is next from the prefetch buffer. + assign stall_dummy_instr = insert_dummy_instr; + + // Register the dummy instruction indication into the ID stage + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + dummy_instr_id_o <= 1'b0; + end else if (if_id_pipe_reg_we) begin + dummy_instr_id_o <= insert_dummy_instr; + end + end + + end else begin : gen_no_dummy_instr + logic unused_dummy_en; + logic [2:0] unused_dummy_mask; + logic unused_dummy_seed_en; + logic [31:0] unused_dummy_seed; + + assign unused_dummy_en = dummy_instr_en_i; + assign unused_dummy_mask = dummy_instr_mask_i; + assign unused_dummy_seed_en = dummy_instr_seed_en_i; + assign unused_dummy_seed = dummy_instr_seed_i; + assign fetch_valid_out = fetch_valid; + assign instr_out = instr_decompressed; + assign instr_is_compressed_out = instr_is_compressed; + assign illegal_c_instr_out = illegal_c_insn; + assign instr_err_out = fetch_err; + assign stall_dummy_instr = 1'b0; + assign dummy_instr_id_o = 1'b0; + end + // The ID stage becomes valid as soon as any instruction is registered in the ID stage flops. // Note that the current instruction is squashed by the incoming pc_set_i signal. // Valid is held until it is explicitly cleared (due to an instruction completing or an exception) - assign instr_valid_id_d = (fetch_valid & id_in_ready_i & ~pc_set_i) | + assign instr_valid_id_d = (fetch_valid_out & id_in_ready_i & ~pc_set_i) | (instr_valid_id_q & ~instr_valid_clear_i); - assign instr_new_id_d = fetch_valid & id_in_ready_i; + assign instr_new_id_d = fetch_valid_out & id_in_ready_i; always_ff @(posedge clk_i or negedge rst_ni) begin if (!rst_ni) begin @@ -256,14 +328,14 @@ module ibex_if_stage #( always_ff @(posedge clk_i) begin if (if_id_pipe_reg_we) begin - instr_rdata_id_o <= instr_decompressed; + instr_rdata_id_o <= instr_out; // To reduce fan-out and help timing from the instr_rdata_id flops they are replicated. - instr_rdata_alu_id_o <= instr_decompressed; - instr_fetch_err_o <= fetch_err; + instr_rdata_alu_id_o <= instr_out; + instr_fetch_err_o <= instr_err_out; instr_fetch_err_plus2_o <= fetch_err_plus2; instr_rdata_c_id_o <= fetch_rdata[15:0]; - instr_is_compressed_id_o <= instr_is_compressed_int; - illegal_c_insn_id_o <= illegal_c_insn; + instr_is_compressed_id_o <= instr_is_compressed_out; + illegal_c_insn_id_o <= illegal_c_instr_out; pc_id_o <= pc_if_o; end end diff --git a/rtl/ibex_pkg.sv b/rtl/ibex_pkg.sv index c9873851..1bd61098 100644 --- a/rtl/ibex_pkg.sv +++ b/rtl/ibex_pkg.sv @@ -448,7 +448,8 @@ typedef enum logic[11:0] { CSR_MHPMCOUNTER29H = 12'hB9D, CSR_MHPMCOUNTER30H = 12'hB9E, CSR_MHPMCOUNTER31H = 12'hB9F, - CSR_CPUCTRL = 12'h7C0 + CSR_CPUCTRL = 12'h7C0, + CSR_SECURESEED = 12'h7C1 } csr_num_e; // CSR pmp-related offsets diff --git a/rtl/ibex_register_file_ff.sv b/rtl/ibex_register_file_ff.sv index 4dcfaeb0..715a97c8 100644 --- a/rtl/ibex_register_file_ff.sv +++ b/rtl/ibex_register_file_ff.sv @@ -11,14 +11,16 @@ * targeting FPGA synthesis or Verilator simulation. */ module ibex_register_file #( - parameter bit RV32E = 0, - parameter int unsigned DataWidth = 32 + parameter bit RV32E = 0, + parameter int unsigned DataWidth = 32, + parameter bit DummyInstructions = 0 ) ( // Clock and Reset input logic clk_i, input logic rst_ni, input logic test_en_i, + input logic dummy_instr_id_i, //Read port R1 input logic [4:0] raddr_a_i, @@ -60,8 +62,34 @@ module ibex_register_file #( end end - // R0 is nil - assign rf_reg[0] = '0; + // With dummy instructions enabled, R0 behaves as a real register but will always return 0 for + // real instructions. + if (DummyInstructions) begin : g_dummy_r0 + logic we_r0_dummy; + logic [31:0] rf_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_id_i; + + always_ff @(posedge clk_i or negedge rst_ni) begin + if (!rst_ni) begin + rf_r0 <= '0; + end else if (we_r0_dummy) begin + rf_r0 <= wdata_a_i; + end + end + + // Output the dummy data for dummy instructions, otherwise R0 reads as zero + assign rf_reg[0] = dummy_instr_id_i ? rf_r0 : '0; + + end else begin : g_normal_r0 + logic unused_dummy_instr_id; + assign unused_dummy_instr_id = dummy_instr_id_i; + + // R0 is nil + assign rf_reg[0] = '0; + end + assign rf_reg[NUM_WORDS-1:1] = rf_reg_tmp[NUM_WORDS-1:1]; assign rdata_a_o = rf_reg[raddr_a_i]; diff --git a/rtl/ibex_register_file_fpga.sv b/rtl/ibex_register_file_fpga.sv index 13ba74a3..aa88cdc4 100644 --- a/rtl/ibex_register_file_fpga.sv +++ b/rtl/ibex_register_file_fpga.sv @@ -12,14 +12,16 @@ * FPGA architectures, it will produce RAM32M primitives. Other vendors have not yet been tested. */ module ibex_register_file #( - parameter bit RV32E = 0, - parameter int unsigned DataWidth = 32 + parameter bit RV32E = 0, + parameter int unsigned DataWidth = 32, + parameter bit DummyInstructions = 0 ) ( // Clock and Reset input logic clk_i, input logic rst_ni, input logic test_en_i, + input logic dummy_instr_id_i, //Read port R1 input logic [ 4:0] raddr_a_i, @@ -54,4 +56,12 @@ module ibex_register_file #( end end : sync_write + // 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; + endmodule : ibex_register_file diff --git a/rtl/ibex_register_file_latch.sv b/rtl/ibex_register_file_latch.sv index a5ec5503..5b99761b 100644 --- a/rtl/ibex_register_file_latch.sv +++ b/rtl/ibex_register_file_latch.sv @@ -12,14 +12,16 @@ * register file when targeting ASIC synthesis or event-based simulators. */ module ibex_register_file #( - parameter bit RV32E = 0, - parameter int unsigned DataWidth = 32 + parameter bit RV32E = 0, + parameter int unsigned DataWidth = 32, + parameter bit DummyInstructions = 0 ) ( // Clock and Reset input logic clk_i, input logic rst_ni, input logic test_en_i, + input logic dummy_instr_id_i, //Read port R1 input logic [4:0] raddr_a_i, @@ -87,7 +89,7 @@ module ibex_register_file #( // Write address decoding always_comb begin : wad for (int i = 1; i < NUM_WORDS; i++) begin : wad_word_iter - if (we_a_i && (waddr_a_int == i)) begin + 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; @@ -109,7 +111,6 @@ module ibex_register_file #( // Generate the sequential process for the NUM_WORDS words of the memory. // The process is synchronized with the clocks mem_clocks[k], k = 1, ..., NUM_WORDS-1. always_latch begin : latch_wdata - mem[0] = '0; for (int k = 1; k < NUM_WORDS; k++) begin : latch_wdata_word_iter if (mem_clocks[k]) begin mem[k] = wdata_a_q; @@ -117,6 +118,40 @@ module ibex_register_file #( 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 + logic we_r0_dummy; + logic r0_clock; + logic [31: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_id_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 : '0; + + end else begin : g_normal_r0 + logic unused_dummy_instr_id; + assign unused_dummy_instr_id = dummy_instr_id_i; + + assign mem[0] = '0; + end + `ifdef VERILATOR initial begin $display("Latch-based register file not supported for Verilator simulation"); diff --git a/shared/prim_lfsr.core b/shared/prim_lfsr.core new file mode 100644 index 00000000..c552f4ae --- /dev/null +++ b/shared/prim_lfsr.core @@ -0,0 +1,17 @@ +CAPI=2: +# Copyright lowRISC contributors. +# Licensed under the Apache License, Version 2.0, see LICENSE for details. +# SPDX-License-Identifier: Apache-2.0 + +name: "lowrisc:prim:lfsr:0.1" +description: "LFSR primitive" +filesets: + files_rtl: + files: + - rtl/prim_lfsr.sv + file_type: systemVerilogSource + +targets: + default: + filesets: + - files_rtl diff --git a/shared/rtl/prim_lfsr.sv b/shared/rtl/prim_lfsr.sv new file mode 100644 index 00000000..3c162bee --- /dev/null +++ b/shared/rtl/prim_lfsr.sv @@ -0,0 +1,485 @@ +// Copyright lowRISC contributors. +// Licensed under the Apache License, Version 2.0, see LICENSE for details. +// SPDX-License-Identifier: Apache-2.0 +// +// This module implements different LFSR types: +// +// 0) Galois XOR type LFSR ([1], internal XOR gates, very fast). +// Parameterizable width from 4 to 64 bits. +// Coefficients obtained from [2]. +// +// 1) Fibonacci XNOR type LFSR, parameterizable from 3 to 168 bits. +// Coefficients obtained from [3]. +// +// All flavors have an additional entropy input and lockup protection, which +// reseeds the state once it has accidentally fallen into the all-zero (XOR) or +// all-one (XNOR) state. Further, an external seed can be loaded into the LFSR +// state at runtime. If that seed is all-zero (XOR case) or all-one (XNOR case), +// the state will be reseeded in the next cycle using the lockup protection mechanism. +// Note that the external seed input takes precedence over internal state updates. +// +// All polynomials up to 34 bit in length have been verified in simulation. +// +// Refs: [1] https://en.wikipedia.org/wiki/Linear-feedback_shift_register +// [2] https://users.ece.cmu.edu/~koopman/lfsr/ +// [3] https://www.xilinx.com/support/documentation/application_notes/xapp052.pdf + +`include "prim_assert.sv" + +module prim_lfsr #( + // Lfsr Type, can be FIB_XNOR or GAL_XOR + parameter LfsrType = "GAL_XOR", + // Lfsr width + parameter int unsigned LfsrDw = 32, + // Width of the entropy input to be XOR'd into state (lfsr_q[EntropyDw-1:0]) + parameter int unsigned EntropyDw = 8, + // Width of output tap (from lfsr_q[StateOutDw-1:0]) + parameter int unsigned StateOutDw = 8, + // Lfsr reset state, must be nonzero! + parameter logic [LfsrDw-1:0] DefaultSeed = LfsrDw'(1), + // Custom polynomial coeffs + parameter logic [LfsrDw-1:0] CustomCoeffs = '0, + // Enable this for DV, disable this for long LFSRs in FPV + parameter bit MaxLenSVA = 1'b1, + // Can be disabled in cases where seed and entropy + // inputs are unused in order to not distort coverage + // (the SVA will be unreachable in such cases) + parameter bit LockupSVA = 1'b1, + parameter bit ExtSeedSVA = 1'b1 +) ( + input clk_i, + input rst_ni, + input seed_en_i, // load external seed into the state (takes precedence) + input [LfsrDw-1:0] seed_i, // external seed input + input lfsr_en_i, // enables the LFSR + input [EntropyDw-1:0] entropy_i, // additional entropy to be XOR'ed into the state + output logic [StateOutDw-1:0] state_o // (partial) LFSR state output +); + + // automatically generated with get-lfsr-coeffs.py script + localparam int unsigned GAL_XOR_LUT_OFF = 4; + localparam logic [63:0] GAL_XOR_COEFFS [61] = + '{ 64'h9, + 64'h12, + 64'h21, + 64'h41, + 64'h8E, + 64'h108, + 64'h204, + 64'h402, + 64'h829, + 64'h100D, + 64'h2015, + 64'h4001, + 64'h8016, + 64'h10004, + 64'h20013, + 64'h40013, + 64'h80004, + 64'h100002, + 64'h200001, + 64'h400010, + 64'h80000D, + 64'h1000004, + 64'h2000023, + 64'h4000013, + 64'h8000004, + 64'h10000002, + 64'h20000029, + 64'h40000004, + 64'h80000057, + 64'h100000029, + 64'h200000073, + 64'h400000002, + 64'h80000003B, + 64'h100000001F, + 64'h2000000031, + 64'h4000000008, + 64'h800000001C, + 64'h10000000004, + 64'h2000000001F, + 64'h4000000002C, + 64'h80000000032, + 64'h10000000000D, + 64'h200000000097, + 64'h400000000010, + 64'h80000000005B, + 64'h1000000000038, + 64'h200000000000E, + 64'h4000000000025, + 64'h8000000000004, + 64'h10000000000023, + 64'h2000000000003E, + 64'h40000000000023, + 64'h8000000000004A, + 64'h100000000000016, + 64'h200000000000031, + 64'h40000000000003D, + 64'h800000000000001, + 64'h1000000000000013, + 64'h2000000000000034, + 64'h4000000000000001, + 64'h800000000000000D }; + + // automatically generated with get-lfsr-coeffs.py script + localparam int unsigned FIB_XNOR_LUT_OFF = 3; + localparam logic [167:0] FIB_XNOR_COEFFS [166] = + '{ 168'h6, + 168'hC, + 168'h14, + 168'h30, + 168'h60, + 168'hB8, + 168'h110, + 168'h240, + 168'h500, + 168'h829, + 168'h100D, + 168'h2015, + 168'h6000, + 168'hD008, + 168'h12000, + 168'h20400, + 168'h40023, + 168'h90000, + 168'h140000, + 168'h300000, + 168'h420000, + 168'hE10000, + 168'h1200000, + 168'h2000023, + 168'h4000013, + 168'h9000000, + 168'h14000000, + 168'h20000029, + 168'h48000000, + 168'h80200003, + 168'h100080000, + 168'h204000003, + 168'h500000000, + 168'h801000000, + 168'h100000001F, + 168'h2000000031, + 168'h4400000000, + 168'hA000140000, + 168'h12000000000, + 168'h300000C0000, + 168'h63000000000, + 168'hC0000030000, + 168'h1B0000000000, + 168'h300003000000, + 168'h420000000000, + 168'hC00000180000, + 168'h1008000000000, + 168'h3000000C00000, + 168'h6000C00000000, + 168'h9000000000000, + 168'h18003000000000, + 168'h30000000030000, + 168'h40000040000000, + 168'hC0000600000000, + 168'h102000000000000, + 168'h200004000000000, + 168'h600003000000000, + 168'hC00000000000000, + 168'h1800300000000000, + 168'h3000000000000030, + 168'h6000000000000000, + 168'hD800000000000000, + 168'h10000400000000000, + 168'h30180000000000000, + 168'h60300000000000000, + 168'h80400000000000000, + 168'h140000028000000000, + 168'h300060000000000000, + 168'h410000000000000000, + 168'h820000000001040000, + 168'h1000000800000000000, + 168'h3000600000000000000, + 168'h6018000000000000000, + 168'hC000000018000000000, + 168'h18000000600000000000, + 168'h30000600000000000000, + 168'h40200000000000000000, + 168'hC0000000060000000000, + 168'h110000000000000000000, + 168'h240000000480000000000, + 168'h600000000003000000000, + 168'h800400000000000000000, + 168'h1800000300000000000000, + 168'h3003000000000000000000, + 168'h4002000000000000000000, + 168'hC000000000000000018000, + 168'h10000000004000000000000, + 168'h30000C00000000000000000, + 168'h600000000000000000000C0, + 168'hC00C0000000000000000000, + 168'h140000000000000000000000, + 168'h200001000000000000000000, + 168'h400800000000000000000000, + 168'hA00000000001400000000000, + 168'h1040000000000000000000000, + 168'h2004000000000000000000000, + 168'h5000000000028000000000000, + 168'h8000000004000000000000000, + 168'h18600000000000000000000000, + 168'h30000000000000000C00000000, + 168'h40200000000000000000000000, + 168'hC0300000000000000000000000, + 168'h100010000000000000000000000, + 168'h200040000000000000000000000, + 168'h5000000000000000A0000000000, + 168'h800000010000000000000000000, + 168'h1860000000000000000000000000, + 168'h3003000000000000000000000000, + 168'h4010000000000000000000000000, + 168'hA000000000140000000000000000, + 168'h10080000000000000000000000000, + 168'h30000000000000000000180000000, + 168'h60018000000000000000000000000, + 168'hC0000000000000000300000000000, + 168'h140005000000000000000000000000, + 168'h200000001000000000000000000000, + 168'h404000000000000000000000000000, + 168'h810000000000000000000000000102, + 168'h1000040000000000000000000000000, + 168'h3000000000000006000000000000000, + 168'h5000000000000000000000000000000, + 168'h8000000004000000000000000000000, + 168'h18000000000000000000000000030000, + 168'h30000000030000000000000000000000, + 168'h60000000000000000000000000000000, + 168'hA0000014000000000000000000000000, + 168'h108000000000000000000000000000000, + 168'h240000000000000000000000000000000, + 168'h600000000000C00000000000000000000, + 168'h800000040000000000000000000000000, + 168'h1800000000000300000000000000000000, + 168'h2000000000000010000000000000000000, + 168'h4008000000000000000000000000000000, + 168'hC000000000000000000000000000000600, + 168'h10000080000000000000000000000000000, + 168'h30600000000000000000000000000000000, + 168'h4A400000000000000000000000000000000, + 168'h80000004000000000000000000000000000, + 168'h180000003000000000000000000000000000, + 168'h200001000000000000000000000000000000, + 168'h600006000000000000000000000000000000, + 168'hC00000000000000006000000000000000000, + 168'h1000000000000100000000000000000000000, + 168'h3000000000000006000000000000000000000, + 168'h6000000003000000000000000000000000000, + 168'h8000001000000000000000000000000000000, + 168'h1800000000000000000000000000C000000000, + 168'h20000000000001000000000000000000000000, + 168'h48000000000000000000000000000000000000, + 168'hC0000000000000006000000000000000000000, + 168'h180000000000000000000000000000000000000, + 168'h280000000000000000000000000000005000000, + 168'h60000000C000000000000000000000000000000, + 168'hC00000000000000000000000000018000000000, + 168'h1800000600000000000000000000000000000000, + 168'h3000000C00000000000000000000000000000000, + 168'h4000000080000000000000000000000000000000, + 168'hC000300000000000000000000000000000000000, + 168'h10000400000000000000000000000000000000000, + 168'h30000000000000000000006000000000000000000, + 168'h600000000000000C0000000000000000000000000, + 168'hC0060000000000000000000000000000000000000, + 168'h180000006000000000000000000000000000000000, + 168'h3000000000C0000000000000000000000000000000, + 168'h410000000000000000000000000000000000000000, + 168'hA00140000000000000000000000000000000000000 }; + + logic lockup; + logic [LfsrDw-1:0] lfsr_d, lfsr_q; + logic [LfsrDw-1:0] next_lfsr_state, coeffs; + + + //////////////// + // Galois XOR // + //////////////// + if (64'(LfsrType) == 64'("GAL_XOR")) begin : gen_gal_xor + + // if custom polynomial is provided + if (CustomCoeffs > 0) begin : gen_custom + assign coeffs = CustomCoeffs[LfsrDw-1:0]; + end else begin : gen_lut + assign coeffs = GAL_XOR_COEFFS[LfsrDw-GAL_XOR_LUT_OFF][LfsrDw-1:0]; + // check that the most significant bit of polynomial is 1 + `ASSERT_INIT(MinLfsrWidth_A, LfsrDw >= $low(GAL_XOR_COEFFS)+GAL_XOR_LUT_OFF) + `ASSERT_INIT(MaxLfsrWidth_A, LfsrDw <= $high(GAL_XOR_COEFFS)+GAL_XOR_LUT_OFF) + end + + // calculate next state using internal XOR feedback and entropy input + assign next_lfsr_state = LfsrDw'(entropy_i) ^ ({LfsrDw{lfsr_q[0]}} & coeffs) ^ (lfsr_q >> 1); + + // lockup condition is all-zero + assign lockup = ~(|lfsr_q); + + // check that seed is not all-zero + `ASSERT_INIT(DefaultSeedNzCheck_A, |DefaultSeed) + + + //////////////////// + // Fibonacci XNOR // + //////////////////// + end else if (64'(LfsrType) == "FIB_XNOR") begin : gen_fib_xnor + + // if custom polynomial is provided + if (CustomCoeffs > 0) begin : gen_custom + assign coeffs = CustomCoeffs[LfsrDw-1:0]; + end else begin : gen_lut + assign coeffs = FIB_XNOR_COEFFS[LfsrDw-FIB_XNOR_LUT_OFF][LfsrDw-1:0]; + // check that the most significant bit of polynomial is 1 + `ASSERT_INIT(MinLfsrWidth_A, LfsrDw >= $low(FIB_XNOR_COEFFS)+FIB_XNOR_LUT_OFF) + `ASSERT_INIT(MaxLfsrWidth_A, LfsrDw <= $high(FIB_XNOR_COEFFS)+FIB_XNOR_LUT_OFF) + end + + // calculate next state using external XNOR feedback and entropy input + assign next_lfsr_state = LfsrDw'(entropy_i) ^ {lfsr_q[LfsrDw-2:0], ~(^(lfsr_q & coeffs))}; + + // lockup condition is all-ones + assign lockup = &lfsr_q; + + // check that seed is not all-ones + `ASSERT_INIT(DefaultSeedNzCheck_A, !(&DefaultSeed)) + + + ///////////// + // Unknown // + ///////////// + end else begin : gen_unknown_type + `ASSERT_INIT(UnknownLfsrType_A, 0) + end + + + ////////////////// + // Shared logic // + ////////////////// + + assign lfsr_d = (seed_en_i) ? seed_i : + (lfsr_en_i && lockup) ? DefaultSeed : + (lfsr_en_i) ? next_lfsr_state : + lfsr_q; + + assign state_o = lfsr_q[StateOutDw-1:0]; + + always_ff @(posedge clk_i or negedge rst_ni) begin : p_reg + if (!rst_ni) begin + lfsr_q <= DefaultSeed; + end else begin + lfsr_q <= lfsr_d; + end + end + + + /////////////////////// + // shared assertions // + /////////////////////// + + `ASSERT_KNOWN(DataKnownO_A, state_o) + +// the code below is not meant to be synthesized, +// but it is intended to be used in simulation and FPV +`ifndef SYNTHESIS + function automatic logic[LfsrDw-1:0] compute_next_state(logic[LfsrDw-1:0] lfsrcoeffs, + logic[EntropyDw-1:0] entropy, + logic[LfsrDw-1:0] state); + logic state0; + + // Galois XOR + if (64'(LfsrType) == 64'("GAL_XOR")) begin + if (state == 0) begin + state = DefaultSeed; + end else begin + state0 = state[0]; + state = state >> 1; + if (state0) state ^= lfsrcoeffs; + state ^= LfsrDw'(entropy); + end + // Fibonacci XNOR + end else if (64'(LfsrType) == "FIB_XNOR") begin + if (&state) begin + state = DefaultSeed; + end else begin + state0 = ~(^(state & lfsrcoeffs)); + state = state << 1; + state[0] = state0; + state ^= LfsrDw'(entropy); + end + end else begin + $error("unknown lfsr type"); + end + + return state; + endfunction : compute_next_state + + // check whether next state is computed correctly + `ASSERT(NextStateCheck_A, lfsr_en_i && !seed_en_i |=> lfsr_q == + compute_next_state(coeffs, $past(entropy_i,1), $past(lfsr_q,1))) +`endif + + `ASSERT_INIT(InputWidth_A, LfsrDw >= EntropyDw) + `ASSERT_INIT(OutputWidth_A, LfsrDw >= StateOutDw) + + // MSB must be one in any case + `ASSERT(CoeffCheck_A, coeffs[LfsrDw-1]) + + // output check + `ASSERT_KNOWN(OutputKnown_A, state_o) + `ASSERT(OutputCheck_A, state_o == StateOutDw'(lfsr_q)) + + // if no external input changes the lfsr state, a lockup must not occur (by design) + //`ASSERT(NoLockups_A, (!entropy_i) && (!seed_en_i) |=> !lockup, clk_i, !rst_ni) + `ASSERT(NoLockups_A, lfsr_en_i && !entropy_i && !seed_en_i |=> !lockup) + + // this can be disabled if unused in order to not distort coverage + if (ExtSeedSVA) begin : gen_ext_seed_sva + // check that external seed is correctly loaded into the state + `ASSERT(ExtDefaultSeedInputCheck_A, seed_en_i |=> lfsr_q == $past(seed_i)) + end + + // if the external seed mechanism is not used, + // there is theoretically no way we end up in a lockup condition + // in order to not distort coverage, this SVA can be disabled in such cases + if (LockupSVA) begin : gen_lockup_mechanism_sva + // check that a stuck LFSR is correctly reseeded + `ASSERT(LfsrLockupCheck_A, lfsr_en_i && lockup && !seed_en_i |=> !lockup) + end + + if (MaxLenSVA) begin : gen_max_len_sva + +`ifndef SYNTHESIS + // the code below is a workaround to enable long sequences to be checked. + // some simulators do not support SVA sequences longer than 2**32-1. + logic [LfsrDw-1:0] cnt_d, cnt_q; + logic perturbed_d, perturbed_q; + logic [LfsrDw-1:0] cmp_val; + + assign cmp_val = {{(LfsrDw-1){1'b1}}, 1'b0}; // 2**LfsrDw-2 + assign cnt_d = (lfsr_en_i && lockup) ? '0 : + (lfsr_en_i && (cnt_q == cmp_val)) ? '0 : + (lfsr_en_i) ? cnt_q + 1'b1 : + cnt_q; + + assign perturbed_d = perturbed_q | (|entropy_i) | seed_en_i; + + always_ff @(posedge clk_i or negedge rst_ni) begin : p_max_len + if (!rst_ni) begin + cnt_q <= '0; + perturbed_q <= 1'b0; + end else begin + cnt_q <= cnt_d; + perturbed_q <= perturbed_d; + end + end +`endif + + `ASSERT(MaximalLengthCheck0_A, cnt_q == 0 |-> lfsr_q == DefaultSeed, + clk_i, !rst_ni || perturbed_q) + `ASSERT(MaximalLengthCheck1_A, cnt_q != 0 |-> lfsr_q != DefaultSeed, + clk_i, !rst_ni || perturbed_q) + end + +endmodule