diff --git a/fuzzers/005-tilegrid/generate_full.py b/fuzzers/005-tilegrid/generate_full.py index 4b5277c5..476c9221 100644 --- a/fuzzers/005-tilegrid/generate_full.py +++ b/fuzzers/005-tilegrid/generate_full.py @@ -245,6 +245,78 @@ def propagate_rebuf(database, tiles_by_grid): database[rebuf_above]['bits']['CLB_IO_CLK']['words'] = 4 +def propagate_IOB_SING(database, tiles_by_grid): + """ The IOB_SING are half tiles at top and bottom of every IO column. + + Unlike most tiles, they do not behave consistently. The tile at the top + of the column is the bottom half of a full IOB, and the tile at the bottom + of the column is the top half of a full IOB. For this reason, explicit + bit aliasing is used to map the full IOB bits into the two halves, and a + mapping is provided for the site naming. + + """ + + seen_iobs = set() + for tile in database: + if tile in seen_iobs: + continue + + if database[tile]["type"] not in ["LIOB33", "RIOB33"]: + continue + + while True: + prev_tile = tile + tile = tiles_by_grid[( + database[tile]['grid_x'], database[tile]['grid_y'] + 1)] + if '_SING' in database[tile]['type']: + break + + bottom_tile = tile + seen_iobs.add(bottom_tile) + + bits = database[prev_tile]['bits']['CLB_IO_CLK'] + + while True: + tile = tiles_by_grid[( + database[tile]['grid_x'], database[tile]['grid_y'] - 1)] + seen_iobs.add(tile) + + if '_SING' in database[tile]['type']: + break + + if 'CLB_IO_CLK' in database[tile]['bits']: + assert bits['baseaddr'] == database[tile]['bits'][ + 'CLB_IO_CLK']['baseaddr'] + assert bits['frames'] == database[tile]['bits']['CLB_IO_CLK'][ + 'frames'] + assert bits['words'] == database[tile]['bits']['CLB_IO_CLK'][ + 'words'] + + top_tile = tile + + database[top_tile]['bits']['CLB_IO_CLK'] = copy.deepcopy(bits) + database[top_tile]['bits']['CLB_IO_CLK']['words'] = 2 + database[top_tile]['bits']['CLB_IO_CLK']['offset'] = 99 + database[top_tile]['bits']['CLB_IO_CLK']['alias'] = { + 'type': database[prev_tile]['type'], + 'start_offset': 0, + 'sites': { + 'IOB33_Y0': 'IOB33_Y1', + } + } + + database[bottom_tile]['bits']['CLB_IO_CLK'] = copy.deepcopy(bits) + database[bottom_tile]['bits']['CLB_IO_CLK']['words'] = 2 + database[bottom_tile]['bits']['CLB_IO_CLK']['offset'] = 0 + database[bottom_tile]['bits']['CLB_IO_CLK']['alias'] = { + 'type': database[prev_tile]['type'], + 'start_offset': 2, + 'sites': { + 'IOB33_Y0': 'IOB33_Y0', + } + } + + def run(json_in_fn, json_out_fn, verbose=False): # Load input files database = json.load(open(json_in_fn, "r")) @@ -253,6 +325,7 @@ def run(json_in_fn, json_out_fn, verbose=False): propagate_INT_lr_bits(database, tiles_by_grid, verbose=verbose) propagate_INT_bits_in_column(database, tiles_by_grid) propagate_rebuf(database, tiles_by_grid) + propagate_IOB_SING(database, tiles_by_grid) # Save xjson.pprint(open(json_out_fn, "w"), database) diff --git a/prjxray/db.py b/prjxray/db.py index d222d237..d0fa2f3a 100644 --- a/prjxray/db.py +++ b/prjxray/db.py @@ -114,7 +114,7 @@ class Database(object): def grid(self): """ Return Grid object for database. """ self._read_tilegrid() - return grid.Grid(self.tilegrid) + return grid.Grid(self, self.tilegrid) def _read_tile_types(self): for tile_type, db in self.tile_types.items(): diff --git a/prjxray/fasm_assembler.py b/prjxray/fasm_assembler.py index 8c04c006..4b55fca6 100644 --- a/prjxray/fasm_assembler.py +++ b/prjxray/fasm_assembler.py @@ -98,26 +98,18 @@ class FasmAssembler(object): def enable_feature(self, tile, feature, address, line): gridinfo = self.grid.gridinfo_at_tilename(tile) - def update_segbit(block_type, bit): + def update_segbit(bit): '''Set or clear a single bit in a segment at the given word column and word bit position''' - bits = gridinfo.bits[block_type] - - seg_baseaddr = bits.base_address - seg_word_base = bits.offset - - # 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 + frame_addr = bit.word_column + word_addr = 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) + segbits = self.grid.get_tile_segbits_at_tilename(tile) self.seen_tile.add(tile) @@ -126,9 +118,10 @@ class FasmAssembler(object): any_bits = set() try: - for block_type, bit in segbits.feature_to_bits(db_k, address): + for block_type, bit in segbits.feature_to_bits(gridinfo.bits, db_k, + address): any_bits.add(block_type) - update_segbit(block_type, bit) + update_segbit(bit) except KeyError: raise FasmLookupError( "Segment DB %s, key %s not found from line '%s'" % diff --git a/prjxray/fasm_disassembler.py b/prjxray/fasm_disassembler.py index 0f7c6708..85bdc381 100644 --- a/prjxray/fasm_disassembler.py +++ b/prjxray/fasm_disassembler.py @@ -48,7 +48,7 @@ class FasmDisassembler(object): gridinfo = self.grid.gridinfo_at_tilename(tile_name) try: - tile_segbits = self.db.get_tile_segbits(gridinfo.tile_type) + tile_segbits = self.grid.get_tile_segbits_at_tilename(tile_name) except KeyError as e: if not verbose: return @@ -163,3 +163,19 @@ class FasmDisassembler(object): annotations=tuple(annotations), comment=None, ) + + def is_zero_feature(self, feature): + parts = feature.split('.') + tile = parts[0] + gridinfo = self.grid.gridinfo_at_tilename(tile) + feature = '.'.join(parts[1:]) + + db_k = '%s.%s' % (gridinfo.tile_type, feature) + segbits = self.grid.get_tile_segbits_at_tilename(tile) + any_bits = False + for block_type, bit in segbits.feature_to_bits(gridinfo.bits, db_k): + if bit.isset: + any_bits = True + break + + return not any_bits diff --git a/prjxray/grid.py b/prjxray/grid.py index 78e08e16..b7246713 100644 --- a/prjxray/grid.py +++ b/prjxray/grid.py @@ -1,20 +1,6 @@ -from collections import namedtuple -import enum from prjxray import segment_map - - -class BlockType(enum.Enum): - # Frames describing CLB features, interconnect, clocks and IOs. - CLB_IO_CLK = 'CLB_IO_CLK' - - # Frames describing block RAM initialization. - BLOCK_RAM = 'BLOCK_RAM' - - -GridLoc = namedtuple('GridLoc', 'grid_x grid_y') -GridInfo = namedtuple('GridInfo', 'bits sites tile_type') -Bits = namedtuple('Bits', 'base_address frames offset words') -BitsInfo = namedtuple('BitsInfo', 'block_type tile bits') +from prjxray.grid_types import BlockType, GridLoc, GridInfo, BitAlias, Bits, BitsInfo +from prjxray.tile_segbits_alias import TileSegbitsAlias class Grid(object): @@ -24,7 +10,8 @@ class Grid(object): of segment offsets for particular grid locations and their tile types. """ - def __init__(self, tilegrid): + def __init__(self, db, tilegrid): + self.db = db self.tilegrid = tilegrid self.loc = {} self.tileinfo = {} @@ -41,11 +28,22 @@ class Grid(object): for k in tileinfo['bits']: segment_type = BlockType(k) base_address = int(tileinfo['bits'][k]['baseaddr'], 0) + + alias = None + if 'alias' in tileinfo['bits'][k]: + alias = BitAlias( + tile_type=tileinfo['bits'][k]['alias']['type'], + start_offset=tileinfo['bits'][k]['alias'] + ['start_offset'], + sites=tileinfo['bits'][k]['alias']['sites'], + ) + bits[segment_type] = Bits( base_address=base_address, frames=tileinfo['bits'][k]['frames'], offset=tileinfo['bits'][k]['offset'], words=tileinfo['bits'][k]['words'], + alias=alias, ) self.tileinfo[tile] = GridInfo( @@ -103,3 +101,17 @@ class Grid(object): tile_type = gridinfo.tile_type return (tile_type, loc.grid_x, -loc.grid_y) + + def get_tile_segbits_at_tilename(self, tilename): + gridinfo = self.gridinfo_at_tilename(tilename) + + # Check to see if alias is present + any_alias = False + for block_type, bits in gridinfo.bits.items(): + if bits.alias is not None: + any_alias = True + + if any_alias: + return TileSegbitsAlias(self.db, gridinfo.tile_type, gridinfo.bits) + else: + return self.db.get_tile_segbits(gridinfo.tile_type) diff --git a/prjxray/grid_types.py b/prjxray/grid_types.py new file mode 100644 index 00000000..92062596 --- /dev/null +++ b/prjxray/grid_types.py @@ -0,0 +1,17 @@ +from collections import namedtuple +import enum + + +class BlockType(enum.Enum): + # Frames describing CLB features, interconnect, clocks and IOs. + CLB_IO_CLK = 'CLB_IO_CLK' + + # Frames describing block RAM initialization. + BLOCK_RAM = 'BLOCK_RAM' + + +GridLoc = namedtuple('GridLoc', 'grid_x grid_y') +GridInfo = namedtuple('GridInfo', 'bits sites tile_type') +BitAlias = namedtuple('BitAlias', 'tile_type start_offset sites') +Bits = namedtuple('Bits', 'base_address frames offset words alias') +BitsInfo = namedtuple('BitsInfo', 'block_type tile bits') diff --git a/prjxray/tile_segbits.py b/prjxray/tile_segbits.py index cb6b8856..09c055c8 100644 --- a/prjxray/tile_segbits.py +++ b/prjxray/tile_segbits.py @@ -99,7 +99,7 @@ class TileSegbits(object): self.feature_addresses[base_feature][int( feature[sidx + 1:eidx])] = (block_type, feature) - def match_bitdata(self, block_type, bits, bitdata): + def match_bitdata(self, block_type, bits, bitdata, match_filter=None): """ Return matching features for tile bits data (grid.Bits) and bitdata. See bitstream.load_bitdata for details on bitdata structure. @@ -111,7 +111,13 @@ class TileSegbits(object): for feature, segbit in self.segbits[block_type].items(): match = True + skip = False for query_bit in segbit: + if match_filter is not None and not match_filter(block_type, + query_bit): + skip = True + break + frame = bits.base_address + query_bit.word_column bitidx = bits.offset * bitstream.WORD_SIZE_BITS + query_bit.word_bit @@ -128,7 +134,7 @@ class TileSegbits(object): if not match: break - if not match: + if not match or skip: continue def inner(): @@ -140,16 +146,26 @@ class TileSegbits(object): yield (tuple(inner()), feature) - def feature_to_bits(self, feature, address=0): + def map_bit_to_frame(self, block_type, bits, bit): + """ Convert bit from segbit to frame data. """ + return Bit( + word_column=bits.base_address + bit.word_column, + word_bit=bits.offset * bitstream.WORD_SIZE_BITS + bit.word_bit, + isset=bit.isset, + ) + + def feature_to_bits(self, bits_map, feature, address=0): if feature in self.ppips: return for block_type in self.segbits: if address == 0 and feature in self.segbits[block_type]: for bit in self.segbits[block_type][feature]: - yield block_type, bit + yield block_type, self.map_bit_to_frame( + block_type, bits_map[block_type], bit) return block_type, feature = self.feature_addresses[feature][address] for bit in self.segbits[block_type][feature]: - yield block_type, bit + yield block_type, self.map_bit_to_frame( + block_type, bits_map[block_type], bit) diff --git a/prjxray/tile_segbits_alias.py b/prjxray/tile_segbits_alias.py new file mode 100644 index 00000000..6216648f --- /dev/null +++ b/prjxray/tile_segbits_alias.py @@ -0,0 +1,106 @@ +""" TileSegbitsAlias provides an alias from one tile type to another. + +TileSegbitsAlias performs severals functions to achieve the alias: + - Remaps tile type from the original tile type to the alias tile type + - Offsets the bits from the original to the alias type + - Renames sites from the original to the alias type + - Filters bits outside of the alias. + + +""" + +from prjxray import bitstream +from prjxray.grid_types import Bits + + +class TileSegbitsAlias(object): + def __init__(self, db, tile_type, bits_map): + # Name of tile_type that is using the alias + self.tile_type = tile_type + + # Name of aliased tile_type + self.alias_tile_type = None + + # BlockType -> BitAlias map + self.alias = {} + + self.bits_map = bits_map + + # BlockType -> aliased Bits map + self.alias_bits_map = {} + + # aliased site name to site name map + self.sites_rev_map = {} + + for block_type in bits_map: + self.alias[block_type] = bits_map[block_type].alias + self.alias_bits_map[block_type] = Bits( + base_address=bits_map[block_type].base_address, + frames=bits_map[block_type].frames, + offset=bits_map[block_type].offset - + self.alias[block_type].start_offset, + words=bits_map[block_type].words, + alias=None, + ) + + if self.alias_tile_type is None: + self.alias_tile_type = self.alias[block_type].tile_type + else: + assert self.alias_tile_type == self.alias[block_type].tile_type + + self.sites_rev_map[block_type] = {} + for site, alias_site in self.alias[block_type].sites.items(): + assert alias_site not in self.sites_rev_map[block_type] + self.sites_rev_map[block_type][alias_site] = site + + self.tile_segbits = db.get_tile_segbits(self.alias_tile_type) + + def map_feature_to_segbits(self, feature): + """ Map from the output feature name to the aliased feature name. """ + parts = feature.split('.') + + assert parts[0] == self.tile_type + parts[0] = self.alias_tile_type + + for block_type in self.alias: + if len(parts) > 1 and parts[1] in self.alias[block_type].sites: + parts[1] = self.alias[block_type].sites[parts[1]] + + return '.'.join(parts) + + def map_feature_from_segbits(self, feature): + """ Map from the aliases feature name to the output feature name. """ + parts = feature.split('.') + + assert parts[0] == self.alias_tile_type + parts[0] = self.tile_type + + for block_type in self.alias: + if len(parts) > 1 and parts[1] in self.sites_rev_map[block_type]: + parts[1] = self.sites_rev_map[block_type][parts[1]] + + return '.'.join(parts) + + def match_filter(self, block_type, query_bit): + word = query_bit.word_bit // bitstream.WORD_SIZE_BITS + real_word = word - self.alias[block_type].start_offset + if real_word < 0 or real_word >= self.bits_map[block_type].words: + return False + + return True + + def match_bitdata(self, block_type, bits, bitdata): + alias_bits = self.alias_bits_map[block_type] + + for bits_found, alias_feature in self.tile_segbits.match_bitdata( + block_type, alias_bits, bitdata, + match_filter=self.match_filter): + feature = self.map_feature_from_segbits(alias_feature) + + yield (bits_found, feature) + + def feature_to_bits(self, bits_map, feature, address=0): + alias_feature = self.map_feature_to_segbits(feature) + for block_type, bit in self.tile_segbits.feature_to_bits( + self.alias_bits_map, alias_feature, address): + yield block_type, bit diff --git a/utils/bit2fasm.py b/utils/bit2fasm.py index 56fa3fbc..46dba630 100755 --- a/utils/bit2fasm.py +++ b/utils/bit2fasm.py @@ -35,24 +35,9 @@ def bits_to_fasm(db_root, bits_file, verbose, canonical): with open(bits_file) as f: bitdata = bitstream.load_bitdata(f) - def is_zero_feature(feature): - parts = feature.split('.') - tile = parts[0] - gridinfo = grid.gridinfo_at_tilename(tile) - feature = '.'.join(parts[1:]) - - db_k = '%s.%s' % (gridinfo.tile_type, feature) - segbits = db.get_tile_segbits(gridinfo.tile_type) - any_bits = False - for block_type, bit in segbits.feature_to_bits(db_k): - if bit.isset: - any_bits = True - - return not any_bits - model = fasm.output.merge_and_sort( disassembler.find_features_in_bitstream(bitdata, verbose=verbose), - zero_function=is_zero_feature, + zero_function=disassembler.is_zero_feature, sort_key=grid.tile_key, )