prjxray/utils/bits2fasm.py

309 lines
7.9 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
'''
Take raw .bits files and decode them to higher level functionality
This output is intended for debugging and not directly related to FASM
However, as of 2018-10-16, the output is being parsed to create FASM,
so be mindful when changing output format
TODO:
'''
import sys, os, json, re
class NoDB(Exception):
pass
def line(s=''):
print(s)
def comment(s):
print('# %s' % s)
enumdb = dict()
def get_enums(tile_type):
if tile_type in enumdb:
return enumdb[tile_type]
enumdb[tile_type] = {}
def process(l):
l = l.strip()
# CLBLM_L.SLICEL_X1.ALUT.INIT[10] 29_14
parts = line.split()
name = parts[0]
bit_vals = parts[1:]
# Assumption
# only 1 bit => non-enumerated value
enumdb[tile_type][name] = len(bit_vals) != 1
main_fn = "%s/%s/segbits_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type.lower())
int_fn = "%s/%s/segbits_int_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type[-1].lower())
if not os.path.exists(main_fn) or not os.path.exists(int_fn):
raise NoDB(tile_type)
with open(main_fn, "r") as f:
for line in f:
process(line)
with open(int_fn, "r") as f:
for line in f:
process(line)
return enumdb[tile_type]
def isenum(tilename, tag):
return get_enums(tilename)[tag]
# cache
segbitsdb = dict()
# TODO: migrate to library
def get_database(tile_type):
if tile_type in segbitsdb:
return segbitsdb[tile_type]
main_fn = "%s/%s/segbits_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type.lower())
int_fn = "%s/%s/segbits_int_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type[-1].lower())
if not os.path.exists(main_fn) or not os.path.exists(int_fn):
raise NoDB(tile_type)
segbitsdb[tile_type] = list()
with open(main_fn, "r") as f:
for line in f:
line = line.split()
segbitsdb[tile_type].append(line)
with open(int_fn, "r") as f:
for line in f:
line = line.split()
segbitsdb[tile_type].append(line)
return segbitsdb[tile_type]
def mk_fasm(segj, entry):
tile_name = segj['tile_name']
# ex: CLBLL_L.SLICEL_X0.AFF.DMUX.O6
tag = entry[0]
m = re.match(r'([A-Za-z0-9_]+)[.](.*)', tag)
# tile_type = m.group(1)
# the postfix, O6 in the above example
tag_post = m.group(2)
if not isenum(segj['type'], tag):
return '%s.%s 1' % (tile_name, tag_post)
else:
# Make the selection an argument of the configruation
m = re.match(r'(.*)[.]([A-Za-z0-9_]+)', tag_post)
which = m.group(1)
value = m.group(2)
return '%s.%s %s' % (tile_name, which, value)
def mk_segbits(seginfo, bitdata):
baseframe = int(seginfo["baseaddr"][0], 16)
basewordidx = int(seginfo["baseaddr"][1])
numframes = int(seginfo["frames"])
numwords = int(seginfo["words"])
segbits = set()
for frame in range(baseframe, baseframe + numframes):
if frame not in bitdata:
continue
for wordidx in range(basewordidx, basewordidx + numwords):
if wordidx not in bitdata[frame]:
continue
for bitidx in bitdata[frame][wordidx]:
segbits.add(
"%02d_%02d" %
(frame - baseframe, 32 * (wordidx - basewordidx) + bitidx))
return segbits
def tagmatch(entry, segbits):
for bit in entry[1:]:
if bit[0] != "!" and bit not in segbits:
return False
if bit[0] == "!" and bit[1:] in segbits:
return False
return True
def tag_matched(entry, segbits):
for bit in entry[1:]:
if bit[0] != "!":
segbits.remove(bit)
def seg_decode(seginfo, segbits):
fasms = set()
try:
for entry in get_database(seginfo["type"]):
if not tagmatch(entry, segbits):
continue
tag_matched(entry, segbits)
#fasms.add('%s.%s 1' % (seginfo['tile_name'], entry[0]))
fasms.add(mk_fasm(seginfo, entry))
except NoDB:
print("WARNING: failed to load DB for %s" % seginfo["type"])
return fasms
def handle_segment(segname, grid, bitdata, verbose=False):
assert segname
# only print bitstream tiles
if segname not in grid["segments"]:
return
seginfo = grid["segments"][segname]
segbits = mk_segbits(seginfo, bitdata)
fasms = seg_decode(seginfo, segbits)
# Found something to print?
if len(segbits) == 0 and len(fasms) == 0:
return
line('')
comment("seg %s" % (segname, ))
for fasm in sorted(fasms):
print(fasm)
if verbose and len(segbits) > 0:
comment('%u unknown bits' % len(segbits))
for bit in sorted(segbits):
comment("bit %s" % bit)
def load_bitdata(bits_file):
bitdata = dict()
with open(bits_file, "r") as f:
for line in f:
line = line.split("_")
frame = int(line[1], 16)
wordidx = int(line[2], 10)
bitidx = int(line[3], 10)
if frame not in bitdata:
bitdata[frame] = dict()
if wordidx not in bitdata[frame]:
bitdata[frame][wordidx] = set()
bitdata[frame][wordidx].add(bitidx)
return bitdata
def mk_grid():
'''Load tilegrid, flattening all blocks into one dictionary'''
with open("%s/%s/tilegrid.json" % (os.getenv("XRAY_DATABASE_DIR"),
os.getenv("XRAY_DATABASE")), "r") as f:
new_grid = json.load(f)
# TODO: Migrate to new tilegrid format via library.
grid = {'tiles': new_grid, 'segments': {}}
for tile_name, tile in grid['tiles'].items():
bits = tile.get('bits', None)
if not bits:
continue
for block_name, block in bits.items():
segname = mksegment(tile_name, block_name)
grid['segments'][segname] = {
'baseaddr': [
block['baseaddr'],
block['offset'],
],
'type': tile['type'],
'frames': block['frames'],
'words': block['words'],
'tile_name': tile_name,
'block_name': block_name,
}
return grid
def mksegment(tile_name, block_name):
'''Create a segment name'''
return '%s:%s' % (tile_name, block_name)
def tile_segnames(grid):
ret = []
for tile_name, tile in grid['tiles'].items():
for block_name in tile['bits'].keys():
ret.append(mksegment(tile_name, block_name))
return ret
def run(bits_file, segnames, verbose=False):
grid = mk_grid()
bitdata = load_bitdata(bits_file)
# Default: print all
if segnames:
for i, segname in enumerate(segnames):
# Default to common tile config area if tile given without explicit block
if ':' not in segname:
segnames[i] = mksegment(segname, 'CLB_IO_CLK')
else:
segnames = sorted(tile_segnames(grid))
comment('Segments: %u' % len(segnames))
# XXX: previously this was sorted by address, not name
# revisit?
for segname in segnames:
handle_segment(segname, grid, bitdata, verbose=verbose)
def main():
import argparse
# XXX: tool still works, but not well
# need to eliminate segments entirely
parser = argparse.ArgumentParser(
description='XXX: does not print all data?')
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('bits_file', help='')
parser.add_argument(
'segnames', nargs='*', help='List of tile or tile:block to print')
args = parser.parse_args()
run(args.bits_file, args.segnames, verbose=args.verbose)
if __name__ == '__main__':
main()