From 334f28a9a402569f1e30cd68999d652b27c76808 Mon Sep 17 00:00:00 2001 From: John McMaster Date: Tue, 6 Nov 2018 11:59:57 -0800 Subject: [PATCH] segprint: decode bits shared by overlapping tiles Signed-off-by: John McMaster --- utils/segprint.py | 325 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 254 insertions(+), 71 deletions(-) diff --git a/utils/segprint.py b/utils/segprint.py index 2c7caea5..e5a3704b 100755 --- a/utils/segprint.py +++ b/utils/segprint.py @@ -1,7 +1,9 @@ #!/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 + +A segment as currently used is defined as a tile type + a memory region +Ex: BRAM_L_X6Y100:CLB_IO_CLK ''' import sys, os, json, re @@ -20,7 +22,6 @@ segbitsdb = dict() # int and sites are loaded together so that bit coverage can be checked together # however, as currently written, each segment is essentially printed twice def process_db(db, tile_type, process, verbose): - print(db.get_tile_types()) ttdb = db.get_tile_type(tile_type) fns = [ttdb.tile_dbs.segbits, ttdb.tile_dbs.ppips] @@ -39,7 +40,29 @@ def get_database(db, tile_type, verbose=False): return segbitsdb[tile_type] def process(l): - tags.append(l.split()) + # l like: CLBLL_L.SLICEL_X0.AMUX.CY !30_07 !30_11 30_06 30_08 + # Parse tags to do math when multiple tiles share an address space + parts = l.split() + name = parts[0] + + def parsetag(x): + # !30_07 + if x[0] == '!': + isset = False + numstr = x[1:] + else: + isset = True + numstr = x + frame, word = numstr.split("_") + # second part forms a tuple refereced in sets + return (isset, (int(frame, 10), int(word, 10))) + + if parts[1] == 'always': + tagbits = [] + else: + tagbits = [parsetag(x) for x in parts[1:]] + + tags.append(list([name] + tagbits)) process_db(db, tile_type, process, verbose=verbose) @@ -51,22 +74,33 @@ def get_database(db, tile_type, verbose=False): def mk_segbits(seginfo, bitdata): - baseframe = int(seginfo["baseaddr"][0], 16) - basewordidx = int(seginfo["baseaddr"][1]) - numframes = int(seginfo["frames"]) - numwords = int(seginfo["words"]) + ''' + Given a tile memory region (seginfo), return list of bits in that region + + seginfo: mk_segments()s object supplying address range + bitdata: all bits in the entire bitstream + ''' segbits = set() - for frame in range(baseframe, baseframe + numframes): + + block = seginfo["block"] + baseaddr = int(block["baseaddr"], 0) + frames = block["frames"] + word_offset = block["offset"] + words = block["words"] + + for frame in range(baseaddr, baseaddr + frames): if frame not in bitdata: continue - for wordidx in range(basewordidx, basewordidx + numwords): + for wordidx in range(word_offset, word_offset + words): if wordidx not in bitdata[frame]: continue for bitidx in bitdata[frame][wordidx]: - segbits.add( - "%02d_%02d" % - (frame - baseframe, 32 * (wordidx - basewordidx) + bitidx)) + frame_addr = frame - baseaddr + bit_addr = 32 * (wordidx - word_offset) + bitidx + #segbits.add( "%02d_%02d" % (frame_addr, word_addr)) + segbits.add((frame_addr, bit_addr)) + return segbits @@ -79,12 +113,13 @@ def print_unknown_bits(segments, bitdata): # seggrames[address] = set() # where set contains word numbers segframes = dict() - for segname, segdata in segments.items(): - framebase = int(segdata["baseaddr"][0], 16) - for i in range(segdata["frames"]): + for segname, segment in segments.items(): + block = segment["block"] + framebase = int(block["baseaddr"][0], 16) + for i in range(block["frames"]): words = segframes.setdefault(framebase + i, set()) - for j in range(segdata["baseaddr"][1], - segdata["baseaddr"][1] + segdata["words"]): + for j in range(block["baseaddr"][1], + block["baseaddr"][1] + block["words"]): words.add(j) # print uncovered locations @@ -98,66 +133,165 @@ def print_unknown_bits(segments, bitdata): def tagmatch(entry, segbits): + '''Does tag appear in segbits?''' + + # Entry like "CLBLL_L.SLICEL_X0.AMUX.CY !30_07 !30_11 30_06 30_08".split() for bit in entry[1:]: - if bit[0] != "!" and bit not in segbits: - return False - if bit[0] == "!" and bit[1:] in segbits: + isset, bitaddr = bit + + # Reject if bit polarity is incorrect + if bitaddr not in segbits if isset else bitaddr in segbits: return False return True def tag_matched(entry, segbits): for bit in entry[1:]: - if bit[0] != "!": - segbits.remove(bit) + isset, bitaddr = bit + if isset: + segbits.remove(bitaddr) +# tile types that failed to decode decode_warnings = set() -def seg_decode(flag_decode_emit, db, seginfo, segbits, verbose=False): +def seg_decode(db, seginfo, segbits, segments, verbose=False): + ''' + Remove matched tags from segbits + Returns a list of all matched tags + ''' + segtags = set() - # already failed? - if seginfo["type"] in decode_warnings: - return segtags + # Valid addresses for refereced tiles + ref_block = seginfo["block"] + # ref_frame_as = (int(ref_block["baseaddr"], 0), int(ref_block["baseaddr"], 0) + ref_block["frames"] - 1) + ref_frame_as = (0, ref_block["frames"] - 1) + ref_bit_as = ( + 32 * ref_block["offset"], + 32 * (ref_block["offset"] + ref_block["words"]) - 1) + + def process(cmp_seginfo, ref_tile_name): + tile_type = cmp_seginfo["tile"]["type"] + + # already failed? + if tile_type in decode_warnings: + return + + try: + entries = get_database(db, tile_type, verbose=verbose) + except NoDB: + verbose and print("WARNING: failed to load DB for %s" % tile_type) + assert tile_type != 'BRAM_L' + decode_warnings.add(tile_type) + return + + cmp_block = cmp_seginfo["block"] + ref_frame_delta = int(cmp_block["baseaddr"], 0) - int( + ref_block["baseaddr"], 0) + ref_bit_delta = 32 * (cmp_block["offset"] - ref_block["offset"]) + + def adjust_entry_addr(entry): + '''Return bits that apply to this tile at the correct address''' + tagname = entry[0] + bits = entry[1:] + if len(bits) == 0: + return None + ret = [tagname] + + def adjust_entry(): + '''Adjust entry in another tile address space to be in our reference tile address space''' + for isset, old_bitaddr, in bits: + old_frame_addr, old_bit_addr = old_bitaddr + new_frame_addr = old_frame_addr + ref_frame_delta + if not (ref_frame_as[0] <= new_frame_addr <= + ref_frame_as[1]): + verbose and print( + "out frame range: %d <= %d <= %d" % + (ref_frame_as[0], new_frame_addr, ref_frame_as[1])) + return False + + new_bit_addr = old_bit_addr + ref_bit_delta + # Verify in range of original tile + # This can happen if a smaller tile references a larger tile + if not (ref_bit_as[0] <= new_bit_addr <= ref_bit_as[1]): + verbose and print( + "out bit range: %d <= %d <= %d" % + (ref_bit_as[0], new_bit_addr, ref_bit_as[1])) + return False + ret.append((isset, (new_frame_addr, new_bit_addr))) + verbose and print( + "ent %02d_%02d => %02d_%02d" % ( + old_frame_addr, old_bit_addr, new_frame_addr, + new_bit_addr)) + return True + + if not adjust_entry(): + return None + return ret + + for entry in entries: + if ref_tile_name: + entry = adjust_entry_addr(entry) + if entry is None: + continue + verbose and print('adjusted entry', entry) - try: - for entry in get_database(db, seginfo["type"], verbose=verbose): if not tagmatch(entry, segbits): continue tag_matched(entry, segbits) - if flag_decode_emit: - segtags.add(entry[0]) - except NoDB: - verbose and print( - "WARNING: failed to load DB for %s" % seginfo["type"]) - decode_warnings.add(seginfo["type"]) + tagname = entry[0] + # Prefix matches not from this tile + if ref_tile_name: + segtags.add('%s:%s' % (ref_tile_name, tagname)) + else: + segtags.add(tagname) + + # Reference tile + process(seginfo, None) + # Tiles that share our address space + for (ref_tile_name, cmp_block_name) in seginfo['segtiles']: + process( + segments[mksegment(ref_tile_name, cmp_block_name)], ref_tile_name) + return segtags +def print_seg(segname, segbits, segtags, decode_emit): + '''Print segment like used by segmaker/segmatch''' + + print("seg %s" % (segname, )) + + # Bits that weren't decoded + for bit in sorted(segbits): + print("bit %02d_%02d" % bit) + + if decode_emit: + for tag in sorted(segtags): + print("tag %s" % tag) + + def handle_segment( db, segname, - segments, bitdata, - flag_decode_emit, - flag_decode_omit, + decode_emit, + decode_omit, omit_empty_segs, + segments, verbose=False): - assert segname + seginfo = segments[segname] # only print bitstream tiles - if segname not in segments: - return - seginfo = segments[segname] + #if segname not in segments: + # return segbits = mk_segbits(seginfo, bitdata) - if flag_decode_emit or flag_decode_omit: - segtags = seg_decode( - flag_decode_emit, db, seginfo, segbits, verbose=verbose) + if decode_emit or decode_omit: + segtags = seg_decode(db, seginfo, segbits, segments, verbose=verbose) else: segtags = set() @@ -166,52 +300,94 @@ def handle_segment( return print() - print("seg %s" % (segname, )) + print_seg(segname, segbits, segtags, decode_emit) - for bit in sorted(segbits): - print("bit %s" % bit) - for tag in sorted(segtags): - print("tag %s" % tag) +def overlap(a, b): + return a[0] <= b[0] <= a[1] or b[0] <= a[0] <= b[1] + + +def mk_segtiles(tiles): + ''' + Return a dictionary of tile_name:tiles + Where tiles is a list of tiles that are in our address space + + Assumption: tiles in the same minor address region have the same base address and number frames + + As DB is written, not all have the same number of frames + Ex: CLBLM_R_X7Y108 36 frames, INT_R_X7Y108 28 frames + We could check for this, but don't think its worth the effort + Maybe this should be corrected in the DB? + ''' + + segtiles = {} + + # Group by base address + baseaddrs = {} + for tile_name, tile in tiles.items(): + for block_name, block in tile['bits'].items(): + baseaddrs.setdefault(block["baseaddr"], []).append( + (block["offset"], tile_name, block, block_name)) + + for baseaddr, values in baseaddrs.items(): + ''' + There are only 256 addresses per minor address + Just do a set brute force search for now? + Maybe too slow with the number of tiles + + Around 50 IP blocks max per minor address + ''' + + # Sort by block offset + values = sorted(values) + + for refi, (_ref_block_offset, ref_tile_name, ref_block, + ref_block_name) in enumerate(values): + seglets = segtiles.setdefault(ref_tile_name, []) + ref_as = ( + ref_block["offset"], + ref_block["offset"] + ref_block["height"] - 1) + + for cmpi in range(refi + 1, len(values)): + (_cmp_block_offset, cmp_tile_name, cmp_block, + cmp_block_name) = values[cmpi] + cmp_as = ( + cmp_block["offset"], + cmp_block["offset"] + cmp_block["height"] - 1) + + if overlap(ref_as, cmp_as): + seglets.append((cmp_tile_name, cmp_block_name)) + # sorting => first non-intersection means no future will intersect + else: + break + + return segtiles def mk_segments(tiles): segments = {} + segtiles = mk_segtiles(tiles) for tile_name, tile in tiles.items(): - bits = tile.get('bits', None) - if not bits: - continue - for block_name, block in bits.items(): + for block_name, block in tile['bits'].items(): segname = mksegment(tile_name, block_name) segments[segname] = { - 'baseaddr': [ - block['baseaddr'], - block['offset'], - ], - 'type': tile['type'], - 'frames': block['frames'], - 'words': block['words'], + 'tile': tile, 'tile_name': tile_name, + 'block': block, 'block_name': block_name, + 'segtiles': segtiles[tile_name], } return segments -def mk_grid(db_root): - with open(os.path.join(db_root, "tilegrid.json"), "r") as f: - tiles = json.load(f) - '''Load tilegrid, flattening all blocks into one dictionary''' - # TODO: Migrate to new tilegrid format via library. - return tiles, mk_segments(tiles) - - def mksegment(tile_name, block_name): '''Create a segment name''' return '%s:%s' % (tile_name, block_name) def tile_segnames(tiles): + '''Create a list of all (tile_name, block_name) from input tiles''' ret = [] for tile_name, tile in tiles.items(): if 'bits' not in tile: @@ -222,6 +398,13 @@ def tile_segnames(tiles): return ret +def load_tiles(db_root): + # TODO: Migrate to new tilegrid format via library. + with open("%s/tilegrid.json" % (db_root), "r") as f: + tiles = json.load(f) + return tiles + + def run( db_root, bits_file, @@ -231,9 +414,9 @@ def run( flag_decode_emit=False, flag_decode_omit=False, verbose=False): - tiles, segments = mk_grid(db_root) db = prjxraydb.Database(db_root) - + tiles = load_tiles(db_root) + segments = mk_segments(tiles) bitdata = bitstream.load_bitdata2(open(bits_file, "r")) if flag_unknown_bits: @@ -255,11 +438,11 @@ def run( handle_segment( db, segname, - segments, bitdata, flag_decode_emit, flag_decode_omit, omit_empty_segs, + segments, verbose=verbose)