Working bits2fasm using prjxray and fasm library.

Signed-off-by: Keith Rothman <537074+litghost@users.noreply.github.com>
This commit is contained in:
Keith Rothman 2018-10-19 08:33:31 -07:00
parent 6dd9626cfc
commit 41d9ede26a
15 changed files with 689 additions and 582 deletions

View File

@ -0,0 +1,7 @@
set -ex
BIT_IN=$1
BITS=$(tempfile --suffix .bits)
${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o ${BITS} -z -y ${BIT_IN}
${XRAY_DIR}/utils/bits2fasm.py ${BITS}

View File

@ -27,7 +27,7 @@ echo "Design .fasm: $fasm_in"
echo "Harness .bit: $bit_in"
echo "Out .bit: $bit_out"
${XRAY_DIR}/tools/fasm2frame.py $fasm_in roi_partial.frm
${XRAY_DIR}/utils/fasm2frames.py --sparse $fasm_in roi_partial.frm
${XRAY_TOOLS_DIR}/xc7patch \
--part_name ${XRAY_PART} \

27
prjxray/bitstream.py Normal file
View File

@ -0,0 +1,27 @@
# Break frames into WORD_SIZE bit words.
WORD_SIZE_BITS = 32
def load_bitdata(f):
""" Read bit file and return bitdata map.
bitdata is a map of of two sets.
The map key is the frame address.
The first sets are the word columns that have any bits set.
Word columsn are WORD_SIZE_BITS wide.
The second sets are bit index within the frame and word if it is set.
"""
bitdata = dict()
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] = set(), set()
bitdata[frame][0].add(wordidx)
bitdata[frame][1].add(wordidx*WORD_SIZE_BITS + bitidx)
return bitdata

View File

@ -2,6 +2,7 @@ import os.path
import json
from prjxray import grid
from prjxray import tile
from prjxray import tile_segbits
from prjxray import site_type
from prjxray import connections
@ -37,7 +38,9 @@ class Database(object):
self.tile_types = None
self.tile_types = {}
self.tile_segbits = {}
self.site_types = {}
for f in os.listdir(self.db_root):
if f.endswith('.json') and f.startswith('tile_type_'):
tile_type = f[len('tile_type_'):-len('.json')].lower()
@ -47,6 +50,11 @@ class Database(object):
if not os.path.isfile(segbits):
segbits = None
ppips = os.path.join(
self.db_root, 'ppips_{}.db'.format(tile_type))
if not os.path.isfile(ppips):
ppips = None
mask = os.path.join(
self.db_root, 'mask_{}.db'.format(tile_type))
if not os.path.isfile(mask):
@ -60,6 +68,7 @@ class Database(object):
self.tile_types[tile_type.upper()] = tile.TileDbs(
segbits=segbits,
ppips=ppips,
mask=mask,
tile_type=tile_type_file,
)
@ -124,3 +133,10 @@ class Database(object):
site_type_data = json.load(f)
return site_type.SiteType(site_type_data)
def get_tile_segbits(self, tile_type):
if tile_type not in self.tile_segbits:
self.tile_segbits[tile_type] = tile_segbits.TileSegbits(
self.tile_types[tile_type.upper()])
return self.tile_segbits[tile_type]

140
prjxray/fasm_assembler.py Normal file
View File

