[otbn] Add instruction counter.

A 32-bit register is added to count instructions during the OTBN
procedure.

Signed-off-by: Vladimir Rozic <vrozic@lowrisc.org>
diff --git a/hw/ip/otbn/data/otbn.hjson b/hw/ip/otbn/data/otbn.hjson
index 9637400..bafcc5c 100644
--- a/hw/ip/otbn/data/otbn.hjson
+++ b/hw/ip/otbn/data/otbn.hjson
@@ -253,7 +253,22 @@
           desc: "Set on any ECC error in a register file"
         }
       ]
-    }
+    } // register : fatal_alert_cause
+    { name: "INSN_CNT",
+      desc: "Instruction Counter",
+      swaccess: "ro",
+      hwaccess: "hwo",
+      fields: [
+        { bits: "31:0",
+          name: "insn_cnt",
+          desc: '''
+            The number of instructions executed in the current or last OTBN run. 
+            Saturates at 2^32-1. The counter is reset to zero when a new operation
+            is started. Instructions triggering an error do not count as being executed.
+          '''
+        }
+      ]
+    } // register : insn_cnt
 
     // Give IMEM and DMEM 16 KiB address space, each, to allow for easy expansion
     // of the actual IMEM and DMEM sizes without changing the address map.
diff --git a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
index b3b6810..ec4e6c1 100644
--- a/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
+++ b/hw/ip/otbn/dv/verilator/otbn_top_sim.sv
@@ -89,7 +89,8 @@
 
     .edn_urnd_req_o  ( edn_urnd_req     ),
     .edn_urnd_ack_i  ( edn_urnd_ack     ),
-    .edn_urnd_data_i ( edn_urnd_data    )
+    .edn_urnd_data_i ( edn_urnd_data    ),
+    .insn_cnt_o      ()
   );
 
   // The top bits of IMEM rdata aren't currently used (they will eventually be used for integrity
diff --git a/hw/ip/otbn/rtl/otbn.sv b/hw/ip/otbn/rtl/otbn.sv
index e41955c..8e37e7d 100644
--- a/hw/ip/otbn/rtl/otbn.sv
+++ b/hw/ip/otbn/rtl/otbn.sv
@@ -520,6 +520,11 @@
   assign hw2reg.fatal_alert_cause.reg_error.de = 0;
   assign hw2reg.fatal_alert_cause.reg_error.d  = 0;
 
+  // INSN_CNT register
+  logic [31:0] insn_cnt;
+  assign hw2reg.insn_cnt.d = insn_cnt;
+  assign hw2reg.insn_cnt.de = 1'b1;
+
   // Alerts ====================================================================
 
   logic [NumAlerts-1:0] alert_test;
@@ -698,7 +703,9 @@
 
       .edn_urnd_req_o  (edn_urnd_req),
       .edn_urnd_ack_i  (edn_urnd_ack),
-      .edn_urnd_data_i (edn_urnd_data)
+      .edn_urnd_data_i (edn_urnd_data),
+
+      .insn_cnt_o      (insn_cnt)
     );
   `else
     otbn_core #(
@@ -741,7 +748,9 @@
 
       .edn_urnd_req_o  (edn_urnd_req),
       .edn_urnd_ack_i  (edn_urnd_ack),
-      .edn_urnd_data_i (edn_urnd_data)
+      .edn_urnd_data_i (edn_urnd_data),
+
+      .insn_cnt_o      (insn_cnt)
     );
   `endif
 
diff --git a/hw/ip/otbn/rtl/otbn_controller.sv b/hw/ip/otbn/rtl/otbn_controller.sv
index 29fc216..de7788c 100644
--- a/hw/ip/otbn/rtl/otbn_controller.sv
+++ b/hw/ip/otbn/rtl/otbn_controller.sv
@@ -121,7 +121,9 @@
 
   output logic rnd_req_o,
   output logic rnd_prefetch_req_o,
-  input  logic rnd_valid_i
+  input  logic rnd_valid_i,
+
+  output logic [31:0] insn_cnt_o
 );
   otbn_state_e state_q, state_d, state_raw;
 
@@ -201,6 +203,9 @@
 
   logic rf_a_indirect_err, rf_b_indirect_err, rf_d_indirect_err, rf_indirect_err;
 
+  logic insn_cnt_en;
+  logic [31:0] insn_cnt_d, insn_cnt_q;
+
   // Stall a cycle on loads to allow load data writeback to happen the following cycle. Stall not
   // required on stores as there is no response to deal with.
   // TODO: Possibility of error response on store? Probably still don't need to stall in that case
