[dv/common] add ECC support to mem_bkdr_if

This PR adds full ECC support to the memory backdoor interface.

To do this, secded_gen.py was modified to also output an enum of the
different ECC variants, which are used to parameterize the mem_bkdr_if
so that we can pick up the correct data widths internally, etc...

Some ECC-specific wrapper functions for `read()` have also been added to
mem_bkdr_if that return the syndrome and error information as well as
the read data - these can be useful for ECC error tests
(sram/flash/otp).

A followup PR will enable this functionality in the OTP and Flash
testbenches.

Signed-off-by: Udi Jonnalagadda <udij@google.com>
diff --git a/hw/dv/sv/mem_bkdr_if/mem_bkdr_if.core b/hw/dv/sv/mem_bkdr_if/mem_bkdr_if.core
index d66b65c..f2ddb61 100644
--- a/hw/dv/sv/mem_bkdr_if/mem_bkdr_if.core
+++ b/hw/dv/sv/mem_bkdr_if/mem_bkdr_if.core
@@ -12,6 +12,7 @@
       - lowrisc:dv:dv_utils
       - lowrisc:dv:crypto_dpi_prince:0.1
       - lowrisc:prim:cipher_pkg:0.1
+      - lowrisc:prim:secded:0.1
     files:
       - sram_scrambler_pkg.sv
       - mem_bkdr_if.sv
diff --git a/hw/dv/sv/mem_bkdr_if/mem_bkdr_if.sv b/hw/dv/sv/mem_bkdr_if/mem_bkdr_if.sv
index dfe2e6a..46a9b27 100644
--- a/hw/dv/sv/mem_bkdr_if/mem_bkdr_if.sv
+++ b/hw/dv/sv/mem_bkdr_if/mem_bkdr_if.sv
@@ -16,30 +16,51 @@
 // any parameter set and can be set into the uvm_config_db to allow us to manipulate the mem
 // contents from the testbench without having us to add heirarchy information, making the chip
 // testbench portable.
+
+
+// The `MEM_PARITY` parameter.
 //
 // The `MEM_PARITY` parameter is used to indicate whether the target memory uses parity checks.
 // If so, we need to take care to modify the read/write indices, as the total memory width will be
 // `mem_bytes_per_word * 8 + mem_bytes_per_word` as we have one parity bit per data byte.
 // Enabling this parameter also allows access to parity error injection functionality - to use this
-// all that needs to be done is to set the `inject_parity_err` argument in any of the backdoor write
+// all that needs to be done is to set the `inject_err` argument in any of the backdoor write
 // functions.
+
+
+// The `MEM_ECC` parameter.
 //
 // The `MEM_ECC` parameter is used to indicate whether the target memory uses an ECC implementation,
-// so we can ensure to usethe proper data encoding scheme.
-// TODO: ECC implementation has not been completed yet, only basic stub functionality is added for
-//       now.
+// so we can ensure to use the proper data encoding scheme.
+// The value of this parameter must be a `prim_secded_e` enumerated value, which can be found in
+// `hw/ip/prim/rtl/prim_secded_pkg.sv`.
+// Setting this parameter to `SecdedNone` disables ECC functionality, and setting it to any other
+// enum value configures this interface to use the correct ECC bit-widths.
+// Enabling ECC also allows access to ECC error injection functionality - to use this
+// all that needs to be done is to set the `inject_err` argument in any of the backdoor write
+// functions.
 //
-// The `MEM_PARITY` and `MEM_ECC` parameters must not be set concurrently,
+// One important note when using this parameter is that the normal `read##()` functions cannot be
+// used if it is desired to access ECC-related syndrome and error information.
+// In this case, it is required to use the `ecc_read##()` functions, which will return a struct
+// containing read data, syndrome, and error data fields.
+
+
+// The `MEM_PARITY` and `MEM_ECC` parameters must not be enabled concurrently,
 // all resultant behavior is undefined.
 //
 // Note: The maximum data width currently supported by this interface is 64-bits (8 bytes).
 //       If support for wider memories is desired, it will need to be implemented.
 
