[prim_secded] Add C reference models for Hsiao encode

This adds generation of C code for the Hsiao code encode. This is
intended to be used by DV environments for backdoor memory loading,
which only requires encode.

No C code is generated for the Hamming codes. These are slightly more
complex as the final code bit factors in previously computed bits where
the Hsiao code does not. So they need a slightly different approach to
the C code. Hamming codes aren't currently used in OpenTitan so C code
for their encodes aren't required.

Signed-off-by: Greg Chadwick <gac@lowrisc.org>
diff --git a/hw/ip/prim/dv/prim_secded/secded_enc.c b/hw/ip/prim/dv/prim_secded/secded_enc.c
new file mode 100644
index 0000000..b48197b
--- /dev/null
+++ b/hw/ip/prim/dv/prim_secded/secded_enc.c
@@ -0,0 +1,90 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// SECDED encode code generated by
+// util/design/secded_gen.py from util/design/data/secded_cfg.hjson
+
+#include <stdbool.h>
+#include <stdint.h>
+
+#include "secded_enc.h"
+
+// Calculates even parity for a 64-bit word
+static uint8_t calc_parity(uint64_t word) {
+  bool parity = false;
+
+  while (word) {
+    if (word & 1) {
+      parity = !parity;
+    }
+
+    word >>= 1;
+  }
+
+  return parity;
+}
+
+uint8_t enc_secded_22_16(const uint8_t bytes[2]) {
+  uint16_t word = ((uint16_t)bytes[0] << 0) | ((uint16_t)bytes[1] << 8);
+
+  return (calc_parity(word & 0x496e) << 0) | (calc_parity(word & 0xf20b) << 1) |
+         (calc_parity(word & 0x8ed8) << 2) | (calc_parity(word & 0x7714) << 3) |
+         (calc_parity(word & 0xaca5) << 4) | (calc_parity(word & 0x11f3) << 5);
+}
+
+uint8_t enc_secded_28_22(const uint8_t bytes[3]) {
+  uint32_t word = ((uint32_t)bytes[0] << 0) | ((uint32_t)bytes[1] << 8) |
+                  ((uint32_t)bytes[2] << 16);
+
+  return (calc_parity(word & 0x3003ff) << 0) |
+         (calc_parity(word & 0x10fc0f) << 1) |
+         (calc_parity(word & 0x271c71) << 2) |
+         (calc_parity(word & 0x3b6592) << 3) |
+         (calc_parity(word & 0x3daaa4) << 4) |
+         (calc_parity(word & 0x3ed348) << 5);
+}
+
+uint8_t enc_secded_39_32(const uint8_t bytes[4]) {
+  uint32_t word = ((uint32_t)bytes[0] << 0) | ((uint32_t)bytes[1] << 8) |
+                  ((uint32_t)bytes[2] << 16) | ((uint32_t)bytes[3] << 24);
+
+  return (calc_parity(word & 0x2606bd25) << 0) |
+         (calc_parity(word & 0xdeba8050) << 1) |
+         (calc_parity(word & 0x413d89aa) << 2) |
+         (calc_parity(word & 0x31234ed1) << 3) |
+         (calc_parity(word & 0xc2c1323b) << 4) |
+         (calc_parity(word & 0x2dcc624c) << 5) |
+         (calc_parity(word & 0x98505586) << 6);
+}
+
+uint8_t enc_secded_64_57(const uint8_t bytes[8]) {
+  uint64_t word = ((uint64_t)bytes[0] << 0) | ((uint64_t)bytes[1] << 8) |
+                  ((uint64_t)bytes[2] << 16) | ((uint64_t)bytes[3] << 24) |
+                  ((uint64_t)bytes[4] << 32) | ((uint64_t)bytes[5] << 40) |
+                  ((uint64_t)bytes[6] << 48) | ((uint64_t)bytes[7] << 56);
+
+  return (calc_parity(word & 0x103fff800007fff) << 0) |
+         (calc_parity(word & 0x17c1ff801ff801f) << 1) |
+         (calc_parity(word & 0x1bde1f87e0781e1) << 2) |
+         (calc_parity(word & 0x1deee3b8e388e22) << 3) |
+         (calc_parity(word & 0x1ef76cdb2c93244) << 4) |
+         (calc_parity(word & 0x1f7bb56d5525488) << 5) |
+         (calc_parity(word & 0x1fbdda769a46910) << 6);
+}
+
+uint8_t enc_secded_72_64(const uint8_t bytes[8]) {
+  uint64_t word = ((uint64_t)bytes[0] << 0) | ((uint64_t)bytes[1] << 8) |
+                  ((uint64_t)bytes[2] << 16) | ((uint64_t)bytes[3] << 24) |
+                  ((uint64_t)bytes[4] << 32) | ((uint64_t)bytes[5] << 40) |
+                  ((uint64_t)bytes[6] << 48) | ((uint64_t)bytes[7] << 56);
+
+  return (calc_parity(word & 0xb9000000001fffff) << 0) |
+         (calc_parity(word & 0x5e00000fffe0003f) << 1) |
+         (calc_parity(word & 0x67003ff003e007c1) << 2) |
+         (calc_parity(word & 0xcd0fc0f03c207842) << 3) |
+         (calc_parity(word & 0xb671c711c4438884) << 4) |
+         (calc_parity(word & 0xb5b65926488c9108) << 5) |
+         (calc_parity(word & 0xcbdaaa4a91152210) << 6) |
+         (calc_parity(word & 0x7aed348d221a4420) << 7);
+}
diff --git a/hw/ip/prim/dv/prim_secded/secded_enc.core b/hw/ip/prim/dv/prim_secded/secded_enc.core
new file mode 100644
index 0000000..ebc0665
--- /dev/null
+++ b/hw/ip/prim/dv/prim_secded/secded_enc.core
@@ -0,0 +1,18 @@
+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:dv:secded_enc"
+description: "Hsiao SECDED encode reference C implementation"
+filesets:
+  files_dv:
+    files:
+      - secded_enc.h: {is_include_file: true}
+      - secded_enc.c
+    file_type: cSource
+
+targets:
+  default:
+    filesets:
+      - files_dv
diff --git a/hw/ip/prim/dv/prim_secded/secded_enc.h b/hw/ip/prim/dv/prim_secded/secded_enc.h
new file mode 100644
index 0000000..2ed52a2
--- /dev/null
+++ b/hw/ip/prim/dv/prim_secded/secded_enc.h
@@ -0,0 +1,31 @@
+// Copyright lowRISC contributors.
+// Licensed under the Apache License, Version 2.0, see LICENSE for details.
+// SPDX-License-Identifier: Apache-2.0
+//
+// SECDED encode code generated by
+// util/design/secded_gen.py from util/design/data/secded_cfg.hjson
+
+#ifndef OPENTITAN_HW_IP_PRIM_DV_PRIM_SECDED_SECDED_ENC_H_
+#define OPENTITAN_HW_IP_PRIM_DV_PRIM_SECDED_SECDED_ENC_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+// Integrity encode functions for varying bit widths matching the functionality
+// of the RTL modules of the same name. Each takes an array of bytes in
+// little-endian order and returns the calculated integrity bits.
+
+uint8_t enc_secded_22_16(const uint8_t bytes[2]);
+uint8_t enc_secded_28_22(const uint8_t bytes[3]);
+uint8_t enc_secded_39_32(const uint8_t bytes[4]);
+uint8_t enc_secded_64_57(const uint8_t bytes[8]);
+uint8_t enc_secded_72_64(const uint8_t bytes[8]);
+
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif  // OPENTITAN_HW_IP_PRIM_DV_PRIM_SECDED_SECDED_ENC_H_
diff --git a/util/design/secded_gen.py b/util/design/secded_gen.py
index d94ffa1..08351a6 100755
--- a/util/design/secded_gen.py
+++ b/util/design/secded_gen.py
@@ -17,12 +17,59 @@
 import math
 import random
 import hjson
