Merge pull request #159 from mcmasterg/db_bits

tilegrid multiple address support, misc cleanup
This commit is contained in:
John McMaster 2018-10-17 18:07:57 -07:00 committed by GitHub
commit 38207ffb84
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 2067 additions and 1501 deletions

View File

@ -1,248 +1,569 @@
#!/usr/bin/env python3
'''
Historically we grouped data into "segments"
These were a region of the bitstream that encoded one or more tiles
However, this didn't scale with certain tiles like BRAM
Some sites had multiple bitstream areas and also occupied multiple tiles
Decoding was then shifted to instead describe how each title is encoded
A post processing step verifies that two tiles don't reference the same bitstream area
'''
import os, sys, json, re
#######################################
# Read
# matches lib/include/prjxray/xilinx/xc7series/block_type.h
block_type_i2s = {
0: 'CLB_IO_CLK',
1: 'BLOCK_RAM',
2: 'CFG_CLB',
# special...maybe should error until we know what it is?
# 3: 'RESERVED',
}
tiles = list()
site_baseaddr = dict()
tile_baseaddr = dict()
with open("tiles.txt") as f:
for line in f:
tiles.append(line.split())
def addr2btype(base_addr):
'''
Convert integer address to block type
for arg in sys.argv[1:]:
with open(arg) as f:
line = f.read().strip()
site = arg[7:-6]
frame = int(line[5:5 + 8], 16)
site_baseaddr[site] = "0x%08x" % (frame & ~0x7f)
Table 5-24: Frame Address Register Description
Bit Index: [25:23]
https://www.xilinx.com/support/documentation/user_guides/ug470_7Series_Config.pdf
"Valid block types are CLB, I/O, CLK ( 000 ), block RAM content ( 001 ), and CFG_CLB ( 010 ). A normal bitstream does not include type 011 ."
'''
block_type_i = (base_addr >> 23) & 0x7
return block_type_i2s[block_type_i]
#######################################
# Create initial database
database = dict()
database["tiles"] = dict()
database["segments"] = dict()
tiles_by_grid = dict()
def nolr(tile_type):
'''
Remove _L or _R suffix tile_type suffix, if present
Ex: BRAM_INT_INTERFACE_L => BRAM_INT_INTERFACE
Ex: VBRK => VBRK
'''
postfix = tile_type[-2:]
if postfix in ('_L', '_R'):
return tile_type[:-2]
else:
return tile_type
for record in tiles:
tile_type, tile_name, grid_x, grid_y = record[0:4]
grid_x, grid_y = int(grid_x), int(grid_y)
tiles_by_grid[(grid_x, grid_y)] = tile_name
framebaseaddr = None
database["tiles"][tile_name] = {
"type": tile_type,
"sites": dict(),
"grid_x": grid_x,
"grid_y": grid_y
}
def load_tiles(tiles_fn):
'''
"$type $tile $grid_x $grid_y $typed_sites"
typed_sites: foreach t $site_types s $sites
'''
tiles = list()
if len(record) > 4:
for i in range(4, len(record), 2):
site_type, site_name = record[i:i + 2]
if site_name in site_baseaddr:
framebaseaddr = site_baseaddr[site_name]
database["tiles"][tile_name]["sites"][site_name] = site_type
with open(tiles_fn) as f:
for line in f:
# CLBLM_L CLBLM_L_X10Y98 30 106 SLICEL SLICE_X13Y98 SLICEM SLICE_X12Y98
record = line.split()
tile_type, tile_name, grid_x, grid_y = record[0:4]
grid_x, grid_y = int(grid_x), int(grid_y)
sites = {}
for i in range(4, len(record), 2):
site_type, site_name = record[i:i + 2]
sites[site_name] = site_type
tile = {
'type': tile_type,
'name': tile_name,
'grid_x': grid_x,
'grid_y': grid_y,
'sites': sites,
}
tiles.append(tile)
if framebaseaddr is not None:
tile_baseaddr[tile_name] = [framebaseaddr, 0]
return tiles
#######################################
# Add Segments
for tile_name, tile_data in database["tiles"].items():
tile_type = tile_data["type"]
grid_x = tile_data["grid_x"]
grid_y = tile_data["grid_y"]
def load_baseaddrs(deltas_fns):
site_baseaddr = dict()
for arg in deltas_fns:
with open(arg) as f:
line = f.read().strip()
site = arg[7:-6]
frame = int(line[5:5 + 8], 16)
# was "0x%08x"
site_baseaddr[site] = frame & ~0x7f
if tile_type in ["CLBLL_L", "CLBLL_R", "CLBLM_L", "CLBLM_R"]:
if tile_type in ["CLBLL_L", "CLBLM_L"]:
int_tile_name = tiles_by_grid[(grid_x + 1, grid_y)]
else:
int_tile_name = tiles_by_grid[(grid_x - 1, grid_y)]
return site_baseaddr
segment_name = "SEG_" + tile_name
segtype = tile_type.lower()
database["segments"][segment_name] = dict()
database["segments"][segment_name]["tiles"] = [
tile_name, int_tile_name
]
database["segments"][segment_name]["type"] = segtype
database["segments"][segment_name]["frames"] = 36
database["segments"][segment_name]["words"] = 2
def make_database(tiles):
# tile database with X, Y, and list of sites
# tile name as keys
database = dict()
if tile_name in tile_baseaddr:
database["segments"][segment_name]["baseaddr"] = tile_baseaddr[
tile_name]
for tile in tiles:
database[tile["name"]] = {
"type": tile["type"],
"sites": tile["sites"],
"grid_x": tile["grid_x"],
"grid_y": tile["grid_y"],
"bits": {},
}
database["tiles"][tile_name]["segment"] = segment_name
database["tiles"][int_tile_name]["segment"] = segment_name
return database
if tile_type in ["HCLK_L", "HCLK_R"]:
segment_name = "SEG_" + tile_name
segtype = tile_type.lower()
database["segments"][segment_name] = dict()
database["segments"][segment_name]["tiles"] = [tile_name]
database["segments"][segment_name]["type"] = segtype
database["segments"][segment_name]["frames"] = 26
database["segments"][segment_name]["words"] = 1
database["tiles"][tile_name]["segment"] = segment_name
def make_tile_baseaddrs(tiles, site_baseaddr, verbose=False):
# Look up a base address by tile name
tile_baseaddrs = dict()
if tile_type in ["BRAM_L", "DSP_L", "BRAM_R", "DSP_R"]:
for k in range(5):
if tile_type in ["BRAM_L", "DSP_L"]:
interface_tile_name = tiles_by_grid[(grid_x + 1, grid_y - k)]
int_tile_name = tiles_by_grid[(grid_x + 2, grid_y - k)]
verbose and print('')
verbose and print('%u tiles' % len(tiles))
added = 0
for tile in tiles:
for site_name in tile["sites"].keys():
if site_name not in site_baseaddr:
continue
framebaseaddr = site_baseaddr[site_name]
bt = addr2btype(framebaseaddr)
tile_baseaddr = tile_baseaddrs.setdefault(tile["name"], {})
if bt in tile_baseaddr:
# actually lets just fail these, better to remove at tcl level to speed up processing
assert 0, 'duplicate base address'
assert tile_baseaddr[bt] == [framebaseaddr, 0]
else:
interface_tile_name = tiles_by_grid[(grid_x - 1, grid_y - k)]
int_tile_name = tiles_by_grid[(grid_x - 2, grid_y - k)]
tile_baseaddr[bt] = [framebaseaddr, 0]
verbose and print(
"baseaddr: %s.%s @ %s.0x%08x" %
(tile["name"], site_name, bt, framebaseaddr))
added += 1
segment_name = "SEG_" + tile_name.replace("_", "%d_" % k, 1)
segtype = tile_type.lower().replace("_", "%d_" % k, 1)
assert added
return tile_baseaddrs
database["segments"][segment_name] = dict()
database["segments"][segment_name]["type"] = segtype
database["segments"][segment_name]["frames"] = 28
database["segments"][segment_name]["words"] = 2
if k == 0:
database["segments"][segment_name]["tiles"] = [
tile_name, interface_tile_name, int_tile_name
]
database["tiles"][tile_name]["segment"] = segment_name
database["tiles"][interface_tile_name][
"segment"] = segment_name
database["tiles"][int_tile_name]["segment"] = segment_name
def make_tiles_by_grid(tiles):
# lookup tile names by (X, Y)
tiles_by_grid = dict()
for tile in tiles:
tiles_by_grid[(tile["grid_x"], tile["grid_y"])] = tile["name"]
return tiles_by_grid
def make_segments(database, tiles_by_grid, tile_baseaddrs, verbose=False):
'''
Create segments data structure
Indicates how tiles are related to bitstream locations
Also modify database to annotate which segment the tiles belong to
segments key examples:
SEG_CLBLM_R_X13Y72
SEG_BRAM3_L_X6Y85
'''
segments = dict()
verbose and print('')
for tile_name, tile_data in database.items():
tile_type = tile_data["type"]
grid_x = tile_data["grid_x"]
grid_y = tile_data["grid_y"]
def add_segment(name, tiles, segtype, baseaddr=None):
assert name not in segments
segment = segments.setdefault(name, {})
segment["tiles"] = tiles
segment["type"] = segtype
if baseaddr:
verbose and print(
'make_segment: %s baseaddr %s' % (
name,
baseaddr,
))
segment["baseaddr"] = baseaddr
for tile_name in tiles:
database[tile_name]["segment"] = name
def process_clb():
if tile_type in ["CLBLL_L", "CLBLM_L"]:
int_tile_name = tiles_by_grid[(grid_x + 1, grid_y)]
else:
database["segments"][segment_name]["tiles"] = [
interface_tile_name, int_tile_name
]
database["tiles"][interface_tile_name][
"segment"] = segment_name
database["tiles"][int_tile_name]["segment"] = segment_name
int_tile_name = tiles_by_grid[(grid_x - 1, grid_y)]
#######################################
# Populate segment base addresses: L/R along INT column
add_segment(
name="SEG_" + tile_name,
tiles=[tile_name, int_tile_name],
segtype=tile_type.lower(),
baseaddr=tile_baseaddrs.get(tile_name, None))
for segment_name in database["segments"].keys():
if "baseaddr" in database["segments"][segment_name]:
framebase, wordbase = database["segments"][segment_name]["baseaddr"]
inttile = [
tile for tile in database["segments"][segment_name]["tiles"]
if database["tiles"][tile]["type"] in ["INT_L", "INT_R"]
][0]
grid_x = database["tiles"][inttile]["grid_x"]
grid_y = database["tiles"][inttile]["grid_y"]
def process_hclk():
add_segment(
name="SEG_" + tile_name,
tiles=[tile_name],
segtype=tile_type.lower())
if database["tiles"][inttile]["type"] == "INT_L":
grid_x += 1
framebase = "0x%08x" % (int(framebase, 16) + 0x80)
else:
grid_x -= 1
framebase = "0x%08x" % (int(framebase, 16) - 0x80)
def process_bram_dsp():
for k in range(5):
if tile_type in ["BRAM_L", "DSP_L"]:
interface_tile_name = tiles_by_grid[(
grid_x + 1, grid_y - k)]
int_tile_name = tiles_by_grid[(grid_x + 2, grid_y - k)]
elif tile_type in ["BRAM_R", "DSP_R"]:
interface_tile_name = tiles_by_grid[(
grid_x - 1, grid_y - k)]
int_tile_name = tiles_by_grid[(grid_x - 2, grid_y - k)]
else:
assert 0
'''
BRAM/DSP itself is at the base y address
There is one huge switchbox on the right for the 5 tiles
These fan into 5 BRAM_INT_INTERFACE tiles each which feed into their own CENTER_INTER (just like a CLB has)
'''
if k == 0:
tiles = [tile_name, interface_tile_name, int_tile_name]
baseaddr = tile_baseaddrs.get(tile_name, None)
else:
tiles = [interface_tile_name, int_tile_name]
baseaddr = None
if (grid_x, grid_y) not in tiles_by_grid:
add_segment(
# BRAM_L_X6Y70 => SEG_BRAM4_L_X6Y70
name="SEG_" + tile_name.replace("_", "%d_" % k, 1),
tiles=tiles,
# BRAM_L => bram4_l
segtype=tile_type.lower().replace("_", "%d_" % k, 1),
baseaddr=baseaddr)
def process_default():
#verbose and nolr(tile_type) not in ('VBRK', 'INT', 'NULL') and print('make_segment: drop %s' % (tile_type,))
pass
{
"CLBLL": process_clb,
"CLBLM": process_clb,
"HCLK": process_hclk,
"BRAM": process_bram_dsp,
"DSP": process_bram_dsp,
}.get(nolr(tile_type), process_default)()
return segments
def get_inttile(database, segment):
'''Return interconnect tile for given segment'''
inttiles = [
tile for tile in segment["tiles"]
if database[tile]["type"] in ["INT_L", "INT_R"]
]
assert len(inttiles) == 1
return inttiles[0]
def get_bramtile(database, segment):
inttiles = [
tile for tile in segment["tiles"]
if database[tile]["type"] in ["BRAM_L", "BRAM_R"]
]
assert len(inttiles) == 1
return inttiles[0]
def seg_base_addr_lr_INT(database, segments, tiles_by_grid, verbose=False):
'''Populate segment base addresses: L/R along INT column'''
'''
Create BRAM base addresses based on nearby CLBs
ie if we have a BRAM_L, compute as nearby CLB_R base address + offset
'''
verbose and print('')
for segment_name in sorted(segments.keys()):
segment = segments[segment_name]
baseaddrs = segment.get("baseaddr", None)
if not baseaddrs:
continue
tile = tiles_by_grid[(grid_x, grid_y)]
for block_type, (framebase, wordbase) in sorted(baseaddrs.items()):
verbose and print(
'lr_INT: %s: %s.0x%08X:%u' %
(segment_name, block_type, framebase, wordbase))
if block_type != 'CLB_IO_CLK':
verbose and print(' Skip non CLB')
continue
if database["tiles"][inttile]["type"] == "INT_L":
assert database["tiles"][tile]["type"] == "INT_R"
elif database["tiles"][inttile]["type"] == "INT_R":
assert database["tiles"][tile]["type"] == "INT_L"
else:
assert 0
inttile = get_inttile(database, segment)
grid_x = database[inttile]["grid_x"]
grid_y = database[inttile]["grid_y"]
assert "segment" in database["tiles"][tile]
if database[inttile]["type"] == "INT_L":
grid_x += 1
framebase = framebase + 0x80
elif database[inttile]["type"] == "INT_R":
grid_x -= 1
framebase = framebase - 0x80
else:
assert 0
seg = database["tiles"][tile]["segment"]
# ROI at edge?
if (grid_x, grid_y) not in tiles_by_grid:
verbose and print(' Skip edge')
continue
if "baseaddr" in database["segments"][seg]:
assert database["segments"][seg]["baseaddr"] == [
framebase, wordbase
]
else:
database["segments"][seg]["baseaddr"] = [framebase, wordbase]
tile = tiles_by_grid[(grid_x, grid_y)]
#######################################
# Populate segment base addresses: Up along INT/HCLK columns
if database[inttile]["type"] == "INT_L":
assert database[tile]["type"] == "INT_R"
elif database[inttile]["type"] == "INT_R":
assert database[tile]["type"] == "INT_L"
else:
assert 0
start_segments = list()
assert "segment" in database[tile]
for segment_name in database["segments"].keys():
if "baseaddr" in database["segments"][segment_name]:
start_segments.append(segment_name)
seg = database[tile]["segment"]
for segment_name in start_segments:
framebase, wordbase = database["segments"][segment_name]["baseaddr"]
inttile = [
tile for tile in database["segments"][segment_name]["tiles"]
if database["tiles"][tile]["type"] in ["INT_L", "INT_R"]
][0]
grid_x = database["tiles"][inttile]["grid_x"]
grid_y = database["tiles"][inttile]["grid_y"]
seg_baseaddrs = segments[seg].setdefault("baseaddr", {})
# At least one duplicate when we re-compute the entry for the base address
# should give the same address
if block_type in seg_baseaddrs:
assert seg_baseaddrs[block_type] == [
framebase, wordbase
], (seg_baseaddrs[block_type], [framebase, wordbase])
verbose and print(' Existing OK')
else:
seg_baseaddrs[block_type] = [framebase, wordbase]
verbose and print(' Add new')
for i in range(50):
grid_y -= 1
if wordbase == 50:
wordbase += 1
else:
wordbase += 2
def seg_base_addr_up_INT(database, segments, tiles_by_grid, verbose=False):
'''Populate segment base addresses: Up along INT/HCLK columns'''
segname = database["tiles"][tiles_by_grid[(grid_x, grid_y)]]["segment"]
database["segments"][segname]["baseaddr"] = [framebase, wordbase]
verbose and print('')
# Copy the initial list containing only base addresses
# and soon to have derived addresses
src_segment_names = list()
for segment_name in segments.keys():
if "baseaddr" in segments[segment_name]:
src_segment_names.append(segment_name)
#######################################
# Transfer segment data into tiles
verbose and print('up_INT: %u base addresses' % len(src_segment_names))
for segment_name in database["segments"].keys():
try:
baseaddr, offset = database["segments"][segment_name]["baseaddr"]
except:
print('Failed on segment name %s' % segment_name)
raise
for tile_name in database["segments"][segment_name]["tiles"]:
tile_type = database["tiles"][tile_name]["type"]
if tile_type in ["CLBLL_L", "CLBLL_R", "CLBLM_L", "CLBLM_R", "INT_L",
"INT_R"]:
database["tiles"][tile_name]["baseaddr"] = baseaddr
database["tiles"][tile_name]["offset"] = offset
database["tiles"][tile_name]["height"] = 2
elif tile_type in ["HCLK_L", "HCLK_R"]:
database["tiles"][tile_name]["baseaddr"] = baseaddr
database["tiles"][tile_name]["offset"] = offset
database["tiles"][tile_name]["height"] = 1
elif tile_type in ["BRAM_L", "BRAM_R", "DSP_L", "DSP_R"]:
database["tiles"][tile_name]["baseaddr"] = baseaddr
database["tiles"][tile_name]["offset"] = offset
database["tiles"][tile_name]["height"] = 10
elif tile_type in ["INT_INTERFACE_L", "INT_INTERFACE_R",
"BRAM_INT_INTERFACE_L", "BRAM_INT_INTERFACE_R"]:
continue
else:
# print(tile_type, offset)
assert False
for src_segment_name in sorted(src_segment_names):
src_segment = segments[src_segment_name]
# TODO: Migrate to new tilegrid format via library. This data is added for
# compability with unconverted tools. Update tools then remove this data from
# tilegrid.json.
for tiledata in database['tiles'].values():
if "segment" in tiledata:
segment = tiledata['segment']
tiledata['frames'] = database['segments'][segment]['frames']
tiledata['words'] = database['segments'][segment]['words']
tiledata['segment_type'] = database['segments'][segment]['type']
for block_type, (framebase,
wordbase) in sorted(src_segment["baseaddr"].items()):
verbose and print(
'up_INT: %s: %s.0x%08X:%u' %
(src_segment_name, block_type, framebase, wordbase))
database = database["tiles"]
def process_CLB_IO_CLK(wordbase):
'''
Lookup interconnect tile associated with this segment
Use it to locate in the grid, and find other segments related by tile offset
'''
#######################################
# Write
inttile = get_inttile(database, src_segment)
verbose and print(
' up_INT CLK_IO_CLK: %s => inttile %s' %
(src_segment_name, inttile))
grid_x = database[inttile]["grid_x"]
grid_y = database[inttile]["grid_y"]
print(json.dumps(database, sort_keys=True, indent="\t"))
for i in range(50):
grid_y -= 1
dst_tile = database[tiles_by_grid[(grid_x, grid_y)]]
if wordbase == 50:
wordbase += 1
else:
wordbase += 2
#verbose and print(' dst_tile', dst_tile)
dst_segment_name = dst_tile["segment"]
#verbose and print('up_INT: %s => %s' % (src_segment_name, dst_segment_name))
segments[dst_segment_name].setdefault(
"baseaddr", {})[block_type] = [framebase, wordbase]
def process_BLOCK_RAM(wordbase):
'''
Lookup BRAM0 tile associated with this segment
Use it to locate in the grid, and find other BRAM0 related by tile offset
From minitest:
build/roi_bramd_bit01.diff (lowest BRAM coordinate)
> bit_00c00000_000_00
build/roi_bramds_bit01.diff
> bit_00c00000_000_00
> bit_00c00000_010_00
> bit_00c00000_020_00
> bit_00c00000_030_00
> bit_00c00000_040_00
> bit_00c00000_051_00
> bit_00c00000_061_00
> bit_00c00000_071_00
> bit_00c00000_081_00
> bit_00c00000_091_00
'''
src_tile_name = get_bramtile(database, src_segment)
verbose and print(
' up_INT BLOCK_RAM: %s => %s' %
(src_segment_name, src_tile_name))
grid_x = database[src_tile_name]["grid_x"]
grid_y = database[src_tile_name]["grid_y"]
for i in range(9):
grid_y -= 5
wordbase += 10
# Skip HCLK
if i == 4:
grid_y -= 1
wordbase += 1
dst_tile = database[tiles_by_grid[(grid_x, grid_y)]]
assert nolr(dst_tile['type']) == 'BRAM', dst_tile
dst_segment_name = dst_tile["segment"]
assert 'BRAM0' in dst_segment_name
segments[dst_segment_name].setdefault(
"baseaddr", {})[block_type] = [framebase, wordbase]
{
'CLB_IO_CLK': process_CLB_IO_CLK,
'BLOCK_RAM': process_BLOCK_RAM,
}[block_type](
wordbase)
def add_tile_bits(tile_db, baseaddr, offset, frames, words, height=None):
'''
Record data structure geometry for the given tile baseaddr
For most tiles there is only one baseaddr, but some like BRAM have multiple
Notes on multiple block types:
https://github.com/SymbiFlow/prjxray/issues/145
'''
bits = tile_db['bits']
block_type = addr2btype(baseaddr)
assert 0 <= offset <= 100, offset
assert 1 <= words <= 101
assert offset + words <= 101, (
tile_db, offset + words, offset, words, block_type)
assert block_type not in bits
block = bits.setdefault(block_type, {})
# FDRI address
block["baseaddr"] = '0x%08X' % baseaddr
# Number of frames this entry is sretched across
# that is the following FDRI addresses are used: range(baseaddr, baseaddr + frames)
block["frames"] = frames
# Index of first word used within each frame
block["offset"] = offset
# Number of words consumed in each frame
block["words"] = words
# related to words...
# deprecated field? Don't worry about for now
if height is not None:
block["height"] = height
def db_add_bits(database, segments):
'''Transfer segment data into tiles'''
for segment_name in segments.keys():
for block_type, (baseaddr,
offset) in segments[segment_name]["baseaddr"].items():
for tile_name in segments[segment_name]["tiles"]:
tile_type = database[tile_name]["type"]
entry = {
# (tile_type, block_type): (frames, words, height)
("CLBLL", "CLB_IO_CLK"): (36, 2, 2),
("CLBLM", "CLB_IO_CLK"): (36, 2, 2),
("HCLK", "CLB_IO_CLK"): (26, 1, 1),
("INT", "CLB_IO_CLK"): (28, 2, 2),
("BRAM", "CLB_IO_CLK"): (28, 2, None),
("BRAM", "BLOCK_RAM"): (128, 5, None),
("DSP", "CLB_IO_CLK"): (28, 2, 10),
("INT_INTERFACE", "CLB_IO_CLK"): (28, 2, None),
("BRAM_INT_INTERFACE", "CLB_IO_CLK"): (28, 2, None),
}.get((nolr(tile_type), block_type), None)
if entry is None:
# Other types are rare, not expected to have these
if block_type == "CLB_IO_CLK":
raise ValueError("Unknown tile type %s" % tile_type)
continue
frames, words, height = entry
if frames:
# if we have a width, we should have a height
assert frames and words
add_tile_bits(
database[tile_name], baseaddr, offset, frames, words,
height)
def db_add_segments(database, segments):
# TODO: Migrate to new tilegrid format via library. This data is added for
# compability with unconverted tools. Update tools then remove this data from
# tilegrid.json.
# looks like only htmlgen is using this?
for tiledata in database.values():
if "segment" in tiledata:
segment = tiledata["segment"]
tiledata["segment_type"] = segments[segment]["type"]
def run(tiles_fn, json_fn, deltas_fns, verbose=False):
# Load input files
tiles = load_tiles(tiles_fn)
site_baseaddr = load_baseaddrs(deltas_fns)
# Index input
database = make_database(tiles)
tile_baseaddrs = make_tile_baseaddrs(tiles, site_baseaddr, verbose=verbose)
tiles_by_grid = make_tiles_by_grid(tiles)
segments = make_segments(
database, tiles_by_grid, tile_baseaddrs, verbose=verbose)
# Reference adjacent CLBs to locate adjacent tiles by known offsets
seg_base_addr_lr_INT(database, segments, tiles_by_grid, verbose=verbose)
seg_base_addr_up_INT(database, segments, tiles_by_grid, verbose=verbose)
db_add_bits(database, segments)
db_add_segments(database, segments)
# Save
json.dump(
database,
open(json_fn, 'w'),
sort_keys=True,
indent=4,
separators=(',', ': '))
def main():
import argparse
import glob
parser = argparse.ArgumentParser(
description='Generate tilegrid.json from bitstream deltas')
parser.add_argument('--verbose', action='store_true', help='')
parser.add_argument('--out', default='/dev/stdout', help='Output JSON')
parser.add_argument(
'--tiles', default='tiles.txt', help='Input tiles.txt tcl output')
parser.add_argument(
'deltas', nargs='*', help='.bit diffs to create base addresses from')
args = parser.parse_args()
deltas = args.deltas
if not args.deltas:
deltas = glob.glob('*.delta')
run(args.tiles, args.out, deltas, verbose=args.verbose)
if __name__ == '__main__':
main()