-interface mem_bkdr_if #(parameter bit MEM_PARITY = 0,
-                        parameter bit MEM_ECC = 0) ();
+interface mem_bkdr_if #(
+    parameter bit MEM_PARITY = 0,
+    parameter prim_secded_pkg::prim_secded_e MEM_ECC = prim_secded_pkg::SecdedNone
+) ();
+
   import uvm_pkg::*;
   import bus_params_pkg::BUS_AW;
   import sram_scrambler_pkg::*;
+  import prim_secded_pkg::*;
 
   `include "uvm_macros.svh"
   `include "dv_macros.svh"
@@ -54,12 +75,15 @@
 
   // Represents the highest bit position of a data byte.
   // If parity is enabled, it is 9 because we add a parity tag bit.
-  // If no parity, then no extra bits are added.
-  //
-  // TODO - need to update to hanies
+  // If parity is disabled, then no extra bits are added to this parameter.
   localparam int MEM_BYTE_MSB = (MEM_PARITY) ? 9 : 8;
 
-  localparam int MAX_MEM_WIDTH = MEM_BYTE_MSB * 8;
+  localparam int ECC_PARITY_BITS = prim_secded_pkg::get_ecc_parity_width(MEM_ECC);
+
+  localparam int ECC_DATA_WIDTH = prim_secded_pkg::get_ecc_data_width(MEM_ECC);
+
+  localparam int MAX_MEM_WIDTH = (MEM_ECC == SecdedNone) ?
+      (MEM_BYTE_MSB * 8) : ECC_DATA_WIDTH + ECC_PARITY_BITS;
 
   // derive memory specifics such as depth, width, addr_msb mem size etc.
   bit initialized;
@@ -74,13 +98,17 @@
 
   function automatic void init();
     // Check that both MEM_PARITY and MEM_ECC are not concurrently enabled
-    `DV_CHECK_FATAL((MEM_PARITY && MEM_ECC) == 0,
+    `DV_CHECK_FATAL(!(MEM_PARITY && MEM_ECC != SecdedNone),
         "Cannot enable both parity checks and ECC",
         path)