+import subprocess
 
 COPYRIGHT = """// Copyright lowRISC contributors.
 // Licensed under the Apache License, Version 2.0, see LICENSE for details.
 // SPDX-License-Identifier: Apache-2.0
 //
 """
+
+C_SRC_TOP = """#include <stdbool.h>
+#include <stdint.h>
+
+#include "secded_enc.h"
+
+// Calculates even parity for a 64-bit word
+static uint8_t calc_parity(uint64_t word) {
+  bool parity = false;
+
+  while (word) {
+    if (word & 1) {
+      parity = !parity;
+    }
+
+    word >>= 1;
+  }
+
+  return parity;
+}
+"""
+
+C_H_TOP = """
+#ifndef OPENTITAN_HW_IP_PRIM_DV_PRIM_SECDED_SECDED_ENC_H_
+#define OPENTITAN_HW_IP_PRIM_DV_PRIM_SECDED_SECDED_ENC_H_
+
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif  // __cplusplus
+
+// Integrity encode functions for varying bit widths matching the functionality
+// of the RTL modules of the same name. Each takes an array of bytes in
+// little-endian order and returns the calculated integrity bits.
+
+"""
+
+C_H_FOOT = """
+#ifdef __cplusplus
+}  // extern "C"
+#endif  // __cplusplus
+
+#endif  // OPENTITAN_HW_IP_PRIM_DV_PRIM_SECDED_SECDED_ENC_H_
+"""
+
 CODE_OPTIONS = {'hsiao': '', 'hamming': '_hamming'}
 PRINT_OPTIONS = {"logic": "assign ", "function": "  "}
 
@@ -285,6 +332,22 @@
 def generate(cfgs, args):
     pkg_out_str = ""
     pkg_type_str = ""