@ -0,0 +1,140 @@
import fasm
from prjxray import bitstream
from prjxray import grid
class FasmLookupError(Exception):
pass
class FasmInconsistentBits(Exception):
pass
# How many 32-bit words for frame in a 7-series bitstream?
FRAME_WORD_COUNT = 101
def init_frame_at_address(frames, addr):
'''Set given frame to 0 if not initialized '''
if not addr in frames:
frames[addr] = [0 for _i in range(FRAME_WORD_COUNT)]
class FasmAssembler(object):
def __init__(self, db):
self.db = db
self.grid = db.grid()
self.frames = {}
self.frames_line = {}
def get_frames(self, sparse=False):
if not sparse:
frames = self.frames_init()
else:
frames = {}
for (frame_addr, word_addr, bit_index), is_set in self.frames.items():
init_frame_at_address(frames, frame_addr)
if is_set:
frames[frame_addr][word_addr] |= 1 << bit_index
return frames
def frames_init(self):
'''Set all frames to 0'''
frames = {}
for bits_info in self.grid.iter_all_frames():
for coli in range(bits_info.bits.frames):
init_frame_at_address(frames, bits_info.bits.base_address + coli)
return frames
def frame_set(self, frame_addr, word_addr, bit_index, line):
'''Set given bit in given frame address and word'''
assert bit_index is not None
key = (frame_addr, word_addr, bit_index)
if key in self.frames:
if self.frames[key] != 1:
raise FasmInconsistentBits(
'FASM line "{}" wanted to set bit {} but was cleared by FASM line "{}"'.format(
line, key, self.frames_line[key],
))
return
self.frames[key] = 1
self.frames_line[key] = line
def frame_clear(self, frame_addr, word_addr, bit_index, line):
'''Set given bit in given frame address and word'''
assert bit_index is not None
key = (frame_addr, word_addr, bit_index)
if key in self.frames:
if self.frames[key] != 0:
raise FasmInconsistentBits(
'FASM line "{}" wanted to clear bit {} but was set by FASM line "{}"'.format(
line, key, self.frames_line[key],
))
return
self.frames[key] = 0
self.frames_line[key] = line
def enable_feature(self, tile, feature, address, line):
gridinfo = self.grid.gridinfo_at_tilename(tile)
# TODO: How to determine if the feature targets BLOCK_RAM segment type?
bits = gridinfo.bits[grid.SegmentType.CLB_IO_CLK]
seg_baseaddr = bits.base_address
seg_word_base = bits.offset
def update_segbit(bit):
'''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 + bit.word_column
# 2 words per segment
word_addr = seg_word_base + bit.word_bit // bitstream.WORD_SIZE_BITS
bit_index = bit.word_bit % bitstream.WORD_SIZE_BITS
if bit.isset:
self.frame_set(frame_addr, word_addr, bit_index, line)
else:
self.frame_clear(frame_addr, word_addr, bit_index, line)
segbits = self.db.get_tile_segbits(gridinfo.tile_type)
db_k = '%s.%s' % (gridinfo.tile_type, feature)
try:
for bit in segbits.feature_to_bits(db_k, address):
update_segbit(bit)
except KeyError:
raise FasmLookupError(
"Segment DB %s, key %s not found from line '%s'" %
(gridinfo.tile_type, db_k, line))
def parse_fasm_filename(self, filename):
for line in fasm.parse_fasm_filename(filename):
if not line.set_feature:
continue
line_strs = tuple(fasm.fasm_line_to_string(line))
assert len(line_strs) == 1
line_str = line_strs[0]
parts = line.set_feature.feature.split('.')
tile = parts[0]
feature = '.'.join(parts[1:])
# canonical_features flattens multibit feature enables to only
# single bit features, which is what enable_feature expects.
#
# canonical_features also filters out features that are not enabled,
# which are no-ops.
for flat_set_feature in fasm.canonical_features(line.set_feature):
address = 0
if flat_set_feature.start is not None:
address = flat_set_feature.start
self.enable_feature(tile, feature, address, line_str)

View File

@ -0,0 +1,130 @@
import re
import fasm
from prjxray import bitstream
def mk_fasm(tile_name, feature):
""" Convert matches tile and feature to FasmLine tuple. """
# Seperate addressing of multi-bit features:
# TILE.ALUT[0] -> ('TILE', 'ALUT', '0')
# TILE.ALUT.SMALL -> ('TILE', 'ALUT.SMALL', None)
m = re.match(r'([A-Za-z0-9_]+).([^\[]+)(\[[0-9]+\])?', feature)
tag_post = m.group(2)
address = None
if m.group(3) is not None:
address = int(m.group(3)[1:-1])
feature = '{}.{}'.format(tile_name, tag_post)
return fasm.FasmLine(
set_feature=fasm.SetFasmFeature(
feature=feature,
start=address,
end=None,
value=1,
value_format=None,
),
annotations=None,
comment=None)
class FasmDisassembler(object):
""" Given a Project X-ray data, outputs FasmLine tuples for bits set. """
def __init__(self, db):
self.db = db
self.grid = self.db.grid()
self.segment_map = self.grid.get_segment_map()
self.decode_warnings = set()
def find_features_in_tile(self, tile_name, bits, bitdata, verbose=False):
gridinfo = self.grid.gridinfo_at_tilename(tile_name)
try:
tile_segbits = self.db.get_tile_segbits(gridinfo.tile_type)
except KeyError as e:
if not verbose:
return
if gridinfo.tile_type in self.decode_warnings:
return
comment = " WARNING: failed to load DB for tile type {}".format(
gridinfo.tile_type)
yield fasm.FasmLine(
set_feature=None,
annotations=None,
comment=comment,
)
yield fasm.FasmLine(
set_feature=None,
annotations=[
fasm.Annotation('missing_segbits', gridinfo.tile_type),
fasm.Annotation('exception', str(e)),
],
comment=None,
)
self.decode_warnings.add(gridinfo.tile_type)
return
for ones_matched, feature in tile_segbits.match_bitdata(
bits, bitdata):
for frame, bit in ones_matched:
bitdata[frame][1].remove(bit)
yield mk_fasm(tile_name=tile_name, feature=feature)
def find_features_in_bitstream(self, bitdata, verbose=False):
frames = set(bitdata.keys())
tiles_checked = set()
while len(frames) > 0:
frame = frames.pop()
# Skip frames that were emptied in a previous iteration.
if not bitdata[frame]:
continue
# Iterate over all tiles that use this frame.
for bits_info in self.segment_map.segment_info_for_frame(frame):
# Don't examine a tile twice
if bits_info.tile in tiles_checked:
continue
# Check if this frame has any data for the relevant tile.
any_column = False
for word_idx in range(bits_info.bits.words):
if word_idx + bits_info.bits.offset in bitdata[frame][0]:
any_column = True
break
if not any_column:
continue
tiles_checked.add(bits_info.tile)
for fasm_line in self.find_features_in_tile(
bits_info.tile,
bits_info.bits,
bitdata,
verbose=verbose):
yield fasm_line
if len(bitdata[frame]) > 0 and verbose:
# Some bits were not decoded, add warning and annotations to
# FASM.
yield fasm.FasmLine(
set_feature=None,
annotations=None,
comment=" In frame 0x{:08x} {} bits were not converted.".format(
frame, len(bitdata[frame]),
))
for bit in bitdata[frame][1]:
wordidx = bit // bitstream.WORD_SIZE_BITS
bitidx = bit % bitstream.WORD_SIZE_BITS
annotation = fasm.Annotation('unknown_bit',
'{:08x}_{}_{}'.format(frame, wordidx, bitidx))
yield fasm.FasmLine(
set_feature=None,
annotations=[annotation],
comment=None,
)

View File

@ -1,8 +1,18 @@
from collections import namedtuple
import enum
from prjxray import segment_map
class SegmentType(enum.Enum):
# Segments describing CLB features, interconnect, clocks and IOs.
CLB_IO_CLK = 'CLB_IO_CLK'
# Segments describing block RAM initialization.
BLOCK_RAM = 'BLOCK_RAM'
GridLoc = namedtuple('GridLoc', 'grid_x grid_y')
GridInfo = namedtuple('GridInfo', 'segment sites tile_type in_roi')
GridInfo = namedtuple('GridInfo', 'segment bits sites tile_type in_roi')
Bits = namedtuple('Bits', 'base_address frames offset words')
BitsInfo = namedtuple('BitsInfo', 'segment_type tile bits')
class Grid(object):
""" Object that represents grid for a given database.
@ -15,6 +25,14 @@ class Grid(object):
self.tilegrid = tilegrid
self.loc = {}
self.tileinfo = {}
# Map of segment name to tiles in that segment
self.segments = {}
# Map of (base_address, segment type) -> segment name
self.base_addresses = {}
# Map of base_address -> (segment type, segment name)
self.base_addresses = {}
for tile in self.tilegrid:
tileinfo = self.tilegrid[tile]
@ -27,8 +45,28 @@ class Grid(object):
else:
in_roi = True
bits = {}
if 'segment' in tileinfo:
if tileinfo['segment'] not in self.segments:
self.segments[tileinfo['segment']] = []
self.segments[tileinfo['segment']].append(tile)
if 'bits' in tileinfo:
for k in tileinfo['bits']:
segment_type = SegmentType(k)
base_address = int(tileinfo['bits'][k]['baseaddr'], 0)
bits[segment_type] = Bits(
base_address=base_address,
frames=tileinfo['bits'][k]['frames'],
offset=tileinfo['bits'][k]['offset'],
words=tileinfo['bits'][k]['words'],
)
self.tileinfo[tile] = GridInfo(
segment=tileinfo['segment'] if 'segment' in tileinfo else None,
bits=bits,
sites=tileinfo['sites'],
tile_type=tileinfo['type'],
in_roi=in_roi,
@ -64,3 +102,15 @@ class Grid(object):
def gridinfo_at_tilename(self, tilename):
return self.tileinfo[tilename]
def iter_all_frames(self):
for tile, tileinfo in self.tileinfo.items():
for segment_type, bits in tileinfo.bits.items():
yield BitsInfo(
segment_type=segment_type,
tile=tile,
bits=bits,
)
def get_segment_map(self):
return segment_map.SegmentMap(self)

49
prjxray/roi.py Normal file
View File

@ -0,0 +1,49 @@
import json
class Roi(object):
def __init__(self, tilegrid_file, x1, x2, y1, y2):
self.tilegrid_file = tilegrid_file
self.tilegrid = None
self.x1 = x1
self.x2 = x2
self.y1 = y1
self.y2 = y2
def tile_in_roi(self, tilej):
x = int(tilej['grid_x'])
y = int(tilej['grid_y'])
return self.x1 <= x and x <= self.x2 and self.y1 <= y and y <= self.y2
def read_tilegrid(self):
if not self.tilegrid:
with open(self.tilegrid_file) as f:
self.tilegrid = json.load(f)
def gen_tiles(self, tile_types=None):
'''
tile_types: list of tile types to keep, or None for all
'''
self.read_tilegrid()
for tile_name, tilej in self.tilegrid.items():
if self.tile_in_roi(tilej) and (tile_types is None
or tilej['type'] in tile_types):
yield (tile_name, tilej)
def gen_sites(self, site_types=None):
'''
site_types: list of site types to keep, or None for all
'''
self.read_tilegrid()
for tile_name, tilej in self.tilegrid.items():
if not self.tile_in_roi(tilej):
continue
for site_name, site_type in tilej['sites'].items():
if site_types is None or site_type in site_types:
yield (tile_name, site_name, site_type)

18
prjxray/segment_map.py Normal file
View File

@ -0,0 +1,18 @@
from intervaltree import IntervalTree, Interval
from prjxray import bitstream
class SegmentMap(object):
def __init__(self, grid):
self.segment_tree = IntervalTree()
for bits_info in grid.iter_all_frames():
self.segment_tree.add(Interval(
begin=bits_info.bits.base_address,
end=bits_info.bits.base_address+bits_info.bits.frames,
data=bits_info,
))
def segment_info_for_frame(self, frame):
""" Return all bits info that match frame address. """
for frame in self.segment_tree[frame]:
yield frame.data

View File

@ -1,8 +1,9 @@
from collections import namedtuple
import json
from prjxray import lib
""" Database files available for a tile """
TileDbs = namedtuple('TileDbs', 'segbits mask tile_type')
TileDbs = namedtuple('TileDbs', 'segbits ppips mask tile_type')
Pip = namedtuple(
'Pip', 'name net_to net_from can_invert is_directional is_pseudo')

136
prjxray/tile_segbits.py Normal file
View File

@ -0,0 +1,136 @@
from collections import namedtuple
from prjxray import bitstream
import enum
class PsuedoPipType(enum.Enum):
ALWAYS = 'always'
DEFAULT = 'default'
HINT = 'hint'
def read_ppips(f):
ppips = {}
for l in f:
l = l.strip()
if not l:
continue
feature, ppip_type = l.split(' ')
ppips[feature] = PsuedoPipType(ppip_type)
return ppips
Bit = namedtuple('Bit', 'word_column word_bit isset')
def parsebit(val):
'''Return "!012_23" => (12, 23, False)'''
isset = True
# Default is 0. Skip explicit call outs
if val[0] == '!':
isset = False
val = val[1:]
# 28_05 => 28, 05
seg_word_column, word_bit_n = val.split('_')
return Bit(
word_column=int(seg_word_column),
word_bit=int(word_bit_n),
isset=isset,
)
def read_segbits(f):
segbits = {}
for l in f:
# CLBLM_L.SLICEL_X1.ALUT.INIT[10] 29_14
l = l.strip()
if not l:
continue
parts = l.split(' ')
assert len(parts) > 1
segbits[parts[0]] = [parsebit(val) for val in parts[1:]]
return segbits
class TileSegbits(object):
def __init__(self, tile_db):
self.segbits = {}
self.ppips = {}
self.feature_addresses = {}
if tile_db.ppips is not None:
with open(tile_db.ppips) as f:
self.ppips = read_ppips(f)
if tile_db.segbits is not None:
with open(tile_db.segbits) as f:
self.segbits = read_segbits(f)
for feature in self.segbits:
sidx = feature.rfind('[')
eidx = feature.rfind(']')
if sidx != -1:
assert eidx != -1
base_feature = feature[:sidx]
if base_feature not in self.feature_addresses:
self.feature_addresses[base_feature] = {}
self.feature_addresses[base_feature][int(feature[sidx+1:eidx])] = feature
def match_bitdata(self, bits, bitdata):
""" Return matching features for tile bits data (grid.Bits) and bitdata.
See bitstream.load_bitdata for details on bitdata structure.
"""
for feature, segbit in self.segbits.items():
match = True
for query_bit in segbit:
frame = bits.base_address + query_bit.word_column
bitidx = bits.offset*bitstream.WORD_SIZE_BITS + query_bit.word_bit
if frame not in bitdata:
match = not query_bit.isset
if match:
continue
else:
break
found_bit = bitidx in bitdata[frame][1]
match = found_bit == query_bit.isset
if not match:
break
if not match:
continue
def inner():
for query_bit in segbit:
if query_bit.isset:
frame = bits.base_address + query_bit.word_column
bitidx = bits.offset*bitstream.WORD_SIZE_BITS + query_bit.word_bit
yield (frame, bitidx)
yield (tuple(inner()), feature)
def feature_to_bits(self, feature, address=0):
if feature in self.ppips:
return
if address == 0 and feature in self.segbits:
for bit in self.segbits[feature]:
yield bit
else:
for bit in self.segbits[self.feature_addresses[feature][address]]:
yield bit

2
third_party/fasm vendored

@ -1 +1 @@
Subproject commit faed2f5e9915497c3774ec201ced8dc017cc03ae
Subproject commit 46eadd95407381752160a62972ec256a97e03f3d

View File

@ -1,310 +1,47 @@
#!/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:
Take raw .bits files and decode them to FASM.
'''
import sys, os, json, re
import os
import fasm
from prjxray import db
from prjxray import fasm_disassembler
from prjxray import bitstream
def run(db_root, bits_file):
disassembler = fasm_disassembler.FasmDisassembler(db.Database(db_root))
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)
with open(bits_file) as f:
bitdata = bitstream.load_bitdata(f)
for fasm_line in disassembler.find_features_in_bitstream(bitdata):
for line in fasm.fasm_line_to_string(fasm_line):
print(line)
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?')
description='Convert 7-series bits file to FASM.')
database_dir = os.getenv("XRAY_DATABASE_DIR")
database = os.getenv("XRAY_DATABASE")
db_root_kwargs = {}
if database_dir is None or database is None:
db_root_kwargs['required'] = True
else:
db_root_kwargs['required'] = False
db_root_kwargs['default'] = os.path.join(database_dir, database)
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')
'--db_root', help="Database root.", **db_root_kwargs)
parser.add_argument('bits_file', help='')
parser.add_argument('verbose', help='Print lines for unknown tiles and bits',
action='store_true')
args = parser.parse_args()
run(args.bits_file, args.segnames, verbose=args.verbose)
run(args.db_root, args.bits_file)
if __name__ == '__main__':
main()