View File

@ -1,100 +1,152 @@
create_project -force -part $::env(XRAY_PART) design design
proc make_project {} {
create_project -force -part $::env(XRAY_PART) design design
read_verilog ../top.v
synth_design -top top
read_verilog ../top.v
synth_design -top top
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports do]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports stb]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports di]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports do]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports stb]
create_pblock roi
add_cells_to_pblock [get_pblocks roi] [get_cells roi]
resize_pblock [get_pblocks roi] -add "$::env(XRAY_ROI)"
create_pblock roi
add_cells_to_pblock [get_pblocks roi] [get_cells roi]
resize_pblock [get_pblocks roi] -add "$::env(XRAY_ROI)"
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
set_param tcl.collectionResultDisplayLimit 0
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
set_param tcl.collectionResultDisplayLimit 0
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
set luts [get_bels -of_objects [get_sites -of_objects [get_pblocks roi]] -filter {TYPE =~ LUT*} */A6LUT]
set selected_luts {}
set lut_index 0
if 0 {
set grid_min_x -1
set grid_max_x -1
set grid_min_y -1
set grid_max_y -1
} {
set grid_min_x $::env(XRAY_ROI_GRID_X1)
set grid_max_x $::env(XRAY_ROI_GRID_X2)
set grid_min_y $::env(XRAY_ROI_GRID_Y1)
set grid_max_y $::env(XRAY_ROI_GRID_Y2)
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF]
}
# LOC one LUT (a "selected_lut") into each CLB segment configuration column (ie 50 per column)
# Also, if GRID_MIN/MAX is not defined, automatically create it based on used CLBs
# See caveat in README on automatic creation
foreach lut $luts {
set tile [get_tile -of_objects $lut]
set grid_x [get_property GRID_POINT_X $tile]
set grid_y [get_property GRID_POINT_Y $tile]
proc loc_luts {} {
set luts [get_bels -of_objects [get_sites -of_objects [get_pblocks roi]] -filter {TYPE =~ LUT*} */A6LUT]
set selected_luts {}
set lut_index 0
if [expr $grid_min_x < 0 || $grid_x < $grid_min_x] {set grid_min_x $grid_x}
if [expr $grid_max_x < 0 || $grid_x > $grid_max_x] {set grid_max_x $grid_x}
# LOC one LUT (a "selected_lut") into each CLB segment configuration column (ie 50 per CMT column)
foreach lut $luts {
set tile [get_tile -of_objects $lut]
set grid_x [get_property GRID_POINT_X $tile]
set grid_y [get_property GRID_POINT_Y $tile]
if [expr $grid_min_y < 0 || $grid_y < $grid_min_y] {set grid_min_y $grid_y}
if [expr $grid_max_y < 0 || $grid_y > $grid_max_y] {set grid_max_y $grid_y}
# 50 per column => 50, 100, 150, etc
if [regexp "Y(0|[0-9]*[05]0)/" $lut] {
set cell [get_cells roi/is[$lut_index].lut]
set_property LOC [get_sites -of_objects $lut] $cell
set lut_index [expr $lut_index + 1]
lappend selected_luts $lut
}
# 50 per column => 50, 100, 150, etc
# ex: SLICE_X2Y50/A6LUT
# Only take one of the CLBs within a slice
if [regexp "X.*[02468]Y.*[05]0/" $lut] {
set cell [get_cells roi/luts[$lut_index].lut]
set_property LOC [get_sites -of_objects $lut] $cell
set lut_index [expr $lut_index + 1]
lappend selected_luts $lut
}
}
return $selected_luts
}
place_design
route_design
proc loc_brams {} {
# XXX: for some reason this doesn't work if there is a cell already there
# but LUTs don't have this issue
set brams [get_bels -of_objects [get_sites -of_objects [get_pblocks roi]] -filter {TYPE =~ RAMBFIFO36E1*}]
set selected_brams {}
set bram_index 0
write_checkpoint -force design.dcp
write_bitstream -force design.bit
# LOC one BRAM (a "selected_lut") into each BRAM segment configuration column (ie 10 per CMT column)
foreach bram $brams {
set tile [get_tile -of_objects $bram]
set grid_x [get_property GRID_POINT_X $tile]
set grid_y [get_property GRID_POINT_Y $tile]
# Get all tiles in ROI, ie not just the selected LUTs
set tiles [get_tiles -filter "GRID_POINT_X >= $grid_min_x && GRID_POINT_X <= $grid_max_x && GRID_POINT_Y >= $grid_min_y && GRID_POINT_Y <= $grid_max_y"]
# Write tiles.txt with site metadata
set fp [open "tiles.txt" w]
foreach tile $tiles {
set type [get_property TYPE $tile]
set grid_x [get_property GRID_POINT_X $tile]
set grid_y [get_property GRID_POINT_Y $tile]
set sites [get_sites -quiet -of_objects $tile]
set typed_sites {}
if [llength $sites] {
set site_types [get_property SITE_TYPE $sites]
foreach t $site_types s $sites {
lappend typed_sites $t $s
}
}
puts $fp "$type $tile $grid_x $grid_y $typed_sites"
}
close $fp
# Toggle one bit in each selected LUT to generate base addresses
for {set i 0} {$i < $lut_index} {incr i} {
set cell [get_cells roi/is[$i].lut]
set orig_init [get_property INIT $cell]
# Flip a bit by changing MSB 0 => 1
set new_init [regsub "h8" $orig_init "h0"]
set_property INIT $new_init $cell
write_bitstream -force design_[get_sites -of_objects [lindex $selected_luts $i]].bit
set_property INIT $orig_init $cell
# 10 per column => 10, 20, ,etc
# ex: RAMB36_X0Y10/RAMBFIFO36E1
if [regexp "Y.*0/" $bram] {
set cell [get_cells roi/brams[$bram_index].bram]
set_property LOC [get_sites -of_objects $bram] $cell
set bram_index [expr $bram_index + 1]
lappend selected_brams $bram
}
}
return $selected_brams
}
proc write_tiles_txt {} {
# Get all tiles in ROI, ie not just the selected LUTs
set grid_min_x $::env(XRAY_ROI_GRID_X1)
set grid_max_x $::env(XRAY_ROI_GRID_X2)
set grid_min_y $::env(XRAY_ROI_GRID_Y1)
set grid_max_y $::env(XRAY_ROI_GRID_Y2)
set tiles [get_tiles -filter "GRID_POINT_X >= $grid_min_x && GRID_POINT_X <= $grid_max_x && GRID_POINT_Y >= $grid_min_y && GRID_POINT_Y <= $grid_max_y"]
# Write tiles.txt with site metadata
set fp [open "tiles.txt" w]
foreach tile $tiles {
set type [get_property TYPE $tile]
set grid_x [get_property GRID_POINT_X $tile]
set grid_y [get_property GRID_POINT_Y $tile]
set sites [get_sites -quiet -of_objects $tile]
set typed_sites {}
if [llength $sites] {
set site_types [get_property SITE_TYPE $sites]
foreach t $site_types s $sites {
lappend typed_sites $t $s
}
}
puts $fp "$type $tile $grid_x $grid_y $typed_sites"
}
close $fp
}
proc write_clbs { selected_luts } {
# Toggle one bit in each selected LUT to generate base addresses
for {set i 0} {$i < [llength $selected_luts]} {incr i} {
puts ""
set cell [get_cells roi/luts[$i].lut]
puts "LUT $cell"
set orig_init [get_property INIT $cell]
# Flip a bit by changing MSB 0 => 1
set new_init [regsub "h8" $orig_init "h0"]
puts "INIT $orig_init => $new_init"
set_property INIT $new_init $cell
write_bitstream -force design_[get_sites -of_objects [lindex $selected_luts $i]].bit
set_property INIT $orig_init $cell
}
}
proc write_brams { selected_brams } {
# Toggle one bit in each selected BRAM to generate base addresses
for {set i 0} {$i < [llength $selected_brams]} {incr i} {
puts ""
set cell [get_cells roi/brams[$i].bram]
puts "BRAM $cell"
set orig_init [get_property INIT_00 $cell]
# Flip a bit by changing MSB 0 => 1
set new_init [regsub "h8" $orig_init "h0"]
puts "INIT_00 $orig_init => $new_init"
set_property INIT_00 $new_init $cell
write_bitstream -force design_[get_sites -of_objects [lindex $selected_brams $i]].bit
set_property INIT_00 $orig_init $cell
}
}
proc run {} {
make_project
set selected_luts [loc_luts]
puts "Selected LUTs: [llength $selected_luts]"
set selected_brams [loc_brams]
puts "Selected LUTs: [llength $selected_brams]"
place_design
route_design
write_checkpoint -force design.dcp
write_bitstream -force design.bit
write_tiles_txt
write_clbs $selected_luts
write_brams $selected_brams
}
run

View File

@ -1,9 +1,10 @@
//Need at least one LUT per frame base address we want
`define N 100
`define N_LUT 100
`define N_BRAM 8
module top(input clk, stb, di, output do);
localparam integer DIN_N = 6;
localparam integer DOUT_N = `N;
localparam integer DIN_N = 8;
localparam integer DOUT_N = `N_LUT + `N_BRAM;
reg [DIN_N-1:0] din;
wire [DOUT_N-1:0] dout;
@ -29,10 +30,10 @@ module top(input clk, stb, di, output do);
);
endmodule
module roi(input clk, input [5:0] din, output [`N-1:0] dout);
module roi(input clk, input [7:0] din, output [`N_LUT + `N_BRAM-1:0] dout);
genvar i;
generate
for (i = 0; i < `N; i = i+1) begin:is
for (i = 0; i < `N_LUT; i = i+1) begin:luts
LUT6 #(
.INIT(64'h8000_0000_0000_0001 + (i << 16))
) lut (
@ -46,4 +47,36 @@ module roi(input clk, input [5:0] din, output [`N-1:0] dout);
);
end
endgenerate
genvar j;
generate
for (j = 0; j < `N_BRAM; j = j+1) begin:brams
(* KEEP, DONT_TOUCH *)
RAMB36E1 #(
.INIT_00(256'h8000000000000000000000000000000000000000000000000000000000000000 + (j << 16))
) bram (
.CLKARDCLK(din[0]),
.CLKBWRCLK(din[1]),
.ENARDEN(din[2]),
.ENBWREN(din[3]),
.REGCEAREGCE(din[4]),
.REGCEB(din[5]),
.RSTRAMARSTRAM(din[6]),
.RSTRAMB(din[7]),
.RSTREGARSTREG(din[0]),
.RSTREGB(din[1]),
.ADDRARDADDR(din[2]),
.ADDRBWRADDR(din[3]),
.DIADI(din[4]),
.DIBDI(din[5]),
.DIPADIP(din[6]),
.DIPBDIP(din[7]),
.WEA(din[0]),
.WEBWE(din[1]),
.DOADO(dout[j + `N_LUT]),
.DOBDO(),
.DOPADOP(),
.DOPBDOP());
end
endgenerate
endmodule