+    `DV_CHECK_FATAL(MAX_MEM_WIDTH != 0,
+        $sformatf("MEM_ECC %0s is incorrect!", MEM_ECC),
+        path)
     if (!initialized) begin
       mem_depth = $size(`MEM_ARR_PATH_SLICE);
       mem_width = $bits(`MEM_ARR_PATH_SLICE) / mem_depth;
-      mem_bytes_per_word = mem_width / MEM_BYTE_MSB;
+      // Need to account for any extra ecc parity bits when doing this calculation
+      mem_bytes_per_word = (mem_width - ECC_PARITY_BITS) / MEM_BYTE_MSB;
       mem_size_bytes = mem_depth * mem_bytes_per_word;
       mem_addr_lsb = $clog2(mem_bytes_per_word);
       mem_addr_width = $clog2(mem_depth);
@@ -158,15 +186,27 @@
 
   function automatic void write8(input bit [bus_params_pkg::BUS_AW-1:0] addr,
                                  input bit [7:0] data,
-                                 input bit inject_parity_err = 0);
+                                 input bit inject_err = 0);
     if (is_addr_valid(addr)) begin
       int mem_index = addr >> mem_addr_lsb;
       bit [MAX_MEM_WIDTH-1:0] rw_data = `MEM_ARR_PATH_SLICE[mem_index];
 
-      // Prepare corrupted data if a parity error injection is requested
-      bit [2:0] idx_to_corrupt = $urandom_range(0, 7);
-      bit [7:0] corrupted_data = data;
-      corrupted_data[idx_to_corrupt] = !corrupted_data[idx_to_corrupt];
+      // Prepare corrupted data if an error injection is requested.
+      //
+      // If using parity, we want to randomly inject errors into the parity bits
+      // themselves for better error coverage.
+      //
+      // If using ECC, we also want to test double bit errors as well as single bit errors,
+      // so prepare a second index to randomly flip - note that this index can be anywhere
+      // within the valid data word, not just within the "active" byte we are writing.
+      int idx_to_corrupt = $urandom_range(0, MEM_BYTE_MSB - 1);
+
+      // for ECC we want to be able to to corrupt a random second bit.
+      // this bit can be anywhere within the entire memory line, even within
+      // the ECC parity bits themselves.
+      int second_idx_to_corrupt = $urandom_range(0, MAX_MEM_WIDTH - 1);
+
+      bit [MEM_BYTE_MSB-1:0] corrupted_data;
 
       // Note that if memory parity checks are enabled,
       // we have to write the correct parity bit as well.
@@ -174,12 +214,15 @@
       //
       // TODO: odd/even parity checks could be made configurable.
       case (mem_bytes_per_word)
+        // ECC is unavailable if only 1 byte in each memory word
         1: begin
           rw_data[0 +: 8] = data;
           if (MEM_PARITY) begin
             rw_data[0 + 8] = ~(^data);
-            if (inject_parity_err) begin
-              rw_data[0 +: 8] = corrupted_data;
+            if (inject_err) begin
+              corrupted_data = rw_data[0 +: MEM_BYTE_MSB];
+              corrupted_data[idx_to_corrupt] = !corrupted_data[idx_to_corrupt];
+              rw_data[0 +: MEM_BYTE_MSB] = corrupted_data;
             end
           end
         end
@@ -187,8 +230,30 @@
           rw_data[addr[0] * MEM_BYTE_MSB +: 8] = data;
           if (MEM_PARITY) begin
             rw_data[addr[0] * MEM_BYTE_MSB + 8] = ~(^data);
-            if (inject_parity_err) begin
+            if (inject_err) begin
+              corrupted_data = rw_data[addr[0] * MEM_BYTE_MSB +: MEM_BYTE_MSB];
+              corrupted_data[idx_to_corrupt] = !corrupted_data[idx_to_corrupt];
+              rw_data[addr[0] * MEM_BYTE_MSB +: MEM_BYTE_MSB] = corrupted_data;
+            end
+          end else if (MEM_ECC != SecdedNone) begin
+            case (MEM_ECC)
+              Secded_22_16: begin
+                rw_data = prim_secded_22_16_enc(rw_data[ECC_DATA_WIDTH-1:0]);
+              end
+              SecdedHamming_22_16: begin
+                rw_data = prim_secded_hamming_22_16_enc(rw_data[ECC_DATA_WIDTH-1:0]);
+              end
+              default: begin
+                `uvm_error(path,
+                    $sformatf("MEM_ECC %0s is unsupported at mem_width[%0d]", MEM_ECC, mem_width))
+              end
+            endcase
+            if (inject_err) begin
               rw_data[addr[0] * MEM_BYTE_MSB +: 8] = corrupted_data;
+              // 50% of the time randomly enable a second error bit
+              if ($urandom_range(0, 1)) begin
+                rw_data[second_idx_to_corrupt] = !rw_data[second_idx_to_corrupt];
+              end
             end
           end
         end
@@ -196,8 +261,30 @@
           rw_data[addr[1:0] * MEM_BYTE_MSB +: 8] = data;
           if (MEM_PARITY) begin
             rw_data[addr[1:0] * MEM_BYTE_MSB + 8] = ~(^data);
-            if (inject_parity_err) begin
+            if (inject_err) begin
+              corrupted_data = rw_data[addr[1:0] * MEM_BYTE_MSB +: MEM_BYTE_MSB];
+              corrupted_data[idx_to_corrupt] = !corrupted_data[idx_to_corrupt];
+              rw_data[addr[1:0] * MEM_BYTE_MSB +: MEM_BYTE_MSB] = corrupted_data;
+            end
+          end else if (MEM_ECC != SecdedNone) begin
+            case (MEM_ECC)
+              Secded_39_32: begin
+                rw_data = prim_secded_39_32_enc(rw_data[ECC_DATA_WIDTH-1:0]);
+              end
+              SecdedHamming_39_32: begin
+                rw_data = prim_secded_hamming_39_32_enc(rw_data[ECC_DATA_WIDTH-1:0]);
+              end
+              default: begin
+                `uvm_error(path,
+                    $sformatf("MEM_ECC %0s is unsupported at mem_width[%0d]", MEM_ECC, mem_width))
+              end
+            endcase
+            if (inject_err) begin
               rw_data[addr[1:0] * MEM_BYTE_MSB +: 8] = corrupted_data;
+              // 50% of the time randomly enable a second error bit
+              if ($urandom_range(0, 1)) begin
+                rw_data[second_idx_to_corrupt] = !rw_data[second_idx_to_corrupt];
+              end
             end
           end
         end
@@ -205,8 +292,30 @@
           rw_data[addr[2:0] * MEM_BYTE_MSB +: 8] = data;
           if (MEM_PARITY) begin
             rw_data[addr[2:0] * MEM_BYTE_MSB + 8] = ~(^data);
-            if (inject_parity_err) begin
+            if (inject_err) begin
+              corrupted_data = rw_data[addr[2:0] * MEM_BYTE_MSB +: MEM_BYTE_MSB];
+              corrupted_data[idx_to_corrupt] = !corrupted_data[idx_to_corrupt];
+              rw_data[addr[2:0] * MEM_BYTE_MSB +: MEM_BYTE_MSB] = corrupted_data;
+            end
+          end else if (MEM_ECC != SecdedNone) begin
+            case (MEM_ECC)
+              Secded_72_64: begin
+                rw_data = prim_secded_72_64_enc(rw_data[ECC_DATA_WIDTH-1:0]);
+              end
+              SecdedHamming_72_64: begin
+                rw_data = prim_secded_hamming_72_64_enc(rw_data[ECC_DATA_WIDTH-1:0]);
+              end
+              default: begin
+                `uvm_error(path,
+                    $sformatf("MEM_ECC %0s is unsupported at mem_width[%0d]", MEM_ECC, mem_width))
+              end
+            endcase
+            if (inject_err) begin
               rw_data[addr[2:0] * MEM_BYTE_MSB +: 8] = corrupted_data;
+              // 50% of the time randomly enable a second error bit
+              if ($urandom_range(0, 1)) begin
+                rw_data[second_idx_to_corrupt] = !rw_data[second_idx_to_corrupt];
+              end
             end
           end
         end
@@ -218,36 +327,76 @@
 
   function automatic void write16(input bit [bus_params_pkg::BUS_AW-1:0] addr,
                                   input bit [15:0] data,
-                                  input bit inject_parity_err = 0);
-    // ensure that parity errors are only injected into one sub-byte write
-    bit inject_parity_err_in_first_sub_write = $urandom_range(0, 1);
+                                  input bit inject_err = 0);
+    // inject errors into only sub-byte write
+    bit inject_err_in_first_sub_write = $urandom_range(0, 1);
     `DV_CHECK_EQ_FATAL(addr[0], '0, $sformatf("addr 0x%0h not 16-bit aligned", addr), path)
     if (is_addr_valid(addr)) begin
-      write8(addr, data[7:0], inject_parity_err_in_first_sub_write && inject_parity_err);
-      write8(addr + 1, data[15:8], !inject_parity_err_in_first_sub_write && inject_parity_err);
+      write8(addr, data[7:0], inject_err_in_first_sub_write && inject_err);
+      write8(addr + 1, data[15:8], !inject_err_in_first_sub_write && inject_err);
     end
   endfunction
 
   function automatic void write32(input bit [bus_params_pkg::BUS_AW-1:0] addr,
                                   input bit [31:0] data,
-                                  input bit inject_parity_err = 0);
+                                  input bit inject_err = 0);
     // ensure that parity errors are only injected into one sub-byte write
-    bit inject_parity_err_in_first_sub_write = $urandom_range(0, 1);
+    bit inject_err_in_first_sub_write = $urandom_range(0, 1);
     `DV_CHECK_EQ_FATAL(addr[1:0], '0, $sformatf("addr 0x%0h not 32-bit aligned", addr), path)
     if (is_addr_valid(addr)) begin
-      write16(addr, data[15:0], inject_parity_err_in_first_sub_write && inject_parity_err);
-      write16(addr + 2, data[31:16], !inject_parity_err_in_first_sub_write && inject_parity_err);
+      write16(addr, data[15:0], inject_err_in_first_sub_write && inject_err);
+      write16(addr + 2, data[31:16], !inject_err_in_first_sub_write && inject_err);
     end
   endfunction
 
   function automatic void write64(input bit [bus_params_pkg::BUS_AW-1:0] addr,
                                   input bit [63:0] data,
-                                  input bit inject_parity_err = 0);
+                                  input bit inject_err = 0);
     // ensure that parity errors are only injected into one sub-byte write
-    bit inject_parity_err_in_first_sub_write = $urandom_range(0, 1);
+    bit inject_err_in_first_sub_write = $urandom_range(0, 1);
     `DV_CHECK_EQ_FATAL(addr[2:0], '0, $sformatf("addr 0x%0h not 64-bit aligned", addr), path)
-    write32(addr, data[31:0], inject_parity_err_in_first_sub_write && inject_parity_err);
-    write32(addr + 4, data[63:32], !inject_parity_err_in_first_sub_write && inject_parity_err);
+    write32(addr, data[31:0], inject_err_in_first_sub_write && inject_err);
+    write32(addr + 4, data[63:32], !inject_err_in_first_sub_write && inject_err);
+  endfunction
+
+  /////////////////////////////////////////////////////////
+  // Wrapper functions for memory reads with ECC enabled //
+  /////////////////////////////////////////////////////////
+  // Some notes:
+  // - ECC isn't supported for 8-bit wide memories
+  // - (28, 22) and (64, 57) ECC configurations aren't supported
+
+  // Intended for use with memories which have data width of 16 bits and 6 ECC bits.
+  function automatic secded_22_16_t ecc_read16(input bit [bus_params_pkg::BUS_AW-1:0] addr);
+    if (is_addr_valid(addr)) begin
+      int mem_index = addr >> mem_addr_lsb;
+      // 22-bit wide memory word includes 16-bit data, 6-bit ECC bits
+      bit [MAX_MEM_WIDTH-1:0] mem_data = `MEM_ARR_PATH_SLICE[mem_index];
+      return prim_secded_22_16_dec(mem_data);
+    end
+    return 'x;
+  endfunction
+
+  // Intended for use with memories which have data width of 32 bits and 7 ECC bits.
+  function automatic secded_39_32_t ecc_read32(input bit [bus_params_pkg::BUS_AW-1:0] addr);
+    if (is_addr_valid(addr)) begin
+      int mem_index = addr >> mem_addr_lsb;
+      // 39-bit wide memory word includes 32-bit data, 7-bit ECC bits
+      bit [MAX_MEM_WIDTH-1:0] mem_data = `MEM_ARR_PATH_SLICE[mem_index];
+      return prim_secded_39_32_dec(mem_data);
+    end
+    return 'x;
+  endfunction
+
+  // Intended for use with memories which have data width of 64 bits and 8 ECC bits.
+  function automatic secded_72_64_t ecc_read64(input bit [bus_params_pkg::BUS_AW-1:0] addr);
+    if (is_addr_valid(addr)) begin
+      int mem_index = addr >> mem_addr_lsb;
+      // 72-bit wide memory word includes 64-bit data, 8-bit ECC bits
+      bit [MAX_MEM_WIDTH-1:0] mem_data = `MEM_ARR_PATH_SLICE[mem_index];
+      return prim_secded_72_64_dec(mem_data);
+    end
+    return 'x;
   endfunction
 
   ///////////////////////////////////////////////////////////
@@ -630,14 +779,11 @@
   function automatic void clear_mem();
     init();
     `uvm_info(path, "Clear memory", UVM_LOW)
-    if (MEM_PARITY) begin
+    if (MEM_PARITY || MEM_ECC != SecdedNone) begin
       // Have to manually loop over memory and clear to avoid overwriting any parity bits.
       for (int i = 0; i < mem_size_bytes; i++) begin
         write8(i, '0);
       end
-    end else if (MEM_ECC) begin
-    // TODO - temporary workaround until ECC encoding is implemented
-      `MEM_ARR_PATH_SLICE = '{default:'0};
     end else begin
       `MEM_ARR_PATH_SLICE = '{default:'0};
     end
@@ -647,18 +793,14 @@
   function automatic void set_mem();
     init();
     `uvm_info(path, "Set memory", UVM_LOW)
-    if (MEM_PARITY) begin
+    if (MEM_PARITY || MEM_ECC != SecdedNone) begin
       // Have to manually loop over memory and set to avoid overwriting any parity bits.
       for (int i = 0; i < mem_size_bytes; i++) begin
         write8(i, '1);
       end
-    end else if (MEM_ECC) begin
-    // TODO - temporary workaround until ECC encoding is implemented
-      `MEM_ARR_PATH_SLICE = '{default:'1};
     end else begin
       `MEM_ARR_PATH_SLICE = '{default:'1};
     end
-
   endfunction
 
   // randomize the memory
@@ -666,21 +808,9 @@
     logic [7:0] rand_val;
     init();
     `uvm_info(path, "Randomizing mem contents", UVM_LOW)
-    if (MEM_PARITY) begin
-      // Have to manually loop over memory and randomize to avoid overwriting any parity bits.
-      for (int i = 0; i < mem_size_bytes; i++) begin
-        `DV_CHECK_STD_RANDOMIZE_FATAL(rand_val, "Randomization failed!", path)
-        write8(i, rand_val);
-      end
-    end else begin
-      // TODO - temporary workaround until ECC encoding is implemented
-      foreach (`MEM_ARR_PATH_SLICE[i]) begin
-        // Workaround to avoid Xcelium compile error:
-        // `DV_CHECK_STD_RANDOMIZE_FATAL(`MEM_ARR_PATH_SLICE[i], , path)
-        bit [MAX_MEM_WIDTH-1:0] val = `MEM_ARR_PATH_SLICE[i];
-        `DV_CHECK_STD_RANDOMIZE_FATAL(val, , path)
-        for (int j = 0; j < mem_width; j++)  `MEM_ARR_PATH_SLICE[i][j] = val[j];
-      end
+    for (int i = 0; i < mem_size_bytes; i++) begin
+      `DV_CHECK_STD_RANDOMIZE_FATAL(rand_val, "Randomization failed!", path)
+      write8(i, rand_val);
     end
   endfunction
 
