blob: 479e3b1dcd5163af85c7b7da745c28f0a6608b00 [file] [log] [blame]
lowRISC Contributors802543a2019-08-31 12:12:56 +01001#!/usr/bin/env python3
2# Copyright lowRISC contributors.
3# Licensed under the Apache License, Version 2.0, see LICENSE for details.
4# SPDX-License-Identifier: Apache-2.0
5r"""SECDED encoder/decoder generator
6
7Current version doesn't optimize Fan-In. It uses Hsiao code (modified version
8of Hamming code + parity). Please refer https://arxiv.org/pdf/0803.1217.pdf
lowRISC Contributors802543a2019-08-31 12:12:56 +01009
Michael Schaffner960e57d2021-01-25 15:13:12 -080010For some further background and info on the differences between Hamming and
11Hsiao SECDED codes, refer to https://ieeexplore.ieee.org/document/8110065.g
12"""
Eunchan Kime19e99f2020-03-06 14:44:21 -080013
lowRISC Contributors802543a2019-08-31 12:12:56 +010014import argparse
15import itertools
16import logging as log
17import math
lowRISC Contributors802543a2019-08-31 12:12:56 +010018import random
Timothy Chen6c72d842021-03-05 16:53:19 -080019import hjson
Greg Chadwickc13cc282021-05-15 17:09:52 +010020import subprocess
lowRISC Contributors802543a2019-08-31 12:12:56 +010021
22COPYRIGHT = """// Copyright lowRISC contributors.
23// Licensed under the Apache License, Version 2.0, see LICENSE for details.
24// SPDX-License-Identifier: Apache-2.0
25//
26"""
Greg Chadwickc13cc282021-05-15 17:09:52 +010027
28C_SRC_TOP = """#include <stdbool.h>
29#include <stdint.h>
30
31#include "secded_enc.h"
32
33// Calculates even parity for a 64-bit word
34static uint8_t calc_parity(uint64_t word) {
35 bool parity = false;
36
37 while (word) {
38 if (word & 1) {
39 parity = !parity;
40 }
41
42 word >>= 1;
43 }
44
45 return parity;
46}
47"""
48
49C_H_TOP = """
50#ifndef OPENTITAN_HW_IP_PRIM_DV_PRIM_SECDED_SECDED_ENC_H_
51#define OPENTITAN_HW_IP_PRIM_DV_PRIM_SECDED_SECDED_ENC_H_
52
53#include <stdint.h>
54
55#ifdef __cplusplus
56extern "C" {
57#endif // __cplusplus
58
59// Integrity encode functions for varying bit widths matching the functionality
60// of the RTL modules of the same name. Each takes an array of bytes in
61// little-endian order and returns the calculated integrity bits.
62
63"""
64
65C_H_FOOT = """
66#ifdef __cplusplus
67} // extern "C"
68#endif // __cplusplus
69
70#endif // OPENTITAN_HW_IP_PRIM_DV_PRIM_SECDED_SECDED_ENC_H_
71"""
72
Timothy Chen6c72d842021-03-05 16:53:19 -080073CODE_OPTIONS = {'hsiao': '', 'hamming': '_hamming'}
Udi Jonnalagadda2f5e5cf2021-03-08 11:51:17 -080074PRINT_OPTIONS = {"logic": "assign ", "function": " "}
Timothy Chen6c72d842021-03-05 16:53:19 -080075
76# secded configurations
77SECDED_CFG_FILE = "util/design/data/secded_cfg.hjson"
lowRISC Contributors802543a2019-08-31 12:12:56 +010078
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +010079# The seed we use to initialise the PRNG when running the randomised algorithm
80# to choose constants for Hsiao codes.
81_RND_SEED = 123
82
Michael Schaffner960e57d2021-01-25 15:13:12 -080083
lowRISC Contributors802543a2019-08-31 12:12:56 +010084def min_paritysize(k):
85 # SECDED --> Hamming distance 'd': 4
86 # 2^(m-1) should cover (m+k)
87 for m in range(2, 10):
88 if 2**m >= (k + m + 1):
89 return m + 1
90 return -1
91
92
93def ideal_fanin(k, m):
94 """Compute Ideal Max Fanin of any bit in the ecc codes."""
95 fanin = 0
96 needed = k
97 for select in range(3, m + 1, 2):
98 combinations = list(itertools.combinations(range(m), select))
99 if len(combinations) <= needed:
100 fanin += int(math.ceil(float(len(combinations) * select) / m))
101 needed -= len(combinations)
102 else:
103 fanin += int(math.ceil(float(needed * select) / m))
104 needed = 0
105 if not needed:
106 break
107 return fanin
108
109
Eunchan Kime19e99f2020-03-06 14:44:21 -0800110def calc_fanin(width, codes):
111 """Sum the ones in a column"""
112 fanins = [0] * width
113 log.info("Calc Code: {}".format(codes))
114 for i in codes:
115 for e in i:
116 fanins[e] += 1
117
118 return fanins
119
120
Michael Schaffner960e57d2021-01-25 15:13:12 -0800121def calc_bitmasks(k, m, codes, dec):
122 # Transform fanin indices into bitmask.
123 fanin_masks = [0] * m
124 for i, c in enumerate(codes):
125 for j in c:
126 fanin_masks[j] += 1 << i
127 # For decode ops, include ECC bit position.
128 if dec:
129 for j in range(m):
130 fanin_masks[j] += 1 << (k + j)
131 return fanin_masks
lowRISC Contributors802543a2019-08-31 12:12:56 +0100132
133
Udi Jonnalagadda18c5c432021-05-05 10:04:49 -0700134def print_secded_enum_and_util_fns(cfgs):
135 enum_vals = [" SecdedNone"]
136 parity_width_vals = []
137 data_width_vals = []
138 for cfg in cfgs:
139 k = cfg['k']
140 m = cfg['m']
141 n = k + m
142 suffix = CODE_OPTIONS[cfg['code_type']]
143 formatted_suffix = suffix.replace('_', '').capitalize()
144
145 enum_name = " Secded%s_%s_%s" % (formatted_suffix, n, k)
146 enum_vals.append(enum_name)
147
148 parity_width = " %s: return %s;" % (enum_name, m)
149 parity_width_vals.append(parity_width)
150
151 data_width = " %s: return %s;" % (enum_name, k)
152 data_width_vals.append(data_width)
153
154 enum_str = ",\n".join(enum_vals)
155 parity_width_fn_str = "\n".join(parity_width_vals)
156 data_width_fn_str = "\n".join(data_width_vals)
157
158 enum_str = '''
159 typedef enum int {{
160{}
161 }} prim_secded_e;
162
163 function automatic int get_ecc_data_width(prim_secded_e ecc_type);
164 case (ecc_type)
165{}
166 // Return a non-zero width to avoid VCS compile issues
167 default: return 32;
168 endcase
169 endfunction
170
171 function automatic int get_ecc_parity_width(prim_secded_e ecc_type);
172 case (ecc_type)
173{}
174 default: return 0;
175 endcase
176 endfunction
177'''.format(enum_str, data_width_fn_str, parity_width_fn_str)
178
179 return enum_str
180
181
Udi Jonnalagaddac4547402021-03-08 16:54:46 -0800182def print_pkg_types(n, k, m, codes, suffix, codetype):
183 typename = "secded%s_%d_%d_t" % (suffix, n, k)
184
185 typestr = '''
186 typedef struct packed {{
187 logic [{}:0] data;
188 logic [{}:0] syndrome;
189 logic [1:0] err;
190 }} {};
191'''.format((k - 1), (m - 1), typename)
192
193 return typestr
194
195
Timothy Chen6c72d842021-03-05 16:53:19 -0800196def print_fn(n, k, m, codes, suffix, codetype):
197 enc_out = print_enc(n, k, m, codes)
Udi Jonnalagadda2f5e5cf2021-03-08 11:51:17 -0800198 dec_out = print_dec(n, k, m, codes, codetype, "function")
Timothy Chen6c72d842021-03-05 16:53:19 -0800199
Udi Jonnalagadda18c5c432021-05-05 10:04:49 -0700200 typename = "secded_%d_%d_t" % (n, k)
Timothy Chen6c72d842021-03-05 16:53:19 -0800201 module_name = "prim_secded%s_%d_%d" % (suffix, n, k)
202
203 outstr = '''
Philipp Wagner19044ed2021-05-10 17:23:13 +0100204 function automatic logic [{}:0] {}_enc (logic [{}:0] data_i);
205 logic [{}:0] data_o;
206{} return data_o;
Timothy Chen6c72d842021-03-05 16:53:19 -0800207 endfunction
Udi Jonnalagadda2f5e5cf2021-03-08 11:51:17 -0800208
Philipp Wagner19044ed2021-05-10 17:23:13 +0100209 function automatic {} {}_dec (logic [{}:0] data_i);
210 logic [{}:0] data_o;
Udi Jonnalagaddac4547402021-03-08 16:54:46 -0800211 logic [{}:0] syndrome_o;
212 logic [1:0] err_o;
213
214 {} dec;
215
Udi Jonnalagadda2f5e5cf2021-03-08 11:51:17 -0800216{}
Philipp Wagner19044ed2021-05-10 17:23:13 +0100217 dec.data = data_o;
Udi Jonnalagaddac4547402021-03-08 16:54:46 -0800218 dec.syndrome = syndrome_o;
219 dec.err = err_o;
220 return dec;
221
Udi Jonnalagadda2f5e5cf2021-03-08 11:51:17 -0800222 endfunction
223'''.format((n - 1), module_name, (k - 1), (n - 1), enc_out,
Udi Jonnalagaddac4547402021-03-08 16:54:46 -0800224 typename, module_name, (n - 1), (k - 1), (m - 1), typename, dec_out)
Timothy Chen6c72d842021-03-05 16:53:19 -0800225
226 return outstr
227
228
lowRISC Contributors802543a2019-08-31 12:12:56 +0100229def print_enc(n, k, m, codes):
Philipp Wagner19044ed2021-05-10 17:23:13 +0100230 outstr = " data_o = {}'(data_i);\n".format(n)
231 format_str = " data_o[{}] = ^(data_o & " + str(n) + "'h{:0" + str(
Michael Schaffner960e57d2021-01-25 15:13:12 -0800232 (n + 3) // 4) + "X});\n"
233 # Print parity computation
234 for j, mask in enumerate(calc_bitmasks(k, m, codes, False)):
235 outstr += format_str.format(j + k, mask)
lowRISC Contributors802543a2019-08-31 12:12:56 +0100236 return outstr
237
238
239def calc_syndrome(code):
Michael Schaffner960e57d2021-01-25 15:13:12 -0800240 log.info("in syndrome {}".format(code))
lowRISC Contributors802543a2019-08-31 12:12:56 +0100241 return sum(map((lambda x: 2**x), code))
242
Michael Schaffner960e57d2021-01-25 15:13:12 -0800243
Timothy Chen6c72d842021-03-05 16:53:19 -0800244def print_dec(n, k, m, codes, codetype, print_type="logic"):
245
246 preamble = PRINT_OPTIONS[print_type]
247
Michael Schaffner960e57d2021-01-25 15:13:12 -0800248 outstr = ""
Timothy Chen6c72d842021-03-05 16:53:19 -0800249 if codetype == "hsiao":
Udi Jonnalagaddac4547402021-03-08 16:54:46 -0800250 outstr += " {}logic single_error;\n".format(
251 preamble if print_type == "function" else "")
Timothy Chen6c72d842021-03-05 16:53:19 -0800252
Michael Schaffner960e57d2021-01-25 15:13:12 -0800253 outstr += "\n"
Udi Jonnalagadda2f5e5cf2021-03-08 11:51:17 -0800254 outstr += " {}// Syndrome calculation\n".format(
255 preamble if print_type == "function" else "")
Philipp Wagner19044ed2021-05-10 17:23:13 +0100256 format_str = " {}".format(preamble) + "syndrome_o[{}] = ^(data_i & " \
Timothy Chen6c72d842021-03-05 16:53:19 -0800257 + str(n) + "'h{:0" + str((n + 3) // 4) + "X});\n"
258
Michael Schaffner960e57d2021-01-25 15:13:12 -0800259 # Print syndrome computation
260 for j, mask in enumerate(calc_bitmasks(k, m, codes, True)):
261 outstr += format_str.format(j, mask)
262 outstr += "\n"
Udi Jonnalagadda2f5e5cf2021-03-08 11:51:17 -0800263 outstr += " {}// Corrected output calculation\n".format(
264 preamble if print_type == "function" else "")
Michael Schaffner960e57d2021-01-25 15:13:12 -0800265 for i in range(k):
Udi Jonnalagadda18c5c432021-05-05 10:04:49 -0700266 outstr += " {}".format(preamble)
267 outstr += "data_o[%d] = (syndrome_o == %d'h%x) ^ data_i[%d];\n" % (
Michael Schaffner960e57d2021-01-25 15:13:12 -0800268 i, m, calc_syndrome(codes[i]), i)
269 outstr += "\n"
Udi Jonnalagadda2f5e5cf2021-03-08 11:51:17 -0800270 outstr += " {}// err_o calc. bit0: single error, bit1: double error\n".format(
271 preamble if print_type == "function" else "")
Michael Schaffner960e57d2021-01-25 15:13:12 -0800272 # The Hsiao and Hamming syndromes are interpreted slightly differently.
273 if codetype == "hamming":
Timothy Chen6c72d842021-03-05 16:53:19 -0800274 outstr += " {}".format(preamble) + "err_o[0] = syndrome_o[%d];\n" % (m - 1)
275 outstr += " {}".format(preamble) + "err_o[1] = |syndrome_o[%d:0] & ~syndrome_o[%d];\n" % (
Michael Schaffner960e57d2021-01-25 15:13:12 -0800276 m - 2, m - 1)
277 else:
Timothy Chen6c72d842021-03-05 16:53:19 -0800278 outstr += " {}".format(preamble) + "single_error = ^syndrome_o;\n"
279 outstr += " {}".format(preamble) + "err_o[0] = single_error;\n"
280 outstr += " {}".format(preamble) + "err_o[1] = ~single_error & (|syndrome_o);\n"
Michael Schaffner960e57d2021-01-25 15:13:12 -0800281 return outstr
282
283
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800284# return whether an integer is a power of 2
285def is_pow2(n):
Michael Schaffner960e57d2021-01-25 15:13:12 -0800286 return (n & (n - 1) == 0) and n != 0
287
lowRISC Contributors802543a2019-08-31 12:12:56 +0100288
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800289def is_odd(n):
290 return (n % 2) > 0
lowRISC Contributors802543a2019-08-31 12:12:56 +0100291
Michael Schaffner960e57d2021-01-25 15:13:12 -0800292
Timothy Chen6c72d842021-03-05 16:53:19 -0800293def verify(cfgs):
294 error = 0
295 for cfg in cfgs['cfgs']:
296 if (cfg['k'] <= 1 or cfg['k'] > 120):
297 error += 1
298 log.error("Current tool doesn't support the value k (%d)", cfg['k'])
299
300 if (cfg['m'] <= 1 or cfg['m'] > 20):
301 error += 1
302 log.error("Current tool doesn't support the value m (%d)", cfg['m'])
303
304 # Calculate 'm' (parity size)
305 min_m = min_paritysize(cfg['k'])
306 if (cfg['m'] < min_m):
307 error += 1
308 log.error("given \'m\' argument is smaller than minimum requirement " +
309 "using calculated minimum (%d)", min_m)
310
311 # Error check code selection
312 if (cfg['code_type'] not in CODE_OPTIONS):
313 error += 1
314 log.error("Invalid code {} selected, use one of {}".format(
315 cfg['code_type'], CODE_OPTIONS))
316
317 return error
318
319
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100320def gen_code(codetype, k, m):
321 # The hsiao_code generator uses (pseudo)random values to pick good ECC
322 # constants. Rather than exposing the seed, we pick a fixed one here to
323 # ensure everything stays stable in future.
324 old_rnd_state = random.getstate()
325 random.seed(_RND_SEED)
326 try:
327 return globals()["_{}_code".format(codetype)](k, m)
328 finally:
329 random.setstate(old_rnd_state)
330
331
332def generate(cfgs, args):
Timothy Chen6c72d842021-03-05 16:53:19 -0800333 pkg_out_str = ""
Udi Jonnalagaddac4547402021-03-08 16:54:46 -0800334 pkg_type_str = ""
Greg Chadwickc13cc282021-05-15 17:09:52 +0100335
336 c_src_filename = args.c_outdir + "/" + "secded_enc.c"
337 c_h_filename = args.c_outdir + "/" + "secded_enc.h"
338
339 with open(c_src_filename, "w") as f:
340 f.write(COPYRIGHT)
341 f.write("// SECDED encode code generated by\n")
342 f.write(f"// util/design/secded_gen.py from {SECDED_CFG_FILE}\n\n")
343 f.write(C_SRC_TOP)
344
345 with open(c_h_filename, "w") as f:
346 f.write(COPYRIGHT)
347 f.write("// SECDED encode code generated by\n")
348 f.write(f"// util/design/secded_gen.py from {SECDED_CFG_FILE}\n")
349 f.write(C_H_TOP)
350
Timothy Chen6c72d842021-03-05 16:53:19 -0800351 for cfg in cfgs['cfgs']:
352 log.debug("Working on {}".format(cfg))
353 k = cfg['k']
354 m = cfg['m']
355 n = k + m
356 codetype = cfg['code_type']
357 suffix = CODE_OPTIONS[codetype]
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100358 codes = gen_code(codetype, k, m)
Timothy Chen6c72d842021-03-05 16:53:19 -0800359
360 # write out rtl files
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100361 write_enc_dec_files(n, k, m, codes, suffix, args.outdir, codetype)
Timothy Chen6c72d842021-03-05 16:53:19 -0800362
Greg Chadwickc13cc282021-05-15 17:09:52 +0100363 # write out C files, only hsiao codes are supported
364 if codetype == "hsiao":
365 write_c_files(n, k, m, codes, suffix, c_src_filename, c_h_filename)
366
Udi Jonnalagaddac4547402021-03-08 16:54:46 -0800367 # write out package typedefs
368 pkg_type_str += print_pkg_types(n, k, m, codes, suffix, codetype)
Timothy Chen6c72d842021-03-05 16:53:19 -0800369 # print out functions
370 pkg_out_str += print_fn(n, k, m, codes, suffix, codetype)
371
372 if not args.no_fpv:
Cindy Chen2f3bba82021-05-24 16:35:41 -0700373 write_fpv_files(n, k, m, codes, suffix, args.fpv_outdir)
Timothy Chen6c72d842021-03-05 16:53:19 -0800374
Greg Chadwickc13cc282021-05-15 17:09:52 +0100375 with open(c_h_filename, "a") as f:
376 f.write(C_H_FOOT)
377
378 format_c_files(c_src_filename, c_h_filename)
379
Udi Jonnalagadda18c5c432021-05-05 10:04:49 -0700380 # create enum of various ECC types - useful for DV purposes in mem_bkdr_if
381 enum_str = print_secded_enum_and_util_fns(cfgs['cfgs'])
Timothy Chen6c72d842021-03-05 16:53:19 -0800382 # write out package file
Udi Jonnalagadda18c5c432021-05-05 10:04:49 -0700383 full_pkg_str = enum_str + pkg_type_str + pkg_out_str
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100384 write_pkg_file(args.outdir, full_pkg_str)
Timothy Chen6c72d842021-03-05 16:53:19 -0800385
386
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800387# k = data bits
388# m = parity bits
389# generate hsiao code
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100390def _hsiao_code(k, m):
lowRISC Contributors802543a2019-08-31 12:12:56 +0100391 # using itertools combinations, generate odd number of 1 in a row
392
393 required_row = k # k rows are needed, decreasing everytime when it acquite
394
395 fanin_ideal = ideal_fanin(k, m)
396 log.info("Ideal Fan-In value: %d" % fanin_ideal)
397
Eunchan Kime19e99f2020-03-06 14:44:21 -0800398 # Each entry represents a row in below parity matrix
399 # Entry is tuple and the value inside is the position of ones
400 # e.g. (0,1,2) in m:=7
401 # row -> [1 1 1 0 0 0 0]
lowRISC Contributors802543a2019-08-31 12:12:56 +0100402 codes = []
403
Michael Schaffner960e57d2021-01-25 15:13:12 -0800404 # Find code matrix =======================================================
Eunchan Kime19e99f2020-03-06 14:44:21 -0800405 # This is main part to find the parity matrix.
Eunchan Kim920b48f2020-03-12 09:40:57 -0700406 # For example, find SECDED for 4bit message is to find 4x4 matrix as below
407 # | 1 0 0 0 x x x x |
408 # | 0 1 0 0 x x x x |
409 # | 0 0 1 0 x x x x |
410 # | 0 0 0 1 x x x x |
Eunchan Kime19e99f2020-03-06 14:44:21 -0800411 # Then message _k_ X matrix_code ==> original message with parity
412 #
413 # Make a row to have even number of 1 including the I matrix.
414 # This helps to calculate the syndrom at the decoding stage.
415 # To reduce the max fan-in, Starting with smallest number 3.
416 # the number means the number of one in a row.
417 # Small number of ones means smaller fan-in overall.
418
lowRISC Contributors802543a2019-08-31 12:12:56 +0100419 for step in range(3, m + 1, 2):
420 # starting from 3 as I matrix represents data
421 # Increased by 2 as number of 1 should be even in a row (odd excluding I)
422
Eunchan Kime19e99f2020-03-06 14:44:21 -0800423 # get the list of combinations [0, .., m-1] with `step`
424 # e.g. step := 3 ==> [(0,1,2), (0,1,3), ... ]
lowRISC Contributors802543a2019-08-31 12:12:56 +0100425 candidate = list(itertools.combinations(range(m), step))
426
lowRISC Contributors802543a2019-08-31 12:12:56 +0100427 if len(candidate) <= required_row:
428 # we need more round use all of them
429 codes.extend(candidate)
430 required_row -= len(candidate)
431 else:
Michael Schaffner960e57d2021-01-25 15:13:12 -0800432 # Find optimized fan-in ==========================================
Eunchan Kime19e99f2020-03-06 14:44:21 -0800433
434 # Calculate each row fan-in with current
435 fanins = calc_fanin(m, codes)
436 while required_row != 0:
437 # Let's shuffle
438 # Shuffling makes the sequence randomized --> it reduces the
439 # fanin as the code takes randomly at the end of the round
440
441 # TODO: There should be a clever way to find the subset without
442 # random retrying.
443 # Suggested this algorithm
444 # https://en.wikipedia.org/wiki/Assignment_problem
445 random.shuffle(candidate)
446
447 # Take a subset
448 subset = candidate[0:required_row]
449
450 subset_fanins = calc_fanin(m, subset)
451 # Check if it exceeds Ideal Fan-In
452 ideal = True
453 for i in range(m):
454 if fanins[i] + subset_fanins[i] > fanin_ideal:
455 # Exceeded. Retry
456 ideal = False
457 break
458
459 if ideal:
460 required_row = 0
461
462 # Append to the code matrix
463 codes.extend(subset)
lowRISC Contributors802543a2019-08-31 12:12:56 +0100464
465 if required_row == 0:
466 # Found everything!
467 break
468
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800469 log.info("Hsiao codes {}".format(codes))
470 return codes
471
Michael Schaffner960e57d2021-01-25 15:13:12 -0800472
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800473# n = total bits
474# k = data bits
475# m = parity bits
476# generate hamming code
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100477def _hamming_code(k, m):
Timothy Chen6c72d842021-03-05 16:53:19 -0800478
479 n = k + m
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800480
481 # construct a list of code tuples.
482 # Tuple corresponds to each bit position and shows which parity bit it participates in
483 # Only the data bits are shown, the parity bits are not.
484 codes = []
Michael Schaffner960e57d2021-01-25 15:13:12 -0800485 for pos in range(1, n + 1):
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800486 # this is a valid parity bit position or the final parity bit
487 if (is_pow2(pos) or pos == n):
488 continue
489 else:
490 code = ()
491 for p in range(m):
492
493 # this is the starting parity position
494 parity_pos = 2**p
495
496 # back-track to the closest parity bit multiple and see if it is even or odd
497 # If even, we are in the skip phase, do not include
498 # If odd, we are in the include phase
499 parity_chk = int((pos - (pos % parity_pos)) / parity_pos)
Michael Schaffner960e57d2021-01-25 15:13:12 -0800500 log.debug("At position {} parity value {}, {}".format(
501 pos, parity_pos, parity_chk))
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800502
503 # valid for inclusion or final parity bit that includes everything
Michael Schaffner960e57d2021-01-25 15:13:12 -0800504 if is_odd(parity_chk) or p == m - 1:
505 code = code + (p, )
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800506 log.info("add {} to tuple {}".format(p, code))
507
508 codes.append(code)
509
Michael Schaffner960e57d2021-01-25 15:13:12 -0800510 # final parity bit includes all ECC bits
511 for p in range(m - 1):
512 codes.append((m - 1, ))
513
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800514 log.info("Hamming codes {}".format(codes))
515 return codes
516
517
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100518def write_pkg_file(outdir, pkg_str):
Timothy Chen6c72d842021-03-05 16:53:19 -0800519 with open(outdir + "/" + "prim_secded_pkg.sv", "w") as f:
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100520 outstr = '''{}// SECDED package generated by
521// util/design/secded_gen.py from {}
Timothy Chen6c72d842021-03-05 16:53:19 -0800522
523package prim_secded_pkg;
524{}
525
526endpackage
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100527'''.format(COPYRIGHT, SECDED_CFG_FILE, pkg_str)
Timothy Chen6c72d842021-03-05 16:53:19 -0800528 f.write(outstr)
529
530
Greg Chadwickc13cc282021-05-15 17:09:52 +0100531def bytes_to_c_type(num_bytes):
532 if num_bytes == 1:
533 return 'uint8_t'
534 elif num_bytes <= 2:
535 return 'uint16_t'
536 elif num_bytes <= 4:
537 return 'uint32_t'
538 elif num_bytes <= 8:
539 return 'uint64_t'
540
541 return None
542
543
544def write_c_files(n, k, m, codes, suffix, c_src_filename, c_h_filename):
545 in_bytes = math.ceil(k / 8)
546 out_bytes = math.ceil(m / 8)
547
548 if (k > 64):
549 log.warning(f"Cannot generate C encoder for k = {k}."
550 " The tool has no support for k > 64 for C encoder "
551 "generation")
552 return
553
554 in_type = bytes_to_c_type(in_bytes)
555 out_type = bytes_to_c_type(out_bytes)
556
557 assert in_type
558 assert out_type
559
560 with open(c_src_filename, "a") as f:
561 # Write out function prototype in src
562 f.write(f"\n{out_type} enc_secded_{n}_{k}{suffix}"
563 f"(const uint8_t bytes[{in_bytes}]) {{\n")
564
565 # Form a single word from the incoming byte data
566 f.write(f"{in_type} word = ")
567 f.write(" | ".join(
568 [f"(({in_type})bytes[{i}] << {i*8})" for i in range(in_bytes)]))
569 f.write(";\n\n")
570
571 # AND the word with the codes, calculating parity of each and combine
572 # into a single word of integrity bits
573 f.write("return ")
574 parity_bit_masks = enumerate(calc_bitmasks(k, m, codes, False))
575 f.write(" | ".join(
576 [f"(calc_parity(word & 0x{mask:x}) << {par_bit})" for par_bit,
577 mask in parity_bit_masks]))
578
579 f.write(";\n}\n")
580
581 with open(c_h_filename, "a") as f:
582 # Write out function declaration in header
583 f.write(f"{out_type} enc_secded_{n}_{k}{suffix}"
584 f"(const uint8_t bytes[{in_bytes}]);\n")
585
586
587def format_c_files(c_src_filename, c_h_filename):
588 try:
589 # Call clang-format to in-place format generated C code. If there are
590 # any issues log a warning.
591 result = subprocess.run(['clang-format', '-i', c_src_filename,
592 c_h_filename], stderr=subprocess.PIPE,
593 universal_newlines=True)
594 result.check_returncode()
595 except Exception as e:
596 stderr = ''
597 if result:
598 stderr = '\n' + result.stderr
599
600 log.warning(f"Could not format generated C source: {e}{stderr}")
601
602
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100603def write_enc_dec_files(n, k, m, codes, suffix, outdir, codetype):
Michael Schaffner960e57d2021-01-25 15:13:12 -0800604 enc_out = print_enc(n, k, m, codes)
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800605
Michael Schaffner960e57d2021-01-25 15:13:12 -0800606 module_name = "prim_secded%s_%d_%d" % (suffix, n, k)
607
608 with open(outdir + "/" + module_name + "_enc.sv", "w") as f:
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100609 outstr = '''{}// SECDED encoder generated by util/design/secded_gen.py
Michael Schaffner960e57d2021-01-25 15:13:12 -0800610
611module {}_enc (
Philipp Wagner19044ed2021-05-10 17:23:13 +0100612 input [{}:0] data_i,
613 output logic [{}:0] data_o
Michael Schaffner960e57d2021-01-25 15:13:12 -0800614);
615
Timothy Chen6c72d842021-03-05 16:53:19 -0800616 always_comb begin : p_encode
617{} end
618
Michael Schaffner960e57d2021-01-25 15:13:12 -0800619endmodule : {}_enc
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100620'''.format(COPYRIGHT, module_name, (k - 1), (n - 1), enc_out, module_name)
Michael Schaffner960e57d2021-01-25 15:13:12 -0800621 f.write(outstr)
622
623 dec_out = print_dec(n, k, m, codes, codetype)
624
625 with open(outdir + "/" + module_name + "_dec.sv", "w") as f:
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100626 outstr = '''{}// SECDED decoder generated by util/design/secded_gen.py
Michael Schaffner960e57d2021-01-25 15:13:12 -0800627
628module {}_dec (
Philipp Wagner19044ed2021-05-10 17:23:13 +0100629 input [{}:0] data_i,
630 output logic [{}:0] data_o,
Michael Schaffner960e57d2021-01-25 15:13:12 -0800631 output logic [{}:0] syndrome_o,
632 output logic [1:0] err_o
633);
634
635{}
636endmodule : {}_dec
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100637'''.format(COPYRIGHT, module_name, (n - 1), (k - 1), (m - 1),
638 dec_out, module_name)
Michael Schaffner960e57d2021-01-25 15:13:12 -0800639 f.write(outstr)
640
641
642def write_fpv_files(n, k, m, codes, suffix, outdir):
643 module_name = "prim_secded%s_%d_%d" % (suffix, n, k)
644
645 with open(outdir + "/tb/" + module_name + "_fpv.sv", "w") as f:
646 outstr = '''{}// SECDED FPV testbench generated by util/design/secded_gen.py
647
648module {}_fpv (
649 input clk_i,
650 input rst_ni,
Philipp Wagner19044ed2021-05-10 17:23:13 +0100651 input [{}:0] data_i,
652 output logic [{}:0] data_o,
Michael Schaffner960e57d2021-01-25 15:13:12 -0800653 output logic [{}:0] syndrome_o,
654 output logic [1:0] err_o,
655 input [{}:0] error_inject_i
656);
657
658 logic [{}:0] data_enc;
659
660 {}_enc {}_enc (
Philipp Wagner19044ed2021-05-10 17:23:13 +0100661 .data_i,
662 .data_o(data_enc)
Michael Schaffner960e57d2021-01-25 15:13:12 -0800663 );
664
665 {}_dec {}_dec (
Philipp Wagner19044ed2021-05-10 17:23:13 +0100666 .data_i(data_enc ^ error_inject_i),
667 .data_o,
Michael Schaffner960e57d2021-01-25 15:13:12 -0800668 .syndrome_o,
669 .err_o
670 );
671
672endmodule : {}_fpv
673'''.format(COPYRIGHT, module_name, (k - 1), (k - 1), (m - 1), (n - 1), (n - 1),
674 module_name, module_name, module_name, module_name, module_name)
675 f.write(outstr)
676
677 with open(outdir + "/vip/" + module_name + "_assert_fpv.sv", "w") as f:
678 outstr = '''{}// SECDED FPV assertion file generated by util/design/secded_gen.py
679
680module {}_assert_fpv (
681 input clk_i,
682 input rst_ni,
Philipp Wagner19044ed2021-05-10 17:23:13 +0100683 input [{}:0] data_i,
684 input [{}:0] data_o,
Michael Schaffner960e57d2021-01-25 15:13:12 -0800685 input [{}:0] syndrome_o,
686 input [1:0] err_o,
687 input [{}:0] error_inject_i
688);
689
690 // Inject a maximum of two errors simultaneously.
691 `ASSUME_FPV(MaxTwoErrors_M, $countones(error_inject_i) <= 2)
692 // This bounds the input data state space to make sure the solver converges.
Philipp Wagner19044ed2021-05-10 17:23:13 +0100693 `ASSUME_FPV(DataLimit_M, $onehot0(data_i) || $onehot0(~data_i))
Michael Schaffner960e57d2021-01-25 15:13:12 -0800694 // Single bit error detection
695 `ASSERT(SingleErrorDetect_A, $countones(error_inject_i) == 1 |-> err_o[0])
696 `ASSERT(SingleErrorDetectReverse_A, err_o[0] |-> $countones(error_inject_i) == 1)
697 // Double bit error detection
698 `ASSERT(DoubleErrorDetect_A, $countones(error_inject_i) == 2 |-> err_o[1])
699 `ASSERT(DoubleErrorDetectReverse_A, err_o[1] |-> $countones(error_inject_i) == 2)
700 // Single bit error correction (implicitly tests the syndrome output)
Cindy Chen2f3bba82021-05-24 16:35:41 -0700701 `ASSERT(SingleErrorCorrect_A, $countones(error_inject_i) < 2 |-> data_i == data_o)
Michael Schaffner960e57d2021-01-25 15:13:12 -0800702 // Basic syndrome check
703 `ASSERT(SyndromeCheck_A, |syndrome_o |-> $countones(error_inject_i) > 0)
704 `ASSERT(SyndromeCheckReverse_A, $countones(error_inject_i) > 0 |-> |syndrome_o)
705
706endmodule : {}_assert_fpv
707'''.format(COPYRIGHT, module_name, (k - 1), (k - 1), (m - 1), (n - 1),
708 module_name)
709 f.write(outstr)
710
711 with open(outdir + "/tb/" + module_name + "_bind_fpv.sv", "w") as f:
712 outstr = '''{}// SECDED FPV bind file generated by util/design/secded_gen.py
713
714module {}_bind_fpv;
715
716 bind {}_fpv
717 {}_assert_fpv {}_assert_fpv (
718 .clk_i,
719 .rst_ni,
Philipp Wagner19044ed2021-05-10 17:23:13 +0100720 .data_i,
721 .data_o,
Michael Schaffner960e57d2021-01-25 15:13:12 -0800722 .syndrome_o,
723 .err_o,
724 .error_inject_i
725 );
726
727endmodule : {}_bind_fpv
728'''.format(COPYRIGHT, module_name, module_name, module_name, module_name,
729 module_name)
730 f.write(outstr)
731
732 with open(outdir + "/" + module_name + "_fpv.core", "w") as f:
733 outstr = '''CAPI=2:
734# Copyright lowRISC contributors.
735# Licensed under the Apache License, Version 2.0, see LICENSE for details.
736# SPDX-License-Identifier: Apache-2.0
737name: "lowrisc:fpv:{}_fpv:0.1"
738description: "SECDED FPV target"
739filesets:
740 files_formal:
741 depend:
742 - lowrisc:prim:all
743 - lowrisc:prim:secded
744 files:
745 - vip/{}_assert_fpv.sv
746 - tb/{}_fpv.sv
747 - tb/{}_bind_fpv.sv
748 file_type: systemVerilogSource
749
750targets:
751 default: &default_target
752 # note, this setting is just used
753 # to generate a file list for jg
754 default_tool: icarus
755 filesets:
756 - files_formal
757 toplevel:
758 - {}_fpv
759
760 formal:
761 <<: *default_target
762
763 lint:
764 <<: *default_target
765
766'''.format(module_name, module_name, module_name, module_name, module_name)
767 f.write(outstr)
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800768
769
770def main():
771 parser = argparse.ArgumentParser(
772 prog="secded_gen",
773 description='''This tool generates Single Error Correction Double Error
774 Detection(SECDED) encoder and decoder modules in SystemVerilog.
775 ''')
Michael Schaffner960e57d2021-01-25 15:13:12 -0800776 parser.add_argument('--no_fpv',
777 action='store_true',
778 help='Do not generate FPV testbench.')
779 parser.add_argument('--outdir',
780 default='hw/ip/prim/rtl/',
781 help='''
782 Output directory. The output file will be named
783 `prim_secded_<n>_<k>_enc/dec.sv` (default: %(default)s)
784 ''')
785 parser.add_argument('--fpv_outdir',
786 default='hw/ip/prim/fpv/',
787 help='''
788 FPV output directory. The output files will have
789 the base name `prim_secded_<n>_<k>_*_fpv` (default: %(default)s)
790 ''')
Greg Chadwickc13cc282021-05-15 17:09:52 +0100791 parser.add_argument('--c_outdir',
792 default='hw/ip/prim/dv/prim_secded',
793 help='''
794 C output directory. The output files are named secded_enc.c and
795 secded_enc.h
796 ''')
Timothy Cheneb4b0d32020-12-28 12:32:53 -0800797 parser.add_argument('--verbose', '-v', action='store_true', help='Verbose')
798
799 args = parser.parse_args()
800
801 if (args.verbose):
802 log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG)
803 else:
804 log.basicConfig(format="%(levelname)s: %(message)s")
805
Timothy Chen6c72d842021-03-05 16:53:19 -0800806 with open(SECDED_CFG_FILE, 'r') as infile:
807 config = hjson.load(infile)
lowRISC Contributors802543a2019-08-31 12:12:56 +0100808
Timothy Chen6c72d842021-03-05 16:53:19 -0800809 # Error checking
810 error = verify(config)
811 if (error):
812 exit(1)
lowRISC Contributors802543a2019-08-31 12:12:56 +0100813
Timothy Chen6c72d842021-03-05 16:53:19 -0800814 # Generate outputs
Rupert Swarbrickd6bdc5e2021-04-19 11:08:08 +0100815 generate(config, args)
lowRISC Contributors802543a2019-08-31 12:12:56 +0100816
lowRISC Contributors802543a2019-08-31 12:12:56 +0100817
lowRISC Contributors802543a2019-08-31 12:12:56 +0100818if __name__ == "__main__":
819 main()