blob: f934216e1c131f73bc5562eb9efcd006029fd454 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
r"""SECDED encoder/decoder generator
Current version doesn't optimize Fan-In. It uses Hsiao code (modified version
of Hamming code + parity). Please refer https://arxiv.org/pdf/0803.1217.pdf
"""
# TODO: Add FPV assertions in the encoder/decoder module
import argparse
import itertools
import logging as log
import math
import os
import random
import sys
import time
from pathlib import PurePath
COPYRIGHT = """// Copyright lowRISC contributors.
// Licensed under the Apache License, Version 2.0, see LICENSE for details.
// SPDX-License-Identifier: Apache-2.0
//
"""
def min_paritysize(k):
# SECDED --> Hamming distance 'd': 4
# 2^(m-1) should cover (m+k)
for m in range(2, 10):
if 2**m >= (k + m + 1):
return m + 1
return -1
def ideal_fanin(k, m):
"""Compute Ideal Max Fanin of any bit in the ecc codes."""
fanin = 0
needed = k
for select in range(3, m + 1, 2):
combinations = list(itertools.combinations(range(m), select))
if len(combinations) <= needed:
fanin += int(math.ceil(float(len(combinations) * select) / m))
needed -= len(combinations)
else:
fanin += int(math.ceil(float(needed * select) / m))
needed = 0
if not needed:
break
return fanin
def calc_fanin(width, codes):
"""Sum the ones in a column"""
fanins = [0] * width
log.info("Calc Code: {}".format(codes))
for i in codes:
for e in i:
fanins[e] += 1
return fanins
def print_comb(n,
k,
m,
cur_m,
codes,
start_cnt,
max_width=100,
prefix="",
first_indent=0):
"""Print XOR comb.
@param[max_width] Maximum Width of str
@param[prefix] The prepend string at the first line
@param[first_indent] The number of character that indented at the first line
e.g. first_indent := 2
{prefix}in[nn] ...
^ in[nn] ^ in[nn]
result:
{prefix}in[nn] ^ ... in[nn]
^ in[nn] ^ ... in[nn];
"""
outstr = ""
line = prefix
prepend_len = len(prefix)
cnt = start_cnt
first = True
for j in range(k):
temp_str = ""
if cur_m in codes[j]:
if not first:
temp_str += " ^"
if first:
first = False
temp_str += " in[%d]" % (j)
temp_len = len(temp_str)
if len(line) + temp_len > max_width:
outstr += line + "\n"
line = ' ' * (prepend_len - first_indent) + temp_str
else:
line += temp_str
outstr += line + ";\n"
return outstr
def print_enc(n, k, m, codes):
outstr = ""
for i in range(k):
outstr += " assign out[%d] = in[%d] ;\n" % (i, i)
for i in range(m):
# Print parity computation
outstr += print_comb(n, k, m, i, codes, 0, 100,
" assign out[%d] =" % (i + k), 2)
return outstr
def calc_syndrome(code):
return sum(map((lambda x: 2**x), code))
def print_dec(n, k, m, codes):
outstr = ""
outstr += " logic single_error;\n"
outstr += "\n"
outstr += " // Syndrome calculation\n"
for i in range(m):
# Print combination
outstr += print_comb(n, k, m, i, codes, 1, 100,
" assign syndrome_o[%d] = in[%d] ^" % (i, k + i),
len(" in[%d] ^" % (k + i)) + 2)
outstr += "\n"
outstr += " // Corrected output calculation\n"
for i in range(k):
synd_v = calc_syndrome(codes[i])
outstr += " assign d_o[%d] = (syndrome_o == %d'h%x) ^ in[%d];\n" % (
i, m, calc_syndrome(codes[i]), i)
outstr += "\n"
outstr += " // err_o calc. bit0: single error, bit1: double error\n"
outstr += " assign single_error = ^syndrome_o;\n"
outstr += " assign err_o[0] = single_error;\n"
outstr += " assign err_o[1] = ~single_error & (|syndrome_o);\n"
return outstr
def main():
parser = argparse.ArgumentParser(
prog="secded_gen",
description='''This tool generates Single Error Correction Double Error
Detection(SECDED) encoder and decoder modules in SystemVerilog.
''')
parser.add_argument(
'-m',
type=int,
default=7,
help=
'parity length. If fan-in is too big, increasing m helps. (default: %(default)s)'
)
parser.add_argument(
'-k',
type=int,
default=32,
help=
'code length. Minimum \'m\' is calculated by the tool (default: %(default)s)'
)
parser.add_argument(
'--outdir',
default='../rtl',
help=
'output directory. The output file will be named `prim_secded_<n>_<k>_enc/dec.sv` (default: %(default)s)'
)
parser.add_argument('--verbose', '-v', action='store_true', help='Verbose')
args = parser.parse_args()
if (args.verbose):
log.basicConfig(format="%(levelname)s: %(message)s", level=log.DEBUG)
else:
log.basicConfig(format="%(levelname)s: %(message)s")
# Error checking
if (args.k <= 1 or args.k > 120):
log.error("Current tool doesn't support the value k (%d)", args.k)
k = args.k
if (args.m <= 1 or args.m > 20):
log.error("Current tool doesn't support the value m (%d)", args.m)
# Calculate 'm' (parity size)
min_m = min_paritysize(k)
if (args.m < min_m):
log.error("given \'m\' argument is smaller than minimum requirement")
m = min_m
else:
m = args.m
n = m + k
log.info("n(%d), k(%d), m(%d)", n, k, m)
random.seed(time.time())
# using itertools combinations, generate odd number of 1 in a row
required_row = k # k rows are needed, decreasing everytime when it acquite
fanin_ideal = ideal_fanin(k, m)
log.info("Ideal Fan-In value: %d" % fanin_ideal)
# Each entry represents a row in below parity matrix
# Entry is tuple and the value inside is the position of ones
# e.g. (0,1,2) in m:=7
# row -> [1 1 1 0 0 0 0]
codes = []
## Find code matrix =======================================================
# This is main part to find the parity matrix.
# For example, find SECDED for 4bit message is to find 4x4 matrix as below
# | 1 0 0 0 x x x x |
# | 0 1 0 0 x x x x |
# | 0 0 1 0 x x x x |
# | 0 0 0 1 x x x x |
# Then message _k_ X matrix_code ==> original message with parity
#
# Make a row to have even number of 1 including the I matrix.
# This helps to calculate the syndrom at the decoding stage.
# To reduce the max fan-in, Starting with smallest number 3.
# the number means the number of one in a row.
# Small number of ones means smaller fan-in overall.
for step in range(3, m + 1, 2):
# starting from 3 as I matrix represents data
# Increased by 2 as number of 1 should be even in a row (odd excluding I)
# get the list of combinations [0, .., m-1] with `step`
# e.g. step := 3 ==> [(0,1,2), (0,1,3), ... ]
candidate = list(itertools.combinations(range(m), step))
if len(candidate) <= required_row:
# we need more round use all of them
codes.extend(candidate)
required_row -= len(candidate)
else:
## Find optimized fan-in ==========================================
# Calculate each row fan-in with current
fanins = calc_fanin(m, codes)
while required_row != 0:
# Let's shuffle
# Shuffling makes the sequence randomized --> it reduces the
# fanin as the code takes randomly at the end of the round
# TODO: There should be a clever way to find the subset without
# random retrying.
# Suggested this algorithm
# https://en.wikipedia.org/wiki/Assignment_problem
random.shuffle(candidate)
# Take a subset
subset = candidate[0:required_row]
subset_fanins = calc_fanin(m, subset)
# Check if it exceeds Ideal Fan-In
ideal = True
for i in range(m):
if fanins[i] + subset_fanins[i] > fanin_ideal:
# Exceeded. Retry
ideal = False
break
if ideal:
required_row = 0
# Append to the code matrix
codes.extend(subset)
if required_row == 0:
# Found everything!
break
log.info(codes)
# Print Encoder
enc_out = print_enc(n, k, m, codes)
#log.info(enc_out)
module_name = "prim_secded_%d_%d" % (n, k)
with open(args.outdir + "/" + module_name + "_enc.sv", "w") as f:
f.write(COPYRIGHT)
f.write("// SECDED Encoder generated by secded_gen.py\n\n")
f.write("module " + module_name + "_enc (\n")
f.write(" input [%d:0] in,\n" % (k - 1))
f.write(" output logic [%d:0] out\n" % (n - 1))
f.write(");\n\n")
f.write(enc_out)
f.write("endmodule\n\n")
dec_out = print_dec(n, k, m, codes)
with open(args.outdir + "/" + module_name + "_dec.sv", "w") as f:
f.write(COPYRIGHT)
f.write("// SECDED Decoder generated by secded_gen.py\n\n")
f.write("module " + module_name + "_dec (\n")
f.write(" input [%d:0] in,\n" % (n - 1))
f.write(" output logic [%d:0] d_o,\n" % (k - 1))
f.write(" output logic [%d:0] syndrome_o,\n" % (m - 1))
f.write(" output logic [1:0] err_o\n")
f.write(");\n\n")
f.write(dec_out)
f.write("endmodule\n\n")
if __name__ == "__main__":
main()