blob: 35fba633aa1f33a9a0669d4423ea60c7f69caaea [file] [log] [blame]
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2017-2020 The Project X-Ray Authors.
#
# Use of this source code is governed by a ISC-style
# license that can be found in the LICENSE file or at
# https://opensource.org/licenses/ISC
#
# SPDX-License-Identifier: ISC
'''
Take raw .bits files and decode them to higher level functionality
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
import copy
from prjxray import bitstream
from prjxray import db as prjxraydb
from prjxray.util import OpenSafeFile, parse_tagbit, db_root_arg, part_arg
class NoDB(Exception):
pass
# cache
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):
ttdb = db.get_tile_type(tile_type)
fns = [ttdb.tile_dbs.segbits, ttdb.tile_dbs.ppips]
verbose and print("process_db(%s): %s" % (tile_type, fns))
for fn in fns:
if fn:
with OpenSafeFile(fn, "r") as f:
for line in f:
process(line)
def get_database(db, tile_type, bit_only=False, verbose=False):
tags = list()
if tile_type in segbitsdb:
return segbitsdb[tile_type]
def process(l):
# 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]
if parts[1] == 'always' or parts[1] == 'hint' or parts[1] == 'default':
if bit_only:
return
tagbits = []
else:
tagbits = [parse_tagbit(x) for x in parts[1:]]
tags.append(list([name] + tagbits))
process_db(db, tile_type, process, verbose=verbose)
if len(tags) == 0:
raise NoDB(tile_type)
segbitsdb[tile_type] = tags
return tags
def mk_segbits(seginfo, bitdata):
'''
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()
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(word_offset, word_offset + words):
if wordidx not in bitdata[frame]:
continue
for bitidx in bitdata[frame][wordidx]:
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
def gen_tilegrid_masks(tiles):
"""yield (addr_min, addr_max + 1, word_min, word_max + 1)"""
for tilek, tilev in tiles.items():
for block_type, blockj in tilev["bits"].items():
baseaddr = int(blockj["baseaddr"], 0)
frames = blockj["frames"]
offset = blockj["offset"]
words = blockj["words"]
yield (baseaddr, baseaddr + frames, offset, offset + words)
def print_unknown_bits(tiles, bitdata):
'''
Print bits not covered by known tiles
tiles: tilegrid json
bitdata[addr][word] = set of bit indices (0 to 31)
'''
# Start with an open set and remove elements as we find them
tocheck = copy.deepcopy(bitdata)
for addr_min, addr_max_p1, word_min, word_max_p1 in gen_tilegrid_masks(
tiles):
for addr in range(addr_min, addr_max_p1):
if addr not in tocheck:
continue
for word in range(word_min, word_max_p1):
if word not in tocheck[addr]:
continue
del tocheck[addr][word]
# print uncovered locations
print('Non-database bits:')
for frame in sorted(tocheck.keys()):
for wordidx in sorted(tocheck[frame].keys()):
for bitidx in sorted(tocheck[frame][wordidx]):
print("bit_%08x_%03d_%02d" % (frame, wordidx, bitidx))
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:]:
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:]:
isset, bitaddr = bit
if isset:
segbits.remove(bitaddr)
# tile types that failed to decode
decode_warnings = set()
def seg_decode(db, seginfo, segbits, segments, bit_only=False, verbose=False):
'''
Remove matched tags from segbits
Returns a list of all matched tags
'''
segtags = set()
# 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, bit_only=bit_only, 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)
if not tagmatch(entry, segbits):
continue
tag_matched(entry, segbits)
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, seginfo, nbits, segbits, segtags, decode_emit, verbose=False):
'''Print segment like used by segmaker/segmatch'''
print("seg %s" % (segname, ))
if verbose:
print("Bits: %s" % nbits)
print(
"Address: %s, +%s" %
(seginfo["block"]["baseaddr"], seginfo["block"]["frames"]))
print(
"Words: %s, +%s" %
(seginfo["block"]["offset"], seginfo["block"]["words"]))
# 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,
bitdata,
decode_emit,
decode_omit,
omit_empty_segs,
segments,
bit_only=False,
verbose=False):
seginfo = segments[segname]
segbits = mk_segbits(seginfo, bitdata)
nbits = len(segbits)
if decode_emit or decode_omit:
segtags = seg_decode(
db, seginfo, segbits, segments, bit_only=bit_only, verbose=verbose)
else:
segtags = set()
# Found something to print?
keep = not omit_empty_segs or len(segbits) > 0 or (
len(segtags) > 0 and not decode_omit)
if not keep:
return
print()
print_seg(
segname,
seginfo,
nbits,
segbits,
segtags,
decode_emit,
verbose=verbose)
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["words"] - 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["words"] - 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():
for block_name, block in tile['bits'].items():
segname = mksegment(tile_name, block_name)
segments[segname] = {
'tile': tile,
'tile_name': tile_name,
'block': block,
'block_name': block_name,
'segtiles': segtiles[tile_name],
}
return segments
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:
continue
for block_name in tile['bits'].keys():
ret.append(mksegment(tile_name, block_name))
return ret
def load_tiles(db_root, part):
# TODO: Migrate to new tilegrid format via library.
with OpenSafeFile("%s/%s/tilegrid.json" % (db_root, part), "r") as f:
tiles = json.load(f)
return tiles
def run(
db_root,
part,
bits_file,
segnames,
omit_empty_segs=False,
flag_unknown_bits=False,
flag_decode_emit=False,
flag_decode_omit=False,
bit_only=False,
verbose=False):
db = prjxraydb.Database(db_root, part)
tiles = load_tiles(db_root, part)
segments = mk_segments(tiles)
with OpenSafeFile(bits_file) as f:
bitdata = bitstream.load_bitdata2(f)
if flag_unknown_bits:
print_unknown_bits(tiles, bitdata)
print("")
# 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(tiles))
print('Segments: %u' % len(segnames))
# XXX: previously this was sorted by address, not name
# revisit?
for segname in segnames:
handle_segment(
db,
segname,
bitdata,
flag_decode_emit,
flag_decode_omit,
omit_empty_segs,
segments,
bit_only=bit_only,
verbose=verbose)
def main():
import argparse
parser = argparse.ArgumentParser(
description="Decode bits within a tile's address space")
db_root_arg(parser)
part_arg(parser)
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument(
'-z',
action='store_true',
help="do not print a 'seg' header for empty segments")
parser.add_argument(
'-b', action='store_true', help='print bits outside of known segments')
parser.add_argument(
'-d',
action='store_true',
help='decode known segment bits and write them as tags')
parser.add_argument(
'-D',
action='store_true',
help='decode known segment bits and omit them in the output')
parser.add_argument(
'--bit-only',
action='store_true',
help='only decode real bitstream directives')
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.db_root,
args.part,
args.bits_file,
args.segnames,
args.z,
args.b,
args.d,
args.D,
bit_only=args.bit_only,
verbose=args.verbose)
if __name__ == '__main__':
main()