Merge pull request #220 from mcmasterg/segprint_bram

Segprint: print related tiles
This commit is contained in:
litghost 2018-11-06 12:15:43 -08:00 committed by GitHub
commit eddf249df1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 254 additions and 71 deletions

View File

@ -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' or parts[1] == 'hint':
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)