blob: 99b62eaf04d602c5a062b7c3a3ee14fde2716fa9 [file] [log] [blame] [edit]
'''
NOTE: "segments" as used in this file is mostly unrelated to tilegrid.json usage
ie tilegrid.json has names like SEG_CLBLL_L_X2Y50 where as here they are tile based and named like seg_00400100_02
Instead of using tilegrid.json "segments, segments are formed by looking for tiles that use the same address + offset
Sample segdata.txt output (from 015-clbnffmux/specimen_001/segdata_clbll_r.txt):
seg 00020880_048
bit 30_00
bit 31_49
tag CLB.SLICE.AFF.DMUX.CY 1
tag CLB.SLICE.BFF.DMUX.BX 0
tilegrid.json provides tile addresses
'''
import os, json, re
from utils import util
BLOCK_TYPES = set(('CLB_IO_CLK', 'BLOCK_RAM', 'CFG_CLB'))
def recurse_sum(x):
'''Count number of nested iterable occurances'''
if type(x) in (str, bytearray):
return 1
if type(x) in (dict, ):
return sum([recurse_sum(y) for y in x.values()])
else:
try:
return sum([recurse_sum(y) for y in x])
except TypeError:
return 1
def json_hex2i(s):
'''Convert a JSON hex literal into an integer (it can't store hex natively)'''
# TODO: maybe just do int(x, 0)
return int(s[2:], 16)
def add_site_group_zero(segmk, site, prefix, vals, zero_val, val):
'''
Correctly add tags for a multi-bit enumerated value
Naively adding them directly doesn't work correctly because overlapping bits won't solve correctly
Instead, you need to carefully diff against a known zero value
Good zero values:
-An enum known to be zero
-A site that doesn't contain the enum
segmak: Segmaker object
site: the site to add tags to
prefix: tag string to prefix onto vals
vals: all possible tag enum vals
zero_val: tag value known to have no bits set
'''
# assert zero_val in vals, "Got %s, need %s" % (zero_val, vals)
assert val in vals or val == zero_val, "Got %s, need %s" % (val, vals)
if val == zero_val:
# Zero symbol occured, none of the others did
for aval in vals:
tag = prefix + aval
segmk.add_site_tag(site, tag, aval == val)
else:
# Only add the occured symbol
tag = prefix + val
segmk.add_site_tag(site, tag, True)
if zero_val in vals:
# And zero so that it has something to solve against
tag = prefix + zero_val
segmk.add_site_tag(site, tag, False)
class Segmaker:
def __init__(self,
bitsfile,
verbose=False,
db_root=None,
part=None,
bits_per_word=32):
self.db_root = db_root
if self.db_root is None:
self.db_root = util.get_db_root()
self.part = part
if self.part is None:
self.part = util.get_part()
assert self.part, "No part specified."
self.verbose = verbose if verbose is not None else os.getenv(
'VERBOSE', 'N') == 'Y'
self.bits_per_word = bits_per_word
self.load_grid()
self.load_bits(bitsfile)
'''
self.tags[site][name] = value
Where:
-site: ex 'SLICE_X13Y101'
-name: ex 'CLB.SLICE_X0.AFF.DMUX.CY'
'''
self.site_tags = dict()
self.tile_tags = dict()
# output after compiling
self.segments_by_type = None
# hacky...improve if we encounter this more
self.def_bt = 'CLB_IO_CLK'
self.index_sites()
def index_sites(self):
self.verbose and print("Indexing sites")
self.sites = {}
for tilename, tiledata in self.grid.items():
for site in tiledata["sites"]:
self.sites[site] = tilename
self.verbose and print("Sites indexed")
def set_def_bt(self, block_type):
'''Set default block type when more than one block present'''
assert block_type in BLOCK_TYPES, (
"Unknown block type %r (known %r)" % (block_type, BLOCK_TYPES))
self.def_bt = block_type
def load_grid(self):
'''Load self.grid holding tile addresses'''
with open(os.path.join(self.db_root, self.part, "tilegrid.json"),
"r") as f:
self.grid = json.load(f)
assert "segments" not in self.grid, "Old format tilegrid.json"
def load_bits(self, bitsfile):
'''Load self.bits holding the bits that occured in the bitstream'''
'''
Format:
self.bits[base_frame][bit_wordidx] = set()
Where elements are (bit_frame, bit_wordidx, bit_bitidx))
bit_frame is a relatively large number forming the FDRI address
base_frame is a truncated bit_frame address of related FDRI addresses
0 <= bit_wordidx <= 100
0 <= bit_bitidx < 31
Sample bits input
bit_00020500_000_08
bit_00020500_000_14
bit_00020500_000_17
'''
self.bits = dict()
print("Loading bits from %s." % bitsfile)
with open(bitsfile, "r") as f:
for line in f:
# ex: bit_00020500_000_17
line = line.split("_")
bit_frame = int(line[1], 16)
# Word indexes in the .bits file for US+/US assume 32-bits per word
bit_wordidx = int(line[2],
10) * (32 / self.bits_per_word) + int(
line[3], 10) // self.bits_per_word
bit_bitidx = int(line[3], 10) % self.bits_per_word
# Bit ranges in the Frame Address Register Description differs between US+ and US devices
# For US the ranges are identical to 7-series, but US+ is shifted 1 bit left
# Refer to UG570 Table 9-21
base_frame = bit_frame & (~0xff if os.getenv("URAY_ARCH") in
"UltraScalePlus" else ~0x7f)
self.bits.setdefault(base_frame, dict()).setdefault(
bit_wordidx, set()).add((bit_frame, bit_wordidx,
bit_bitidx))
if self.verbose:
print('Loaded bits: %u bits in %u base frames' % (recurse_sum(
self.bits), len(self.bits)))
def add_site_tag(self, site, name, value):
'''
XXX: can add tags in two ways:
-By site name
-By tile name (used for pips?)
Consider splitting into two separate data structures
Record, according to value, if (site, name) exists
Ex:
self.addtag('SLICE_X13Y101', 'CLB.SLICE_X0.AFF.DMUX.CY', 1)
Indicates that the SLICE_X13Y101 site has an element called 'CLB.SLICE_X0.AFF.DMUX.CY'
'''
if '"' in site:
raise ValueError("Invalid site: %s" % site)
self.verbose and print(
'segmaker add tag: site %s tag %s = %s' % (site, name, value))
assert site in self.sites, "Unknown site %s" % (site, )
self.site_tags.setdefault(site, dict())[name] = value
def add_tile_tag(self, tile, name, value):
# TODO: test this out
# assert tile in self.grid
self.verbose and print(
'segmaker add tag: tile %s tag %s = %s' % (tile, name, value))
self.tile_tags.setdefault(tile, dict())[name] = value
def compile(self, bitfilter=None):
print("Compiling segment data.")
tags_used = set()
sites_used = set()
tile_types_found = set()
self.segments_by_type = dict()
def add_segbits(segments, segname, tiledata, bitfilter=None):
'''
Add and populate segments[segname]["bits"]
Gives all of the bits that could exist for the space we are exploring
Also add segments[segname]["tags"], but don't fill
segments is a group related to a specific tile type (ex: CLBLM_L)
It is composed of bits (possible bits) and tags (observed instances)
segments[segname]["bits"].add(bitname)
segments[segname]["tags"][tag] = value
segname: FDRI address + word offset string
tiledata: tilegrid info for this tile
'''
assert segname not in segments
segment = segments.setdefault(
segname,
{
"bits": set(),
"tags": dict(),
# verify new entries match this
"offset": bitj["offset"],
"words": bitj["words"],
"frames": bitj["frames"],
})
base_frame = json_hex2i(bitj["baseaddr"])
for wordidx in range(bitj["offset"],
bitj["offset"] + bitj["words"]):
if base_frame not in self.bits:
continue
if wordidx not in self.bits[base_frame]:
continue
for bit_frame, bit_wordidx, bit_bitidx in self.bits[
base_frame][wordidx]:
bitname_frame = bit_frame - base_frame
bitname_bit = self.bits_per_word * (
bit_wordidx - bitj["offset"]) + bit_bitidx
# Skip bits above the frame limit.
if bitname_frame >= bitj["frames"]:
continue
# some bits are hard to de-correlate
# allow force dropping some bits from search space for practicality
if bitfilter is None or bitfilter(bitname_frame,
bitname_bit):
bitname = "%02d_%02d" % (bitname_frame, bitname_bit)
segment["bits"].add(bitname)
return segment
'''
XXX: wouldn't it be better to iterate over tags? Easy to drop tags
For now, add a check that all tags are used
'''
for tilename, tiledata in self.grid.items():
def getseg(segname):
if not segname in segments:
return add_segbits(
segments, segname, tiledata, bitfilter=bitfilter)
else:
segment = segments[segname]
assert segment["offset"] == bitj["offset"]
assert segment["words"] == bitj["words"]
assert segment["frames"] == bitj["frames"]
return segment
def add_tilename_tags():
self.verbose and print("Tile %s: check tags" % tilename)
segment = getseg(segname)
for name, value in self.tile_tags[tilename].items():
tags_used.add((tilename, name))
tag = "%s.%s" % (tile_type_norm, name)
segment["tags"][tag] = value
def add_site_tags():
site_prefix = site.split('_')[0]
def name_slice():
'''
Simplify SLICE names like:
-SLICE_X12Y102 => SLICE
'''
if re.match(r"SLICE_X[0-9]*[0123456789]Y", site):
return "SLICE"
else:
assert False, "Invalid name in %s" % site
def name_bram18():
# RAMB18_X0Y41
if re.match(r"^RAMB18_X.*Y[0-9]*[02468]$", site):
return "RAMB18_Y0"
elif re.match(r"^RAMB18_X.*Y[0-9]*[13579]$", site):
return "RAMB18_Y1"
else:
assert False, "Invalid name in %s" % site
def name_y0y1():
# RAMB18_X0Y41
m = re.match(r"^(.*)_X.*Y[0-9]*[02468]$", site)
if m:
return "%s_Y0" % m.group(1)
m = re.match(r"^(.*)_X.*Y[0-9]*[13579]$", site)
if m:
return "%s_Y1" % m.group(1)
assert 0, site
def name_default():
# most sites are unique within their tile
# TODO: maybe verify against DB?
return site_prefix
sitekey = {
'SLICE': name_slice,
'RAMB18': name_bram18,
'IOB': name_y0y1,
'IDELAY': name_y0y1,
'ILOGIC': name_y0y1,
'OLOGIC': name_y0y1,
}.get(site_prefix, name_default)()
self.verbose and print('site %s w/ %s prefix => tag %s' %
(site, site_prefix, sitekey))
for name, value in self.site_tags[site].items():
self.verbose and print("Site %s: check tags" % site)
tags_used.add((site, name))
tag = "%s.%s.%s" % (tile_type_norm, sitekey, name)
# XXX: does this come from name?
tag = tag.replace(".SLICEM.", ".")
tag = tag.replace(".SLICEL.", ".")
segment = getseg(segname)
segment["tags"][tag] = value
sites_used.add(site)
tile_type = tiledata["type"]
tile_types_found.add(tile_type)
segments = self.segments_by_type.setdefault(tile_type, dict())
'''
Simplify names by simplifying like:
-CLBLM_L => CLB
-CENTER_INTER_R => CENTER_INTER
-CLK_HROW_TOP_R => CLK_HROW
-LIOB33 => IOB33
-LIOI3 => IOI3
'''
tile_type_norm = re.sub("(_TOP|_BOT|LL|LM)?_[LR]$", "", tile_type)
tile_type_norm = re.sub("_TOP_[LR]_UPPER", "_UPPER",
tile_type_norm)
if tile_type_norm in ['LIOB33', 'RIOB33']:
tile_type_norm = 'IOB33'
if tile_type_norm in ['LIOI3', 'RIOI3']:
tile_type_norm = 'IOI3'
if tile_type_norm in ['LIOI3_TBYTESRC', 'RIOI3_TBYTESRC']:
tile_type_norm = 'IOI3'
if tile_type_norm in ['LIOI3_TBYTETERM', 'RIOI3_TBYTETERM']:
tile_type_norm = 'IOI3'
# ignore dummy tiles (ex: VBRK)
if len(tiledata['bits']) == 0:
if self.verbose:
for site in tiledata["sites"]:
assert site not in self.site_tags, "Site %s does not have bitstream info" % site
this_tile_tags = len(self.tile_tags.get(tilename, {}))
assert this_tile_tags == 0, "Tile %s does not have bitstream info but %s tags" % (
tilename, this_tile_tags)
continue
elif len(tiledata['bits']) == 1:
bitj = list(tiledata['bits'].values())[0]
else:
assert self.def_bt in tiledata[
'bits'], 'Default block not present: %s' % self.def_bt
bitj = tiledata['bits'][self.def_bt]
# NOTE: multiple tiles may have the same base addr + offset
segname = "%s_%03d" % (
# truncate 0x to leave hex string
bitj["baseaddr"][2:],
bitj["offset"])
# process tile name tags
if tilename in self.tile_tags:
add_tilename_tags()
# process site name tags
for site in tiledata["sites"]:
if site not in self.site_tags:
continue
add_site_tags()
n_site_tags = recurse_sum(self.site_tags)
n_tile_tags = recurse_sum(self.tile_tags)
ntags = n_site_tags + n_tile_tags
if self.verbose:
assert ntags, "No tags"
print("Used %u / %u tags" % (len(tags_used), ntags))
print("Tag sites: %u" % (n_site_tags, ))
if n_site_tags:
print(' Ex: %s' % list(self.site_tags.keys())[0])
print("Tag tiles: %u" % (n_tile_tags, ))
print("Used %u sites" % len(sites_used))
print("Grid DB had %u tile types" % len(tile_types_found))
assert ntags == len(tags_used), "Unused tags, %s used out of %s" % (
len(tags_used), ntags)
def write(self, suffix=None, roi=False, allow_empty=False):
assert self.segments_by_type, 'No data to write'
if not allow_empty:
assert sum([
len(segments) for segments in self.segments_by_type.values()
]) != 0, "Didn't generate any segments"
for segtype in self.segments_by_type.keys():
if suffix is not None:
filename = "segdata_%s_%s.txt" % (segtype.lower(), suffix)
else:
filename = "segdata_%s.txt" % (segtype.lower())
segments = self.segments_by_type[segtype]
if segments:
print("Writing %s." % filename)
with open(filename, "w") as f:
for segname, segdata in sorted(segments.items()):
# seg 00020300_010
print("seg %s" % segname, file=f)
for bitname in sorted(segdata["bits"]):
print("bit %s" % bitname, file=f)
for tagname, tagval in sorted(segdata["tags"].items()):
print("tag %s %d" % (tagname, tagval), file=f)