| #!/usr/bin/env python3 |
| # -*- coding: utf-8 -*- |
| # |
| # Copyright (C) 2017-2020 The Project X-Ray Authors. |
| # |
| # Use of this source code is governed by a ISC-style |
| # license that can be found in the LICENSE file or at |
| # https://opensource.org/licenses/ISC |
| # |
| # SPDX-License-Identifier: ISC |
| 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_lr_bits( |
| database, tiles_by_grid, tile_frames_map, verbose=False): |
| '''Populate segment base addresses: L/R along INT column''' |
| |
| int_frames, int_words, _ = localutil.get_entry('INT', 'CLB_IO_CLK') |
| |
| verbose and print('') |
| for tile in database: |
| if database[tile]["type"] not in ["INT_L", "INT_R"]: |
| continue |
| |
| if not database[tile]["bits"]: |
| continue |
| |
| grid_x = database[tile]["grid_x"] |
| grid_y = database[tile]["grid_y"] |
| baseaddr = int(database[tile]["bits"]["CLB_IO_CLK"]["baseaddr"], 0) |
| offset = database[tile]["bits"]["CLB_IO_CLK"]["offset"] |
| |
| if database[tile]["type"] == "INT_L": |
| grid_x += 1 |
| baseaddr = baseaddr + 0x80 |
| elif database[tile]["type"] == "INT_R": |
| grid_x -= 1 |
| baseaddr = baseaddr - 0x80 |
| else: |
| assert 0, database[tile]["type"] |
| |
| # ROI at edge? |
| if (grid_x, grid_y) not in tiles_by_grid: |
| verbose and print(' Skip edge') |
| continue |
| |
| other_tile = tiles_by_grid[(grid_x, grid_y)] |
| |
| if database[tile]["type"] == "INT_L": |
| assert database[other_tile]["type"] == "INT_R" |
| elif database[tile]["type"] == "INT_R": |
| assert database[other_tile]["type"] == "INT_L" |
| else: |
| assert 0 |
| |
| localutil.add_tile_bits( |
| other_tile, database[other_tile], baseaddr, offset, int_frames, |
| int_words, tile_frames_map) |
| |
| |
| def propagate_INT_bits_in_column(database, tiles_by_grid, tile_frames_map): |
| """ 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 2. The HCLK tile |
| is surrounded above and sometimes below by an INT tile. Because the HCLK |
| tile only useds one word, the offset increase by one at the HCLK. |
| |
| """ |
| |
| seen_int = set() |
| |
| int_frames, int_words, _ = localutil.get_entry('INT', 'CLB_IO_CLK') |
| hclk_frames, hclk_words, _ = localutil.get_entry('HCLK', 'CLB_IO_CLK') |
| |
| for tile_name in sorted(database.keys()): |
| tile = database[tile_name] |
| |
| if tile['type'] not in ['INT_L', 'INT_R']: |
| continue |
| |
| l_or_r = tile['type'][-1] |
| |
| 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 [ |
| 'B_TERM_INT', 'BRKH_INT', 'BRKH_B_TERM_INT' |
| ], next_tile_type |
| break |
| |
| baseaddr = int(tile['bits']['CLB_IO_CLK']['baseaddr'], 0) |
| offset = tile['bits']['CLB_IO_CLK']['offset'] |
| |
| if tile['type'].startswith( |
| '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, tile_frames_map) |
| elif tile['type'].startswith('INT_'): |
| # INT above HCLK |
| assert next_tile_type.startswith( |
| 'HCLK_{}'.format(l_or_r)), next_tile_type |
| |
| offset -= hclk_words |
| localutil.add_tile_bits( |
| next_tile, database[next_tile], baseaddr, offset, |
| hclk_frames, hclk_words, tile_frames_map) |
| else: |
| # HCLK above INT |
| assert tile['type'].startswith( |
| 'HCLK_{}'.format(l_or_r)), tile['type'] |
| if next_tile_type == 'INT_{}'.format(l_or_r): |
| offset -= int_words |
| localutil.add_tile_bits( |
| next_tile, database[next_tile], baseaddr, offset, |
| int_frames, int_words, tile_frames_map) |
| else: |
| # Handle special case column where the PCIE tile is present. |
| assert next_tile_type in ['PCIE_NULL'], 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'] == 99: |
| assert next_tile_type in [ |
| 'T_TERM_INT', 'BRKH_INT', 'BRKH_TERM_INT', 'BRKH_INT_PSS' |
| ], next_tile_type |
| break |
| |
| baseaddr = int(tile['bits']['CLB_IO_CLK']['baseaddr'], 0) |
| offset = tile['bits']['CLB_IO_CLK']['offset'] |
| |
| if tile['type'].startswith( |
| '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, tile_frames_map) |
| elif tile['type'].startswith('INT_'): |
| # INT below HCLK |
| assert next_tile_type.startswith( |
| 'HCLK_{}'.format(l_or_r)), next_tile_type |
| |
| offset += int_words |
| localutil.add_tile_bits( |
| next_tile, database[next_tile], baseaddr, offset, |
| hclk_frames, hclk_words, tile_frames_map) |
| else: |
| # HCLK below INT |
| assert tile['type'].startswith( |
| 'HCLK_{}'.format(l_or_r)), tile['type'] |
| assert next_tile_type == 'INT_{}'.format( |
| l_or_r), next_tile_type |
| |
| offset += hclk_words |
| localutil.add_tile_bits( |
| next_tile, database[next_tile], baseaddr, offset, |
| int_frames, int_words, tile_frames_map) |
| |
| tile_name = next_tile |
| tile = database[tile_name] |
| |
| |
| def propagate_INT_INTERFACE_bits_in_column( |
| database, tiles_by_grid, int_interface_name, tile_frames_map): |
| """ Propagate INT_INTERFACE column for a given INT_INTERFACE tile name. |
| |
| INT_INTERFACE tiles do not usually have any PIPs or baseaddresses, |
| except for a few cases such as PCIE or GTP INTERFACE tiles. |
| |
| These are very regular tiles, except for the horizontal clock line, |
| which adds a one-word offset. |
| |
| This function replicates the baseaddress and calculates the correct offset |
| for each INT INTERFACE tile in a column, starting from a tile in the column |
| that has the baseaddress calculated from the corresponding tilegrid fuzzer. |
| """ |
| |
| seen_int = set() |
| |
| int_frames, int_words, _ = localutil.get_entry('INT', 'CLB_IO_CLK') |
| hclk_frames, hclk_words, _ = localutil.get_entry('HCLK', 'CLB_IO_CLK') |
| |
| for tile_name in sorted(database.keys()): |
| tile = database[tile_name] |
| |
| if not tile['type'].startswith(int_interface_name): |
| continue |
| |
| if not tile['bits']: |
| continue |
| |
| if tile_name in seen_int: |
| continue |
| |
| # Walk down INT column |
| down_tile = tile |
| down_tile_name = tile_name |
| while True: |
| seen_int.add(down_tile_name) |
| |
| baseaddr = int(down_tile['bits']['CLB_IO_CLK']['baseaddr'], 0) |
| offset = down_tile['bits']['CLB_IO_CLK']['offset'] |
| extra_offset = 0 |
| |
| next_tile = tiles_by_grid[( |
| down_tile['grid_x'], down_tile['grid_y'] + 1)] |
| if next_tile.startswith("HCLK"): |
| next_tile = tiles_by_grid[( |
| down_tile['grid_x'], down_tile['grid_y'] + 2)] |
| extra_offset = hclk_words |
| |
| next_tile_type = database[next_tile]['type'] |
| |
| if next_tile_type != tile['type']: |
| break |
| |
| if next_tile_type == down_tile['type']: |
| # INT next to INT |
| offset -= (int_words + extra_offset) |
| localutil.add_tile_bits( |
| next_tile, database[next_tile], baseaddr, offset, |
| int_frames, int_words, tile_frames_map) |
| |
| down_tile_name = next_tile |
| down_tile = database[down_tile_name] |
| |
| # Walk up INT column |
| up_tile = tile |
| up_tile_name = tile_name |
| while True: |
| seen_int.add(up_tile_name) |
| |
| baseaddr = int(up_tile['bits']['CLB_IO_CLK']['baseaddr'], 0) |
| offset = up_tile['bits']['CLB_IO_CLK']['offset'] |
| extra_offset = 0 |
| |
| next_tile = tiles_by_grid[( |
| up_tile['grid_x'], up_tile['grid_y'] - 1)] |
| if next_tile.startswith("HCLK"): |
| next_tile = tiles_by_grid[( |
| up_tile['grid_x'], up_tile['grid_y'] - 2)] |
| extra_offset = hclk_words |
| |
| next_tile_type = database[next_tile]['type'] |
| |
| if next_tile_type != tile['type']: |
| break |
| |
| if next_tile_type == up_tile['type']: |
| # INT next to INT |
| offset += (int_words + extra_offset) |
| localutil.add_tile_bits( |
| next_tile, database[next_tile], baseaddr, offset, |
| int_frames, int_words, tile_frames_map) |
| |
| up_tile_name = next_tile |
| up_tile = database[up_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", "RIOB18"]: |
| 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", "RIOI"]: |
| 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. |
| |
| """ |
| ioi_tiles = os.getenv('XRAY_IOI3_TILES') |
| |
| assert ioi_tiles is not None, "XRAY_IOI3_TILES env variable not set" |
| tiles = ioi_tiles.split(" ") |
| |
| 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) |
| |
| tile_frames_map = localutil.TileFrames() |
| propagate_INT_lr_bits( |
| database, tiles_by_grid, tile_frames_map, verbose=verbose) |
| propagate_INT_bits_in_column(database, tiles_by_grid, tile_frames_map) |
| propagate_INT_INTERFACE_bits_in_column( |
| database, tiles_by_grid, "GTP_INT_INTERFACE", tile_frames_map) |
| propagate_INT_INTERFACE_bits_in_column( |
| database, tiles_by_grid, "GTX_INT_INTERFACE", tile_frames_map) |
| propagate_INT_INTERFACE_bits_in_column( |
| database, tiles_by_grid, "PCIE_INT_INTERFACE", tile_frames_map) |
| 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() |