| # Copyright lowRISC contributors. |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| # SPDX-License-Identifier: Apache-2.0 |
| r"""Shared subfunctions. |
| """ |
| import random |
| import textwrap |
| from math import ceil, log2 |
| |
| |
| def wrapped_docstring(): |
| '''Return a text-wrapped version of the module docstring''' |
| paras = [] |
| para = [] |
| for line in __doc__.strip().split('\n'): |
| line = line.strip() |
| if not line: |
| if para: |
| paras.append('\n'.join(para)) |
| para = [] |
| else: |
| para.append(line) |
| if para: |
| paras.append('\n'.join(para)) |
| |
| return '\n\n'.join(textwrap.fill(p) for p in paras) |
| |
| |
| def check_bool(x): |
| """check_bool checks if input 'x' either a bool or |
| one of the following strings: ["true", "false"] |
| It returns value as Bool type. |
| """ |
| if isinstance(x, bool): |
| return x |
| if not x.lower() in ["true", "false"]: |
| raise RuntimeError("{} is not a boolean value.".format(x)) |
| else: |
| return (x.lower() == "true") |
| |
| |
| def check_int(x): |
| """check_int checks if input 'x' is decimal integer. |
| It returns value as an int type. |
| """ |
| if isinstance(x, int): |
| return x |
| if not x.isdecimal(): |
| raise RuntimeError("{} is not a decimal number".format(x)) |
| return int(x) |
| |
| |
| def as_snake_case_prefix(name): |
| """ Convert PascalCase name into snake_case name""" |
| outname = "" |
| for c in name: |
| if c.isupper() and len(outname) > 0: |
| outname += '_' |
| outname += c.lower() |
| return outname + ('_' if name else '') |
| |
| |
| def get_random_data_hex_literal(width): |
| """ Fetch 'width' random bits and return them as hex literal""" |
| width = int(width) |
| literal_str = hex(random.getrandbits(width)) |
| return blockify(literal_str, width, 64) |
| |
| |
| def blockify(s, size, limit): |
| """ Make sure the output does not exceed a certain size per line""" |
| |
| str_idx = 2 |
| remain = size % (limit * 4) |
| numbits = remain if remain else limit * 4 |
| s_list = [] |
| |
| remain = size |
| while remain > 0: |
| s_incr = int(numbits / 4) |
| string = s[str_idx:str_idx + s_incr] |
| # Separate 32-bit words for readability. |
| for i in range(s_incr - 1, 0, -1): |
| if (s_incr - i) % 8 == 0: |
| string = string[:i] + "_" + string[i:] |
| s_list.append("{}'h{}".format(numbits, string)) |
| str_idx += s_incr |
| remain -= numbits |
| numbits = limit * 4 |
| |
| return (",\n ".join(s_list)) |
| |
| |
| def get_random_perm_hex_literal(numel): |
| """ Compute a random permutation of 'numel' elements and |
| return as packed hex literal""" |
| num_elements = int(numel) |
| width = int(ceil(log2(num_elements))) |
| idx = [x for x in range(num_elements)] |
| random.shuffle(idx) |
| literal_str = "" |
| for k in idx: |
| literal_str += format(k, '0' + str(width) + 'b') |
| # convert to hex for space efficiency |
| literal_str = hex(int(literal_str, 2)) |
| return blockify(literal_str, width * numel, 64) |
| |
| |
| def hist_to_bars(hist, m): |
| '''Convert histogramm list into ASCII bar plot''' |
| bars = [] |
| for i, j in enumerate(hist): |
| bar_prefix = "{:2}: ".format(i) |
| spaces = len(str(m)) - len(bar_prefix) |
| hist_bar = bar_prefix + (" " * spaces) |
| for k in range(j * 20 // max(hist)): |
| hist_bar += "|" |
| hist_bar += " ({:.2f}%)".format(100.0 * j / sum(hist)) if j else "--" |
| bars += [hist_bar] |
| return bars |
| |
| |
| def get_hd(word1, word2): |
| '''Calculate Hamming distance between two words.''' |
| if len(word1) != len(word2): |
| raise RuntimeError('Words are not of equal size') |
| return bin(int(word1, 2) ^ int(word2, 2)).count('1') |
| |
| |
| def hd_histogram(existing_words): |
| '''Build Hamming distance histogram''' |
| minimum_hd = len(existing_words[0]) |
| maximum_hd = 0 |
| minimum_hw = len(existing_words[0]) |
| maximum_hw = 0 |
| hist = [0] * (len(existing_words[0]) + 1) |
| for i, j in enumerate(existing_words): |
| minimum_hw = min(j.count('1'), minimum_hw) |
| maximum_hw = max(j.count('1'), maximum_hw) |
| if i < len(existing_words) - 1: |
| for k in existing_words[i + 1:]: |
| dist = get_hd(j, k) |
| hist[dist] += 1 |
| minimum_hd = min(dist, minimum_hd) |
| maximum_hd = max(dist, maximum_hd) |
| |
| stats = {} |
| stats["hist"] = hist |
| stats["bars"] = hist_to_bars(hist, len(existing_words)) |
| stats["min_hd"] = minimum_hd |
| stats["max_hd"] = maximum_hd |
| stats["min_hw"] = minimum_hw |
| stats["max_hw"] = maximum_hw |
| return stats |
| |
| |
| def is_valid_codeword(config, codeword): |
| '''Checks whether the bitstring is a valid ECC codeword.''' |
| |
| data_width = config['secded']['data_width'] |
| ecc_width = config['secded']['ecc_width'] |
| if len(codeword) != (data_width + ecc_width): |
| raise RuntimeError("Invalid codeword length {}".format(len(codeword))) |
| |
| # Build syndrome and check whether it is zero. |
| syndrome = [0 for k in range(ecc_width)] |
| |
| # The bitstring must be formatted as "data bits[N-1:0]" + "ecc bits[M-1:0]". |
| for j, fanin in enumerate(config['secded']['ecc_matrix']): |
| syndrome[j] = int(codeword[ecc_width - 1 - j]) |
| for k in fanin: |
| syndrome[j] ^= int(codeword[ecc_width + data_width - 1 - k]) |
| |
| return sum(syndrome) == 0 |
| |
| |
| def ecc_encode(config, dataword): |
| '''Calculate and prepend ECC bits.''' |
| if len(dataword) != config['secded']['data_width']: |
| raise RuntimeError("Invalid codeword length {}".format(len(dataword))) |
| |
| # Note that certain codes like the Hamming code refer to previously |
| # calculated parity bits. Hence, we incrementally build the codeword |
| # and extend it such that previously calculated bits can be referenced. |
| codeword = dataword |
| for j, fanin in enumerate(config['secded']['ecc_matrix']): |
| bit = 0 |
| for k in fanin: |
| bit ^= int(codeword[config['secded']['data_width'] + j - 1 - k]) |
| codeword = str(bit) + codeword |
| |
| return codeword |
| |
| |
| def scatter_bits(mask, bits): |
| '''Scatter the bits into unset positions of mask.''' |
| j = 0 |
| scatterword = '' |
| for b in mask: |
| if b == '1': |
| scatterword += '1' |
| else: |
| scatterword += bits[j] |
| j += 1 |
| |
| return scatterword |
| |
| |
| def permute_bits(bits, permutation): |
| '''Permute the bits in a bitstring''' |
| bitlen = len(bits) |
| assert bitlen == len(permutation) |
| permword = '' |
| for k in permutation: |
| permword = bits[bitlen - k - 1] + permword |
| return permword |
| |
| |
| def _parse_hex(value): |
| '''Parse a hex value into an integer. |
| |
| Args: |
| value: list[str] or str: |
| If a `list[str]`, parse each element as a 32-bit integer. |
| If a `str`, parse as a single hex string. |
| Returns: |
| int |
| ''' |
| if isinstance(value, list): |
| result = 0 |
| for (i, v) in enumerate(value): |
| result |= int(v, 16) << (i * 32) |
| return result |
| else: |
| value = value.translate(str.maketrans('', '', ' \r\n\t')) |
| return int(value, 16) |
| |
| |
| def random_or_hexvalue(dict_obj, key, num_bits): |
| '''Convert hex value at "key" to an integer or draw a random number.''' |
| |
| # Initialize to default if this key does not exist. |
| dict_obj.setdefault(key, '0x0') |
| |
| # Generate a random number of requested size in this case. |
| if dict_obj[key] == '<random>': |
| dict_obj[key] = random.getrandbits(num_bits) |
| # Otherwise attempt to convert this number to an int. |
| # Check that the range is correct. |
| else: |
| try: |
| dict_obj[key] = _parse_hex(dict_obj[key]) |
| if dict_obj[key] >= 2**num_bits: |
| raise RuntimeError( |
| 'Value "{}" is out of range.' |
| .format(dict_obj[key])) |
| except ValueError: |
| raise RuntimeError( |
| 'Invalid value "{}". Must be hex or "<random>".' |
| .format(dict_obj[key])) |