@@ -345,6 +350,18 @@
     end
   end
 
+  assign insn_cnt_d = start_i ? 32'd0 : (insn_cnt_q + 32'd1);
+  assign insn_cnt_en = (insn_executing & ~stall & (insn_cnt_q != 32'hffffffff)) | start_i;
+  assign insn_cnt_o = insn_cnt_q;
+
+  always_ff @(posedge clk_i or negedge rst_ni) begin
+    if (!rst_ni) begin
+      insn_cnt_q <= 32'd0;
+    end else if (insn_cnt_en) begin
+      insn_cnt_q <= insn_cnt_d;
+    end
+  end
+
   otbn_loop_controller #(
     .ImemAddrWidth(ImemAddrWidth)
   ) u_otbn_loop_controller (
diff --git a/hw/ip/otbn/rtl/otbn_core.sv b/hw/ip/otbn/rtl/otbn_core.sv
index 13ec3a0..cfc7ac7 100644
--- a/hw/ip/otbn/rtl/otbn_core.sv
+++ b/hw/ip/otbn/rtl/otbn_core.sv
@@ -64,7 +64,9 @@
 
   output logic                    edn_urnd_req_o,
   input  logic                    edn_urnd_ack_i,
-  input  logic [EdnDataWidth-1:0] edn_urnd_data_i
+  input  logic [EdnDataWidth-1:0] edn_urnd_data_i,
+
+  output logic [31:0]             insn_cnt_o
 );
   // Fetch request (the next instruction)
   logic [ImemAddrWidth-1:0] insn_fetch_req_addr;
@@ -164,6 +166,8 @@
   logic                     controller_start;
   logic [ImemAddrWidth-1:0] controller_start_addr;
 
+  logic [31:0] insn_cnt;
+
   // Start stop control start OTBN execution when requested and deals with any pre start or post
   // stop actions.
   otbn_start_stop_control #(
@@ -339,9 +343,13 @@
 
     .rnd_req_o          (rnd_req),
     .rnd_prefetch_req_o (rnd_prefetch_req),
-    .rnd_valid_i        (rnd_valid)
+    .rnd_valid_i        (rnd_valid),
+
+    .insn_cnt_o         (insn_cnt)
   );
 
+  assign insn_cnt_o = insn_cnt;
+
   // Load store unit: read and write data from data memory
   otbn_lsu u_otbn_lsu (
     .clk_i,
diff --git a/hw/ip/otbn/rtl/otbn_reg_pkg.sv b/hw/ip/otbn/rtl/otbn_reg_pkg.sv
index 7bbf78e..f44e2e7 100644
--- a/hw/ip/otbn/rtl/otbn_reg_pkg.sv
+++ b/hw/ip/otbn/rtl/otbn_reg_pkg.sv
@@ -112,6 +112,11 @@
     } reg_error;
   } otbn_hw2reg_fatal_alert_cause_reg_t;
 
+  typedef struct packed {
+    logic [31:0] d;
+    logic        de;
+  } otbn_hw2reg_insn_cnt_reg_t;
+
   // Register -> HW type
   typedef struct packed {
     otbn_reg2hw_intr_state_reg_t intr_state; // [41:41]
@@ -124,10 +129,11 @@
 
   // HW -> register type
   typedef struct packed {
-    otbn_hw2reg_intr_state_reg_t intr_state; // [26:25]
-    otbn_hw2reg_status_reg_t status; // [24:24]
-    otbn_hw2reg_err_bits_reg_t err_bits; // [23:8]
-    otbn_hw2reg_fatal_alert_cause_reg_t fatal_alert_cause; // [7:0]
+    otbn_hw2reg_intr_state_reg_t intr_state; // [59:58]
+    otbn_hw2reg_status_reg_t status; // [57:57]
+    otbn_hw2reg_err_bits_reg_t err_bits; // [56:41]
+    otbn_hw2reg_fatal_alert_cause_reg_t fatal_alert_cause; // [40:33]
+    otbn_hw2reg_insn_cnt_reg_t insn_cnt; // [32:0]
   } otbn_hw2reg_t;
 
   // Register offsets
@@ -140,6 +146,7 @@
   parameter logic [BlockAw-1:0] OTBN_ERR_BITS_OFFSET = 16'h 18;
   parameter logic [BlockAw-1:0] OTBN_START_ADDR_OFFSET = 16'h 1c;
   parameter logic [BlockAw-1:0] OTBN_FATAL_ALERT_CAUSE_OFFSET = 16'h 20;
+  parameter logic [BlockAw-1:0] OTBN_INSN_CNT_OFFSET = 16'h 24;
 
   // Reset values for hwext registers and their fields
   parameter logic [0:0] OTBN_INTR_TEST_RESVAL = 1'h 0;
@@ -166,11 +173,12 @@
     OTBN_STATUS,
     OTBN_ERR_BITS,
     OTBN_START_ADDR,
-    OTBN_FATAL_ALERT_CAUSE
+    OTBN_FATAL_ALERT_CAUSE,
+    OTBN_INSN_CNT
   } otbn_id_e;
 
   // Register width information to check illegal writes
