[otbn] Added sparse FSM encoding

Signed-off-by: Vladimir Rozic <vrozic@lowrisc.org>
diff --git a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
index 2ba9f74..ec13a2f 100644
--- a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
+++ b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
@@ -110,6 +110,7 @@
     .illegal_bus_access_i        ( 1'b0                ),
     .lifecycle_escalation_i      ( 1'b0                ),
     .software_errs_fatal_i       ( 1'b0                ),
+    .otbn_scramble_state_error_i ( 1'b0                ),
 
     .sideload_key_shares_i       ( sideload_key_shares ),
     .sideload_key_shares_valid_i ( 2'b11               )
diff --git a/hw/ip/otbn/otbn.core b/hw/ip/otbn/otbn.core
index 3e9be37..cb571d9 100644
--- a/hw/ip/otbn/otbn.core
+++ b/hw/ip/otbn/otbn.core
@@ -15,6 +15,7 @@
       - lowrisc:prim:cipher_pkg
       - lowrisc:prim:mubi
       - lowrisc:prim:crc32
+      - lowrisc:prim:sparse_fsm
       - lowrisc:ip:keymgr_pkg
       - lowrisc:ip:edn_pkg
       - lowrisc:ip:otbn_pkg
diff --git a/hw/ip/otbn/rtl/otbn.sv b/hw/ip/otbn/rtl/otbn.sv
index 90d63fb..bafe5df 100644
--- a/hw/ip/otbn/rtl/otbn.sv
+++ b/hw/ip/otbn/rtl/otbn.sv
@@ -224,6 +224,8 @@
   logic                    otbn_dmem_scramble_valid;
   logic                    unused_otbn_dmem_scramble_key_seed_valid;
 
+  logic otbn_scramble_state_error;
+
   otbn_scramble_ctrl #(
     .RndCnstOtbnKey  (RndCnstOtbnKey),
     .RndCnstOtbnNonce(RndCnstOtbnNonce)
@@ -248,7 +250,9 @@
     .otbn_imem_scramble_key_seed_valid_o(unused_otbn_imem_scramble_key_seed_valid),
 
     .otbn_dmem_scramble_new_req_i(1'b0),
-    .otbn_imem_scramble_new_req_i(1'b0)
+    .otbn_imem_scramble_new_req_i(1'b0),
+
+    .state_error_o(otbn_scramble_state_error)
   );
 
   prim_ram_1p_scr #(
@@ -959,6 +963,8 @@
 
       .software_errs_fatal_i       (software_errs_fatal_q),
 
+      .otbn_scramble_state_error_i (otbn_scramble_state_error),
+
       .sideload_key_shares_i       (keymgr_key_i.key),
       .sideload_key_shares_valid_i ({2{keymgr_key_i.valid}})
     );
@@ -1015,6 +1021,8 @@
 
       .software_errs_fatal_i       (software_errs_fatal_q),
 
+      .otbn_scramble_state_error_i (otbn_scramble_state_error),
+
       .sideload_key_shares_i       (keymgr_key_i.key),
       .sideload_key_shares_valid_i ({2{keymgr_key_i.valid}})
     );
@@ -1051,4 +1059,12 @@
 
   // Constraint from package, check here as we cannot have `ASSERT_INIT in package
   `ASSERT_INIT(WsrESizeMatchesParameter_A, $bits(wsr_e) == WsrNumWidth)
+
+  `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(OtbnStartStopFsmCheck_A,
+    u_otbn_core.u_otbn_start_stop_control.u_state_regs, alert_tx_o[1])
+  `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(OtbnControllerFsmCheck_A,
+    u_otbn_core.u_otbn_controller.u_state_regs, alert_tx_o[1])
+  `ASSERT_PRIM_FSM_ERROR_TRIGGER_ALERT(OtbnScrambleCtrlFsmCheck_A,
+    u_otbn_scramble_ctrl.u_state_regs, alert_tx_o[1])
+
 endmodule
diff --git a/hw/ip/otbn/rtl/otbn_controller.sv b/hw/ip/otbn/rtl/otbn_controller.sv
index 5e2fd16..8eb1f41 100644
--- a/hw/ip/otbn/rtl/otbn_controller.sv
+++ b/hw/ip/otbn/rtl/otbn_controller.sv
@@ -138,6 +138,8 @@
   input  logic        illegal_bus_access_i,
   input  logic        lifecycle_escalation_i,
   input  logic        software_errs_fatal_i,
+  input  logic        start_stop_state_error_i,
+  input  logic        otbn_scramble_state_error_i,
 
   input logic [1:0] sideload_key_shares_valid_i,
 
@@ -157,6 +159,7 @@
   logic recoverable_err;
   logic done_complete;
   logic executing;
+  logic state_error;
 
   logic                     insn_fetch_req_valid_raw;
   logic [ImemAddrWidth-1:0] insn_fetch_req_addr_last;
@@ -304,7 +307,8 @@
     err_bits_en              = 1'b0;
     prefetch_en_o            = 1'b0;
 
-    // TODO: Harden state machine
+    state_error = 1'b0;
+
     unique case (state_q)
       OtbnStateHalt: begin
         if (start_i) begin
@@ -370,7 +374,9 @@
         insn_fetch_req_valid_raw = 1'b0;
         state_d                  = OtbnStateLocked;
       end
-      default: ;
+      default: begin
+        state_error = 1'b1;
+      end
     endcase
 
     // On any error immediately halt, either going to OtbnStateLocked or OtbnStateHalt depending on
@@ -427,7 +433,8 @@
   assign err_bits.fatal_software       = software_err & software_errs_fatal_i;
   assign err_bits.lifecycle_escalation = lifecycle_escalation_i;
   assign err_bits.illegal_bus_access   = illegal_bus_access_i;
-  assign err_bits.bad_internal_state   = 0;
+  assign err_bits.bad_internal_state   = start_stop_state_error_i | state_error |
+                                          otbn_scramble_state_error_i;
   assign err_bits.bus_intg_violation   = bus_intg_violation_i;
   assign err_bits.reg_intg_violation   = rf_base_rd_data_err_i | rf_bignum_rd_data_err_i;
   assign err_bits.dmem_intg_violation  = lsu_rdata_err_i;
@@ -459,6 +466,7 @@
   assign fatal_err = |{err_bits.fatal_software,
                        err_bits.lifecycle_escalation,
                        err_bits.illegal_bus_access,
+                       err_bits.bad_internal_state ,
                        err_bits.bus_intg_violation,
                        err_bits.reg_intg_violation,
                        err_bits.dmem_intg_violation,
@@ -511,13 +519,20 @@
   `ASSERT(NoStallOnBranch,
       insn_valid_i & insn_dec_shared_i.branch_insn |-> state_q != OtbnStateStall)
 
-  always_ff @(posedge clk_i or negedge rst_ni) begin
-    if (!rst_ni) begin
-      state_q <= OtbnStateHalt;
-    end else begin
-      state_q <= state_d;
-    end
-  end
+  // This primitive is used to place a size-only constraint on the
+  // flops in order to prevent FSM state encoding optimizations.
+  logic [StateControllerWidth-1:0] state_raw_q;
+  assign state_q = otbn_state_e'(state_raw_q);
+  prim_sparse_fsm_flop #(
+    .StateEnumT(otbn_state_e),
+    .Width(StateControllerWidth),
+    .ResetValue(StateControllerWidth'(OtbnStateHalt))
+  ) u_state_regs (
+    .clk_i,
+    .rst_ni,
+    .state_i ( state_d     ),
+    .state_o ( state_raw_q )
+  );
 
   assign insn_cnt_clear = state_reset_i | (state_q == OtbnStateLocked) | insn_cnt_clear_i;
 
diff --git a/hw/ip/otbn/rtl/otbn_core.sv b/hw/ip/otbn/rtl/otbn_core.sv
index 0f72f08..e7ec7f1 100644
--- a/hw/ip/otbn/rtl/otbn_core.sv
+++ b/hw/ip/otbn/rtl/otbn_core.sv
@@ -84,6 +84,9 @@
   // When set software errors become fatal errors.
   input logic software_errs_fatal_i,
 
+  // Indicates an invalid state of the scramble controller. Results in a fatal error.
+  input logic otbn_scramble_state_error_i,
+
   input logic [1:0]                       sideload_key_shares_valid_i,
   input logic [1:0][SideloadKeyWidth-1:0] sideload_key_shares_i
 );
@@ -214,6 +217,8 @@
   logic [ImemAddrWidth:0]   prefetch_loop_end_addr;
   logic [ImemAddrWidth-1:0] prefetch_loop_jump_addr;
 
+  logic start_stop_state_error;
+
   // Start stop control start OTBN execution when requested and deals with any pre start or post
   // stop actions.
   otbn_start_stop_control #(
@@ -245,7 +250,8 @@
     .sec_wipe_zero_o    (sec_wipe_zero),
 
     .ispr_init_o  (ispr_init),
-    .state_reset_o(state_reset)
+    .state_reset_o(state_reset),
+    .state_error_o(start_stop_state_error)
   );
 
   // Depending on its usage, the instruction address (program counter) is qualified by two valid
@@ -423,6 +429,8 @@
     .illegal_bus_access_i,
     .lifecycle_escalation_i,
     .software_errs_fatal_i,
+    .start_stop_state_error_i(start_stop_state_error),
+    .otbn_scramble_state_error_i,
 
     .sideload_key_shares_valid_i,
 
diff --git a/hw/ip/otbn/rtl/otbn_pkg.sv b/hw/ip/otbn/rtl/otbn_pkg.sv
index f9708a0..63d92ec 100644
--- a/hw/ip/otbn/rtl/otbn_pkg.sv
+++ b/hw/ip/otbn/rtl/otbn_pkg.sv
@@ -385,24 +385,90 @@
   } mac_bignum_operation_t;
 
   // States for controller state machine
-  typedef enum logic [2:0] {
-    OtbnStateHalt,
-    OtbnStateUrndRefresh,
-    OtbnStateRun,
-    OtbnStateStall,
-    OtbnStateLocked
+  // Encoding generated with:
+  // $ ./util/design/sparse-fsm-encode.py -d 3 -m 5 -n 6 \
+  //      -s 5799399942 --language=sv
+  //
+  // Hamming distance histogram:
+  //
+  //  0: --
+  //  1: --
+  //  2: --
+  //  3: |||||||||||||||||||| (50.00%)
+  //  4: |||||||||||||||| (40.00%)
+  //  5: |||| (10.00%)
+  //  6: --
+  //
+  // Minimum Hamming distance: 3
+  // Maximum Hamming distance: 5
+  // Minimum Hamming weight: 1
+  // Maximum Hamming weight: 4
+  //
+  localparam int StateControllerWidth = 6;
+  typedef enum logic [StateControllerWidth-1:0] {
+    OtbnStateHalt        = 6'b001000,
+    OtbnStateUrndRefresh = 6'b010100,
+    OtbnStateRun         = 6'b100101,
+    OtbnStateStall       = 6'b110011,
+    OtbnStateLocked      = 6'b001111
   } otbn_state_e;
 
-  typedef enum logic [2:0] {
-    OtbnStartStopStateHalt,
-    OtbnStartStopStateUrndRefresh,
-    OtbnStartStopStateRunning,
-    OtbnStartStopSecureWipeWdrUrnd,
-    OtbnStartStopSecureWipeAccModBaseUrnd,
-    OtbnStartStopSecureWipeAllZero,
-    OtbnStartStopSecureWipeComplete
+  // States for start_stop_controller
+  // Encoding generated with:
+  // $ ./util/design/sparse-fsm-encode.py -d 3 -m 7 -n 6 \
+  //      -s 5799399943 --language=sv
+  //
+  // Hamming distance histogram:
+  //
+  //  0: --
+  //  1: --
+  //  2: --
+  //  3: |||||||||||||||||||| (57.14%)
+  //  4: ||||||||||||||| (42.86%)
+  //  5: --
+  //  6: --
+  //
+  // Minimum Hamming distance: 3
+  // Maximum Hamming distance: 4
+  // Minimum Hamming weight: 1
+  // Maximum Hamming weight: 5
+  //
+  localparam int StateStartStopWidth = 6;
+  typedef enum logic [StateStartStopWidth-1:0] {
+    OtbnStartStopStateHalt                = 6'b011100,
+    OtbnStartStopStateUrndRefresh         = 6'b101111,
+    OtbnStartStopStateRunning             = 6'b010111,
+    OtbnStartStopSecureWipeWdrUrnd        = 6'b000010,
+    OtbnStartStopSecureWipeAccModBaseUrnd = 6'b110001,
+    OtbnStartStopSecureWipeAllZero        = 6'b001001,
+    OtbnStartStopSecureWipeComplete       = 6'b100100
   } otbn_start_stop_state_e;
 
+// Encoding generated with:
+// $ ./util/design/sparse-fsm-encode.py -d 3 -m 3 -n 5 \
+//      -s 5799399983 --language=sv
+//
+// Hamming distance histogram:
+//
+//  0: --
+//  1: --
+//  2: --
+//  3: |||||||||||||||||||| (66.67%)
+//  4: |||||||||| (33.33%)
+//  5: --
+//
+// Minimum Hamming distance: 3
+// Maximum Hamming distance: 4
+// Minimum Hamming weight: 1
+// Maximum Hamming weight: 3
+//
+localparam int StateScrambleCtrlWidth = 5;
+typedef enum logic [StateScrambleCtrlWidth-1:0] {
+  ScrambleCtrlIdle = 5'b01101,
+  ScrambleCtrlDmemReq = 5'b10000,
+  ScrambleCtrlImemReq = 5'b00110
+} scramble_ctrl_state_e;
+
   // URNG PRNG default seed.
   // These parameters have been generated with
   // $ ./util/design/gen-lfsr-seed.py --width 256 --seed 2840984437 --prefix "Urnd"
diff --git a/hw/ip/otbn/rtl/otbn_scramble_ctrl.sv b/hw/ip/otbn/rtl/otbn_scramble_ctrl.sv
index 79a9a1d..7f71ed0 100644
--- a/hw/ip/otbn/rtl/otbn_scramble_ctrl.sv
+++ b/hw/ip/otbn/rtl/otbn_scramble_ctrl.sv
@@ -38,15 +38,12 @@
   output logic                    otbn_imem_scramble_key_seed_valid_o,
 
   input  logic otbn_dmem_scramble_new_req_i,
-  input  logic otbn_imem_scramble_new_req_i
-);
-  typedef enum logic [1:0] {
-    ScrambleCtrlIdle,
-    ScrambleCtrlDmemReq,
-    ScrambleCtrlImemReq
-  } scramble_ctrl_state_t;
+  input  logic otbn_imem_scramble_new_req_i,
 
-  scramble_ctrl_state_t state_q, state_d;
+  output logic state_error_o
+);
+
+  scramble_ctrl_state_e state_q, state_d;
 
   logic dmem_key_valid_q, dmem_key_valid_d;
   logic imem_key_valid_q, imem_key_valid_d;
@@ -101,7 +98,6 @@
       imem_key_seed_valid_q       <= 1'b0;
       dmem_scramble_req_pending_q <= 1'b0;
       imem_scramble_req_pending_q <= 1'b0;
-      state_q                     <= ScrambleCtrlIdle;
     end else begin
       dmem_key_valid_q            <= dmem_key_valid_d;
       imem_key_valid_q            <= imem_key_valid_d;
@@ -109,10 +105,24 @@
       imem_key_seed_valid_q       <= imem_key_seed_valid_d;
       dmem_scramble_req_pending_q <= dmem_scramble_req_pending_d;
       imem_scramble_req_pending_q <= imem_scramble_req_pending_d;
-      state_q                     <= state_d;
     end
   end
 
+  // This primitive is used to place a size-only constraint on the
+  // flops in order to prevent FSM state encoding optimizations.
+  logic [StateScrambleCtrlWidth-1:0] state_raw_q;
+  assign state_q = scramble_ctrl_state_e'(state_raw_q);
+  prim_sparse_fsm_flop #(
+    .StateEnumT(scramble_ctrl_state_e),
+    .Width(StateScrambleCtrlWidth),
+    .ResetValue(StateScrambleCtrlWidth'(ScrambleCtrlIdle))
+  ) u_state_regs (
+    .clk_i,
+    .rst_ni,
+    .state_i ( state_d     ),
+    .state_o ( state_raw_q )
+  );
+
   always_comb begin
     dmem_key_valid_d            = dmem_key_valid_q;
     imem_key_valid_d            = imem_key_valid_q;
@@ -124,6 +134,7 @@
     imem_scramble_req_pending_d = imem_scramble_req_pending_q | otbn_imem_scramble_new_req_i;
     state_d                     = state_q;
     otp_key_req                 = 1'b0;
+    state_error_o               = 1'b0;
 
     unique case (state_q)
       ScrambleCtrlIdle: begin
@@ -154,7 +165,9 @@
           imem_key_seed_valid_d       = otp_key_seed_valid;
         end
       end
-      default: ;
+      default: begin
+        state_error_o = 1'b1;
+      end
     endcase
   end
 
diff --git a/hw/ip/otbn/rtl/otbn_start_stop_control.sv b/hw/ip/otbn/rtl/otbn_start_stop_control.sv
index 61d9f70..9ed4a75 100644
--- a/hw/ip/otbn/rtl/otbn_start_stop_control.sv
+++ b/hw/ip/otbn/rtl/otbn_start_stop_control.sv
@@ -54,20 +54,28 @@
   output logic sec_wipe_zero_o,
 
   output logic ispr_init_o,
-  output logic state_reset_o
+  output logic state_reset_o,
+  output logic state_error_o
 );
   otbn_start_stop_state_e state_q, state_d;
 
   logic addr_cnt_inc;
   logic [4:0] addr_cnt_q, addr_cnt_d;
 
-  always_ff @(posedge clk_i or negedge rst_ni) begin
-    if (!rst_ni) begin
-      state_q <= OtbnStartStopStateHalt;
-    end else begin
-      state_q <= state_d;
-    end
-  end
+  // This primitive is used to place a size-only constraint on the
+  // flops in order to prevent FSM state encoding optimizations.
+  logic [StateStartStopWidth-1:0] state_raw_q;
+  assign state_q = otbn_start_stop_state_e'(state_raw_q);
+  prim_sparse_fsm_flop #(
+    .StateEnumT(otbn_start_stop_state_e),
+    .Width(StateStartStopWidth),
+    .ResetValue(StateStartStopWidth'(OtbnStartStopStateHalt))
+  ) u_state_regs (
+    .clk_i,
+    .rst_ni,
+    .state_i ( state_d     ),
+    .state_o ( state_raw_q )
+  );
 
   always_comb begin
     urnd_reseed_req_o      = 1'b0;
@@ -84,6 +92,7 @@
     sec_wipe_zero_o        = 1'b0;
     addr_cnt_inc           = 1'b0;
     secure_wipe_running_o  = 1'b0;
+    state_error_o          = 1'b0;
 
     unique case (state_q)
       OtbnStartStopStateHalt: begin
@@ -155,7 +164,9 @@
         secure_wipe_running_o = 1'b1;
         state_d = OtbnStartStopStateHalt;
       end
-      default: ;
+      default: begin
+        state_error_o = 1'b1;
+      end
     endcase
   end