diff --git a/hw/ip/prim/rtl/prim_secded_pkg.sv b/hw/ip/prim/rtl/prim_secded_pkg.sv
index 984c24f..2172fb3 100644
--- a/hw/ip/prim/rtl/prim_secded_pkg.sv
+++ b/hw/ip/prim/rtl/prim_secded_pkg.sv
@@ -7,6 +7,47 @@
 
 package prim_secded_pkg;
 
+  typedef enum int {
+    SecdedNone,
+    Secded_22_16,
+    Secded_28_22,
+    Secded_39_32,
+    Secded_64_57,
+    Secded_72_64,
+    SecdedHamming_22_16,
+    SecdedHamming_39_32,
+    SecdedHamming_72_64
+  } prim_secded_e;
+
+  function automatic int get_ecc_data_width(prim_secded_e ecc_type);
+    case (ecc_type)
+      Secded_22_16: return 16;
+      Secded_28_22: return 22;
+      Secded_39_32: return 32;
+      Secded_64_57: return 57;
+      Secded_72_64: return 64;
+      SecdedHamming_22_16: return 16;
+      SecdedHamming_39_32: return 32;
+      SecdedHamming_72_64: return 64;
+      // Return a non-zero width to avoid VCS compile issues
+      default: return 32;
+    endcase
+  endfunction
+
+  function automatic int get_ecc_parity_width(prim_secded_e ecc_type);
+    case (ecc_type)
+      Secded_22_16: return 6;
+      Secded_28_22: return 6;
+      Secded_39_32: return 7;
+      Secded_64_57: return 7;
+      Secded_72_64: return 8;
+      SecdedHamming_22_16: return 6;
+      SecdedHamming_39_32: return 7;
+      SecdedHamming_72_64: return 8;
+      default: return 0;
+    endcase
+  endfunction
+
   typedef struct packed {
     logic [15:0] data;
     logic [5:0] syndrome;
@@ -481,12 +522,12 @@
     return data_o;
   endfunction
 
-  function automatic secded_hamming_22_16_t prim_secded_hamming_22_16_dec (logic [21:0] data_i);
+  function automatic secded_22_16_t prim_secded_hamming_22_16_dec (logic [21:0] data_i);
     logic [15:0] data_o;
     logic [5:0] syndrome_o;
     logic [1:0]  err_o;
 
-    secded_hamming_22_16_t dec;
+    secded_22_16_t dec;
 
 
     // Syndrome calculation
@@ -539,12 +580,12 @@
     return data_o;
   endfunction
 
-  function automatic secded_hamming_39_32_t prim_secded_hamming_39_32_dec (logic [38:0] data_i);
+  function automatic secded_39_32_t prim_secded_hamming_39_32_dec (logic [38:0] data_i);
     logic [31:0] data_o;
     logic [6:0] syndrome_o;
     logic [1:0]  err_o;
 
-    secded_hamming_39_32_t dec;
+    secded_39_32_t dec;
 
 
     // Syndrome calculation
@@ -615,12 +656,12 @@
     return data_o;
   endfunction
 
-  function automatic secded_hamming_72_64_t prim_secded_hamming_72_64_dec (logic [71:0] data_i);
+  function automatic secded_72_64_t prim_secded_hamming_72_64_dec (logic [71:0] data_i);
     logic [63:0] data_o;
     logic [7:0] syndrome_o;
     logic [1:0]  err_o;
 
-    secded_hamming_72_64_t dec;
+    secded_72_64_t dec;
 
 
     // Syndrome calculation
diff --git a/util/design/secded_gen.py b/util/design/secded_gen.py
index 98ec4b6..d94ffa1 100755
--- a/util/design/secded_gen.py
+++ b/util/design/secded_gen.py
@@ -84,6 +84,54 @@
     return fanin_masks
 
 
+def print_secded_enum_and_util_fns(cfgs):
+    enum_vals = ["    SecdedNone"]
+    parity_width_vals = []
+    data_width_vals = []
+    for cfg in cfgs:
+        k = cfg['k']
+        m = cfg['m']
+        n = k + m
+        suffix = CODE_OPTIONS[cfg['code_type']]
+        formatted_suffix = suffix.replace('_', '').capitalize()
+
+        enum_name = "    Secded%s_%s_%s" % (formatted_suffix, n, k)
+        enum_vals.append(enum_name)
+
+        parity_width = "  %s: return %s;" % (enum_name, m)
+        parity_width_vals.append(parity_width)
+
+        data_width = "  %s: return %s;" % (enum_name, k)
+        data_width_vals.append(data_width)
+
+    enum_str = ",\n".join(enum_vals)
+    parity_width_fn_str = "\n".join(parity_width_vals)
+    data_width_fn_str = "\n".join(data_width_vals)
+
+    enum_str = '''
+  typedef enum int {{
+{}
+  }} prim_secded_e;
+
+  function automatic int get_ecc_data_width(prim_secded_e ecc_type);
+    case (ecc_type)
+{}
+      // Return a non-zero width to avoid VCS compile issues
+      default: return 32;
+    endcase
+  endfunction
+
+  function automatic int get_ecc_parity_width(prim_secded_e ecc_type);
+    case (ecc_type)
+{}
+      default: return 0;
+    endcase
+  endfunction
+'''.format(enum_str, data_width_fn_str, parity_width_fn_str)
+
+    return enum_str
+
+
 def print_pkg_types(n, k, m, codes, suffix, codetype):
     typename = "secded%s_%d_%d_t" % (suffix, n, k)
 
@@ -102,7 +150,7 @@
     enc_out = print_enc(n, k, m, codes)
     dec_out = print_dec(n, k, m, codes, codetype, "function")
 
-    typename = "secded%s_%d_%d_t" % (suffix, n, k)
+    typename = "secded_%d_%d_t" % (n, k)
     module_name = "prim_secded%s_%d_%d" % (suffix, n, k)
 
     outstr = '''
@@ -168,7 +216,8 @@
     outstr += "  {}// Corrected output calculation\n".format(
         preamble if print_type == "function" else "")
     for i in range(k):
-        outstr += "  {}".format(preamble) + "data_o[%d] = (syndrome_o == %d'h%x) ^ data_i[%d];\n" % (
+        outstr += "  {}".format(preamble)
+        outstr += "data_o[%d] = (syndrome_o == %d'h%x) ^ data_i[%d];\n" % (
             i, m, calc_syndrome(codes[i]), i)
     outstr += "\n"
     outstr += "  {}// err_o calc. bit0: single error, bit1: double error\n".format(
@@ -256,8 +305,10 @@
         if not args.no_fpv:
             write_fpv_files(n, k, m, codes, codetype, args.fpv_outdir)
 
+    # create enum of various ECC types - useful for DV purposes in mem_bkdr_if
+    enum_str = print_secded_enum_and_util_fns(cfgs['cfgs'])
     # write out package file
-    full_pkg_str = pkg_type_str + pkg_out_str
+    full_pkg_str = enum_str + pkg_type_str + pkg_out_str
     write_pkg_file(args.outdir, full_pkg_str)