format_utils.py 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. import math
  2. from bitstring import BitArray
  3. def chunks(lst, n):
  4. """Yield successive n-sized chunks from lst."""
  5. for i in range(0, len(lst), n):
  6. yield lst[i:i + n]
  7. def convert_to_mem(data, width, binary=False, reverse=False, packed=False):
  8. """
  9. Converts to general memory format
  10. :param data: array of BinArray
  11. :param width: width in bits
  12. :param binary: output in binary values
  13. :param reverse: reverse data order
  14. :param packed: do not print any commens or spaces between values
  15. :return: mem formatted text file
  16. """
  17. x = b''
  18. if width % 8 != 0 and not binary:
  19. raise ValueError("Cannot convert non integer byte width to hex")
  20. if reverse:
  21. data = reversed(data)
  22. if packed:
  23. arr = [(c.bin if binary else c.hex).encode() for c in data]
  24. return b''.join(arr)
  25. line_items = (8 if binary else 32)//(width//8)
  26. fa = f'0{math.ceil(math.ceil(math.log2(len(data))) / 4)}x'
  27. for i, chunk in enumerate(chunks(data, line_items)):
  28. arr = [(c.bin if binary else c.hex).encode() for c in chunk]
  29. x += b' '.join(arr) + f' // {format(line_items*i, fa)}\n'.encode()
  30. return x
  31. def convert_to_mif(data, width):
  32. """
  33. :param data: array of BinArray
  34. :param width: width in bits
  35. :return: mif formatted text file
  36. """
  37. radix = 'HEX' if width % 8 == 0 else 'BIN'
  38. x = f'''-- auto-generated memory initialisation file
  39. -- total size: {sizeof_fmt(len(data) * width, 'bits', addi=False)}
  40. DEPTH = {len(data)};
  41. WIDTH = {width};
  42. ADDRESS_RADIX = HEX;
  43. DATA_RADIX = {radix};
  44. CONTENT
  45. BEGIN
  46. '''.encode()
  47. line_items = (8 if radix == 'BIN' else 32)//(width//8)
  48. depth = math.ceil(math.log2(len(data)))
  49. addr_format = f'0{math.ceil(depth / 4)}x'
  50. for i, comp in enumerate(chunks(data, line_items)):
  51. a = ' '.join([c.bin if radix == 'BIN' else c.hex for c in comp])
  52. x += f'{format(i*line_items, addr_format)} : {a};\n'.encode()
  53. x += b"END;"
  54. return x
  55. def sizeof_fmt(num, suffix='B', addi=True):
  56. l = ['', 'Ki', 'Mi', 'Gi', 'Ti', 'Pi', 'Ei', 'Zi'] if addi else ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']
  57. for unit in l:
  58. if abs(num) < 1024.0:
  59. return "%3.1f%s%s" % (num, unit, suffix)
  60. num /= 1024.0
  61. return "%.1f%s%s" % (num, 'Yi' if addi else 'Y', suffix)
  62. # format function map. Function input: [array of BinArray, width in bits]
  63. FORMAT_MAP = {
  64. 'mif': ('mif', convert_to_mif, {}),
  65. 'ubin': ('ubin', convert_to_mem, {'binary': True, 'reverse': True, 'packed': True}),
  66. 'uhex': ('uhex', convert_to_mem, {'binary': False, 'reverse': True, 'packed': True}),
  67. 'memh': ('mem', convert_to_mem, {'binary': False}),
  68. 'memb': ('mem', convert_to_mem, {'binary': True}),
  69. }
  70. FORMAT_INPUT = [
  71. "raw",
  72. "bin",
  73. "hex",
  74. ]
  75. if __name__ == '__main__':
  76. import argparse
  77. import sys
  78. from os import path, mkdir
  79. parser = argparse.ArgumentParser(description='Program formatter', add_help=True)
  80. parser.add_argument('file', help='Files to compile')
  81. parser.add_argument('-o', '--output', help='Output directory')
  82. parser.add_argument('-f', '--force', action='store_true', help='Force override output file')
  83. parser.add_argument('-s', '--stdout', action='store_true', help='Print to stdout')
  84. parser.add_argument('-S', '--slice', type=int, default=0, help='Slice output')
  85. parser.add_argument('-n', '--slice_no', type=int, default=-1, help='Output only nth slice')
  86. parser.add_argument('-w', '--width', type=int, default=8, help='Data width in bits')
  87. parser.add_argument('-i', '--input_format', choices=FORMAT_INPUT, default='raw', help='Input format')
  88. parser.add_argument('-t', '--output_type', choices=list(FORMAT_MAP.keys()), default='mem', help='Output type')
  89. args = parser.parse_args(sys.argv[1:])
  90. bname = path.basename(args.file).rsplit('.', 1)[0]
  91. if args.width < 1:
  92. print(f'Width must be more than 0', file=sys.stderr)
  93. sys.exit(1)
  94. if args.slice < 1:
  95. args.slice = 1
  96. if not path.isfile(args.file):
  97. print(f'No file {args.file}!', file=sys.stderr)
  98. sys.exit(1)
  99. output_dir = args.output or path.dirname(args.file)
  100. if not path.exists(output_dir):
  101. mkdir(output_dir)
  102. ifile = open(args.file, 'rb')
  103. if args.input_format == 'bin':
  104. binary = BitArray(bin=ifile.read().decode(encoding='ascii', errors='ignore'))
  105. elif args.input_format == 'hex':
  106. binary = BitArray(hex=ifile.read().decode(encoding='ascii', errors='ignore'))
  107. else:
  108. binary = BitArray(ifile.read())
  109. ifile.close()
  110. if args.slice > 1 and len(binary) % (args.slice*args.width) != 0:
  111. print(f'File {args.file} (size {len(binary)}B) cannot be sliced into {args.slice} equal parts', file=sys.stderr)
  112. sys.exit(1)
  113. slices_bin = [binary[i * args.width:(i + 1) * args.width] for i in range(len(binary) // args.width)]
  114. ext, func, kwargs = FORMAT_MAP[args.output_type]
  115. for sno in range(args.slice):
  116. if args.slice_no >= 0 and args.slice_no != sno:
  117. continue
  118. if args.slice == 1:
  119. output = path.join(output_dir, f'{bname}.{ext}')
  120. else:
  121. output = path.join(output_dir, f'{bname}.{sno}.{ext}')
  122. if not args.force and not args.stdout:
  123. if path.isfile(output):
  124. print(f'Output file already exists {output}!', file=sys.stderr)
  125. inval = ''
  126. while True:
  127. inval = input('Override? [y/n]: ')
  128. inval = inval.lower().strip()
  129. if inval != 'y' and inval != 'n':
  130. print('Please type y or n')
  131. continue
  132. break
  133. if inval == 'n':
  134. continue
  135. slice_bin = slices_bin[sno::args.slice]
  136. try:
  137. data = func(slice_bin, args.width, **kwargs)
  138. except ValueError as e:
  139. print(e.args[0])
  140. continue
  141. if args.stdout:
  142. sys.stdout.write(data.decode())
  143. else:
  144. with open(output, 'wb') as f:
  145. f.write(data)
  146. print(f'Written {sizeof_fmt(len(slice_bin)*args.width, "bits", addi=False)} to {output}')