View File

@ -18,7 +18,7 @@ with open("design_%s.txt" % sys.argv[1], "r") as f:
for i in range(64):
bitname = "%s.INIT[%02d]" % (bel, i)
bitname = bitname.replace("6LUT", "LUT")
segmk.addtag(site, bitname, ((init >> i) & 1) != 0)
segmk.add_site_tag(site, bitname, ((init >> i) & 1) != 0)
segmk.compile()
segmk.write(sys.argv[1])

View File

@ -1,3 +0,0 @@
/specimen_*/
/tileconn.json
/run.ok

View File

@ -1,26 +0,0 @@
N := 1
SPECIMENS := $(addprefix specimen_,$(shell seq -f '%03.0f' $(N)))
SPECIMENS_OK := $(addsuffix /OK,$(SPECIMENS))
database: $(SPECIMENS_OK)
cp specimen_001/tileconn.json tileconn.json
pushdb:
cp tileconn.json ${XRAY_DATABASE_DIR}/$(XRAY_DATABASE)/tileconn.json
$(SPECIMENS_OK):
bash generate.sh $(subst /OK,,$@)
touch $@
run:
$(MAKE) clean
$(MAKE) database
$(MAKE) pushdb
touch run.ok
clean:
rm -rf specimen_[0-9][0-9][0-9]/ tileconn.json run.ok
.PHONY: database pushdb run clean

