import math from bitstring import BitArray def chunks(lst, n): """Yield successive n-sized chunks from lst.""" for i in range(0, len(lst), n): yield lst[i:i + n] def convert_to_mem(data, width, binary=False, reverse=False, packed=False): """ Converts to general memory format :param data: array of BinArray :param width: width in bits :param binary: output in binary values :param reverse: reverse data order :param packed: do not print any commens or spaces between values :return: mem formatted text file """ x = b'' if width % 8 != 0 and not binary: raise ValueError("Cannot convert non integer byte width to hex") if reverse: data = reversed(data) if packed: arr = [(c.bin if binary else c.hex).encode() for c in data] return b''.join(arr) line_items = (8 if binary else 32)//(width//8) fa = f'0{math.ceil(math.ceil(math.log2(len(data))) / 4)}x' for i, chunk in enumerate(chunks(data, line_items)): arr = [(c.bin if binary else c.hex).encode() for c in chunk] x += b' '.join(arr) + f' // {format(line_items*i, fa)}\n'.encode() return x def convert_to_mif(data, width): """ :param data: array of BinArray :param width: width in bits :return: mif formatted text file """ radix = 'HEX' if width % 8 == 0 else 'BIN' x = f'''-- auto-generated memory initialisation file -- total size: {sizeof_fmt(len(data) * width, 'bits', addi=False)} DEPTH = {len(data)}; WIDTH = {width}; ADDRESS_RADIX = HEX; DATA_RADIX = {radix}; CONTENT BEGIN '''.encode() line_items = (8 if radix == 'BIN' else 32)//(width//8) depth = math.ceil(math.log2(len(data))) addr_format = f'0{math.ceil(depth / 4)}x' for i, comp in enumerate(chunks(data, line_items)): a = ' '.join([c.bin if radix == 'BIN' else c.hex for c in comp]) x += f'{format(i*line_items, addr_format)} : {a};\n'.encode() x += b"END;" return x def sizeof_fmt(num, suffix='B', addi=True): l = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi'] if addi else ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z'] for unit in l: if abs(num) < 1024.0: return "%3.1f%s%s" % (num, unit, suffix) num /= 1024.0 return "%.1f%s%s" % (num, 'Yi' if addi else 'Y', suffix) # format function map. Function input: [array of BinArray, width in bits] FORMAT_MAP = { 'mif': ('mif', convert_to_mif, {}), 'ubin': ('ubin', convert_to_mem, {'binary': True, 'reverse': True, 'packed': True}), 'uhex': ('uhex', convert_to_mem, {'binary': False, 'reverse': True, 'packed': True}), 'memh': ('mem', convert_to_mem, {'binary': False}), 'memb': ('mem', convert_to_mem, {'binary': True}), } FORMAT_INPUT = [ "raw", "bin", "hex", ] if __name__ == '__main__': import argparse import sys from os import path, mkdir parser = argparse.ArgumentParser(description='Program formatter', add_help=True) parser.add_argument('file', help='Files to compile') parser.add_argument('-o', '--output', help='Output directory') parser.add_argument('-f', '--force', action='store_true', help='Force override output file') parser.add_argument('-s', '--stdout', action='store_true', help='Print to stdout') parser.add_argument('-S', '--slice', type=int, default=0, help='Slice output') parser.add_argument('-n', '--slice_no', type=int, default=-1, help='Output only nth slice') parser.add_argument('-w', '--width', type=int, default=8, help='Data width in bits') parser.add_argument('-i', '--input_format', choices=FORMAT_INPUT, default='raw', help='Input format') parser.add_argument('-t', '--output_type', choices=list(FORMAT_MAP.keys()), default='mem', help='Output type') args = parser.parse_args(sys.argv[1:]) bname = path.basename(args.file).rsplit('.', 1)[0] if args.width < 1: print(f'Width must be more than 0', file=sys.stderr) sys.exit(1) if args.slice < 1: args.slice = 1 if not path.isfile(args.file): print(f'No file {args.file}!', file=sys.stderr) sys.exit(1) output_dir = args.output or path.dirname(args.file) if not path.exists(output_dir): mkdir(output_dir) ifile = open(args.file, 'rb') if args.input_format == 'bin': binary = BitArray(bin=ifile.read().decode(encoding='ascii', errors='ignore')) elif args.input_format == 'hex': binary = BitArray(hex=ifile.read().decode(encoding='ascii', errors='ignore')) else: binary = BitArray(ifile.read()) ifile.close() if args.slice > 1 and len(binary) % (args.slice*args.width) != 0: print(f'File {args.file} (size {len(binary)}B) cannot be sliced into {args.slice} equal parts', file=sys.stderr) sys.exit(1) slices_bin = [binary[i * args.width:(i + 1) * args.width] for i in range(len(binary) // args.width)] ext, func, kwargs = FORMAT_MAP[args.output_type] for sno in range(args.slice): if args.slice_no >= 0 and args.slice_no != sno: continue if args.slice == 1: output = path.join(output_dir, f'{bname}.{ext}') else: output = path.join(output_dir, f'{bname}.{sno}.{ext}') if not args.force and not args.stdout: if path.isfile(output): print(f'Output file already exists {output}!', file=sys.stderr) inval = '' while True: inval = input('Override? [y/n]: ') inval = inval.lower().strip() if inval != 'y' and inval != 'n': print('Please type y or n') continue break if inval == 'n': continue slice_bin = slices_bin[sno::args.slice] try: data = func(slice_bin, args.width, **kwargs) except ValueError as e: print(e.args[0]) continue if args.stdout: sys.stdout.write(data.decode()) else: with open(output, 'wb') as f: f.write(data) print(f'Written {sizeof_fmt(len(slice_bin)*args.width, "bits", addi=False)} to {output}')