diff --git a/database/artix7/settings.sh b/database/artix7/settings.sh index adf9b0be..992296aa 100644 --- a/database/artix7/settings.sh +++ b/database/artix7/settings.sh @@ -3,10 +3,11 @@ export XRAY_PART="xc7a50tfgg484-1" export XRAY_ROI_FRAMES="0x00000000:0xffffffff" # All CLB's in part, all BRAM's in part, all DSP's in part. +# tcl queries IOB => don't bother adding export XRAY_ROI_TILEGRID="SLICE_X0Y0:SLICE_X65Y99 SLICE_X0Y100:SLICE_X57Y149 RAMB18_X0Y0:RAMB18_X1Y59 RAMB36_X0Y0:RAMB36_X1Y29 RAMB18_X2Y0:RAMB18_X2Y39 RAMB36_X2Y0:RAMB36_X2Y19 DSP48_X0Y0:DSP48_X1Y59" # These settings must remain in sync -export XRAY_ROI="SLICE_X0Y100:SLICE_X35Y149 RAMB18_X0Y40:RAMB18_X0Y59 RAMB36_X0Y20:RAMB36_X0Y29 DSP48_X0Y40:DSP48_X0Y59" +export XRAY_ROI="SLICE_X0Y100:SLICE_X35Y149 RAMB18_X0Y40:RAMB18_X0Y59 RAMB36_X0Y20:RAMB36_X0Y29 DSP48_X0Y40:DSP48_X0Y59 IOB_X0Y100:IOB_X0Y149" # Most of CMT X0Y2. export XRAY_ROI_GRID_X1="9" export XRAY_ROI_GRID_X2="58" diff --git a/fuzzers/005-tilegrid/Makefile b/fuzzers/005-tilegrid/Makefile index c2801373..65420ee1 100644 --- a/fuzzers/005-tilegrid/Makefile +++ b/fuzzers/005-tilegrid/Makefile @@ -1,13 +1,32 @@ +# TODO: parallelize + +FUZDIR=$(shell pwd) +BUILD_DIR=$(FUZDIR)/build + database: build/tilegrid.json pushdb: cp build/tilegrid.json ${XRAY_DATABASE_DIR}/$(XRAY_DATABASE)/tilegrid.json -build/tilegrid.json: build/deltas generate.py - cd build && python3 ../generate.py design_*.delta > tilegrid.json +build/tiles/tiles.txt: + bash generate.sh build/tiles tiles -build/deltas: - bash generate.sh build +# TODO: only generate tiles +build/tilegrid_basic.json: generate.py build/tiles/tiles.txt + 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 + +build/bram/deltas: + bash generate.sh build/bram bram + +build/iob/deltas: + bash generate.sh build/iob iob + +# TODO: only generate addresses +build/tilegrid.json: generate_full.py build/tilegrid_basic.json build/clb/deltas build/bram/deltas build/iob/deltas + 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 bfddd12e..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,19 +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() - site = arg[7:-6] - 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 @@ -106,501 +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)) - 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 - 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( @@ -622,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.sh b/fuzzers/005-tilegrid/generate.sh index a1674bc3..0064e89f 100644 --- a/fuzzers/005-tilegrid/generate.sh +++ b/fuzzers/005-tilegrid/generate.sh @@ -1,16 +1,21 @@ #!/bin/bash -x +PRJ=$2 + +export FUZDIR=$PWD source ${XRAY_GENHEADER} -vivado -mode batch -source ../generate.tcl +vivado -mode batch -source $FUZDIR/generate_$PRJ.tcl +test -z "$(fgrep CRITICAL vivado.log)" -for x in design*.bit; do - ${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o ${x}s -z -y $x -done - -for x in design_*.bits; do - diff -u design.bits $x | grep '^[-+]bit' > ${x%.bits}.delta -done -touch deltas +if [ $PRJ != "tiles" ] ; then + for x in design*.bit; do + ${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o ${x}s -z -y $x + done + for x in design_*.bits; do + diff -u design.bits $x | grep '^[-+]bit' > ${x%.bits}.delta + done + touch deltas +fi diff --git a/fuzzers/005-tilegrid/generate.tcl b/fuzzers/005-tilegrid/generate.tcl deleted file mode 100644 index 7677b14a..00000000 --- a/fuzzers/005-tilegrid/generate.tcl +++ /dev/null @@ -1,209 +0,0 @@ -proc make_project {} { - create_project -force -part $::env(XRAY_PART) design design - - read_verilog ../top.v - synth_design -top top - - set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk] - set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports di] - set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports do] - set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports stb] - - create_pblock roi - add_cells_to_pblock [get_pblocks roi] [get_cells roi] - foreach roi "$::env(XRAY_ROI_TILEGRID)" { - puts "ROI: $roi" - resize_pblock [get_pblocks roi] -add "$roi" - } - - set_property CFGBVS VCCO [current_design] - set_property CONFIG_VOLTAGE 3.3 [current_design] - set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design] - set_param tcl.collectionResultDisplayLimit 0 - - set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF] -} - -proc loc_luts {} { - set luts [get_bels -of_objects [get_sites -of_objects [get_pblocks roi]] -filter {TYPE =~ LUT*} */A6LUT] - set selected_luts {} - set lut_index 0 - - # LOC one LUT (a "selected_lut") into each CLB segment configuration column (ie 50 per CMT column) - set lut_columns "" - foreach lut $luts { - regexp "SLICE_X([0-9]+)Y([0-9]+)/" $lut match slice_x slice_y - - # Only even SLICEs should be used as column bases. - if { $slice_x % 2 != 0 } { - continue - } - - # 50 per column => 50, 100, 150, etc - # ex: SLICE_X2Y50/A6LUT - # Only take one of the CLBs within a slice - set y_column [expr ($slice_y / 50) * 50] - dict append lut_columns "X${slice_x}Y${y_column}" "$lut " - } - - # Pick the smallest Y in each column. - dict for {column luts_in_column} $lut_columns { - set min_slice_y 9999999 - - foreach lut $luts_in_column { - regexp "SLICE_X([0-9]+)Y([0-9]+)/" $lut slice_x slice_y - - if { $slice_y < $min_slice_y } { - set selected_lut $lut - } - } - - set cell [get_cells roi/luts[$lut_index].lut] - set lut_site [get_sites -of_objects [get_bels $selected_lut]] - puts "LOCing $selected_lut to $lut_site" - set_property LOC $lut_site $cell - set lut_index [expr $lut_index + 1] - lappend selected_luts [get_bels $selected_lut] - } - return $selected_luts -} - -# Return a list of sites containing BRAMs -# sites are better than bels because site type may change and invalidate the bel -proc loc_brams {} { - # BRAM have multiple mutually exclusive sites - # They can be cycled by setting the site type - # Ex: - # - RAMB36_X0Y10/RAMBFIFO36E1 - # - RAMB36_X0Y10/RAMB36E1 - # Default is RAMBFIFO36E1? - # Work primarily on sites, not bels, - # to avoid issues when switching site type during PnR - set bram_sites [get_sites -of_objects [get_pblocks roi] -filter {SITE_TYPE =~ RAMBFIFO36E1*}] - set bram_bels [get_bels -of_objects $bram_sites] - - set selected_bram_sites {} - set bram_index 0 - - # LOC one BRAM (a "selected_lut") into each BRAM segment configuration column (ie 10 per CMT column) - set bram_columns "" - foreach bram $bram_bels { - regexp "RAMB36_X([0-9]+)Y([0-9]+)/" $bram match slice_x slice_y - - # 10 per column => 10, 20, ,etc - # ex: RAMB36_X0Y10/RAMBFIFO36E1 - set y_column [expr ($slice_y / 10) * 10] - dict append bram_columns "X${slice_x}Y${y_column}" "$bram " - } - - # Pick the smallest Y in each column. - dict for {column brams_in_column} $bram_columns { - set min_slice_y 9999999 - - foreach bram $brams_in_column { - regexp "RAMB36_X([0-9]+)Y([0-9]+)/" $bram match slice_x slice_y - - if { $slice_y < $min_slice_y } { - set selected_bram_bel $bram - } - } - - set selected_bram_bel [get_bels $selected_bram_bel] - - set bram_site [get_sites -of_objects $selected_bram_bel] - puts "LOCing BEL: $selected_bram_bel to $bram_site" - set cell [get_cells roi/brams[$bram_index].bram] - set_property LOC $bram_site $cell - if {"$bram_site" == ""} {error "Bad site $bram_site from bel $selected_bram_bel"} - - set bram_index [expr $bram_index + 1] - # Output site, not bel, to avoid reference issues after PnR - lappend selected_bram_sites $bram_site - } - - return $selected_bram_sites -} - -proc write_tiles_txt {} { - # Get all tiles, ie not just the selected LUTs - set tiles [get_tiles] - - # Write tiles.txt with site metadata - set fp [open "tiles.txt" w] - foreach tile $tiles { - set type [get_property TYPE $tile] - set grid_x [get_property GRID_POINT_X $tile] - set grid_y [get_property GRID_POINT_Y $tile] - set sites [get_sites -quiet -of_objects $tile] - set typed_sites {} - - if [llength $sites] { - set site_types [get_property SITE_TYPE $sites] - foreach t $site_types s $sites { - lappend typed_sites $t $s - } - } - - puts $fp "$type $tile $grid_x $grid_y $typed_sites" - } - close $fp -} - -proc write_clbs { selected_luts } { - puts "write_brams: [llength $selected_luts] LUTs" - puts "" - # Toggle one bit in each selected LUT to generate base addresses - for {set i 0} {$i < [llength $selected_luts]} {incr i} { - puts "" - set cell [get_cells roi/luts[$i].lut] - puts "LUT $cell" - set orig_init [get_property INIT $cell] - # Flip a bit by changing MSB 0 => 1 - set new_init [regsub "h8" $orig_init "h0"] - puts "INIT $orig_init => $new_init" - set_property INIT $new_init $cell - set site [get_sites -of_objects [lindex $selected_luts $i]] - write_bitstream -force design_$site.bit - set_property INIT $orig_init $cell - } -} - -proc write_brams { selected_brams_sites } { - puts "write_brams: [llength $selected_brams_sites] BRAMs" - puts "" - # Toggle one bit in each selected BRAM to generate base addresses - for {set i 0} {$i < [llength $selected_brams_sites]} {incr i} { - puts "" - set cell [get_cells roi/brams[$i].bram] - puts "BRAM $cell" - set orig_init [get_property INIT_00 $cell] - # Flip a bit by changing MSB 0 => 1 - set new_init [regsub "h8" $orig_init "h0"] - puts "INIT_00 $orig_init => $new_init" - set_property INIT_00 $new_init $cell - set site [lindex $selected_brams_sites $i] - if {"$site" == ""} {error "Bad site $site"} - write_bitstream -force design_$site.bit - set_property INIT_00 $orig_init $cell - } -} - -proc run {} { - make_project - set selected_luts [loc_luts] - puts "Selected LUTs: [llength $selected_luts]" - set selected_brams_sites [loc_brams] - puts "Selected BRAMs: [llength $selected_brams_sites]" - - place_design - route_design - write_checkpoint -force design.dcp - write_bitstream -force design.bit - - write_tiles_txt - write_clbs $selected_luts - write_brams $selected_brams_sites -} - -run - diff --git a/fuzzers/005-tilegrid/generate_bram.tcl b/fuzzers/005-tilegrid/generate_bram.tcl new file mode 100644 index 00000000..ba98e649 --- /dev/null +++ b/fuzzers/005-tilegrid/generate_bram.tcl @@ -0,0 +1,56 @@ +source "$::env(FUZDIR)/util.tcl" + +# Return a list of sites containing BRAMs +# sites are better than bels because site type may change and invalidate the bel +proc loc_brams {} { + # BRAM have multiple mutually exclusive sites + # They can be cycled by setting the site type + # Ex: + # - RAMB36_X0Y10/RAMBFIFO36E1 + # - RAMB36_X0Y10/RAMB36E1 + # Default is RAMBFIFO36E1? + # Work primarily on sites, not bels, + # to avoid issues when switching site type during PnR + set bram_sites [get_sites -of_objects [get_pblocks roi] -filter {SITE_TYPE =~ RAMBFIFO36E1*}] + set bram_bels [get_bels -of_objects $bram_sites] + + set bram_columns [group_dut_cols $bram_bels 10] + # Output site, not bel, to avoid reference issues after PnR + return [loc_dut_col_sites $bram_columns {roi/brams[} {].bram}] +} + +proc write_brams { selected_brams_sites } { + puts "write_brams: [llength $selected_brams_sites] BRAMs" + puts "" + # Toggle one bit in each selected BRAM to generate base addresses + for {set i 0} {$i < [llength $selected_brams_sites]} {incr i} { + puts "" + set cell [get_cells roi/brams[$i].bram] + puts "BRAM $cell" + set orig_init [get_property INIT_00 $cell] + # Flip a bit by changing MSB 0 => 1 + set new_init [regsub "h8" $orig_init "h0"] + puts "INIT_00 $orig_init => $new_init" + set_property INIT_00 $new_init $cell + set site [lindex $selected_brams_sites $i] + if {"$site" == ""} {error "Bad site $site"} + write_bitstream -force design_$site.bit + set_property INIT_00 $orig_init $cell + } +} + +proc run {} { + make_project + set selected_brams_sites [loc_brams] + puts "Selected BRAMs: [llength $selected_brams_sites]" + + place_design + route_design + write_checkpoint -force design.dcp + write_bitstream -force design.bit + + write_brams $selected_brams_sites +} + +run + diff --git a/fuzzers/005-tilegrid/generate_clb.tcl b/fuzzers/005-tilegrid/generate_clb.tcl new file mode 100644 index 00000000..7ca94e33 --- /dev/null +++ b/fuzzers/005-tilegrid/generate_clb.tcl @@ -0,0 +1,62 @@ +source "$::env(FUZDIR)/util.tcl" + +proc group_lut_cols { lut_bels } { + # LOC one LUT (a "selected_lut") into each CLB segment configuration column (ie 50 per CMT column) + set lut_columns "" + foreach lut $lut_bels { + regexp "SLICE_X([0-9]+)Y([0-9]+)/" $lut match slice_x slice_y + + # Only even SLICEs should be used as column bases. + if { $slice_x % 2 != 0 } { + continue + } + + # 50 per column => 0, 50, 100, 150, etc + # ex: SLICE_X2Y50/A6LUT + # Only take one of the CLBs within a slice + set y_column [expr ($slice_y / 50) * 50] + dict append lut_columns "X${slice_x}Y${y_column}" "$lut " + } + return $lut_columns +} + +proc loc_luts {} { + set lut_bels [get_bels -of_objects [get_sites -of_objects [get_pblocks roi]] -filter {TYPE =~ LUT*} */A6LUT] + set lut_columns [group_lut_cols $lut_bels] + return [loc_dut_col_bels $lut_columns {roi/luts[} {].lut}] +} + +proc write_clbs { selected_luts } { + puts "write_brams: [llength $selected_luts] LUTs" + puts "" + # Toggle one bit in each selected LUT to generate base addresses + for {set i 0} {$i < [llength $selected_luts]} {incr i} { + puts "" + set cell [get_cells roi/luts[$i].lut] + puts "LUT $cell" + set orig_init [get_property INIT $cell] + # Flip a bit by changing MSB 0 => 1 + set new_init [regsub "h8" $orig_init "h0"] + puts "INIT $orig_init => $new_init" + set_property INIT $new_init $cell + set site [get_sites -of_objects [lindex $selected_luts $i]] + write_bitstream -force design_$site.bit + set_property INIT $orig_init $cell + } +} + +proc run {} { + make_project + set selected_luts [loc_luts] + puts "Selected LUTs: [llength $selected_luts]" + + place_design + route_design + write_checkpoint -force design.dcp + write_bitstream -force design.bit + + write_clbs $selected_luts +} + +run + diff --git a/fuzzers/005-tilegrid/generate_full.py b/fuzzers/005-tilegrid/generate_full.py new file mode 100644 index 00000000..1f34d7dc --- /dev/null +++ b/fuzzers/005-tilegrid/generate_full.py @@ -0,0 +1,668 @@ +#!/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 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) + # +bit_00400026_100_02 + _, frame_str, _, _ = line.split('_') + frame = int(frame_str, 16) + 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 add_segment(name, tiles, segtype, 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 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( + name="SEG_" + tile_name, + tiles=[tile_name, int_tile_name], + segtype=tile_type.lower(), + baseaddr=tile_baseaddrs.get(tile_name, None)) + + def process_hclk(): + add_segment( + name="SEG_" + tile_name, + tiles=[tile_name], + segtype=tile_type.lower()) + + def process_iob(): + tiles = [tile_name] + # FIXME mcmaster: removing INT for now + # Geometry doesn't line up with CLB, unclear if they are included + if tile_type.startswith('LIOB'): + # Two INT_L's + # tiles.append(tiles_by_grid[(grid_x + 4, grid_y)]) + # tiles.append(tiles_by_grid[(grid_x + 4, grid_y - 1)]) + # One IO interface tile + tiles.append(tiles_by_grid[(grid_x + 1, grid_y)]) + else: + # Two INT_R's + # tiles.append(tiles_by_grid[(grid_x - 4, grid_y)]) + # tiles.append(tiles_by_grid[(grid_x - 4, grid_y - 1)]) + # One IO interface tile + tiles.append(tiles_by_grid[(grid_x - 1, grid_y)]) + + add_segment( + name="SEG_" + tile_name, + tiles=tiles, + segtype=tile_type.lower(), + baseaddr=tile_baseaddrs.get(tile_name, None)) + + def process_iob_sing(): + # FIXME mcmaster: removing INT for now + # Geometry doesn't line up with CLB, unclear if they are included + tiles = [tile_name] + if tile_type.startswith('LIOB'): + tiles.append(tiles_by_grid[(grid_x + 1, grid_y)]) + # tiles.append(tiles_by_grid[(grid_x + 4, grid_y)]) + else: + tiles.append(tiles_by_grid[(grid_x - 1, grid_y)]) + # tiles.append(tiles_by_grid[(grid_x - 4, grid_y)]) + + add_segment( + name="SEG_" + tile_name, + tiles=tiles, + segtype=tile_type.lower(), + baseaddr=tile_baseaddrs.get(tile_name, None)) + + 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( + # 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) + + 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, + "RIOB33": process_iob, + "LIOB33": process_iob, + "RIOB33_SING": process_iob_sing, + "LIOB33_SING": process_iob_sing, + }.get(nolr(tile_type), process_default)() + + return segments + + +def get_inttile(database, segment): + '''Return interconnect tile for given segment''' + return ( + tile for tile in segment["tiles"] + if database[tile]["type"] in ["INT_L", "INT_R"]) + + +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 + + for inttile in 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]: + continue + + 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 + ''' + + for inttile in get_inttile(database, src_segment): + verbose and print( + ' up_INT CLK_IO_CLK: %s => inttile %s' % + (src_segment_name, inttile), + file=sys.stderr) + grid_x = database[inttile]["grid_x"] + grid_y = database[inttile]["grid_y"] + + for i in range(50): + grid_y -= 1 + loc = (grid_x, grid_y) + + if loc not in tiles_by_grid: + continue + + dst_tile = database[tiles_by_grid[loc]] + + if 'segment' not in dst_tile: + continue + #assert 'segment' in dst_tile, ((grid_x, grid_y), dst_tile, tiles_by_grid[(grid_x, grid_y)]) + + if wordbase == 50: + wordbase += 1 + else: + wordbase += 2 + + #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 + + # FIXME: PCIE block cuts out some BRAM + # this messes up algorithm as is and may cause this to fail + dst_tile_name = tiles_by_grid[(grid_x, grid_y)] + + dst_tile = database[dst_tile_name] + assert nolr(dst_tile['type']) == 'BRAM', dst_tile + + dst_segment_name = dst_tile["segment"] + verbose and print( + ' up_INT BLOCK_RAM: %s => %s' % + (dst_segment_name, dst_tile_name)) + 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), + + # IOB + # design_IOB_X0Y100.delta:+bit_00020027_000_29 + # design_IOB_X0Y104.delta:+bit_00020027_008_29 + # design_IOB_X0Y112.delta:+bit_00020027_024_29 + # design_IOB_X0Y120.delta:+bit_00020027_040_29 + # design_IOB_X0Y128.delta:+bit_00020027_057_29 + # design_IOB_X0Y136.delta:+bit_00020027_073_29 + # design_IOB_X0Y144.delta:+bit_00020027_089_29 + # $XRAY_BLOCKWIDTH design_IOB_X0Y100.bit |grep 00020000 + # 0x00020000: 0x2A (42) + ("RIOI3", "CLB_IO_CLK"): (42, 2, 2), + ("LIOI3", "CLB_IO_CLK"): (42, 2, 2), + ("RIOI3_SING", "CLB_IO_CLK"): (42, 2, 2), + ("LIOI3_SING", "CLB_IO_CLK"): (42, 2, 2), + ("RIOI3_TBYTESRC", "CLB_IO_CLK"): (42, 2, 2), + ("LIOI3_TBYTESRC", "CLB_IO_CLK"): (42, 2, 2), + ("RIOI3_TBYTETERM", "CLB_IO_CLK"): (42, 2, 2), + ("LIOI3_TBYTETERM", "CLB_IO_CLK"): (42, 2, 2), + ("LIOB33", "CLB_IO_CLK"): (42, 2, 2), + ("RIOB33", "CLB_IO_CLK"): (42, 2, 2), + ("LIOB33", "CLB_IO_CLK"): (42, 2, 2), + ("RIOB33_SING", "CLB_IO_CLK"): (42, 2, 2), + ("LIOB33_SING", "CLB_IO_CLK"): (42, 2, 2), + }.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")) + + 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( + database, + open(json_out_fn, "w"), + sort_keys=True, + indent=4, + separators=(",", ": ")) + + +def main(): + import argparse + import glob + + 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") + 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.json_in, args.json_out, args.tiles, deltas, verbose=args.verbose) + + +if __name__ == "__main__": + main() diff --git a/fuzzers/005-tilegrid/generate_iob.tcl b/fuzzers/005-tilegrid/generate_iob.tcl new file mode 100644 index 00000000..00fbae4d --- /dev/null +++ b/fuzzers/005-tilegrid/generate_iob.tcl @@ -0,0 +1,43 @@ +source "$::env(FUZDIR)/util.tcl" + +proc loc_iob {} { + # Some pads are output only (ex: OPAD_X0Y0/PAD) => filt IOB_* + # XXX: GTX bank missing, deal with that later + set roi_sites [get_sites -of_objects [get_pblocks roi]] + set duts [get_bels -of_objects $roi_sites -filter {TYPE =~ PAD && NAME =~ IOB_*}] + + # Sort them into CMT columns + set dut_columns [group_dut_cols $duts 50] + # Assign one from each + return [loc_dut_col_sites $dut_columns {di_bufs[} {].ibuf} ] +} + +proc write_iob { sel_iob_sites } { + foreach site $sel_iob_sites { + puts "" + set port [get_ports -of_objects $site] + set tile [get_tiles -of_objects $site] + set pin [get_property PACKAGE_PIN $port] + puts "IOB $port $site $tile $pin" + set orig_init [get_property PULLTYPE $port] + set_property PULLTYPE PULLUP $port + write_bitstream -force design_$site.bit + set_property PULLTYPE "$orig_init" $port + } +} + +proc run {} { + make_project + set sel_iob_sites [loc_iob] + puts "Selected IOBs: [llength $sel_iob_sites]" + + place_design + route_design + write_checkpoint -force design.dcp + write_bitstream -force design.bit + + write_iob $sel_iob_sites +} + +run + diff --git a/fuzzers/005-tilegrid/generate_tiles.tcl b/fuzzers/005-tilegrid/generate_tiles.tcl new file mode 100644 index 00000000..3c8c16b6 --- /dev/null +++ b/fuzzers/005-tilegrid/generate_tiles.tcl @@ -0,0 +1,41 @@ +source "$::env(FUZDIR)/util.tcl" + +proc write_tiles_txt {} { + # Get all tiles, ie not just the selected LUTs + set tiles [get_tiles] + + # Write tiles.txt with site metadata + set fp [open "tiles.txt" w] + foreach tile $tiles { + set type [get_property TYPE $tile] + set grid_x [get_property GRID_POINT_X $tile] + set grid_y [get_property GRID_POINT_Y $tile] + set sites [get_sites -quiet -of_objects $tile] + set typed_sites {} + + if [llength $sites] { + set site_types [get_property SITE_TYPE $sites] + foreach t $site_types s $sites { + lappend typed_sites $t $s + } + } + + puts $fp "$type $tile $grid_x $grid_y $typed_sites" + } + close $fp +} + +proc run {} { + # Generate grid of entire part + make_project_roi XRAY_ROI_TILEGRID + + place_design + route_design + write_checkpoint -force design.dcp + write_bitstream -force design.bit + + write_tiles_txt +} + +run + diff --git a/fuzzers/005-tilegrid/top.v b/fuzzers/005-tilegrid/top.v index 55edc442..3f9bb6ce 100644 --- a/fuzzers/005-tilegrid/top.v +++ b/fuzzers/005-tilegrid/top.v @@ -1,10 +1,27 @@ //Need at least one LUT per frame base address we want +`ifndef N_LUT `define N_LUT 100 -`define N_BRAM 8 +`endif -module top(input clk, stb, di, output do); - localparam integer DIN_N = 8; - localparam integer DOUT_N = `N_LUT + `N_BRAM; +`ifndef N_BRAM +`define N_BRAM 8 +`endif + +`ifndef N_DI +`define N_DI 1 +`endif + +module top(input clk, stb, [DIN_N-1:0] di, output do); + parameter integer DIN_N = `N_DI; + parameter integer DOUT_N = `N_LUT + `N_BRAM; + + wire [DIN_N-1:0] di_buf; + genvar i; + generate + for (i = 0; i < `N_LUT; i = i+1) begin:di_bufs + IBUF ibuf(.I(di[i]), .O(di_buf[i])); + end + endgenerate reg [DIN_N-1:0] din; wire [DOUT_N-1:0] dout; @@ -13,7 +30,7 @@ module top(input clk, stb, di, output do); reg [DOUT_N-1:0] dout_shr; always @(posedge clk) begin - din_shr <= {din_shr, di}; + din_shr <= {din_shr, di_buf}; dout_shr <= {dout_shr, din_shr[DIN_N-1]}; if (stb) begin din <= din_shr; @@ -25,7 +42,7 @@ module top(input clk, stb, di, output do); roi roi ( .clk(clk), - .din(din), + .din(din[7:0]), .dout(dout) ); endmodule diff --git a/fuzzers/005-tilegrid/util.tcl b/fuzzers/005-tilegrid/util.tcl new file mode 100644 index 00000000..0d9cf4fc --- /dev/null +++ b/fuzzers/005-tilegrid/util.tcl @@ -0,0 +1,170 @@ +proc min_ysite { duts_in_column } { + # Given a list of sites, return the one with the lowest Y coordinate + + set min_dut_y 9999999 + + foreach dut $duts_in_column { + # Ex: SLICE_X2Y50/A6LUT + # Ex: IOB_X1Y50 + regexp ".*_X([0-9]+)Y([0-9]+)" $dut match dut_x dut_y + + if { $dut_y < $min_dut_y } { + set selected_dut $dut + set min_dut_y $dut_y + } + } + return $selected_dut +} + +proc group_dut_cols { duts ypitch } { + # Group a list of sites into pitch sized buckets + # Ex: IOBs occur 75 to a CMT column + # Set pitch to 75 to get 0-74 in one bucket, 75-149 in a second, etc + # X0Y0 {IOB_X0Y49 IOB_X0Y48 IOB_X0Y47 ... } + # Anything with a different x is automatically in a different bucket + + # LOC one LUT (a "selected_lut") into each CLB segment configuration column (ie 50 per CMT column) + set dut_columns "" + foreach dut $duts { + # Ex: SLICE_X2Y50/A6LUT + # Ex: IOB_X1Y50 + regexp ".*_X([0-9]+)Y([0-9]+)" $dut match dut_x dut_y + + # 75 per column => 0, 75, 150, etc + set y_column [expr ($dut_y / $ypitch) * $ypitch] + dict append dut_columns "X${dut_x}Y${y_column}" "$dut " + } + return $dut_columns +} + +proc loc_dut_col_bels { dut_columns cellpre cellpost } { + # set cellpre di + + # Pick the smallest Y in each column and LOC a cell to it + # cells must be named like $cellpre[$dut_index] + # Return the selected sites + + set ret_bels {} + set dut_index 0 + + dict for {column duts_in_column} $dut_columns { + set sel_bel_str [min_ysite $duts_in_column] + set sel_bel [get_bels $sel_bel_str] + if {"$sel_bel" == ""} {error "Bad bel $sel_bel from bel str $sel_bel_str"} + set sel_site [get_sites -of_objects $sel_bel] + if {"$sel_site" == ""} {error "Bad site $sel_site from bel $sel_bel"} + + set cell [get_cells $cellpre$dut_index$cellpost] + puts "LOCing cell $cell to site $sel_site (from bel $sel_bel)" + set_property LOC $sel_site $cell + + set dut_index [expr $dut_index + 1] + lappend ret_bels $sel_bel + } + + return $ret_bels +} + +proc loc_dut_col_sites { dut_columns cellpre cellpost } { + set bels [loc_dut_col_bels $dut_columns $cellpre $cellpost] + set sites [get_sites -of_objects $bels] + return $sites +} + +proc make_io_pad_sites {} { + # get all possible IOB pins + foreach pad [get_package_pins -filter "IS_GENERAL_PURPOSE == 1"] { + set site [get_sites -of_objects $pad] + if {[llength $site] == 0} { + continue + } + if [string match IOB33* [get_property SITE_TYPE $site]] { + dict append io_pad_sites $site $pad + } + } + return $io_pad_sites +} + +proc make_iob_pads {} { + set io_pad_sites [make_io_pad_sites] + + set iopad "" + dict for {key value} $io_pad_sites { + # Some sites have more than one pad? + lappend iopad [lindex $value 0] + } + return $iopad +} + +proc make_iob_sites {} { + set io_pad_sites [make_io_pad_sites] + + set sites "" + dict for {key value} $io_pad_sites { + lappend sites $key + } + return $sites +} + +proc assign_iobs_old {} { + set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk] + set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports di] + set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports do] + set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports stb] +} + +proc assign_iobs {} { + # Set all I/Os on the bus to valid values somewhere on the chip + # The iob fuzzer sets these to more specific values + + # All possible IOs + set iopad [make_iob_pads] + # Basic pins + # XXX: not all pads are valid, but seems to be working for now + # Maybe better to set to XRAY_PIN_* and take out of the list? + set_property -dict "PACKAGE_PIN [lindex $iopad 0] IOSTANDARD LVCMOS33" [get_ports clk] + set_property -dict "PACKAGE_PIN [lindex $iopad 1] IOSTANDARD LVCMOS33" [get_ports do] + set_property -dict "PACKAGE_PIN [lindex $iopad 2] IOSTANDARD LVCMOS33" [get_ports stb] + + # din bus + set fixed_pins 3 + set iports [get_ports di*] + for {set i 0} {$i < [llength $iports]} {incr i} { + set pad [lindex $iopad [expr $i+$fixed_pins]] + set port [lindex $iports $i] + set_property -dict "PACKAGE_PIN $pad IOSTANDARD LVCMOS33" $port + } +} + +proc make_project {} { + # Generate .bit only over ROI + make_project_roi XRAY_ROI +} + +proc make_project_roi { roi_var } { + # 6 CMTs in our reference part + # What is the largest? + set n_di 16 + + create_project -force -part $::env(XRAY_PART) design design + + read_verilog "$::env(FUZDIR)/top.v" + synth_design -top top -verilog_define N_DI=$n_di + + assign_iobs + + create_pblock roi + add_cells_to_pblock [get_pblocks roi] [get_cells roi] + foreach roi "$::env($roi_var)" { + puts "ROI: $roi" + resize_pblock [get_pblocks roi] -add "$roi" + } + + set_property CFGBVS VCCO [current_design] + set_property CONFIG_VOLTAGE 3.3 [current_design] + set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design] + set_param tcl.collectionResultDisplayLimit 0 + + set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF] +} + diff --git a/minitests/iob/.gitignore b/minitests/iob/.gitignore new file mode 100644 index 00000000..378eac25 --- /dev/null +++ b/minitests/iob/.gitignore @@ -0,0 +1 @@ +build diff --git a/minitests/iob/DRIVE.tcl b/minitests/iob/DRIVE.tcl new file mode 100644 index 00000000..171315b2 --- /dev/null +++ b/minitests/iob/DRIVE.tcl @@ -0,0 +1,9 @@ +source "$::env(SRC_DIR)/template.tcl" + +# set vals "0 4 8 12 16 24" +# ERROR: [Common 17-69] Command failed: Illegal DRIVE_STRENGTH value '0' for standard 'LVCMOS33'. +# Legal values: 4, 8, 12, 16 +set prop DRIVE +set port [get_ports do] +source "$::env(SRC_DIR)/sweep.tcl" + diff --git a/minitests/iob/IOSTANDARD.tcl b/minitests/iob/IOSTANDARD.tcl new file mode 100644 index 00000000..09871672 --- /dev/null +++ b/minitests/iob/IOSTANDARD.tcl @@ -0,0 +1,6 @@ +source "$::env(SRC_DIR)/template.tcl" + +set prop IOSTANDARD +set port [get_ports do] +source "$::env(SRC_DIR)/sweep.tcl" + diff --git a/minitests/iob/Makefile b/minitests/iob/Makefile new file mode 100644 index 00000000..33a80839 --- /dev/null +++ b/minitests/iob/Makefile @@ -0,0 +1,37 @@ +all: build/env build/roi_roi_io.diff \ + build/PULLTYPE/run.ok \ + build/SLEW/run.ok \ + build/DRIVE/run.ok \ + build/IOSTANDARD/run.ok \ + + +clean: + rm -rf build + +# hard coded LOCs in .v +build/env: + test "$(XRAY_PART)" = "xc7a50tfgg484-1" + +# Didn't work +build/roi_io.diff: + $(MAKE) -f diff.mk OUT_DIFF=build/roi_io.diff PRJL=roi_io_a PRJR=roi_io_b + +# Didn't work +build/roi_prop.diff: + $(MAKE) -f diff.mk OUT_DIFF=build/roi_prop.diff PRJL=roi_prop_a PRJR=roi_prop_b + +build/PULLTYPE/run.ok: + PROJECT=PULLTYPE bash runme_tcl.sh + diff build/PULLTYPE/design_{PULLDOWN,KEEPER}.bits || true + diff build/PULLTYPE/design_{PULLDOWN,PULLUP}.bits || true + diff build/PULLTYPE/design_{PULLDOWN,NONE}.bits || true + +build/SLEW/run.ok: + PROJECT=SLEW bash runme_tcl.sh + +build/DRIVE/run.ok: + PROJECT=DRIVE bash runme_tcl.sh + +build/IOSTANDARD/run.ok: + PROJECT=IOSTANDARD bash runme_tcl.sh + diff --git a/minitests/iob/PULLTYPE.tcl b/minitests/iob/PULLTYPE.tcl new file mode 100644 index 00000000..c227fe1e --- /dev/null +++ b/minitests/iob/PULLTYPE.tcl @@ -0,0 +1,15 @@ +source "$::env(SRC_DIR)/template.tcl" + +set port [get_ports di] + +set_property PULLTYPE "" $port +write_checkpoint -force design_NONE.dcp +write_bitstream -force design_NONE.bit + +set vals "KEEPER PULLUP PULLDOWN" +foreach {val} $vals { + set_property PULLTYPE $val $port + write_checkpoint -force design_$val.dcp + write_bitstream -force design_$val.bit +} + diff --git a/minitests/iob/README.md b/minitests/iob/README.md new file mode 100644 index 00000000..736770a2 --- /dev/null +++ b/minitests/iob/README.md @@ -0,0 +1,39 @@ +# PULLTYPE + +PULLTYPE 28 29 30 +NONE X +KEEPER X X +PULLDOWN +PULLUP X X + + +# DRIVE + +DRIVE A00 A02 A08 A10 B09 B01 +0 FIXME +4 X X X +8 X X X +12 X X X +16 X X X +24 FIXME + + +# IOSTANDARD + +Effects bits, TBD exactly how +Sample output: + +diff LVCMOS33.bits LVTTL.bits +< bit_00020026_006_00 +> bit_00020026_006_08 + +diff LVCMOS33.bits PCI33_3.bits +< bit_00020026_006_02 +< bit_00020026_006_18 +< bit_00020026_006_22 +> bit_00020026_006_10 +> bit_00020026_006_16 +> bit_00020026_006_20 +> bit_00020027_006_11 +> bit_00020027_006_15 + diff --git a/minitests/iob/SLEW.tcl b/minitests/iob/SLEW.tcl new file mode 100644 index 00000000..ac97d92c --- /dev/null +++ b/minitests/iob/SLEW.tcl @@ -0,0 +1,8 @@ +source "$::env(SRC_DIR)/template.tcl" + +# set vals "SLOW MEDIUM FAST" +# ERROR: [Common 17-69] Command failed: Slew type 'MEDIUM' is not supported by I/O standard 'LVCMOS33' +set prop SLEW +set port [get_ports do] +source "$::env(SRC_DIR)/sweep.tcl" + diff --git a/minitests/iob/diff.mk b/minitests/iob/diff.mk new file mode 100644 index 00000000..b2c2ff60 --- /dev/null +++ b/minitests/iob/diff.mk @@ -0,0 +1,11 @@ +all: $(OUT_DIFF) + +$(OUT_DIFF): build/$(PRJL)/design.bits build/$(PRJR)/design.bits + diff build/$(PRJL)/design.bits build/$(PRJR)/design.bits >$(OUT_DIFF) || true + +build/$(PRJL)/design.bits: + PROJECT=$(PRJL) bash runme.sh + +build/$(PRJR)/design.bits: + PROJECT=$(PRJR) bash runme.sh + diff --git a/minitests/iob/diff_tcl.mk b/minitests/iob/diff_tcl.mk new file mode 100644 index 00000000..5c40cf3a --- /dev/null +++ b/minitests/iob/diff_tcl.mk @@ -0,0 +1,11 @@ +all: $(OUT_DIFF) + +$(OUT_DIFF): build/$(PRJL)/design.bits build/$(PRJR)/design.bits + diff build/$(PRJL)/design.bits build/$(PRJR)/design.bits >$(OUT_DIFF) || true + +build/$(PRJL)/design.bits: + PROJECT=$(PRJL) bash runme_tcl.sh + +build/$(PRJR)/design.bits: + PROJECT=$(PRJR) bash runme_tcl.sh + diff --git a/minitests/iob/runme.sh b/minitests/iob/runme.sh new file mode 100644 index 00000000..9390d5ff --- /dev/null +++ b/minitests/iob/runme.sh @@ -0,0 +1,18 @@ +#!/bin/bash + +set -ex + +: "${PROJECT:?Need to set PROJECT non-empty}" + +# Create build dir +export SRC_DIR=$PWD +BUILD_DIR=build/$PROJECT +mkdir -p $BUILD_DIR +cd $BUILD_DIR + +export TOP_V=$SRC_DIR/top.v + +vivado -mode batch -source $SRC_DIR/runme.tcl +${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o design.bits -z -y design.bit +test -z "$(fgrep CRITICAL vivado.log)" + diff --git a/minitests/iob/runme.tcl b/minitests/iob/runme.tcl new file mode 100644 index 00000000..3f98cb7b --- /dev/null +++ b/minitests/iob/runme.tcl @@ -0,0 +1,31 @@ +create_project -force -part $::env(XRAY_PART) design design +#read_verilog $::env(SRC_DIR)/$::env(PROJECT).v +read_verilog $::env(TOP_V) +synth_design -top top -flatten_hierarchy none -verilog_define ROI=$::env(PROJECT) + +set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk] +set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb] +set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di] +set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do] + +# set_property roi/dut + +create_pblock roi +set_property EXCLUDE_PLACEMENT 1 [get_pblocks roi] +add_cells_to_pblock [get_pblocks roi] [get_cells roi] +resize_pblock [get_pblocks roi] -add "$::env(XRAY_ROI)" + +set_property CFGBVS VCCO [current_design] +set_property CONFIG_VOLTAGE 3.3 [current_design] +set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design] + +set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF] + +place_design +route_design + +write_checkpoint -force design.dcp + +# set_property BITSTREAM.GENERAL.DEBUGBITSTREAM Yes [current_design] +write_bitstream -force design.bit + diff --git a/minitests/iob/runme_tcl.sh b/minitests/iob/runme_tcl.sh new file mode 100644 index 00000000..143dfdbf --- /dev/null +++ b/minitests/iob/runme_tcl.sh @@ -0,0 +1,21 @@ +#!/bin/bash + +set -ex + +: "${PROJECT:?Need to set PROJECT non-empty}" + +# Create build dir +export SRC_DIR=$PWD +BUILD_DIR=build/$PROJECT +mkdir -p $BUILD_DIR +cd $BUILD_DIR + +export TOP_V=$SRC_DIR/tcl.v + +vivado -mode batch -source $SRC_DIR/$PROJECT.tcl +for x in design*.bit; do + ${XRAY_BITREAD} -F $XRAY_ROI_FRAMES -o ${x}s -z -y $x +done +test -z "$(fgrep CRITICAL vivado.log)" +touch run.ok + diff --git a/minitests/iob/sweep.tcl b/minitests/iob/sweep.tcl new file mode 100644 index 00000000..208f2195 --- /dev/null +++ b/minitests/iob/sweep.tcl @@ -0,0 +1,22 @@ +# Sweep all values of $prop on given I/O $port +# Write out bitstream for all legal values + +set vals [list_property_value $prop $port] +foreach {val} $vals { + puts $val + # Not all listable properties are settable + # Its easiest to try setting and see if it sticks + set_property -quiet $prop $val $port + set got [get_property $prop $port] + if {"$got" != "$val"} { + puts " Skipping: wanted $val, got $got" + continue + } + if {[catch {write_bitstream -force design_$val.bit} issue]} { + puts "WARNING failed to write: $issue" + continue + } + # Only write checkpoints for acceptable bitstreams + write_checkpoint -force design_$val.dcp +} + diff --git a/minitests/iob/tcl.v b/minitests/iob/tcl.v new file mode 100644 index 00000000..6535c2d8 --- /dev/null +++ b/minitests/iob/tcl.v @@ -0,0 +1,39 @@ +module top(input clk, stb, di, output do); + localparam integer DIN_N = 256; + localparam integer DOUT_N = 256; + + reg [DIN_N-1:0] din; + wire [DOUT_N-1:0] dout; + + reg [DIN_N-1:0] din_shr; + reg [DOUT_N-1:0] dout_shr; + + always @(posedge clk) begin + din_shr <= {din_shr, di}; + dout_shr <= {dout_shr, din_shr[DIN_N-1]}; + if (stb) begin + din <= din_shr; + dout_shr <= dout; + end + end + + assign do = dout_shr[DOUT_N-1]; + + roi + roi ( + .clk(clk), + .din(din), + .dout(dout) + ); +endmodule + +module roi(input clk, input [255:0] din, output [255:0] dout); + assign dout = din; +endmodule + +/* +module top (input i, output o); + assign o = i; +endmodule +*/ + diff --git a/minitests/iob/template.tcl b/minitests/iob/template.tcl new file mode 100644 index 00000000..4b1ada95 --- /dev/null +++ b/minitests/iob/template.tcl @@ -0,0 +1,26 @@ +# Create a simple design with a few IOs + +create_project -force -part $::env(XRAY_PART) design design +read_verilog $::env(TOP_V) +synth_design -top top -flatten_hierarchy none + +set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_00) IOSTANDARD LVCMOS33" [get_ports clk] +set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_01) IOSTANDARD LVCMOS33" [get_ports stb] +set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_02) IOSTANDARD LVCMOS33" [get_ports di] +set_property -dict "PACKAGE_PIN $::env(XRAY_PIN_03) IOSTANDARD LVCMOS33" [get_ports do] + +set_property CFGBVS VCCO [current_design] +set_property CONFIG_VOLTAGE 3.3 [current_design] +set_property BITSTREAM.GENERAL.PERFRAMECRC YES [current_design] + +set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets clk_IBUF] + +place_design +route_design +write_checkpoint -force design.dcp + +# set port [create_port -direction OUT myport] +# set_property -dict "PACKAGE_PIN D19 IOSTANDARD LVCMOS33" $port +# set_property PULLTYPE PULLUP $port +# set_property PULLTYPE PULLDOWN $port + diff --git a/minitests/iob/top.v b/minitests/iob/top.v new file mode 100644 index 00000000..0e8f32b8 --- /dev/null +++ b/minitests/iob/top.v @@ -0,0 +1,183 @@ +/* +IOBUF + Not a primitive? + Looks like it has an OBUFT + + +Output buffer family: + OBUF + OBUFDS + OBUFT + OBUFTDS +*/ + +`ifndef ROI +ERROR: must set ROI +`endif + +module top(input clk, stb, di, output do); + localparam integer DIN_N = 256; + localparam integer DOUT_N = 256; + + reg [DIN_N-1:0] din; + wire [DOUT_N-1:0] dout; + + reg [DIN_N-1:0] din_shr; + reg [DOUT_N-1:0] dout_shr; + + always @(posedge clk) begin + din_shr <= {din_shr, di}; + dout_shr <= {dout_shr, din_shr[DIN_N-1]}; + if (stb) begin + din <= din_shr; + dout_shr <= dout; + end + end + + assign do = dout_shr[DOUT_N-1]; + + `ROI + roi ( + .clk(clk), + .din(din), + .dout(dout) + ); +endmodule + +module roi_io_a(input clk, input [255:0] din, output [255:0] dout); + assign dout[0] = din[0] & din[1]; + + IOBUF_INTERMDISABLE #( + .DRIVE(12), + .IBUF_LOW_PWR("TRUE"), + .IOSTANDARD("DEFAULT"), + .SLEW("SLOW"), + .USE_IBUFDISABLE("TRUE") + ) IOBUF_INTERMDISABLE_inst ( + .O(1'b0), + .IO(1'bz), + .I(dout[8]), + .IBUFDISABLE(1'b0), + .INTERMDISABLE(1'b0), + .T(1'b1)); + +endmodule + +module roi_io_b(input clk, input [255:0] din, output [255:0] dout); + assign dout[0] = din[0] & din[1]; + + wire onet; + + IOBUF_INTERMDISABLE #( + .DRIVE(12), + .IBUF_LOW_PWR("FALSE"), + .IOSTANDARD("DEFAULT"), + .SLEW("SLOW"), + .USE_IBUFDISABLE("FALSE") + ) IOBUF_INTERMDISABLE_inst ( + .O(onet), + .IO(1'bz), + .I(dout[8]), + .IBUFDISABLE(1'b0), + .INTERMDISABLE(1'b0), + .T(1'b1)); + + PULLUP PULLUP_inst ( + .O(onet) + ); + + IOBUF_INTERMDISABLE #( + .DRIVE(12), + .IBUF_LOW_PWR("FALSE"), + .IOSTANDARD("DEFAULT"), + .SLEW("SLOW"), + .USE_IBUFDISABLE("FALSE") + ) i2 ( + .O(), + .IO(1'bz), + .I(dout[8]), + .IBUFDISABLE(1'b0), + .INTERMDISABLE(1'b0), + .T(1'b1)); + +endmodule + +/* +For some reason this doesn't diff +Was this optimized out? + +ERROR: [Place 30-69] Instance roi/dut/OBUFT (OBUFT) is unplaced after IO placer +ERROR: [Place 30-68] Instance roi/dut/OBUFT (OBUFT) is not placed +*/ + +/* +module roi_prop_a(input clk, input [255:0] din, output [255:0] dout); + assign dout[0] = din[0] & din[1]; + + //(* LOC="D19", KEEP, DONT_TOUCH *) + (* KEEP, DONT_TOUCH *) + IOBUF #( + .DRIVE(8), + .IBUF_LOW_PWR("TRUE"), + .IOSTANDARD("DEFAULT"), + .SLEW("SLOW") + ) dut ( + .O(dout[1]), + .I(din[0]), + .T(din[1])); +endmodule + +module roi_prop_b(input clk, input [255:0] din, output [255:0] dout); + assign dout[0] = din[0] & din[1]; + + //(* LOC="D19", KEEP, DONT_TOUCH *) + (* KEEP, DONT_TOUCH *) + IOBUF #( + .DRIVE(12), + .IBUF_LOW_PWR("TRUE"), + .IOSTANDARD("DEFAULT"), + .SLEW("SLOW") + ) dut ( + .O(dout[1]), + .I(din[0]), + .T(din[1])); +endmodule +*/ + +/* +ERROR: [DRC REQP-1581] obuf_loaded: OBUFT roi/dut pin O drives one or more invalid loads. The loads are: dout_shr[1]_i_1 +ERROR: [Place 30-69] Instance roi/dut (OBUFT) is unplaced after IO placer +hmm +Abandoning verilog approach +tcl seems to work well, just use it directly +*/ +module roi_prop_a(input clk, input [255:0] din, output [255:0] dout); + (* LOC="D19", KEEP, DONT_TOUCH *) + //(* KEEP, DONT_TOUCH *) + OBUFT #( + .DRIVE(8), + .IOSTANDARD("DEFAULT"), + .SLEW("SLOW") + ) dut ( + //.O(dout[1]), + .O(), + .I(din[0]), + .T(din[1])); +endmodule + +module roi_prop_b(input clk, input [255:0] din, output [255:0] dout); + (* LOC="D19", KEEP, DONT_TOUCH *) + //(* KEEP, DONT_TOUCH *) + (* KEEP, DONT_TOUCH *) + OBUFT #( + .DRIVE(12), + .IOSTANDARD("DEFAULT"), + .SLEW("SLOW") + ) dut ( + //.O(dout[1]), + .O(), + .I(din[0]), + .T(din[1])); +endmodule + + diff --git a/utils/genheader.sh b/utils/genheader.sh index edf0cbb5..39094a44 100644 --- a/utils/genheader.sh +++ b/utils/genheader.sh @@ -8,12 +8,13 @@ fi set -ex # for some reason on sourced script set -e doesn't work -test $# = 1 || exit 1 +# Scripts may have additional arguments, but first is reserved for build directory +test $# -ge 1 || exit 1 test ! -e "$SPECN" SPECN=$1 rm -rf "$SPECN" -mkdir "$SPECN" +mkdir -p "$SPECN" cd "$SPECN" export SEED="$(echo $SPECN | md5sum | cut -c1-8)" diff --git a/utils/top_generate.sh b/utils/top_generate.sh index 41056b6b..3a52965c 100644 --- a/utils/top_generate.sh +++ b/utils/top_generate.sh @@ -4,7 +4,7 @@ set -ex -FUZDIR=$PWD +export FUZDIR=$PWD source ${XRAY_GENHEADER} # Some projects have hard coded top.v, others are generated