From 2a385127207711dfac172cc00057b6badce08c1b Mon Sep 17 00:00:00 2001 From: John McMaster Date: Mon, 3 Dec 2018 12:09:18 -0800 Subject: [PATCH] tilegrid: separate tilegrid and address generation Signed-off-by: John McMaster --- fuzzers/005-tilegrid/Makefile | 4 +- fuzzers/005-tilegrid/generate.py | 560 +------------------------ fuzzers/005-tilegrid/generate_full.py | 562 +++++++++++++++++++++++++- 3 files changed, 563 insertions(+), 563 deletions(-) diff --git a/fuzzers/005-tilegrid/Makefile b/fuzzers/005-tilegrid/Makefile index 92adb9bd..8c487919 100644 --- a/fuzzers/005-tilegrid/Makefile +++ b/fuzzers/005-tilegrid/Makefile @@ -13,7 +13,7 @@ build/tiles/tiles.txt: # TODO: only generate tiles build/tilegrid_basic.json: build/tiles/tiles.txt build/clb/deltas build/bram/deltas generate.py - cd build && python3 ${FUZDIR}/generate.py --tiles $(FUZDIR)/build/tiles/tiles.txt */design_*.delta --out ${BUILD_DIR}/tilegrid_basic.json + cd build && python3 ${FUZDIR}/generate.py --tiles $(FUZDIR)/build/tiles/tiles.txt --out ${BUILD_DIR}/tilegrid_basic.json build/clb/deltas: bash generate.sh build/clb clb @@ -23,7 +23,7 @@ build/bram/deltas: # TODO: only generate addresses build/tilegrid.json: build/tilegrid_basic.json - cd build && python3 ${FUZDIR}/generate_full.py --json-in tilegrid_basic.json --json-out ${BUILD_DIR}/tilegrid.json design_*.delta + cd build && python3 ${FUZDIR}/generate_full.py --json-in tilegrid_basic.json --json-out ${BUILD_DIR}/tilegrid.json --tiles $(FUZDIR)/build/tiles/tiles.txt */design_*.delta run: $(MAKE) clean diff --git a/fuzzers/005-tilegrid/generate.py b/fuzzers/005-tilegrid/generate.py index 56cd707a..1f67174f 100644 --- a/fuzzers/005-tilegrid/generate.py +++ b/fuzzers/005-tilegrid/generate.py @@ -1,51 +1,7 @@ #!/usr/bin/env python3 -''' -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 os, sys, json, re -# matches lib/include/prjxray/xilinx/xc7series/block_type.h -block_type_i2s = { - 0: 'CLB_IO_CLK', - 1: 'BLOCK_RAM', - 2: 'CFG_CLB', - # special...maybe should error until we know what it is? - # 3: 'RESERVED', -} - - -def addr2btype(base_addr): - ''' - Convert integer address to block type - - Table 5-24: Frame Address Register Description - Bit Index: [25:23] - https://www.xilinx.com/support/documentation/user_guides/ug470_7Series_Config.pdf - "Valid block types are CLB, I/O, CLK ( 000 ), block RAM content ( 001 ), and CFG_CLB ( 010 ). A normal bitstream does not include type 011 ." - ''' - block_type_i = (base_addr >> 23) & 0x7 - return block_type_i2s[block_type_i] - - -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 load_tiles(tiles_fn): ''' @@ -76,21 +32,6 @@ def load_tiles(tiles_fn): return tiles -def load_baseaddrs(deltas_fns): - site_baseaddr = dict() - for arg in deltas_fns: - with open(arg) as f: - line = f.read().strip() - # clb/design_SLICE_X10Y100.delta - # site = arg[7:-6] - site = re.match(r".*/design_(.*).delta", arg).group(1) - frame = int(line[5:5 + 8], 16) - # was "0x%08x" - site_baseaddr[site] = frame & ~0x7f - - return site_baseaddr - - def make_database(tiles): # tile database with X, Y, and list of sites # tile name as keys @@ -108,503 +49,12 @@ def make_database(tiles): return database -def make_tile_baseaddrs(tiles, site_baseaddr, verbose=False): - # Look up a base address by tile name - tile_baseaddrs = dict() - - verbose and print('') - verbose and print('%u tiles' % len(tiles)) - verbose and print("%u baseaddrs" % len(site_baseaddr)) - added = 0 - for tile in tiles: - for site_name in tile["sites"].keys(): - if site_name not in site_baseaddr: - continue - framebaseaddr = site_baseaddr[site_name] - bt = addr2btype(framebaseaddr) - tile_baseaddr = tile_baseaddrs.setdefault(tile["name"], {}) - if bt in tile_baseaddr: - # actually lets just fail these, better to remove at tcl level to speed up processing - assert 0, 'duplicate base address' - assert tile_baseaddr[bt] == [framebaseaddr, 0] - else: - tile_baseaddr[bt] = [framebaseaddr, 0] - verbose and print( - "baseaddr: %s.%s @ %s.0x%08x" % - (tile["name"], site_name, bt, framebaseaddr)) - added += 1 - - assert added, "Failed to add any base addresses" - assert added == len(site_baseaddr) - return tile_baseaddrs - - -def make_tiles_by_grid(tiles): - # lookup tile names by (X, Y) - tiles_by_grid = dict() - - for tile in tiles: - tiles_by_grid[(tile["grid_x"], tile["grid_y"])] = tile["name"] - - return tiles_by_grid - - -def add_segment( - database, segments, name, tiles, segtype, verbose, baseaddr=None): - assert name not in segments - segment = segments.setdefault(name, {}) - segment["tiles"] = tiles - segment["type"] = segtype - if baseaddr: - verbose and print('make_segment: %s baseaddr %s' % ( - name, - baseaddr, - )) - segment["baseaddr"] = baseaddr - - for tile_name in tiles: - database[tile_name]["segment"] = name - - -def make_segments(database, tiles_by_grid, tile_baseaddrs, verbose=False): - ''' - Create segments data structure - Indicates how tiles are related to bitstream locations - Also modify database to annotate which segment the tiles belong to - - segments key examples: - SEG_CLBLM_R_X13Y72 - SEG_BRAM3_L_X6Y85 - ''' - segments = dict() - - verbose and print('') - for tile_name, tile_data in database.items(): - tile_type = tile_data["type"] - grid_x = tile_data["grid_x"] - grid_y = tile_data["grid_y"] - - def process_clb(): - if tile_type in ["CLBLL_L", "CLBLM_L"]: - int_tile_name = tiles_by_grid[(grid_x + 1, grid_y)] - else: - int_tile_name = tiles_by_grid[(grid_x - 1, grid_y)] - - add_segment( - database=database, - segments=segments, - name="SEG_" + tile_name, - tiles=[tile_name, int_tile_name], - segtype=tile_type.lower(), - baseaddr=tile_baseaddrs.get(tile_name, None), - verbose=verbose, - ) - - def process_hclk(): - add_segment( - database=database, - segments=segments, - name="SEG_" + tile_name, - tiles=[tile_name], - segtype=tile_type.lower(), - verbose=verbose, - ) - - def process_bram_dsp(): - for k in range(5): - if tile_type in ["BRAM_L", "DSP_L"]: - interface_tile_name = tiles_by_grid[( - grid_x + 1, grid_y - k)] - int_tile_name = tiles_by_grid[(grid_x + 2, grid_y - k)] - elif tile_type in ["BRAM_R", "DSP_R"]: - interface_tile_name = tiles_by_grid[( - grid_x - 1, grid_y - k)] - int_tile_name = tiles_by_grid[(grid_x - 2, grid_y - k)] - else: - assert 0 - ''' - BRAM/DSP itself is at the base y address - There is one huge switchbox on the right for the 5 tiles - These fan into 5 BRAM_INT_INTERFACE tiles each which feed into their own CENTER_INTER (just like a CLB has) - ''' - if k == 0: - tiles = [tile_name, interface_tile_name, int_tile_name] - baseaddr = tile_baseaddrs.get(tile_name, None) - else: - tiles = [interface_tile_name, int_tile_name] - baseaddr = None - - add_segment( - database=database, - segments=segments, - # BRAM_L_X6Y70 => SEG_BRAM4_L_X6Y70 - name="SEG_" + tile_name.replace("_", "%d_" % k, 1), - tiles=tiles, - # BRAM_L => bram4_l - segtype=tile_type.lower().replace("_", "%d_" % k, 1), - baseaddr=baseaddr, - verbose=verbose, - ) - - def process_default(): - #verbose and nolr(tile_type) not in ('VBRK', 'INT', 'NULL') and print('make_segment: drop %s' % (tile_type,)) - pass - - { - "CLBLL": process_clb, - "CLBLM": process_clb, - "HCLK": process_hclk, - "BRAM": process_bram_dsp, - "DSP": process_bram_dsp, - }.get(nolr(tile_type), process_default)() - - return segments - - -def get_inttile(database, segment): - '''Return interconnect tile for given segment''' - inttiles = [ - tile for tile in segment["tiles"] - if database[tile]["type"] in ["INT_L", "INT_R"] - ] - assert len(inttiles) == 1 - return inttiles[0] - - -def get_bramtile(database, segment): - inttiles = [ - tile for tile in segment["tiles"] - if database[tile]["type"] in ["BRAM_L", "BRAM_R"] - ] - assert len(inttiles) == 1 - return inttiles[0] - - -def create_segment_for_int_lr( - database, segments, tile, tiles_by_grid, verbose): - """ Creates INT_[LR] segment for interconnect's without direct connectivity. """ - # Some INT_[LR] tiles have no adjacent connectivity, create a segment. - grid_x = database[tile]["grid_x"] - grid_y = database[tile]["grid_y"] - if database[tile]["type"] == "INT_L": - grid_x -= 1 - adjacent_tile = tiles_by_grid[(grid_x, grid_y)] - elif database[tile]["type"] == "INT_R": - grid_x += 1 - adjacent_tile = tiles_by_grid[(grid_x, grid_y)] - else: - assert False, database[tile]["type"] - - if (database[adjacent_tile]['type'].startswith('INT_INTERFACE_') or - database[adjacent_tile]['type'].startswith('PCIE_INT_INTERFACE_') - or - database[adjacent_tile]['type'].startswith('GTP_INT_INTERFACE')): - # This INT_[LR] tile has no adjacent connectivity, - # create a segment. - add_segment( - database=database, - segments=segments, - name='SEG_' + tile, - tiles=[tile], - segtype=database[tile]["type"], - verbose=verbose, - ) - else: - assert False, database[adjacent_tile]['type'] - - -def seg_base_addr_lr_INT(database, segments, tiles_by_grid, verbose=False): - '''Populate segment base addresses: L/R along INT column''' - ''' - Create BRAM base addresses based on nearby CLBs - ie if we have a BRAM_L, compute as nearby CLB_R base address + offset - ''' - - verbose and print('') - for segment_name in sorted(segments.keys()): - segment = segments[segment_name] - baseaddrs = segment.get("baseaddr", None) - if not baseaddrs: - continue - - for block_type, (framebase, wordbase) in sorted(baseaddrs.items()): - verbose and print( - 'lr_INT: %s: %s.0x%08X:%u' % - (segment_name, block_type, framebase, wordbase)) - if block_type != 'CLB_IO_CLK': - verbose and print(' Skip non CLB') - continue - - inttile = get_inttile(database, segment) - grid_x = database[inttile]["grid_x"] - grid_y = database[inttile]["grid_y"] - - if database[inttile]["type"] == "INT_L": - grid_x += 1 - framebase = framebase + 0x80 - elif database[inttile]["type"] == "INT_R": - grid_x -= 1 - framebase = framebase - 0x80 - else: - assert 0 - - # ROI at edge? - if (grid_x, grid_y) not in tiles_by_grid: - verbose and print(' Skip edge') - continue - - tile = tiles_by_grid[(grid_x, grid_y)] - - if database[inttile]["type"] == "INT_L": - assert database[tile]["type"] == "INT_R" - elif database[inttile]["type"] == "INT_R": - assert database[tile]["type"] == "INT_L" - else: - assert 0 - - if 'segment' not in database[tile]: - if database[tile]['type'] in ['T_TERM_INT']: - continue - create_segment_for_int_lr( - database, segments, tile, tiles_by_grid, verbose) - - seg = database[tile]["segment"] - - seg_baseaddrs = segments[seg].setdefault("baseaddr", {}) - # At least one duplicate when we re-compute the entry for the base address - # should give the same address - if block_type in seg_baseaddrs: - assert seg_baseaddrs[block_type] == [ - framebase, wordbase - ], (seg_baseaddrs[block_type], [framebase, wordbase]) - verbose and print(' Existing OK') - else: - seg_baseaddrs[block_type] = [framebase, wordbase] - verbose and print(' Add new') - - -def seg_base_addr_up_INT(database, segments, tiles_by_grid, verbose=False): - '''Populate segment base addresses: Up along INT/HCLK columns''' - - verbose and print('') - # Copy the initial list containing only base addresses - # and soon to have derived addresses - src_segment_names = list() - for segment_name in segments.keys(): - if "baseaddr" in segments[segment_name]: - src_segment_names.append(segment_name) - - verbose and print('up_INT: %u base addresses' % len(src_segment_names)) - - for src_segment_name in sorted(src_segment_names): - src_segment = segments[src_segment_name] - - for block_type, (framebase, - wordbase) in sorted(src_segment["baseaddr"].items()): - verbose and print( - 'up_INT: %s: %s.0x%08X:%u' % - (src_segment_name, block_type, framebase, wordbase)) - - def process_CLB_IO_CLK(wordbase): - ''' - Lookup interconnect tile associated with this segment - Use it to locate in the grid, and find other segments related by tile offset - ''' - - inttile = get_inttile(database, src_segment) - verbose and print( - ' up_INT CLK_IO_CLK: %s => inttile %s' % - (src_segment_name, inttile)) - grid_x = database[inttile]["grid_x"] - grid_y = database[inttile]["grid_y"] - - for i in range(50): - grid_y -= 1 - if grid_y < 0: - break - - dst_tile = database[tiles_by_grid[(grid_x, grid_y)]] - - if wordbase == 50: - wordbase += 1 - else: - wordbase += 2 - - if 'segment' not in dst_tile: - if dst_tile['type'] in ['T_TERM_INT']: - continue - create_segment_for_int_lr( - database, segments, tiles_by_grid[(grid_x, - grid_y)], - tiles_by_grid, verbose) - - #verbose and print(' dst_tile', dst_tile) - dst_segment_name = dst_tile["segment"] - #verbose and print('up_INT: %s => %s' % (src_segment_name, dst_segment_name)) - segments[dst_segment_name].setdefault( - "baseaddr", {})[block_type] = [framebase, wordbase] - - def process_BLOCK_RAM(wordbase): - ''' - Lookup BRAM0 tile associated with this segment - Use it to locate in the grid, and find other BRAM0 related by tile offset - - - From minitest: - - build/roi_bramd_bit01.diff (lowest BRAM coordinate) - > bit_00c00000_000_00 - - build/roi_bramds_bit01.diff - > bit_00c00000_000_00 - > bit_00c00000_010_00 - > bit_00c00000_020_00 - > bit_00c00000_030_00 - > bit_00c00000_040_00 - > bit_00c00000_051_00 - > bit_00c00000_061_00 - > bit_00c00000_071_00 - > bit_00c00000_081_00 - > bit_00c00000_091_00 - ''' - src_tile_name = get_bramtile(database, src_segment) - verbose and print( - ' up_INT BLOCK_RAM: %s => %s' % - (src_segment_name, src_tile_name)) - grid_x = database[src_tile_name]["grid_x"] - grid_y = database[src_tile_name]["grid_y"] - - for i in range(9): - grid_y -= 5 - wordbase += 10 - # Skip HCLK - if i == 4: - grid_y -= 1 - wordbase += 1 - - if (grid_x, grid_y) not in tiles_by_grid: - continue - - dst_tile = database[tiles_by_grid[(grid_x, grid_y)]] - assert nolr(dst_tile['type']) == 'BRAM', dst_tile - - dst_segment_name = dst_tile["segment"] - assert 'BRAM0' in dst_segment_name - segments[dst_segment_name].setdefault( - "baseaddr", {})[block_type] = [framebase, wordbase] - - { - 'CLB_IO_CLK': process_CLB_IO_CLK, - 'BLOCK_RAM': process_BLOCK_RAM, - }[block_type]( - wordbase) - - -def add_tile_bits(tile_db, baseaddr, offset, frames, words, height=None): - ''' - Record data structure geometry for the given tile baseaddr - For most tiles there is only one baseaddr, but some like BRAM have multiple - - Notes on multiple block types: - https://github.com/SymbiFlow/prjxray/issues/145 - ''' - - bits = tile_db['bits'] - block_type = addr2btype(baseaddr) - - assert 0 <= offset <= 100, offset - assert 1 <= words <= 101 - assert offset + words <= 101, ( - tile_db, offset + words, offset, words, block_type) - - assert block_type not in bits - block = bits.setdefault(block_type, {}) - - # FDRI address - block["baseaddr"] = '0x%08X' % baseaddr - # Number of frames this entry is sretched across - # that is the following FDRI addresses are used: range(baseaddr, baseaddr + frames) - block["frames"] = frames - - # Index of first word used within each frame - block["offset"] = offset - - # related to words... - # deprecated field? Don't worry about for now - # DSP has some differences between height and words - block["words"] = words - if height is None: - height = words - block["height"] = height - - -def db_add_bits(database, segments): - '''Transfer segment data into tiles''' - for segment_name in segments.keys(): - if 'baseaddr' not in segments[segment_name]: - continue - - for block_type, (baseaddr, - offset) in segments[segment_name]["baseaddr"].items(): - for tile_name in segments[segment_name]["tiles"]: - tile_type = database[tile_name]["type"] - entry = { - # (tile_type, block_type): (frames, words, height) - ("CLBLL", "CLB_IO_CLK"): (36, 2, 2), - ("CLBLM", "CLB_IO_CLK"): (36, 2, 2), - ("HCLK", "CLB_IO_CLK"): (26, 1, 1), - ("INT", "CLB_IO_CLK"): (28, 2, 2), - ("BRAM", "CLB_IO_CLK"): (28, 10, None), - ("BRAM", "BLOCK_RAM"): (128, 10, None), - ("DSP", "CLB_IO_CLK"): (28, 2, 10), - ("INT_INTERFACE", "CLB_IO_CLK"): (28, 2, None), - ("BRAM_INT_INTERFACE", "CLB_IO_CLK"): (28, 2, None), - }.get((nolr(tile_type), block_type), None) - if entry is None: - # Other types are rare, not expected to have these - if block_type == "CLB_IO_CLK": - raise ValueError("Unknown tile type %s" % tile_type) - continue - - frames, words, height = entry - if frames: - # if we have a width, we should have a height - assert frames and words - add_tile_bits( - database[tile_name], baseaddr, offset, frames, words, - height) - - -def db_add_segments(database, segments): - # TODO: Migrate to new tilegrid format via library. This data is added for - # compability with unconverted tools. Update tools then remove this data from - # tilegrid.json. - # looks like only htmlgen is using this? - for tiledata in database.values(): - if "segment" in tiledata: - segment = tiledata["segment"] - tiledata["segment_type"] = segments[segment]["type"] - - -def run(tiles_fn, json_fn, deltas_fns, verbose=False): +def run(tiles_fn, json_fn, verbose=False): # Load input files tiles = load_tiles(tiles_fn) - site_baseaddr = load_baseaddrs(deltas_fns) # Index input database = make_database(tiles) - tile_baseaddrs = make_tile_baseaddrs(tiles, site_baseaddr, verbose=verbose) - tiles_by_grid = make_tiles_by_grid(tiles) - - segments = make_segments( - database, tiles_by_grid, tile_baseaddrs, verbose=verbose) - - # Reference adjacent CLBs to locate adjacent tiles by known offsets - seg_base_addr_lr_INT(database, segments, tiles_by_grid, verbose=verbose) - seg_base_addr_up_INT(database, segments, tiles_by_grid, verbose=verbose) - - db_add_bits(database, segments) - db_add_segments(database, segments) # Save json.dump( @@ -626,15 +76,9 @@ def main(): parser.add_argument('--out', default='/dev/stdout', help='Output JSON') parser.add_argument( '--tiles', default='tiles.txt', help='Input tiles.txt tcl output') - parser.add_argument( - 'deltas', nargs='*', help='.bit diffs to create base addresses from') args = parser.parse_args() - deltas = args.deltas - if not args.deltas: - deltas = glob.glob('*.delta') - - run(args.tiles, args.out, deltas, verbose=args.verbose) + run(args.tiles, args.out, verbose=args.verbose) if __name__ == '__main__': diff --git a/fuzzers/005-tilegrid/generate_full.py b/fuzzers/005-tilegrid/generate_full.py index 63db7dae..af631270 100644 --- a/fuzzers/005-tilegrid/generate_full.py +++ b/fuzzers/005-tilegrid/generate_full.py @@ -1,11 +1,565 @@ #!/usr/bin/env python3 import os, sys, json, re +''' +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 +''' + +from generate import load_tiles + +# matches lib/include/prjxray/xilinx/xc7series/block_type.h +block_type_i2s = { + 0: 'CLB_IO_CLK', + 1: 'BLOCK_RAM', + 2: 'CFG_CLB', + # special...maybe should error until we know what it is? + # 3: 'RESERVED', +} -def run(json_in_fn, json_out_fn, deltas_fns, verbose=False): +def addr2btype(base_addr): + ''' + Convert integer address to block type + + Table 5-24: Frame Address Register Description + Bit Index: [25:23] + https://www.xilinx.com/support/documentation/user_guides/ug470_7Series_Config.pdf + "Valid block types are CLB, I/O, CLK ( 000 ), block RAM content ( 001 ), and CFG_CLB ( 010 ). A normal bitstream does not include type 011 ." + ''' + block_type_i = (base_addr >> 23) & 0x7 + return block_type_i2s[block_type_i] + + +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 load_baseaddrs(deltas_fns): + site_baseaddr = dict() + for arg in deltas_fns: + with open(arg) as f: + line = f.read().strip() + # clb/design_SLICE_X10Y100.delta + # site = arg[7:-6] + site = re.match(r".*/design_(.*).delta", arg).group(1) + frame = int(line[5:5 + 8], 16) + # was "0x%08x" + site_baseaddr[site] = frame & ~0x7f + + return site_baseaddr + + +def make_tile_baseaddrs(tiles, site_baseaddr, verbose=False): + # Look up a base address by tile name + tile_baseaddrs = dict() + + verbose and print('') + verbose and print('%u tiles' % len(tiles)) + verbose and print("%u baseaddrs" % len(site_baseaddr)) + added = 0 + for tile in tiles: + for site_name in tile["sites"].keys(): + if site_name not in site_baseaddr: + continue + framebaseaddr = site_baseaddr[site_name] + bt = addr2btype(framebaseaddr) + tile_baseaddr = tile_baseaddrs.setdefault(tile["name"], {}) + if bt in tile_baseaddr: + # actually lets just fail these, better to remove at tcl level to speed up processing + assert 0, 'duplicate base address' + assert tile_baseaddr[bt] == [framebaseaddr, 0] + else: + tile_baseaddr[bt] = [framebaseaddr, 0] + verbose and print( + "baseaddr: %s.%s @ %s.0x%08x" % + (tile["name"], site_name, bt, framebaseaddr)) + added += 1 + + assert added, "Failed to add any base addresses" + assert added == len(site_baseaddr) + return tile_baseaddrs + + +def make_tiles_by_grid(tiles): + # lookup tile names by (X, Y) + tiles_by_grid = dict() + + for tile in tiles: + tiles_by_grid[(tile["grid_x"], tile["grid_y"])] = tile["name"] + + return tiles_by_grid + + +def add_segment( + database, segments, name, tiles, segtype, verbose, baseaddr=None): + assert name not in segments + segment = segments.setdefault(name, {}) + segment["tiles"] = tiles + segment["type"] = segtype + if baseaddr: + verbose and print('make_segment: %s baseaddr %s' % ( + name, + baseaddr, + )) + segment["baseaddr"] = baseaddr + + for tile_name in tiles: + database[tile_name]["segment"] = name + + +def make_segments(database, tiles_by_grid, tile_baseaddrs, verbose=False): + ''' + Create segments data structure + Indicates how tiles are related to bitstream locations + Also modify database to annotate which segment the tiles belong to + + segments key examples: + SEG_CLBLM_R_X13Y72 + SEG_BRAM3_L_X6Y85 + ''' + segments = dict() + + verbose and print('') + for tile_name, tile_data in database.items(): + tile_type = tile_data["type"] + grid_x = tile_data["grid_x"] + grid_y = tile_data["grid_y"] + + def process_clb(): + if tile_type in ["CLBLL_L", "CLBLM_L"]: + int_tile_name = tiles_by_grid[(grid_x + 1, grid_y)] + else: + int_tile_name = tiles_by_grid[(grid_x - 1, grid_y)] + + add_segment( + database=database, + segments=segments, + name="SEG_" + tile_name, + tiles=[tile_name, int_tile_name], + segtype=tile_type.lower(), + baseaddr=tile_baseaddrs.get(tile_name, None), + verbose=verbose, + ) + + def process_hclk(): + add_segment( + database=database, + segments=segments, + name="SEG_" + tile_name, + tiles=[tile_name], + segtype=tile_type.lower(), + verbose=verbose, + ) + + def process_bram_dsp(): + for k in range(5): + if tile_type in ["BRAM_L", "DSP_L"]: + interface_tile_name = tiles_by_grid[( + grid_x + 1, grid_y - k)] + int_tile_name = tiles_by_grid[(grid_x + 2, grid_y - k)] + elif tile_type in ["BRAM_R", "DSP_R"]: + interface_tile_name = tiles_by_grid[( + grid_x - 1, grid_y - k)] + int_tile_name = tiles_by_grid[(grid_x - 2, grid_y - k)] + else: + assert 0 + ''' + BRAM/DSP itself is at the base y address + There is one huge switchbox on the right for the 5 tiles + These fan into 5 BRAM_INT_INTERFACE tiles each which feed into their own CENTER_INTER (just like a CLB has) + ''' + if k == 0: + tiles = [tile_name, interface_tile_name, int_tile_name] + baseaddr = tile_baseaddrs.get(tile_name, None) + else: + tiles = [interface_tile_name, int_tile_name] + baseaddr = None + + add_segment( + database=database, + segments=segments, + # BRAM_L_X6Y70 => SEG_BRAM4_L_X6Y70 + name="SEG_" + tile_name.replace("_", "%d_" % k, 1), + tiles=tiles, + # BRAM_L => bram4_l + segtype=tile_type.lower().replace("_", "%d_" % k, 1), + baseaddr=baseaddr, + verbose=verbose, + ) + + def process_default(): + #verbose and nolr(tile_type) not in ('VBRK', 'INT', 'NULL') and print('make_segment: drop %s' % (tile_type,)) + pass + + { + "CLBLL": process_clb, + "CLBLM": process_clb, + "HCLK": process_hclk, + "BRAM": process_bram_dsp, + "DSP": process_bram_dsp, + }.get(nolr(tile_type), process_default)() + + return segments + + +def get_inttile(database, segment): + '''Return interconnect tile for given segment''' + inttiles = [ + tile for tile in segment["tiles"] + if database[tile]["type"] in ["INT_L", "INT_R"] + ] + assert len(inttiles) == 1 + return inttiles[0] + + +def get_bramtile(database, segment): + inttiles = [ + tile for tile in segment["tiles"] + if database[tile]["type"] in ["BRAM_L", "BRAM_R"] + ] + assert len(inttiles) == 1 + return inttiles[0] + + +def create_segment_for_int_lr( + database, segments, tile, tiles_by_grid, verbose): + """ Creates INT_[LR] segment for interconnect's without direct connectivity. """ + # Some INT_[LR] tiles have no adjacent connectivity, create a segment. + grid_x = database[tile]["grid_x"] + grid_y = database[tile]["grid_y"] + if database[tile]["type"] == "INT_L": + grid_x -= 1 + adjacent_tile = tiles_by_grid[(grid_x, grid_y)] + elif database[tile]["type"] == "INT_R": + grid_x += 1 + adjacent_tile = tiles_by_grid[(grid_x, grid_y)] + else: + assert False, database[tile]["type"] + + if (database[adjacent_tile]['type'].startswith('INT_INTERFACE_') or + database[adjacent_tile]['type'].startswith('PCIE_INT_INTERFACE_') + or + database[adjacent_tile]['type'].startswith('GTP_INT_INTERFACE')): + # This INT_[LR] tile has no adjacent connectivity, + # create a segment. + add_segment( + database=database, + segments=segments, + name='SEG_' + tile, + tiles=[tile], + segtype=database[tile]["type"], + verbose=verbose, + ) + else: + assert False, database[adjacent_tile]['type'] + + +def seg_base_addr_lr_INT(database, segments, tiles_by_grid, verbose=False): + '''Populate segment base addresses: L/R along INT column''' + ''' + Create BRAM base addresses based on nearby CLBs + ie if we have a BRAM_L, compute as nearby CLB_R base address + offset + ''' + + verbose and print('') + for segment_name in sorted(segments.keys()): + segment = segments[segment_name] + baseaddrs = segment.get("baseaddr", None) + if not baseaddrs: + continue + + for block_type, (framebase, wordbase) in sorted(baseaddrs.items()): + verbose and print( + 'lr_INT: %s: %s.0x%08X:%u' % + (segment_name, block_type, framebase, wordbase)) + if block_type != 'CLB_IO_CLK': + verbose and print(' Skip non CLB') + continue + + inttile = get_inttile(database, segment) + grid_x = database[inttile]["grid_x"] + grid_y = database[inttile]["grid_y"] + + if database[inttile]["type"] == "INT_L": + grid_x += 1 + framebase = framebase + 0x80 + elif database[inttile]["type"] == "INT_R": + grid_x -= 1 + framebase = framebase - 0x80 + else: + assert 0 + + # ROI at edge? + if (grid_x, grid_y) not in tiles_by_grid: + verbose and print(' Skip edge') + continue + + tile = tiles_by_grid[(grid_x, grid_y)] + + if database[inttile]["type"] == "INT_L": + assert database[tile]["type"] == "INT_R" + elif database[inttile]["type"] == "INT_R": + assert database[tile]["type"] == "INT_L" + else: + assert 0 + + if 'segment' not in database[tile]: + if database[tile]['type'] in ['T_TERM_INT']: + continue + create_segment_for_int_lr( + database, segments, tile, tiles_by_grid, verbose) + + seg = database[tile]["segment"] + + seg_baseaddrs = segments[seg].setdefault("baseaddr", {}) + # At least one duplicate when we re-compute the entry for the base address + # should give the same address + if block_type in seg_baseaddrs: + assert seg_baseaddrs[block_type] == [ + framebase, wordbase + ], (seg_baseaddrs[block_type], [framebase, wordbase]) + verbose and print(' Existing OK') + else: + seg_baseaddrs[block_type] = [framebase, wordbase] + verbose and print(' Add new') + + +def seg_base_addr_up_INT(database, segments, tiles_by_grid, verbose=False): + '''Populate segment base addresses: Up along INT/HCLK columns''' + + verbose and print('') + # Copy the initial list containing only base addresses + # and soon to have derived addresses + src_segment_names = list() + for segment_name in segments.keys(): + if "baseaddr" in segments[segment_name]: + src_segment_names.append(segment_name) + + verbose and print('up_INT: %u base addresses' % len(src_segment_names)) + + for src_segment_name in sorted(src_segment_names): + src_segment = segments[src_segment_name] + + for block_type, (framebase, + wordbase) in sorted(src_segment["baseaddr"].items()): + verbose and print( + 'up_INT: %s: %s.0x%08X:%u' % + (src_segment_name, block_type, framebase, wordbase)) + + def process_CLB_IO_CLK(wordbase): + ''' + Lookup interconnect tile associated with this segment + Use it to locate in the grid, and find other segments related by tile offset + ''' + + inttile = get_inttile(database, src_segment) + verbose and print( + ' up_INT CLK_IO_CLK: %s => inttile %s' % + (src_segment_name, inttile)) + grid_x = database[inttile]["grid_x"] + grid_y = database[inttile]["grid_y"] + + for i in range(50): + grid_y -= 1 + if grid_y < 0: + break + + dst_tile = database[tiles_by_grid[(grid_x, grid_y)]] + + if wordbase == 50: + wordbase += 1 + else: + wordbase += 2 + + if 'segment' not in dst_tile: + if dst_tile['type'] in ['T_TERM_INT']: + continue + create_segment_for_int_lr( + database, segments, tiles_by_grid[(grid_x, + grid_y)], + tiles_by_grid, verbose) + + #verbose and print(' dst_tile', dst_tile) + dst_segment_name = dst_tile["segment"] + #verbose and print('up_INT: %s => %s' % (src_segment_name, dst_segment_name)) + segments[dst_segment_name].setdefault( + "baseaddr", {})[block_type] = [framebase, wordbase] + + def process_BLOCK_RAM(wordbase): + ''' + Lookup BRAM0 tile associated with this segment + Use it to locate in the grid, and find other BRAM0 related by tile offset + + + From minitest: + + build/roi_bramd_bit01.diff (lowest BRAM coordinate) + > bit_00c00000_000_00 + + build/roi_bramds_bit01.diff + > bit_00c00000_000_00 + > bit_00c00000_010_00 + > bit_00c00000_020_00 + > bit_00c00000_030_00 + > bit_00c00000_040_00 + > bit_00c00000_051_00 + > bit_00c00000_061_00 + > bit_00c00000_071_00 + > bit_00c00000_081_00 + > bit_00c00000_091_00 + ''' + src_tile_name = get_bramtile(database, src_segment) + verbose and print( + ' up_INT BLOCK_RAM: %s => %s' % + (src_segment_name, src_tile_name)) + grid_x = database[src_tile_name]["grid_x"] + grid_y = database[src_tile_name]["grid_y"] + + for i in range(9): + grid_y -= 5 + wordbase += 10 + # Skip HCLK + if i == 4: + grid_y -= 1 + wordbase += 1 + + if (grid_x, grid_y) not in tiles_by_grid: + continue + + dst_tile = database[tiles_by_grid[(grid_x, grid_y)]] + assert nolr(dst_tile['type']) == 'BRAM', dst_tile + + dst_segment_name = dst_tile["segment"] + assert 'BRAM0' in dst_segment_name + segments[dst_segment_name].setdefault( + "baseaddr", {})[block_type] = [framebase, wordbase] + + { + 'CLB_IO_CLK': process_CLB_IO_CLK, + 'BLOCK_RAM': process_BLOCK_RAM, + }[block_type]( + wordbase) + + +def add_tile_bits(tile_db, baseaddr, offset, frames, words, height=None): + ''' + Record data structure geometry for the given tile baseaddr + For most tiles there is only one baseaddr, but some like BRAM have multiple + + Notes on multiple block types: + https://github.com/SymbiFlow/prjxray/issues/145 + ''' + + bits = tile_db['bits'] + block_type = addr2btype(baseaddr) + + assert 0 <= offset <= 100, offset + assert 1 <= words <= 101 + assert offset + words <= 101, ( + tile_db, offset + words, offset, words, block_type) + + assert block_type not in bits + block = bits.setdefault(block_type, {}) + + # FDRI address + block["baseaddr"] = '0x%08X' % baseaddr + # Number of frames this entry is sretched across + # that is the following FDRI addresses are used: range(baseaddr, baseaddr + frames) + block["frames"] = frames + + # Index of first word used within each frame + block["offset"] = offset + + # related to words... + # deprecated field? Don't worry about for now + # DSP has some differences between height and words + block["words"] = words + if height is None: + height = words + block["height"] = height + + +def db_add_bits(database, segments): + '''Transfer segment data into tiles''' + for segment_name in segments.keys(): + if 'baseaddr' not in segments[segment_name]: + continue + + for block_type, (baseaddr, + offset) in segments[segment_name]["baseaddr"].items(): + for tile_name in segments[segment_name]["tiles"]: + tile_type = database[tile_name]["type"] + entry = { + # (tile_type, block_type): (frames, words, height) + ("CLBLL", "CLB_IO_CLK"): (36, 2, 2), + ("CLBLM", "CLB_IO_CLK"): (36, 2, 2), + ("HCLK", "CLB_IO_CLK"): (26, 1, 1), + ("INT", "CLB_IO_CLK"): (28, 2, 2), + ("BRAM", "CLB_IO_CLK"): (28, 10, None), + ("BRAM", "BLOCK_RAM"): (128, 10, None), + ("DSP", "CLB_IO_CLK"): (28, 2, 10), + ("INT_INTERFACE", "CLB_IO_CLK"): (28, 2, None), + ("BRAM_INT_INTERFACE", "CLB_IO_CLK"): (28, 2, None), + }.get((nolr(tile_type), block_type), None) + if entry is None: + # Other types are rare, not expected to have these + if block_type == "CLB_IO_CLK": + raise ValueError("Unknown tile type %s" % tile_type) + continue + + frames, words, height = entry + if frames: + # if we have a width, we should have a height + assert frames and words + add_tile_bits( + database[tile_name], baseaddr, offset, frames, words, + height) + + +def db_add_segments(database, segments): + # TODO: Migrate to new tilegrid format via library. This data is added for + # compability with unconverted tools. Update tools then remove this data from + # tilegrid.json. + # looks like only htmlgen is using this? + for tiledata in database.values(): + if "segment" in tiledata: + segment = tiledata["segment"] + tiledata["segment_type"] = segments[segment]["type"] + + +def run(json_in_fn, json_out_fn, tiles_fn, deltas_fns, verbose=False): + # Load input files + tiles = load_tiles(tiles_fn) + site_baseaddr = load_baseaddrs(deltas_fns) + database = json.load(open(json_in_fn, "r")) - # TODO: move address processing here + tile_baseaddrs = make_tile_baseaddrs(tiles, site_baseaddr, verbose=verbose) + tiles_by_grid = make_tiles_by_grid(tiles) + + segments = make_segments( + database, tiles_by_grid, tile_baseaddrs, verbose=verbose) + + # Reference adjacent CLBs to locate adjacent tiles by known offsets + seg_base_addr_lr_INT(database, segments, tiles_by_grid, verbose=verbose) + seg_base_addr_up_INT(database, segments, tiles_by_grid, verbose=verbose) + + db_add_bits(database, segments) + db_add_segments(database, segments) # Save json.dump( @@ -30,6 +584,8 @@ def main(): help="Input .json without addresses") parser.add_argument( "--json-out", default="tilegrid.json", help="Output JSON") + parser.add_argument( + '--tiles', default='tiles.txt', help='Input tiles.txt tcl output') parser.add_argument( "deltas", nargs="*", help=".bit diffs to create base addresses from") args = parser.parse_args() @@ -38,7 +594,7 @@ def main(): if not args.deltas: deltas = glob.glob("*.delta") - run(args.json_in, args.json_out, deltas, verbose=args.verbose) + run(args.json_in, args.json_out, args.tiles, deltas, verbose=args.verbose) if __name__ == "__main__":