View File

@ -1,97 +1,11 @@
#!/usr/bin/env python3
from __future__ import print_function
from prjxray import fasm_assembler
from prjxray import db
import argparse
import os
import re
import sys
import json
import collections
import fasm
def parsebit(val):
'''Return "!012_23" => (12, 23, False)'''
isset = True
# Default is 0. Skip explicit call outs
if val[0] == '!':
isset = False
val = val[1:]
# 28_05 => 28, 05
seg_word_column, word_bit_n = val.split('_')
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?
{
'tile.meh': {
'O5': [(11, 2, False), (12, 2, True)],
'O6': [(11, 2, True), (12, 2, False)],
},
}
'''
segbitsdb = dict()
def get_database(tile_type):
if tile_type in segbitsdb:
return segbitsdb[tile_type]
segbitsdb[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
if len(bit_vals) == 1:
seg_word_column, word_bit_n, isset = parsebit(bit_vals[0])
if not isset:
raise Exception(
"Expect single bit DB entries to be set, got %s" % l)
# Treat like an enumerated value with keys 0 or 1
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)],
}
else:
# An enumerated value
# Split the base name and selected key
m = re.match(r'(.+)[.](.+)', name)
namepart = m.group(1)
key = m.group(2)
# May or may not be the first key encountered
bits_map = segbitsdb[tile_type].setdefault(namepart, {})
bits_map[key] = [parsebit(x) for x in bit_vals]
process_db(tile_type, process)
return segbitsdb[tile_type]
import os.path
def dump_frames_verbose(frames):
print()
@ -102,7 +16,6 @@ def dump_frames_verbose(frames):
'0x%08X ' % addr + ', '.join(['0x%08X' % w for w in words]) +
'...')
def dump_frames_sparse(frames):
print()
print("Frames: %d" % len(frames))
@ -121,7 +34,6 @@ def dump_frames_sparse(frames):
if w:
print(' % 3d: 0x%08X' % (i, w))
def dump_frm(f, frames):
'''Write a .frm file given a list of frames, each containing a list of 101 32 bit words'''
for addr in sorted(frames.keys()):
@ -129,214 +41,40 @@ def dump_frm(f, frames):
f.write(
'0x%08X ' % addr + ','.join(['0x%08X' % w for w in words]) + '\n')
def mksegment(tile_name, block_name):
'''Create a segment name'''
return '%s:%s' % (tile_name, block_name)
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():
for block_name, block in tile['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 frame_init(frames, addr):
'''Set given frame to 0'''
if not addr in frames:
frames[addr] = [0 for _i in range(101)]
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)
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(frames, seg_baseaddr + coli)
# Now lets look up the bits we need frames for
segdb = get_database(segj['type'])
db_k = '%s.%s' % (tilej['type'], name)
try:
db_vals = segdb[db_k]
except KeyError:
raise FASMSyntaxError(
"Segment DB %s, key %s not found from line '%s'" %
(segj['type'], db_k, l)) from None
if not value:
value = default_value(db_vals, name)
# Get the specific entry we need
try:
db_vals = db_vals[value]
except KeyError:
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(
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)
def run(db_root, filename_in, f_out, sparse=False, debug=False):
assembler = fasm_assembler.FasmAssembler(db.Database(db_root))
assembler.parse_fasm_filename(filename_in)
frames = assembler.get_frames(sparse=sparse)
if debug:
#dump_frames_verbose(frames)
dump_frames_sparse(frames)
dump_frm(f_out, frames)
if __name__ == '__main__':
import argparse
def main():
parser = argparse.ArgumentParser(
description=
'Convert FPGA configuration description ("FPGA assembly") into binary frame equivalent'
)
database_dir = os.getenv("XRAY_DATABASE_DIR")
database = os.getenv("XRAY_DATABASE")
db_root_kwargs = {}
if database_dir is None or database is None:
db_root_kwargs['required'] = True
else:
db_root_kwargs['required'] = False
db_root_kwargs['default'] = os.path.join(database_dir, database)
parser.add_argument(
'--db_root', help="Database root.", **db_root_kwargs)
parser.add_argument(
'--sparse', action='store_true', help="Don't zero fill all frames")
parser.add_argument(
'--debug', action='store_true', help="Print debug dump")
parser.add_argument(
'fn_in',
default='/dev/stdin',
nargs='?',
help='Input FPGA assembly (.fasm) file')
parser.add_argument(
'fn_out',
@ -346,7 +84,12 @@ if __name__ == '__main__':
args = parser.parse_args()
run(
open(args.fn_in, 'r'),
open(args.fn_out, 'w'),
db_root=args.db_root,
filename_in=args.fn_in,
f_out=open(args.fn_out, 'w'),
sparse=args.sparse,
debug=args.debug)
debug=args.debug
)
if __name__ == '__main__':
main()

53
utils/fasm2pips.py Normal file
View File

@ -0,0 +1,53 @@
#!/usr/bin/env python3
from __future__ import print_function
import os.path
import fasm
from prjxray import db
def main():
import argparse
parser = argparse.ArgumentParser(
description=
'Convert FASM to pip list'
)
database_dir = os.getenv("XRAY_DATABASE_DIR")
database = os.getenv("XRAY_DATABASE")
db_root_kwargs = {}
if database_dir is None or database is None:
db_root_kwargs['required'] = True
else:
db_root_kwargs['required'] = False
db_root_kwargs['default'] = os.path.join(database_dir, database)
parser.add_argument(
'--db_root', help="Database root.", **db_root_kwargs)
parser.add_argument(
'fn_in',
help='Input FPGA assembly (.fasm) file')
args = parser.parse_args()
database = db.Database(args.db_root)
grid = database.grid()
def inner():
for line in fasm.parse_fasm_filename(args.fn_in):
if not line.set_feature:
continue
parts = line.set_feature.feature.split('.')
tile = parts[0]
gridinfo = grid.gridinfo_at_tilename(tile)
tile_type = database.get_tile_type(gridinfo.tile_type)
for pip in tile_type.pips:
if pip.net_from == parts[2] and pip.net_to == parts[1]:
yield '{}/{}.{}'.format(tile, gridinfo.tile_type, pip.name)
print('highlight_objects [concat {}]'.format(' '.join('[get_pips {}]'.format(pip) for pip in inner())))
if __name__ == '__main__':
main()