From 41d9ede26a312eeb258e1f6ffbb345e514a95960 Mon Sep 17 00:00:00 2001 From: Keith Rothman <537074+litghost@users.noreply.github.com> Date: Fri, 19 Oct 2018 08:33:31 -0700 Subject: [PATCH] Working bits2fasm using prjxray and fasm library. Signed-off-by: Keith Rothman <537074+litghost@users.noreply.github.com> --- minitests/roi_harness/bit2fasm.sh | 7 + minitests/roi_harness/fasm2bit.sh | 2 +- prjxray/bitstream.py | 27 +++ prjxray/db.py | 16 ++ prjxray/fasm_assembler.py | 140 +++++++++++++ prjxray/fasm_disassembler.py | 130 ++++++++++++ prjxray/grid.py | 54 ++++- prjxray/roi.py | 49 +++++ prjxray/segment_map.py | 18 ++ prjxray/tile.py | 3 +- prjxray/tile_segbits.py | 136 +++++++++++++ third_party/fasm | 2 +- utils/bits2fasm.py | 319 +++--------------------------- utils/fasm2frames.py | 315 +++-------------------------- utils/fasm2pips.py | 53 +++++ 15 files changed, 689 insertions(+), 582 deletions(-) create mode 100755 minitests/roi_harness/bit2fasm.sh create mode 100644 prjxray/bitstream.py create mode 100644 prjxray/fasm_assembler.py create mode 100644 prjxray/fasm_disassembler.py create mode 100644 prjxray/roi.py create mode 100644 prjxray/segment_map.py create mode 100644 prjxray/tile_segbits.py create mode 100644 utils/fasm2pips.py diff --git a/minitests/roi_harness/bit2fasm.sh b/minitests/roi_harness/bit2fasm.sh new file mode 100755 index 00000000..13a6c4c6 --- /dev/null +++ b/minitests/roi_harness/bit2fasm.sh @@ -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} diff --git a/minitests/roi_harness/fasm2bit.sh b/minitests/roi_harness/fasm2bit.sh index 5beb90f5..88bff430 100755 --- a/minitests/roi_harness/fasm2bit.sh +++ b/minitests/roi_harness/fasm2bit.sh @@ -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} \ diff --git a/prjxray/bitstream.py b/prjxray/bitstream.py new file mode 100644 index 00000000..4f43a383 --- /dev/null +++ b/prjxray/bitstream.py @@ -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 diff --git a/prjxray/db.py b/prjxray/db.py index c75ce52c..21b6706d 100644 --- a/prjxray/db.py +++ b/prjxray/db.py @@ -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] diff --git a/prjxray/fasm_assembler.py b/prjxray/fasm_assembler.py new file mode 100644 index 00000000..f53e91fa --- /dev/null +++ b/prjxray/fasm_assembler.py @@ -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) diff --git a/prjxray/fasm_disassembler.py b/prjxray/fasm_disassembler.py new file mode 100644 index 00000000..34c4aebc --- /dev/null +++ b/prjxray/fasm_disassembler.py @@ -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, + ) diff --git a/prjxray/grid.py b/prjxray/grid.py index df1a6a1b..a1a043f5 100644 --- a/prjxray/grid.py +++ b/prjxray/grid.py @@ -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) diff --git a/prjxray/roi.py b/prjxray/roi.py new file mode 100644 index 00000000..15934446 --- /dev/null +++ b/prjxray/roi.py @@ -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) diff --git a/prjxray/segment_map.py b/prjxray/segment_map.py new file mode 100644 index 00000000..3a98fa37 --- /dev/null +++ b/prjxray/segment_map.py @@ -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 diff --git a/prjxray/tile.py b/prjxray/tile.py index 85f2ca2a..958f757b 100644 --- a/prjxray/tile.py +++ b/prjxray/tile.py @@ -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') diff --git a/prjxray/tile_segbits.py b/prjxray/tile_segbits.py new file mode 100644 index 00000000..cbf5e1c3 --- /dev/null +++ b/prjxray/tile_segbits.py @@ -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 diff --git a/third_party/fasm b/third_party/fasm index faed2f5e..46eadd95 160000 --- a/third_party/fasm +++ b/third_party/fasm @@ -1 +1 @@ -Subproject commit faed2f5e9915497c3774ec201ced8dc017cc03ae +Subproject commit 46eadd95407381752160a62972ec256a97e03f3d diff --git a/utils/bits2fasm.py b/utils/bits2fasm.py index fa3e6ade..7b132036 100755 --- a/utils/bits2fasm.py +++ b/utils/bits2fasm.py @@ -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() diff --git a/utils/fasm2frames.py b/utils/fasm2frames.py index db64fe7f..84ceb7ca 100755 --- a/utils/fasm2frames.py +++ b/utils/fasm2frames.py @@ -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() diff --git a/utils/fasm2pips.py b/utils/fasm2pips.py new file mode 100644 index 00000000..8b2da11c --- /dev/null +++ b/utils/fasm2pips.py @@ -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()