+
+    c_src_filename = args.c_outdir + "/" + "secded_enc.c"
+    c_h_filename = args.c_outdir + "/" + "secded_enc.h"
+
+    with open(c_src_filename, "w") as f:
+        f.write(COPYRIGHT)
+        f.write("// SECDED encode code generated by\n")
+        f.write(f"// util/design/secded_gen.py from {SECDED_CFG_FILE}\n\n")
+        f.write(C_SRC_TOP)
+
+    with open(c_h_filename, "w") as f:
+        f.write(COPYRIGHT)
+        f.write("// SECDED encode code generated by\n")
+        f.write(f"// util/design/secded_gen.py from {SECDED_CFG_FILE}\n")
+        f.write(C_H_TOP)
+
     for cfg in cfgs['cfgs']:
         log.debug("Working on {}".format(cfg))
         k = cfg['k']
@@ -297,6 +360,10 @@
         # write out rtl files
         write_enc_dec_files(n, k, m, codes, suffix, args.outdir, codetype)
 
+        # write out C files, only hsiao codes are supported
+        if codetype == "hsiao":
+            write_c_files(n, k, m, codes, suffix, c_src_filename, c_h_filename)
+
         # write out package typedefs
         pkg_type_str += print_pkg_types(n, k, m, codes, suffix, codetype)
         # print out functions
@@ -305,6 +372,11 @@
         if not args.no_fpv:
             write_fpv_files(n, k, m, codes, codetype, args.fpv_outdir)
 
+    with open(c_h_filename, "a") as f:
+        f.write(C_H_FOOT)
+
+    format_c_files(c_src_filename, c_h_filename)
+
     # 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
@@ -456,6 +528,78 @@
         f.write(outstr)
 
 
+def bytes_to_c_type(num_bytes):
+    if num_bytes == 1:
+        return 'uint8_t'
+    elif num_bytes <= 2:
+        return 'uint16_t'
+    elif num_bytes <= 4:
+        return 'uint32_t'
+    elif num_bytes <= 8:
+        return 'uint64_t'
+
+    return None
+
+
+def write_c_files(n, k, m, codes, suffix, c_src_filename, c_h_filename):
+    in_bytes = math.ceil(k / 8)
+    out_bytes = math.ceil(m / 8)
+
+    if (k > 64):
+        log.warning(f"Cannot generate C encoder for k = {k}."
+                    " The tool has no support for k > 64 for C encoder "
+                    "generation")
+        return
+
+    in_type = bytes_to_c_type(in_bytes)
+    out_type = bytes_to_c_type(out_bytes)
+
+    assert in_type
+    assert out_type
+
+    with open(c_src_filename, "a") as f:
+        # Write out function prototype in src
+        f.write(f"\n{out_type} enc_secded_{n}_{k}{suffix}"
+                f"(const uint8_t bytes[{in_bytes}]) {{\n")
+
+        # Form a single word from the incoming byte data
+        f.write(f"{in_type} word = ")
+        f.write(" | ".join(
+                [f"(({in_type})bytes[{i}] << {i*8})" for i in range(in_bytes)]))
+        f.write(";\n\n")
+
+        # AND the word with the codes, calculating parity of each and combine
+        # into a single word of integrity bits
+        f.write("return ")
+        parity_bit_masks = enumerate(calc_bitmasks(k, m, codes, False))
+        f.write(" | ".join(
+                [f"(calc_parity(word & 0x{mask:x}) << {par_bit})" for par_bit,
+                    mask in parity_bit_masks]))
+
+        f.write(";\n}\n")
+
+    with open(c_h_filename, "a") as f:
+        # Write out function declaration in header
+        f.write(f"{out_type} enc_secded_{n}_{k}{suffix}"
+                f"(const uint8_t bytes[{in_bytes}]);\n")
+
+
+def format_c_files(c_src_filename, c_h_filename):
+    try:
+        # Call clang-format to in-place format generated C code. If there are
+        # any issues log a warning.
+        result = subprocess.run(['clang-format', '-i', c_src_filename,
+                                c_h_filename], stderr=subprocess.PIPE,
+                                universal_newlines=True)
+        result.check_returncode()
+    except Exception as e:
+        stderr = ''
+        if result:
+            stderr = '\n' + result.stderr
+
+        log.warning(f"Could not format generated C source: {e}{stderr}")
+
+
 def write_enc_dec_files(n, k, m, codes, suffix, outdir, codetype):
     enc_out = print_enc(n, k, m, codes)
 
@@ -644,6 +788,12 @@
         FPV output directory. The output files will have
         the base name `prim_secded_<n>_<k>_*_fpv` (default: %(default)s)
         ''')
+    parser.add_argument('--c_outdir',
+                        default='hw/ip/prim/dv/prim_secded',
+                        help='''
+        C output directory. The output files are named secded_enc.c and
+        secded_enc.h
+        ''')
     parser.add_argument('--verbose', '-v', action='store_true', help='Verbose')
 
     args = parser.parse_args()