View File

@ -1,117 +0,0 @@
#!/usr/bin/env python3
import os, sys, json, re
tilenodes = dict()
grid2tile = dict()
database = dict()
print("Loading %s grid." % os.getenv("XRAY_DATABASE"))
with open("%s/%s/tilegrid.json" % (os.getenv("XRAY_DATABASE_DIR"),
os.getenv("XRAY_DATABASE")), "r") as f:
grid = json.load(f)
for tile, tiledata in grid.items():
grid_xy = (tiledata["grid_x"], tiledata["grid_y"])
grid2tile[grid_xy] = tile
print("Loading nodewires.txt.")
with open("nodewires.txt") as f:
for line in f:
node, *wires = line.split()
for wire in wires:
wire_tile, wire_name = wire.split("/")
if wire_tile not in tilenodes:
tilenodes[wire_tile] = dict()
tilenodes[wire_tile][node] = wire_name
def filter_pair(type1, type2, wire1, wire2, delta_x, delta_y):
if type1 in ["HCLK_L", "HCLK_R"]:
is_vertical_wire = False
if wire1.startswith("HCLK_S"): is_vertical_wire = True
if wire1.startswith("HCLK_N"): is_vertical_wire = True
if wire1.startswith("HCLK_W"): is_vertical_wire = True
if wire1.startswith("HCLK_E"): is_vertical_wire = True
if wire1.startswith("HCLK_LV"): is_vertical_wire = True
if wire1.startswith("HCLK_BYP"): is_vertical_wire = True
if wire1.startswith("HCLK_FAN"): is_vertical_wire = True
if wire1.startswith("HCLK_LEAF_CLK_"): is_vertical_wire = True
is_horizontal_wire = False
if wire1.startswith("HCLK_CK_"): is_horizontal_wire = True
if wire1.startswith("HCLK_INT_"): is_horizontal_wire = True
assert is_vertical_wire != is_horizontal_wire
if is_vertical_wire and delta_y == 0: return True
if is_horizontal_wire and delta_x == 0: return True
if type1 in ["INT_L", "INT_R"]:
# the wires with underscore after BEG/END all connect vertically
if (("BEG_" in wire1) or ("END_" in wire1)) and delta_y == 0:
return True
if type1 in ["BRKH_INT", "BRKH_B_TERM_INT", "T_TERM_INT"]:
if delta_y == 0: return True
return False
def handle_pair(tile1, tile2):
if tile1 not in tilenodes: return
if tile2 not in tilenodes: return
tile1data = grid[tile1]
tile2data = grid[tile2]
grid1_xy = (tile1data["grid_x"], tile1data["grid_y"])
grid2_xy = (tile2data["grid_x"], tile2data["grid_y"])
if grid1_xy > grid2_xy:
return handle_pair(tile2, tile1)
key = (
tile1data["type"], tile2data["type"], grid2_xy[0] - grid1_xy[0],
grid2_xy[1] - grid1_xy[1])
wire_pairs = set()
for node, wire1 in tilenodes[tile1].items():
if node in tilenodes[tile2]:
wire2 = tilenodes[tile2][node]
if filter_pair(key[0], key[1], wire1, wire2, key[2], key[3]):
continue
if filter_pair(key[1], key[0], wire2, wire1, -key[2], -key[3]):
continue
wire_pairs.add((wire1, wire2))
if key not in database:
database[key] = wire_pairs
else:
database[key] &= wire_pairs
for tile, tiledata in grid.items():
grid_right_xy = (tiledata["grid_x"] + 1, tiledata["grid_y"])
grid_below_xy = (tiledata["grid_x"], tiledata["grid_y"] + 1)
if grid_right_xy in grid2tile:
handle_pair(tile, grid2tile[grid_right_xy])
if grid_below_xy in grid2tile:
handle_pair(tile, grid2tile[grid_below_xy])
print("Converting database.")
json_db = list()
for key in sorted(database.keys()):
(t1, t2, dx, dy) = key
entry = dict()
entry["tile_types"] = [t1, t2]
entry["grid_deltas"] = [dx, dy]
entry["wire_pairs"] = list(sorted(database[key]))
if len(entry["wire_pairs"]):
json_db.append(entry)
print("Writing tileconn.json.")
with open("tileconn.json", "w") as f:
print(json.dumps(json_db, sort_keys=True, indent="\t"), file=f)

