blob: 9aa53ccc24834142b8a89452e32e646d2a38fa5a [file] [log] [blame]
# 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]))