-  parameter logic [3:0] OTBN_PERMIT [9] = '{
+  parameter logic [3:0] OTBN_PERMIT [10] = '{
     4'b 0001, // index[0] OTBN_INTR_STATE
     4'b 0001, // index[1] OTBN_INTR_ENABLE
     4'b 0001, // index[2] OTBN_INTR_TEST
@@ -179,7 +187,8 @@
     4'b 0001, // index[5] OTBN_STATUS
     4'b 0001, // index[6] OTBN_ERR_BITS
     4'b 1111, // index[7] OTBN_START_ADDR
-    4'b 0001  // index[8] OTBN_FATAL_ALERT_CAUSE
+    4'b 0001, // index[8] OTBN_FATAL_ALERT_CAUSE
+    4'b 1111  // index[9] OTBN_INSN_CNT
   };
 
 endpackage
diff --git a/hw/ip/otbn/rtl/otbn_reg_top.sv b/hw/ip/otbn/rtl/otbn_reg_top.sv
index bb250f7..aefb4a4 100644
--- a/hw/ip/otbn/rtl/otbn_reg_top.sv
+++ b/hw/ip/otbn/rtl/otbn_reg_top.sv
@@ -187,6 +187,7 @@
   logic fatal_alert_cause_imem_error_qs;
   logic fatal_alert_cause_dmem_error_qs;
   logic fatal_alert_cause_reg_error_qs;
+  logic [31:0] insn_cnt_qs;
 
   // Register instances
   // R[intr_state]: V(False)
@@ -653,9 +654,35 @@
   );
 
 
+  // R[insn_cnt]: V(False)
+
+  prim_subreg #(
+    .DW      (32),
+    .SWACCESS("RO"),
+    .RESVAL  (32'h0)
+  ) u_insn_cnt (
+    .clk_i   (clk_i    ),
+    .rst_ni  (rst_ni  ),
+
+    .we     (1'b0),
+    .wd     ('0  ),
+
+    // from internal hardware
+    .de     (hw2reg.insn_cnt.de),
+    .d      (hw2reg.insn_cnt.d ),
+
+    // to internal hardware
+    .qe     (),
+    .q      (),
+
+    // to register interface (read)
+    .qs     (insn_cnt_qs)
+  );
 
 
-  logic [8:0] addr_hit;
+
+
+  logic [9:0] addr_hit;
   always_comb begin
     addr_hit = '0;
     addr_hit[0] = (reg_addr == OTBN_INTR_STATE_OFFSET);
@@ -667,6 +694,7 @@
     addr_hit[6] = (reg_addr == OTBN_ERR_BITS_OFFSET);
     addr_hit[7] = (reg_addr == OTBN_START_ADDR_OFFSET);
     addr_hit[8] = (reg_addr == OTBN_FATAL_ALERT_CAUSE_OFFSET);
+    addr_hit[9] = (reg_addr == OTBN_INSN_CNT_OFFSET);
   end
 
   assign addrmiss = (reg_re || reg_we) ? ~|addr_hit : 1'b0 ;
@@ -682,7 +710,8 @@
                (addr_hit[5] & (|(OTBN_PERMIT[5] & ~reg_be))) |
                (addr_hit[6] & (|(OTBN_PERMIT[6] & ~reg_be))) |
                (addr_hit[7] & (|(OTBN_PERMIT[7] & ~reg_be))) |
-               (addr_hit[8] & (|(OTBN_PERMIT[8] & ~reg_be)))));
+               (addr_hit[8] & (|(OTBN_PERMIT[8] & ~reg_be))) |
+               (addr_hit[9] & (|(OTBN_PERMIT[9] & ~reg_be)))));
   end
 
   assign intr_state_we = addr_hit[0] & reg_we & !reg_error;
@@ -759,6 +788,10 @@
         reg_rdata_next[3] = fatal_alert_cause_reg_error_qs;
       end
 
+      addr_hit[9]: begin
+        reg_rdata_next[31:0] = insn_cnt_qs;
+      end
+
       default: begin
         reg_rdata_next = '1;
       end