View File

@ -1,8 +0,0 @@
#!/bin/bash -x
source ${XRAY_GENHEADER}
vivado -mode batch -source ../generate.tcl
python3 ../generate.py

View File

@ -1,29 +0,0 @@
create_project -force -part $::env(XRAY_PART) design design
read_verilog ../top.v
synth_design -top top
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports a]
set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports y]
create_pblock roi
resize_pblock [get_pblocks roi] -add "$::env(XRAY_ROI)"
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design]
set_param tcl.collectionResultDisplayLimit 0
place_design
route_design
write_checkpoint -force design.dcp
# write_bitstream -force design.bit
source ../../../utils/utils.tcl
set fp [open "nodewires.txt" w]
foreach node [get_nodes -of_objects [roi_tiles]] {
puts $fp "$node [get_wires -of_objects $node]"
}
close $fp

View File

@ -1,3 +0,0 @@
module top(input a, output y);
assign y = a;
endmodule

File diff suppressed because it is too large Load Diff

View File

@ -1,139 +0,0 @@
#!/usr/bin/env python3
import os
import re
import sys
import json
enumdb = dict()
def get_enums(segtype):
if segtype in enumdb:
return enumdb[segtype]
enumdb[segtype] = {}
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[segtype][name] = len(bit_vals) != 1
with open("%s/%s/segbits_%s.db" % (os.getenv("XRAY_DATABASE_DIR"),
os.getenv("XRAY_DATABASE"), segtype),
"r") as f:
for line in f:
process(line)
with open("%s/%s/segbits_int_%s.db" %
(os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
segtype[-1]), "r") as f:
for line in f:
process(line)
return enumdb[segtype]
def isenum(segtype, tag):
return get_enums(segtype)[tag]
def tag2fasm(grid, seg, tag):
'''Given tilegrid, segment name and tag, return fasm directive'''
segj = grid['segments'][seg]
m = re.match(r'([A-Za-z0-9_]+)[.](.*)', tag)
tile_type = m.group(1)
tag_post = m.group(2)
# Find associated tile
for tile in segj['tiles']:
if grid['tiles'][tile]['type'] == tile_type:
break
else:
raise Exception("Couldn't find tile type %s" % tile_type)
if not isenum(segj['type'], tag):
return '%s.%s 1' % (tile, 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, which, value)
def run(f_in, f_out, sparse=False):
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 tilename, tile in grid['tiles'].items():
if 'segment' in tile:
segment = tile['segment']
if segment not in grid['segments']:
grid['segments'][segment] = {
'baseaddr': (
tile['baseaddr'],
tile['offset'],
),
'type': tile['segment_type'],
'frames': tile['frames'],
'words': tile['words'],
'tiles': [tilename]
}
else:
assert grid['segments'][segment]['baseaddr'] == (
tile['baseaddr'],
tile['offset'],
)
assert grid['segments'][segment]['type'] == tile[
'segment_type']
assert grid['segments'][segment]['frames'] == tile['frames']
assert grid['segments'][segment]['words'] == tile['words']
grid['segments'][segment]['tiles'].append(tilename)
seg = None
for l in f_in:
l = l.strip()
if not l:
continue
# seg SEG_CLBLM_L_X10Y102
# tag CLBLM_L.SLICEM_X0.ALUT.INIT[00]
m = re.match('(seg|tag) (.*)', l)
if not m:
raise Exception("Invalid line %s" % l)
type = m.group(1)
if type == 'seg':
seg = m.group(2)
elif type == 'tag':
f_out.write(tag2fasm(grid, seg, m.group(2)) + '\n')
else:
raise Exception("Invalid type %s" % type)
if __name__ == '__main__':
import argparse
parser = argparse.ArgumentParser(
description='Convert segprint -d output to .fasm file (FPGA assembly)')
parser.add_argument(
'fn_in', default='/dev/stdin', nargs='?', help='Input segment file')
parser.add_argument(
'fn_out', default='/dev/stdout', nargs='?', help='Output .fasm file')
args = parser.parse_args()
run(open(args.fn_in, 'r'), open(args.fn_out, 'w'))

View File

@ -1,17 +0,0 @@
seg SEG_HCLK_L_X31Y130
tag HCLK_L.ENABLE_BUFFER.HCLK_CK_BUFHCLK8
tag HCLK_L.HCLK_LEAF_CLK_B_BOTL5.HCLK_CK_BUFHCLK8
seg SEG_CLBLM_L_X10Y102
tag CLBLM_L.SLICEM_X0.AFF.DMUX.AX
tag CLBLM_L.SLICEM_X0.AFF.ZINI
tag CLBLM_L.SLICEM_X0.AFF.ZRST
tag CLBLM_L.SLICEM_X0.CEUSEDMUX
tag CLBLM_L.SLICEM_X0.SRUSEDMUX
tag INT_L.BYP_ALT0.EE2END0
tag INT_L.BYP_ALT1.EL1END1
tag INT_L.CLK_L1.GCLK_L_B11_WEST
tag INT_L.CTRL_L1.ER1END2
tag INT_L.FAN_ALT7.BYP_BOUNCE0
tag INT_L.WW2BEG0.LOGIC_OUTS_L4

View File

@ -1,22 +0,0 @@
seg SEG_CLBLM_L_X10Y102
tag CLBLM_L.SLICEM_X0.ALUT.INIT[00]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[08]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[10]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[11]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[13]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[14]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[15]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[41]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[43]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[44]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[46]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[47]
tag CLBLM_L.SLICEM_X0.ALUT.INIT[63]
tag INT_L.IMUX_L1.EE2END0
tag INT_L.IMUX_L11.EL1END1
tag INT_L.IMUX_L2.EE2END1
tag INT_L.IMUX_L4.EE2END2
tag INT_L.IMUX_L7.EE2END3
tag INT_L.IMUX_L8.EL1END0
tag INT_L.WW2BEG0.LOGIC_OUTS_L12

View File

@ -1,44 +0,0 @@
import segprint2fasm
import unittest
import StringIO
import re
class TestStringMethods(unittest.TestCase):
def check_segprint_fasm_equiv(self, segp_fn, fasm_fn):
fout = StringIO.StringIO()
segprint2fasm.run(open(segp_fn, 'r'), fout)
fasm_out = fout.getvalue()
fasm_ref = open(fasm_fn, 'r').read()
def normalize(fasm):
'''Remove all comments and sort'''
ret = []
for l in fasm.split('\n'):
# Remove comments
i = l.rfind('#')
if i >= 0:
l = l[0:i]
l = l.strip()
if not l:
continue
ret.append(l)
return sorted(ret)
fasm_out = normalize(fasm_out)
fasm_ref = normalize(fasm_ref)
self.assertEquals(fasm_ref, fasm_out)
def test_lut_int(self):
self.check_segprint_fasm_equiv(
'test_data/lut_int/design.segp', 'test_data/lut_int.fasm')
def test_ff_int(self):
self.check_segprint_fasm_equiv(
'test_data/ff_int/design.segp', 'test_data/ff_int.fasm')
if __name__ == '__main__':
unittest.main()

310
utils/bits2fasm.py Executable file
View File

