|  | # 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])) |