| #!/usr/bin/env python3 | |
| import copy | |
| import json | |
| import os | |
| from utils import xjson | |
| ''' | |
| 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 util as localutil | |
| 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 | |
| def make_tiles_by_grid(database): | |
| # lookup tile names by (X, Y) | |
| tiles_by_grid = dict() | |
| for tile_name in database: | |
| tile = database[tile_name] | |
| tiles_by_grid[(tile["grid_x"], tile["grid_y"])] = tile_name | |
| return tiles_by_grid | |
| def propagate_INT_bits_in_column(database, tiles_by_grid): | |
| """ Propigate INT offsets up and down INT columns. | |
| INT columns appear to be fairly regular, where starting from offset 0, | |
| INT tiles next to INT tiles increase the word offset by 3. | |
| """ | |
| seen_int = set() | |
| int_frames, int_words = localutil.get_entry('INT', 'CLB_IO_CLK') | |
| rclk_frames, rclk_words = localutil.get_entry('RCLK_INT_L', 'CLB_IO_CLK') | |
| for tile_name in sorted(database.keys()): | |
| tile = database[tile_name] | |
| if tile['type'] != 'INT': | |
| continue | |
| if not tile['bits']: | |
| continue | |
| if tile_name in seen_int: | |
| continue | |
| # Walk down INT column | |
| while True: | |
| seen_int.add(tile_name) | |
| next_tile = tiles_by_grid[(tile['grid_x'], tile['grid_y'] + 1)] | |
| next_tile_type = database[next_tile]['type'] | |
| if tile['bits']['CLB_IO_CLK']['offset'] == 0: | |
| assert next_tile_type in [ | |
| 'INT_TERM_B', 'INT_RBRK' | |
| ], next_tile_type | |
| break | |
| baseaddr = int(tile['bits']['CLB_IO_CLK']['baseaddr'], 0) | |
| offset = tile['bits']['CLB_IO_CLK']['offset'] | |
| if tile['type'] == 'INT' and next_tile_type == tile['type']: | |
| # INT next to INT | |
| offset -= int_words | |
| localutil.add_tile_bits( | |
| next_tile, database[next_tile], baseaddr, offset, | |
| int_frames, int_words) | |
| elif tile['type'] == 'INT': | |
| # INT above RCLK | |
| assert next_tile_type in ['RCLK_INT_L', 'RCLK_INT_R'], next_tile_type | |
| offset -= rclk_words | |
| localutil.add_tile_bits( | |
| next_tile, database[next_tile], baseaddr, offset, | |
| rclk_frames, rclk_words) | |
| else: | |
| # RCLK above INT | |
| assert tile['type'] in ['RCLK_INT_L', 'RCLK_INT_R'], tile['type'] | |
| if next_tile_type == 'INT': | |
| offset -= int_words | |
| localutil.add_tile_bits( | |
| next_tile, database[next_tile], baseaddr, offset, | |
| int_frames, int_words) | |
| else: | |
| assert next_tile_type in [], next_tile_type | |
| break | |
| tile_name = next_tile | |
| tile = database[tile_name] | |
| # Walk up INT column | |
| while True: | |
| seen_int.add(tile_name) | |
| next_tile = tiles_by_grid[(tile['grid_x'], tile['grid_y'] - 1)] | |
| next_tile_type = database[next_tile]['type'] | |
| if tile['bits']['CLB_IO_CLK']['offset'] == 183: | |
| assert next_tile_type in [ | |
| 'INT_TERM_T', 'INT_RBRK' | |
| ], next_tile_type | |
| break | |
| baseaddr = int(tile['bits']['CLB_IO_CLK']['baseaddr'], 0) | |
| offset = tile['bits']['CLB_IO_CLK']['offset'] | |
| if tile['type'] == 'INT' and next_tile_type == tile['type']: | |
| # INT next to INT | |
| offset += int_words | |
| localutil.add_tile_bits( | |
| next_tile, database[next_tile], baseaddr, offset, | |
| int_frames, int_words) | |
| elif tile['type'] == 'INT': | |
| # INT below RCLK | |
| assert next_tile_type in ['RCLK_INT_L', 'RCLK_INT_R'], next_tile_type | |
| offset += int_words | |
| localutil.add_tile_bits( | |
| next_tile, database[next_tile], baseaddr, offset, | |
| rclk_frames, rclk_words) | |
| else: | |
| # RCLK below INT | |
| assert tile['type'] in ['RCLK_INT_L', 'RCLK_INT_R'], tile['type'] | |
| assert next_tile_type == 'INT', next_tile_type | |
| offset += rclk_words | |
| localutil.add_tile_bits( | |
| next_tile, database[next_tile], baseaddr, offset, | |
| int_frames, int_words) | |
| tile_name = next_tile | |
| tile = database[tile_name] | |
| def propagate_rebuf(database, tiles_by_grid): | |
| """ Writing a fuzzer for the CLK_BUFG_REBUF tiles is hard, so propigate from CLK_HROW tiles. | |
| In the clock column, there is a CLK_BUFG_REBUF above and below the CLK_HROW | |
| tile. Each clock column appears to use the same offsets, so propigate | |
| the base address and frame count, and update the offset and word count. | |
| """ | |
| for tile_name in sorted(database.keys()): | |
| tile = database[tile_name] | |
| if tile['type'] not in ['CLK_HROW_BOT_R', 'CLK_HROW_TOP_R']: | |
| continue | |
| rebuf_below = tiles_by_grid[(tile['grid_x'], tile['grid_y'] - 12)] | |
| assert database[rebuf_below]['type'] == 'CLK_BUFG_REBUF', database[ | |
| rebuf_below]['type'] | |
| rebuf_above = tiles_by_grid[(tile['grid_x'], tile['grid_y'] + 13)] | |
| assert database[rebuf_above]['type'] == 'CLK_BUFG_REBUF', database[ | |
| rebuf_below]['type'] | |
| assert database[tile_name]['bits']['CLB_IO_CLK'][ | |
| 'offset'] == 42, database[tile_name]['bits']['CLB_IO_CLK'] | |
| database[rebuf_below]['bits'] = copy.deepcopy( | |
| database[tile_name]['bits']) | |
| database[rebuf_below]['bits']['CLB_IO_CLK']['offset'] = 73 | |
| database[rebuf_below]['bits']['CLB_IO_CLK']['words'] = 4 | |
| database[rebuf_above]['bits'] = copy.deepcopy( | |
| database[tile_name]['bits']) | |
| database[rebuf_above]['bits']['CLB_IO_CLK']['offset'] = 24 | |
| 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 propagate_IOI_SING(database, tiles_by_grid): | |
| """ | |
| The IOI_SING, similarly to IOB_SING, are half tiles at top and bottom of every | |
| IO column. | |
| The tile contains half of the sites that are present in the full IOI, | |
| namely one ILOGIC, OLOGIC and IDELAY. | |
| """ | |
| seen_iois = set() | |
| for tile in database: | |
| if tile in seen_iois: | |
| continue | |
| if database[tile]["type"] not in ["LIOI3", "RIOI3"]: | |
| 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_iois.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_iois.add(tile) | |
| if '_SING' in database[tile]['type']: | |
| break | |
| if 'CLB_IO_CLK' in database[tile]['bits']: | |
| if tile.startswith("LIOI") or tile.startswith("RIOI"): | |
| assert bits['baseaddr'] == database[tile]['bits'][ | |
| 'CLB_IO_CLK']['baseaddr'] | |
| assert bits['frames'] == database[tile]['bits'][ | |
| 'CLB_IO_CLK']['frames'], "{}:{} == {}".format( | |
| tile, bits['frames'], | |
| database[tile]['bits']['CLB_IO_CLK']['frames']) | |
| assert bits['words'] == database[tile]['bits'][ | |
| 'CLB_IO_CLK']['words'], "{}: {} != {}".format( | |
| tile, 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': {} | |
| } | |
| 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': {} | |
| } | |
| def propagate_IOI_Y9(database, tiles_by_grid): | |
| """ | |
| There are IOI tiles (X0Y9 and X43Y9) that have the frame address 1 frame | |
| higher than the rest, just like for some of the SING tiles. | |
| """ | |
| arch = os.getenv('URAY_DATABASE') | |
| if arch in 'artix7': | |
| tiles = ['RIOI3_X43Y9', 'LIOI3_X0Y9'] | |
| elif arch in 'kintex7': | |
| tiles = ['LIOI3_X0Y9'] | |
| elif arch in 'zynq7': | |
| tiles = ['RIOI3_X31Y9'] | |
| else: | |
| assert False, "Unsupported architecture" | |
| for tile in tiles: | |
| prev_tile = tiles_by_grid[( | |
| database[tile]['grid_x'], database[tile]['grid_y'] - 1)] | |
| while database[prev_tile]["type"] != database[tile]["type"]: | |
| prev_tile = tiles_by_grid[( | |
| database[prev_tile]['grid_x'], | |
| database[prev_tile]['grid_y'] - 1)] | |
| bits = database[prev_tile]['bits']['CLB_IO_CLK'] | |
| database[tile]['bits']['CLB_IO_CLK'] = copy.deepcopy(bits) | |
| database[tile]['bits']['CLB_IO_CLK']['words'] = 4 | |
| database[tile]['bits']['CLB_IO_CLK']['offset'] = 18 | |
| def alias_HCLKs(database): | |
| """ Generate HCLK aliases for HCLK_[LR] subsets. | |
| There are some HCLK_[LR] tiles that are missing some routing due to | |
| obstructions, e.g. PCIE hardblock. These tiles do not have southbound | |
| clock routing, but are otherwise the same as HCLK_[LR] tiles. | |
| Simply alias their segbits. | |
| """ | |
| for tile in database: | |
| if database[tile]['type'] == "HCLK_L_BOT_UTURN": | |
| database[tile]['bits']['CLB_IO_CLK']['alias'] = { | |
| "sites": {}, | |
| "start_offset": 0, | |
| "type": "HCLK_L" | |
| } | |
| elif database[tile]['type'] == "HCLK_R_BOT_UTURN": | |
| database[tile]['bits']['CLB_IO_CLK']['alias'] = { | |
| "sites": {}, | |
| "start_offset": 0, | |
| "type": "HCLK_R" | |
| } | |
| def run(json_in_fn, json_out_fn, verbose=False): | |
| # Load input files | |
| database = json.load(open(json_in_fn, "r")) | |
| tiles_by_grid = make_tiles_by_grid(database) | |
| propagate_INT_bits_in_column(database, tiles_by_grid) | |
| #propagate_rebuf(database, tiles_by_grid) | |
| #propagate_IOB_SING(database, tiles_by_grid) | |
| #propagate_IOI_SING(database, tiles_by_grid) | |
| #propagate_IOI_Y9(database, tiles_by_grid) | |
| #alias_HCLKs(database) | |
| # Save | |
| xjson.pprint(open(json_out_fn, "w"), database) | |
| def main(): | |
| import argparse | |
| parser = argparse.ArgumentParser( | |
| description="Generate tilegrid.json from bitstream deltas") | |
| parser.add_argument("--verbose", action="store_true", help="") | |
| parser.add_argument( | |
| "--json-in", | |
| default="tiles_basic.json", | |
| help="Input .json without addresses") | |
| parser.add_argument( | |
| "--json-out", default="tilegrid.json", help="Output JSON") | |
| args = parser.parse_args() | |
| run(args.json_in, args.json_out, verbose=args.verbose) | |
| if __name__ == "__main__": | |
| main() |