@ -0,0 +1,310 @@
#!/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()
# TODO: migrate to library
def process_db(tile_type, process):
if tile_type in ('INT_L', 'INT_R'):
# interconnect
fn = "%s/%s/segbits_int_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type[-1].lower())
else:
# sites
fn = "%s/%s/segbits_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type.lower())
if not os.path.exists(fn):
raise NoDB(tile_type)
with open(fn, "r") as f:
for line in f:
process(line)
def get_enums(tile_type):
if tile_type in enumdb:
return enumdb[tile_type]
enumdb[tile_type] = {}
def process(l):
# CLBLM_L.SLICEL_X1.ALUT.INIT[10] 29_14
parts = l.strip().split()
name = parts[0]
bit_vals = parts[1:]
# Assumption
# only 1 bit => non-enumerated value
enumdb[tile_type][name] = len(bit_vals) != 1
process_db(tile_type, process)
return enumdb[tile_type]
def isenum(tilename, tag):
return get_enums(tilename)[tag]
# cache
segbitsdb = dict()
def get_database(tile_type):
if tile_type in segbitsdb:
return segbitsdb[tile_type]
ret = list()
def process(l):
ret.append(l.split())
process_db(tile_type, process)
assert len(ret)
segbitsdb[tile_type] = ret
return ret
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)
decode_warnings = set()
def seg_decode(seginfo, segbits, verbose=False):
fasms = set()
# already failed?
if seginfo["type"] in decode_warnings:
return fasms
try:
db = get_database(seginfo["type"])
except NoDB:
verbose and comment(
"WARNING: failed to load DB for %s" % seginfo["type"])
decode_warnings.add(seginfo["type"])
return fasms
for entry in db:
if not tagmatch(entry, segbits):
continue
tag_matched(entry, segbits)
#fasms.add('%s.%s 1' % (seginfo['tile_name'], entry[0]))
fasm = mk_fasm(seginfo, entry)
fasms.add(fasm)
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, verbose=verbose)
# Found something to print?
if len(segbits) == 0 and len(fasms) == 0:
return
line('')
comment("seg %s" % (segname, ))
for fasm in sorted(fasms):
line(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()

View File

@ -4,6 +4,7 @@ while [ -h "$XRAY_ENV_PATH" ]; do # resolve $XRAY_ENV_PATH until the file is no
XRAY_ENV_PATH="$(readlink "$XRAY_ENV_PATH")"
[[ $XRAY_ENV_PATH != /* ]] && XRAY_ENV_PATH="$XRAY_UTILS_DIR/$XRAY_ENV_PATH" # if $XRAY_ENV_PATH was a relative symlink, we need to resolve it relative to the path where the symlink file was located
done
export PYTHONPATH="${XRAY_DIR}:$PYTHONPATH"
export XRAY_UTILS_DIR="$( cd -P "$( dirname "$XRAY_ENV_PATH" )" && pwd )"
export XRAY_DIR="$( dirname "$XRAY_UTILS_DIR" )"
@ -21,3 +22,7 @@ export XRAY_DBCHECK="bash ${XRAY_UTILS_DIR}/dbcheck.sh"
export XRAY_MASKMERGE="bash ${XRAY_UTILS_DIR}/maskmerge.sh"
export XRAY_SEGMATCH="${XRAY_TOOLS_DIR}/segmatch"
export XRAY_SEGPRINT="python3 ${XRAY_UTILS_DIR}/segprint.py"
export XRAY_BITS2FASM="python3 ${XRAY_UTILS_DIR}/bits2fasm.py"
export XRAY_FASM2FRAMES="python3 ${XRAY_UTILS_DIR}/fasm2frames.py"

View File

@ -23,6 +23,26 @@ def parsebit(val):
return int(seg_word_column), int(word_bit_n), isset
# TODO: migrate to library
def process_db(tile_type, process):
fns = [
# sites
"%s/%s/segbits_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type.lower()),
# interconnect
"%s/%s/segbits_int_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type[-1].lower()),
]
for fn in fns:
if os.path.exists(fn):
with open(fn, "r") as f:
for line in f:
process(line)
'''
Loosely based on segprint function
Maybe better to return as two distinct dictionaries?
@ -37,17 +57,15 @@ Maybe better to return as two distinct dictionaries?
segbitsdb = dict()
def get_database(segtype):
if segtype in segbitsdb:
return segbitsdb[segtype]
def get_database(tile_type):
if tile_type in segbitsdb:
return segbitsdb[tile_type]
segbitsdb[segtype] = {}
segbitsdb[tile_type] = {}
def process(l):
l = l.strip()
# CLBLM_L.SLICEL_X1.ALUT.INIT[10] 29_14
parts = line.split()
parts = l.strip().split()
name = parts[0]
bit_vals = parts[1:]
@ -59,7 +77,8 @@ def get_database(segtype):
raise Exception(
"Expect single bit DB entries to be set, got %s" % l)
# Treat like an enumerated value with keys 0 or 1
segbitsdb[segtype][name] = {
assert name not in segbitsdb[tile_type]
segbitsdb[tile_type][name] = {
'0': [(seg_word_column, word_bit_n, 0)],
'1': [(seg_word_column, word_bit_n, 1)],
}
@ -67,26 +86,16 @@ def get_database(segtype):
# An enumerated value
# Split the base name and selected key
m = re.match(r'(.+)[.](.+)', name)
name = m.group(1)
namepart = m.group(1)
key = m.group(2)
# May or may not be the first key encountered
bits_map = segbitsdb[segtype].setdefault(name, {})
bits_map = segbitsdb[tile_type].setdefault(namepart, {})
bits_map[key] = [parsebit(x) for x in bit_vals]
with open("%s/%s/segbits_%s.db" % (os.getenv("XRAY_DATABASE_DIR"),
os.getenv("XRAY_DATABASE"), segtype),
"r") as f:
for line in f:
process(line)
process_db(tile_type, process)
with open("%s/%s/segbits_int_%s.db" %
(os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
segtype[-1]), "r") as f:
for line in f:
process(line)
return segbitsdb[segtype]
return segbitsdb[tile_type]
def dump_frames_verbose(frames):
@ -126,34 +135,13 @@ def dump_frm(f, frames):
'0x%08X ' % addr + ','.join(['0x%08X' % w for w in words]) + '\n')
def run(f_in, f_out, sparse=False, debug=False):
# address to array of 101 32 bit words
frames = {}
# Directives we've seen so far
# Complain if there is a duplicate
# Contains line number of last entry
used_names = {}
def mksegment(tile_name, block_name):
'''Create a segment name'''
return '%s:%s' % (tile_name, block_name)
def frames_init():
'''Set all frames to 0'''
for segj in grid['segments'].values():
seg_baseaddr, seg_word_base = segj['baseaddr']
seg_baseaddr = int(seg_baseaddr, 0)
for coli in range(segj['frames']):
frame_init(seg_baseaddr + coli)
def frame_init(addr):
'''Set given frame to 0'''
if not addr in frames:
frames[addr] = [0 for _i in range(101)]
def frame_set(frame_addr, word_addr, bit_index):
'''Set given bit in given frame address and word'''
frames[frame_addr][word_addr] |= 1 << bit_index
def frame_clear(frame_addr, word_addr, bit_index):
'''Set given bit in given frame address and word'''
frames[frame_addr][word_addr] &= 0xFFFFFFFF ^ (1 << bit_index)
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:
@ -162,76 +150,129 @@ def run(f_in, f_out, sparse=False, debug=False):
# TODO: Migrate to new tilegrid format via library.
grid = {'tiles': new_grid, 'segments': {}}
for tile in grid['tiles'].values():
if 'segment' in tile:
segment = tile['segment']
grid['segments'][segment] = {
for tile_name, tile in grid['tiles'].items():
for block_name, block in tile['bits'].items():
segname = mksegment(tile_name, block_name)
grid['segments'][segname] = {
'baseaddr': [
tile['baseaddr'],
tile['offset'],
block['baseaddr'],
block['offset'],
],
'type': tile['segment_type'],
'frames': tile['frames'],
'words': tile['words'],
'type': tile['type'],
'frames': block['frames'],
'words': block['words'],
'tile_name': tile_name,
'block_name': block_name,
}
return grid
if not sparse:
# Initiaize bitstream to 0
frames_init()
for line_number, l in enumerate(f_in, 1):
# Comment
# Remove all text including and after #
i = l.rfind('#')
if i >= 0:
l = l[0:i]
l = l.strip()
def frame_init(frames, addr):
'''Set given frame to 0'''
if not addr in frames:
frames[addr] = [0 for _i in range(101)]
# Ignore blank lines
if not l:
continue
# tile.site.stuff value
# INT_L_X10Y102.CENTER_INTER_L.IMUX_L1 EE2END0
# Optional value
m = re.match(r'([a-zA-Z0-9_]+)[.]([a-zA-Z0-9_.\[\]]+)([ ](.+))?', l)
if not m:
raise FASMSyntaxError("Bad line: %s" % l)
tile = m.group(1)
name = m.group(2)
value = m.group(4)
def frames_init(frames, grid):
'''Set all frames to 0'''
for segj in grid['segments'].values():
seg_baseaddr, seg_word_base = segj['baseaddr']
seg_baseaddr = int(seg_baseaddr, 0)
for coli in range(segj['frames']):
frame_init(frames, seg_baseaddr + coli)
used_name = (tile, name)
old_line_number = used_names.get(used_name, None)
if old_line_number:
raise FASMSyntaxError(
"Duplicate name lines %d and %d, second line: %s" %
(old_line_number, line_number, l))
used_names[used_name] = line_number
tilej = grid['tiles'][tile]
seg = tilej['segment']
segj = grid['segments'][seg]
def frame_set(frames, frame_addr, word_addr, bit_index):
'''Set given bit in given frame address and word'''
frames[frame_addr][word_addr] |= 1 << bit_index
def frame_clear(frames, frame_addr, word_addr, bit_index):
'''Set given bit in given frame address and word'''
frames[frame_addr][word_addr] &= 0xFFFFFFFF ^ (1 << bit_index)
def parse_line(l):
# Comment
# Remove all text including and after #
i = l.rfind('#')
if i >= 0:
l = l[0:i]
l = l.strip()
# Ignore blank lines
if not l:
return
# tile.site.stuff value
# INT_L_X10Y102.CENTER_INTER_L.IMUX_L1 EE2END0
# Optional value
m = re.match(r'([a-zA-Z0-9_]+)[.]([a-zA-Z0-9_.\[\]]+)([ ](.+))?', l)
if not m:
raise FASMSyntaxError("Bad line: %s" % l)
tile = m.group(1)
name = m.group(2)
value = m.group(4)
return tile, name, value
def check_duplicate(used_names, tile, name, l, line_number):
'''Throw an exception if a conflicting FASM directive was given'''
used_name = (tile, name)
old_line_number = used_names.get(used_name, None)
if old_line_number:
raise FASMSyntaxError(
"Duplicate name lines %d and %d, second line: %s" %
(old_line_number, line_number, l))
used_names[used_name] = line_number
def update_segbit(
frames, seg_word_column, word_bit_n, isset, seg_baseaddr,
seg_word_base):
'''Set or clear a single bit in a segment at the given word column and word bit position'''
# Now we have the word column and word bit index
# Combine with the segments relative frame position to fully get the position
frame_addr = seg_baseaddr + seg_word_column
# 2 words per segment
word_addr = seg_word_base + word_bit_n // 32
bit_index = word_bit_n % 32
if isset:
frame_set(frames, frame_addr, word_addr, bit_index)
else:
frame_clear(frames, frame_addr, word_addr, bit_index)
def default_value(db_vals, name):
# If its binary, allow omitted value default to 1
if tuple(sorted(db_vals.keys())) == ('0', '1'):
return '1'
else:
raise FASMSyntaxError(
"Enumerable entry %s must have explicit value" % name)
def process_line(line_number, l, grid, frames, used_names):
parsed = parse_line(l)
# empty line
if not parsed:
return
tile_name, name, value = parsed
check_duplicate(used_names, tile_name, name, l, line_number)
tilej = grid['tiles'][tile_name]
for block_name, block in tilej['bits'].items():
segname = mksegment(tile_name, block_name)
segj = grid['segments'][segname]
seg_baseaddr, seg_word_base = segj['baseaddr']
seg_baseaddr = int(seg_baseaddr, 0)
# Ensure that all frames exist for this segment
# FIXME: type dependent
for coli in range(segj['frames']):
frame_init(seg_baseaddr + coli)
def update_segbit(seg_word_column, word_bit_n, isset):
'''Set or clear a single bit in a segment at the given word column and word bit position'''
# Now we have the word column and word bit index
# Combine with the segments relative frame position to fully get the position
frame_addr = seg_baseaddr + seg_word_column
# 2 words per segment
word_addr = seg_word_base + word_bit_n // 32
bit_index = word_bit_n % 32
if isset:
frame_set(frame_addr, word_addr, bit_index)
else:
frame_clear(frame_addr, word_addr, bit_index)
frame_init(frames, seg_baseaddr + coli)
# Now lets look up the bits we need frames for
segdb = get_database(segj['type'])
@ -245,12 +286,8 @@ def run(f_in, f_out, sparse=False, debug=False):
(segj['type'], db_k, l)) from None
if not value:
# If its binary, allow omitted value default to 1
if tuple(sorted(db_vals.keys())) == ('0', '1'):
value = '1'
else:
raise FASMSyntaxError(
"Enumerable entry %s must have explicit value" % name)
value = default_value(db_vals, name)
# Get the specific entry we need
try:
db_vals = db_vals[value]
@ -258,8 +295,29 @@ def run(f_in, f_out, sparse=False, debug=False):
raise FASMSyntaxError(
"Invalid entry %s. Valid entries are %s" %
(value, db_vals.keys()))
for seg_word_column, word_bit_n, isset in db_vals:
update_segbit(seg_word_column, word_bit_n, isset)
update_segbit(
frames, seg_word_column, word_bit_n, isset, seg_baseaddr,
seg_word_base)
def run(f_in, f_out, sparse=False, debug=False):
# address to array of 101 32 bit words
frames = {}
# Directives we've seen so far
# Complain if there is a duplicate
# Contains line number of last entry
used_names = {}
grid = mk_grid()
if not sparse:
# Initiaize bitstream to 0
frames_init(frames, grid)
for line_number, l in enumerate(f_in, 1):
process_line(line_number, l, grid, frames, used_names)
if debug:
#dump_frames_verbose(frames)

View File

@ -1,4 +1,7 @@
'''
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
@ -15,6 +18,8 @@ import os, json, re
XRAY_DATABASE = os.getenv("XRAY_DATABASE")
XRAY_DIR = os.getenv("XRAY_DIR")
BLOCK_TYPES = set(('CLB_IO_CLK', 'BLOCK_RAM', 'CFG_CLB'))
def recurse_sum(x):
'''Count number of nested iterable occurances'''
@ -36,8 +41,9 @@ def json_hex2i(s):
class segmaker:
def __init__(self, bitsfile, verbose=False):
self.verbose = verbose
def __init__(self, bitsfile, verbose=None):
self.verbose = verbose if verbose is not None else os.getenv(
'VERBOSE', 'N') == 'Y'
self.load_grid()
self.load_bits(bitsfile)
'''
@ -46,7 +52,8 @@ class segmaker:
-site: ex 'SLICE_X13Y101'
-name: ex 'CLB.SLICE_X0.AFF.DMUX.CY'
'''
self.tags = dict()
self.site_tags = dict()
self.tile_tags = dict()
# output after compiling
self.segments_by_type = None
@ -94,7 +101,7 @@ class segmaker:
'Loaded bits: %u bits in %u base frames' %
(recurse_sum(self.bits), len(self.bits)))
def addtag(self, site_tile, name, value):
def add_site_tag(self, site, name, value):
'''
XXX: can add tags in two ways:
-By site name
@ -107,7 +114,10 @@ class segmaker:
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'
'''
self.tags.setdefault(site_tile, dict())[name] = value
self.site_tags.setdefault(site, dict())[name] = value
def add_tile_tag(self, tile, name, value):
self.tile_tags.setdefault(tile, dict())[name] = value
def compile(self, bitfilter=None):
print("Compiling segment data.")
@ -131,11 +141,20 @@ class segmaker:
tiledata: tilegrid info for this tile
'''
assert segname not in segments
segments[segname] = {"bits": set(), "tags": dict()}
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(tiledata["baseaddr"])
for wordidx in range(tiledata["offset"],
tiledata["offset"] + tiledata["height"]):
base_frame = json_hex2i(bitj["baseaddr"])
for wordidx in range(bitj["offset"],
bitj["offset"] + bitj["height"]):
if base_frame not in self.bits:
continue
if wordidx not in self.bits[base_frame]:
@ -144,13 +163,14 @@ class segmaker:
base_frame][wordidx]:
bitname_frame = bit_frame - base_frame
bitname_bit = 32 * (
bit_wordidx - tiledata["offset"]) + bit_bitidx
bit_wordidx - bitj["offset"]) + bit_bitidx
# 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)
segments[segname]["bits"].add(bitname)
segment["bits"].add(bitname)
return segment
'''
XXX: wouldn't it be better to iterate over tags? Easy to drop tags
@ -158,20 +178,27 @@ class segmaker:
'''
for tilename, tiledata in self.grid.items():
def add_tilename_tags():
def getseg(segname):
if not segname in segments:
add_segbits(
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
for name, value in self.tags[tilename].items():
def add_tilename_tags():
segment = getseg(segname)
for name, value in self.tile_tags[tilename].items():
tags_used.add((tilename, name))
tag = "%s.%s" % (tile_type_norm, name)
segments[segname]["tags"][tag] = value
segment["tags"][tag] = value
def add_site_tags():
if not segname in segments:
add_segbits(
segments, segname, tiledata, bitfilter=bitfilter)
segment = getseg(segname)
if 'SLICE_' in site:
'''
@ -188,7 +215,7 @@ class segmaker:
else:
assert 0, 'Unhandled site type'
for name, value in self.tags[site].items():
for name, value in self.site_tags[site].items():
tags_used.add((site, name))
tag = "%s.%s.%s" % (tile_type_norm, sitekey, name)
# XXX: does this come from name?
@ -197,7 +224,7 @@ class segmaker:
segments[segname]["tags"][tag] = value
# ignore dummy tiles (ex: VBRK)
if "baseaddr" not in tiledata:
if "bits" not in tiledata:
continue
tile_type = tiledata["type"]
@ -210,23 +237,25 @@ class segmaker:
'''
tile_type_norm = re.sub("(LL|LM)?_[LR]$", "", tile_type)
segname = "%s_%03d" % (
# truncate 0x to leave hex string
tiledata["baseaddr"][2:],
tiledata["offset"])
for block_type, bitj in tiledata['bits'].items():
# 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.tags:
add_tilename_tags()
# 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.tags:
continue
add_site_tags()
# process site name tags
for site in tiledata["sites"]:
if site not in self.site_tags:
continue
add_site_tags()
if self.verbose:
ntags = recurse_sum(self.tags)
ntags = recurse_sum(self.site_tags) + recurse_sum(self.tile_tags)
print("Used %u / %u tags" % (len(tags_used), ntags))
print("Grid DB had %u tile types" % len(tile_types_found))
assert ntags and ntags == len(tags_used)
@ -234,6 +263,10 @@ class segmaker:
def write(self, suffix=None, roi=False):
assert self.segments_by_type, 'No data to write'
assert sum(
[len(segments) for segments in self.segments_by_type.values()
]) != 0
for segtype in self.segments_by_type.keys():
if suffix is not None:
filename = "segdata_%s_%s.txt" % (segtype.lower(), suffix)
@ -244,19 +277,9 @@ class segmaker:
with open(filename, "w") as f:
segments = self.segments_by_type[segtype]
if True:
for segname, segdata in sorted(segments.items()):
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)
else:
print("seg roi", file=f)
for segname, segdata in sorted(segments.items()):
for bitname in sorted(segdata["bits"]):
print("bit %s_%s" % (segname, bitname), file=f)
for tagname, tagval in sorted(segdata["tags"].items()):
print(
"tag %s_%s %d" % (segname, tagname, tagval),
file=f)
for segname, segdata in sorted(segments.items()):
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)

View File

@ -1,182 +1,66 @@
#!/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
'''
import getopt, sys, os, json, re
flag_z = False
flag_b = False
flag_d = False
flag_D = False
import sys, os, json, re
def usage():
print("Usage: %s [options] <bits_file> [segments/tiles]" % sys.argv[0])
print("")
print(" -z")
print(" do not print a 'seg' header for empty segments")
print("")
print(" -b")
print(" print bits outside of known segments")
print("")
print(" -d")
print(" decode known segment bits and write them as tags")
print("")
print(" -D")
print(" decode known segment bits and omit them in the output")
print("")
sys.exit(0)
class NoDB(Exception):
pass
try:
opts, args = getopt.getopt(sys.argv[1:], "zbdD")
except:
usage()
if len(args) == 0:
usage()
for o, a in opts:
if o == "-z":
flag_z = True
elif o == "-b":
flag_b = True
elif o == "-d":
flag_d = True
elif o == "-D":
flag_D = True
else:
usage()
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 in grid['tiles'].values():
if 'segment' in tile:
segment = tile['segment']
grid['segments'][segment] = {
'baseaddr': [
tile['baseaddr'],
tile['offset'],
],
'type': tile['segment_type'],
'frames': tile['frames'],
'words': tile['words'],
}
bitdata = dict()
# print("Loading %s." % sys.argv[1])
with open(args[0], "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)
# cache
segbitsdb = dict()
def get_database(segtype):
if segtype in segbitsdb:
return segbitsdb[segtype]
# 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(tile_type, process):
fns = [
# sites
"%s/%s/segbits_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type.lower()),
# interconnect
"%s/%s/segbits_int_%s.db" % (
os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
tile_type[-1].lower()),
]
segbitsdb[segtype] = list()
with open("%s/%s/segbits_%s.db" % (os.getenv("XRAY_DATABASE_DIR"),
os.getenv("XRAY_DATABASE"), segtype),
"r") as f:
for line in f:
line = line.split()
segbitsdb[segtype].append(line)
with open("%s/%s/segbits_int_%s.db" %
(os.getenv("XRAY_DATABASE_DIR"), os.getenv("XRAY_DATABASE"),
segtype[-1]), "r") as f:
for line in f:
line = line.split()
segbitsdb[segtype].append(line)
return segbitsdb[segtype]
for fn in fns:
if os.path.exists(fn):
with open(fn, "r") as f:
for line in f:
process(line)
def handle_segment(segname):
if segname is None:
segframes = dict()
for segname, segdata in grid["segments"].items():
framebase = int(segdata["baseaddr"][0], 16)
for i in range(segdata["frames"]):
if (framebase + i) not in segframes:
segframes[framebase + i] = set()
for j in range(segdata["baseaddr"][1],
segdata["baseaddr"][1] + segdata["words"]):
segframes[framebase + i].add(j)
for frame in sorted(bitdata.keys()):
for wordidx in sorted(bitdata[frame].keys()):
if frame in segframes and wordidx in segframes[frame]:
continue
for bitidx in sorted(bitdata[frame][wordidx]):
print("bit_%08x_%03d_%02d" % (frame, wordidx, bitidx))
return
if ":" in segname:
seg1, seg2 = segname.split(":")
def get_database(tile_type):
tags = list()
if seg1 in grid["tiles"]:
seg1 = grid["tiles"][seg1]["segment"]
if tile_type in segbitsdb:
return segbitsdb[tile_type]
if seg2 in grid["tiles"]:
seg2 = grid["tiles"][seg2]["segment"]
def process(l):
tags.append(l.split())
seginfo1 = grid["segments"][seg1]
seginfo2 = grid["segments"][seg2]
process_db(tile_type, process)
frame1 = int(seginfo1["baseaddr"][0], 16)
word1 = int(seginfo1["baseaddr"][1])
if len(tags) == 0:
raise NoDB(tile_type)
frame2 = int(seginfo2["baseaddr"][0], 16)
word2 = int(seginfo2["baseaddr"][1])
segbitsdb[tile_type] = tags
return tags
if frame1 > frame2:
frame1, frame2 = frame2, frame1
if word1 > word2:
word1, word2 = word2, word1
segs = list()
for seg, seginfo in sorted(grid["segments"].items()):
frame = int(seginfo["baseaddr"][0], 16)
word = int(seginfo["baseaddr"][1])
if frame1 <= frame <= frame2 and word1 <= word <= word2:
segs.append((frame, word, seg))
for _, _, seg in sorted(segs):
handle_segment(seg)
return
if segname in grid["tiles"]:
segname = grid["tiles"][segname]["segment"]
seginfo = grid["segments"][segname]
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()
segtags = set()
for frame in range(baseframe, baseframe + numframes):
if frame not in bitdata:
continue
@ -187,25 +71,105 @@ def handle_segment(segname):
segbits.add(
"%02d_%02d" %
(frame - baseframe, 32 * (wordidx - basewordidx) + bitidx))
return segbits
if flag_d or flag_D:
def print_unknown_bits(grid, bitdata):
'''
Print bits not covered by known tiles
'''
# Index all known locations
# seggrames[address] = set()
# where set contains word numbers
segframes = dict()
for segname, segdata in grid["segments"].items():
framebase = int(segdata["baseaddr"][0], 16)
for i in range(segdata["frames"]):
words = segframes.setdefault(framebase + i, set())
for j in range(segdata["baseaddr"][1],
segdata["baseaddr"][1] + segdata["words"]):
words.add(j)
# print uncovered locations
print('Non-database bits:')
for frame in sorted(bitdata.keys()):
for wordidx in sorted(bitdata[frame].keys()):
if frame in segframes and wordidx in segframes[frame]:
continue
for bitidx in sorted(bitdata[frame][wordidx]):
print("bit_%08x_%03d_%02d" % (frame, wordidx, bitidx))
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)
decode_warnings = set()
def seg_decode(flag_decode_emit, seginfo, segbits, verbose=False):
segtags = set()
# already failed?
if seginfo["type"] in decode_warnings:
return segtags
try:
for entry in get_database(seginfo["type"]):
match_entry = True
for bit in entry[1:]:
if bit[0] != "!" and bit not in segbits:
match_entry = False
if bit[0] == "!" and bit[1:] in segbits:
match_entry = False
if match_entry:
for bit in entry[1:]:
if bit[0] != "!":
segbits.remove(bit)
if flag_d:
segtags.add(entry[0])
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"])
return segtags
if not flag_z or len(segbits) > 0 or len(segtags) > 0:
print()
print("seg %s" % segname)
def handle_segment(
segname,
grid,
bitdata,
flag_decode_emit,
flag_decode_omit,
omit_empty_segs,
verbose=False):
assert segname
# only print bitstream tiles
if segname not in grid["segments"]:
return
seginfo = grid["segments"][segname]
segbits = mk_segbits(seginfo, bitdata)
if flag_decode_emit or flag_decode_omit:
segtags = seg_decode(
flag_decode_emit, seginfo, segbits, verbose=verbose)
else:
segtags = set()
# Found something to print?
if not (not omit_empty_segs or len(segbits) > 0 or len(segtags) > 0):
return
print()
print("seg %s" % (segname, ))
for bit in sorted(segbits):
print("bit %s" % bit)
@ -214,15 +178,140 @@ def handle_segment(segname):
print("tag %s" % tag)
if flag_b:
handle_segment(None)
def load_bitdata(bits_file):
bitdata = dict()
if len(args) == 1:
seglist = list()
for seg, seginfo in grid["segments"].items():
seglist.append((seginfo["baseaddr"][0], -seginfo["baseaddr"][1], seg))
for _, _, seg in sorted(seglist):
handle_segment(seg)
else:
for arg in args[1:]:
handle_segment(arg)
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,
omit_empty_segs=False,
flag_unknown_bits=False,
flag_decode_emit=False,
flag_decode_omit=False,
verbose=False):
grid = mk_grid()
bitdata = load_bitdata(bits_file)
if flag_unknown_bits:
print_unknown_bits(grid, bitdata)
# 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))
print('Segments: %u' % len(segnames))
# XXX: previously this was sorted by address, not name
# revisit?
for segname in segnames:
handle_segment(
segname,
grid,
bitdata,
flag_decode_emit,
flag_decode_omit,
omit_empty_segs,
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(
'-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')
# XXX: possibly broken, or we have missing DB data
parser.add_argument(
'-D',
action='store_true',
help='decode known segment bits and omit them in the output')
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, args.z, args.b, args.d, args.D,
args.verbose)
if __name__ == '__main__':
main()

View File

@ -1,9 +1,10 @@
#!/usr/bin/env python3
# TODO: need better coverage for different tile types
import fasm2frame
import fasm2frames
import unittest
import StringIO
from io import StringIO
import re
@ -23,7 +24,7 @@ def frm2bits(txt):
assert (101 == len(words))
for wordi, word in enumerate(words):
word = int(word, 0)
for biti in xrange(32):
for biti in range(32):
val = word & (1 << biti)
if val:
bits_out.add((addr, wordi, biti))
@ -51,12 +52,12 @@ def bitread2bits(txt):
class TestStringMethods(unittest.TestCase):
def test_lut(self):
'''Simple smoke test on just the LUTs'''
fout = StringIO.StringIO()
fasm2frame.run(open('test_data/lut.fasm', 'r'), fout)
fout = StringIO()
fasm2frames.run(open('test_data/lut.fasm', 'r'), fout)
def bitread_frm_equals(self, frm_fn, bitread_fn):
fout = StringIO.StringIO()
fasm2frame.run(open(frm_fn, 'r'), fout)
fout = StringIO()
fasm2frames.run(open(frm_fn, 'r'), fout)
# Build a list of output used bits
bits_out = frm2bits(fout.getvalue())
@ -84,24 +85,24 @@ class TestStringMethods(unittest.TestCase):
# Same check as above, but isolated test case
def test_opkey_01_default(self):
'''Optional key with binary omitted value should produce valid result'''
fin = StringIO.StringIO("CLBLM_L_X10Y102.SLICEM_X0.SRUSEDMUX")
fout = StringIO.StringIO()
fasm2frame.run(fin, fout)
fin = StringIO("CLBLM_L_X10Y102.SLICEM_X0.SRUSEDMUX")
fout = StringIO()
fasm2frames.run(fin, fout)
def test_opkey_01_1(self):
fin = StringIO.StringIO("CLBLM_L_X10Y102.SLICEM_X0.SRUSEDMUX 1")
fout = StringIO.StringIO()
fasm2frame.run(fin, fout)
fin = StringIO("CLBLM_L_X10Y102.SLICEM_X0.SRUSEDMUX 1")
fout = StringIO()
fasm2frames.run(fin, fout)
def test_opkey_enum(self):
'''Optional key with enumerated value should produce syntax error'''
# CLBLM_L.SLICEM_X0.AMUX.O6 !30_06 !30_07 !30_08 30_11
fin = StringIO.StringIO("CLBLM_L_X10Y102.SLICEM_X0.AMUX.O6")
fout = StringIO.StringIO()
fin = StringIO("CLBLM_L_X10Y102.SLICEM_X0.AMUX.O6")
fout = StringIO()
try:
fasm2frame.run(fin, fout)
fasm2frames.run(fin, fout)
self.fail("Expected syntax error")
except fasm2frame.FASMSyntaxError:
except fasm2frames.FASMSyntaxError:
pass
def test_ff_int_0s(self):
@ -111,39 +112,39 @@ class TestStringMethods(unittest.TestCase):
def test_badkey(self):
'''Bad key should throw syntax error'''
fin = StringIO.StringIO("CLBLM_L_X10Y102.SLICEM_X0.SRUSEDMUX 2")
fout = StringIO.StringIO()
fin = StringIO("CLBLM_L_X10Y102.SLICEM_X0.SRUSEDMUX 2")
fout = StringIO()
try:
fasm2frame.run(fin, fout)
fasm2frames.run(fin, fout)
self.fail("Expected syntax error")
except fasm2frame.FASMSyntaxError:
except fasm2frames.FASMSyntaxError:
pass
def test_dupkey(self):
'''Duplicate key should throw syntax error'''
fin = StringIO.StringIO(
fin = StringIO(
"""\
CLBLM_L_X10Y102.SLICEM_X0.SRUSEDMUX 0
CLBLM_L_X10Y102.SLICEM_X0.SRUSEDMUX 1
""")
fout = StringIO.StringIO()
fout = StringIO()
try:
fasm2frame.run(fin, fout)
fasm2frames.run(fin, fout)
self.fail("Expected syntax error")
except fasm2frame.FASMSyntaxError:
except fasm2frames.FASMSyntaxError:
pass
def test_sparse(self):
'''Verify sparse equivilent to normal encoding'''
frm_fn = 'test_data/lut_int.fasm'
fout_sparse = StringIO.StringIO()
fasm2frame.run(open(frm_fn, 'r'), fout_sparse, sparse=True)
fout_sparse = StringIO()
fasm2frames.run(open(frm_fn, 'r'), fout_sparse, sparse=True)
fout_sparse_txt = fout_sparse.getvalue()
bits_sparse = frm2bits(fout_sparse_txt)
fout_full = StringIO.StringIO()
fasm2frame.run(open(frm_fn, 'r'), fout_full, sparse=False)
fout_full = StringIO()
fasm2frames.run(open(frm_fn, 'r'), fout_full, sparse=False)
fout_full_txt = fout_full.getvalue()
bits_full = frm2bits(fout_full_txt)