[prim_otp] Rework generic model to match new error behavior

Signed-off-by: Michael Schaffner <msf@opentitan.org>
diff --git a/hw/ip/prim_generic/rtl/prim_generic_otp.sv b/hw/ip/prim_generic/rtl/prim_generic_otp.sv
index e941b3a..b58b026 100644
--- a/hw/ip/prim_generic/rtl/prim_generic_otp.sv
+++ b/hw/ip/prim_generic/rtl/prim_generic_otp.sv
@@ -41,10 +41,10 @@
   output logic [IfWidth-1:0]     rdata_o,
   output err_e                   err_o,
   // External programming voltage
-  inout wire ext_voltage_io, //TODO enable it after the change in prim_otp file
-  input ext_voltage_en_i, // TODO
-  //// alert indication
-  //////////////////////////
+  inout wire ext_voltage_io,
+  input ext_voltage_en_i,
+
+  // alert indication
   output ast_pkg::ast_dif_t otp_alert_src_o,
 
   // Scan
@@ -53,6 +53,11 @@
   input scan_rst_ni  // Scan Reset
 );
 
+  // This is only restricted by the supported ECC poly further
+  // below, and is straightforward to extend, if needed.
+  localparam int EccWidth = 6;
+  `ASSERT_INIT(SecDecWidth_A, Width == 16)
+
   // Not supported in open-source emulation model.
   logic [PwrSeqWidth-1:0] unused_pwr_seq_h;
   assign unused_pwr_seq_h = pwr_seq_h_i;
@@ -160,18 +165,18 @@
   logic valid_d, valid_q;
   logic req, wren, rvalid;
   logic [1:0] rerror;
-  logic [Width-1:0] rdata_d;
-  logic [2**SizeWidth-1:0][Width-1:0] rdata_q, wdata_q;
   logic [AddrWidth-1:0] addr_q;
   logic [SizeWidth-1:0] size_q;
   logic [SizeWidth-1:0] cnt_d, cnt_q;
   logic cnt_clr, cnt_en;
+  logic read_ecc_on;
+  logic wdata_inconsistent;
+
 
   assign cnt_d = (cnt_clr) ? '0           :
                  (cnt_en)  ? cnt_q + 1'b1 : cnt_q;
 
   assign valid_o = valid_q;
-  assign rdata_o = rdata_q;
   assign err_o   = err_q;
 
   always_comb begin : p_fsm
@@ -184,6 +189,7 @@
     wren    = 1'b0;
     cnt_clr = 1'b0;
     cnt_en  = 1'b0;
+    read_ecc_on = 1'b1;
 
     unique case (state_q)
       // Wait here until we receive an initialization command.
@@ -252,37 +258,39 @@
           end
         end
       end
-      // First, perform a blank check.
+      // First, read out to perform the write blank check and
+      // read-modify-write operation.
       WriteCheckSt: begin
         state_d = WriteWaitSt;
         req     = 1'b1;
+        // Register raw memory contents without correction
+        read_ecc_on = 1'b0;
       end
       // Wait for readout to complete first.
-      // If the write data would clear an already programmed bit, or if we got an uncorrectable
-      // ECC error, the check has failed and we abort the write at this point.
       WriteWaitSt: begin
+        // Register raw memory contents without correction
+        read_ecc_on = 1'b0;
         if (rvalid) begin
           cnt_en = 1'b1;
-          // TODO: this blank check needs to be extended to account for the ECC bits as well.
-          if (rerror[1] || (rdata_d & wdata_q[cnt_q]) != rdata_d) begin
-            state_d = IdleSt;
-            valid_d = 1'b1;
-            err_d = MacroWriteBlankError;
+
+          if (cnt_q == size_q) begin
+            cnt_clr = 1'b1;
+            state_d = WriteSt;
           end else begin
-            if (cnt_q == size_q) begin
-              cnt_clr = 1'b1;
-              state_d = WriteSt;
-            end else begin
-              state_d = WriteCheckSt;
-            end
+            state_d = WriteCheckSt;
           end
         end
       end
-      // Now that the write check was successful, we can write all native words in one go.
+      // If the write data attempts to clear an already programmed bit,
+      // the MacroWriteBlankError needs to be asserted.
       WriteSt: begin
         req = 1'b1;
         wren = 1'b1;
         cnt_en = 1'b1;
+        if (wdata_inconsistent) begin
+          err_d = MacroWriteBlankError;
+        end
+
         if (cnt_q == size_q) begin
           valid_d = 1'b1;
           state_d = IdleSt;
@@ -301,27 +309,59 @@
   logic [AddrWidth-1:0] addr;
   assign addr = addr_q + AddrWidth'(cnt_q);
 
+  logic [Width-1:0] rdata_corr;
+  logic [Width+EccWidth-1:0] rdata_d, wdata_ecc, rdata_ecc, wdata_rmw;
+  logic [2**SizeWidth-1:0][Width-1:0] wdata_q, rdata_reshaped;
+  logic [2**SizeWidth-1:0][Width+EccWidth-1:0] rdata_q;
+
+  // Use a standard Hamming ECC for OTP.
+  prim_secded_hamming_22_16_enc u_enc (
+    .data_i(wdata_q[cnt_q]),
+    .data_o(wdata_ecc)
+  );
+
+  prim_secded_hamming_22_16_dec u_dec (
+    .data_i     (rdata_ecc),
+    .data_o     (rdata_corr),
+    .syndrome_o ( ),
+    .err_o      (rerror)
+  );
+
+  assign rdata_d = (read_ecc_on) ? {{EccWidth{1'b0}}, rdata_corr}
+                                 : rdata_ecc;
+
+  // Read-modify-write (OTP can only set bits to 1, but not clear to 0).
+  assign wdata_rmw = wdata_ecc | rdata_q[cnt_q];
+  // This indicates if the write data is inconsistent (i.e., if the operation attempts to
+  // clear an already programmed bit to zero).
+  assign wdata_inconsistent = (rdata_q[cnt_q] & wdata_ecc) != rdata_q[cnt_q];
+
+  // Output data without ECC bits.
+  always_comb begin : p_output_map
+    for (int k = 0; k < 2**SizeWidth; k++) begin
+      rdata_reshaped[k] = rdata_q[k][Width-1:0];
+    end
+    rdata_o = rdata_reshaped;
+  end
+
   prim_ram_1p_adv #(
     .Depth                (Depth),
-    .Width                (Width),
+    .Width                (Width + EccWidth),
     .MemInitFile          (MemInitFile),
-    .EnableECC            (1'b1),
     .EnableInputPipeline  (1),
-    .EnableOutputPipeline (1),
-    // Use a standard Hamming ECC for OTP.
-    .HammingECC           (1)
+    .EnableOutputPipeline (1)
   ) u_prim_ram_1p_adv (
     .clk_i,
     .rst_ni,
-    .req_i    ( req            ),
-    .write_i  ( wren           ),
-    .addr_i   ( addr           ),
-    .wdata_i  ( wdata_q[cnt_q] ),
-    .wmask_i  ( {Width{1'b1}}  ),
-    .rdata_o  ( rdata_d        ),
-    .rvalid_o ( rvalid         ),
-    .rerror_o ( rerror         ),
-    .cfg_i    ( '0             )
+    .req_i    ( req                    ),
+    .write_i  ( wren                   ),
+    .addr_i   ( addr                   ),
+    .wdata_i  ( wdata_rmw              ),
+    .wmask_i  ( {Width+EccWidth{1'b1}} ),
+    .rdata_o  ( rdata_ecc              ),
+    .rvalid_o ( rvalid                 ),
+    .rerror_o (                        ),
+    .cfg_i    ( '0                     )
   );
 
   // Currently it is assumed that no wrap arounds can occur.