diff --git a/fuzzers/039-hclk-config/generate.py b/fuzzers/039-hclk-config/generate.py index eb2e11a8..b38ddff9 100644 --- a/fuzzers/039-hclk-config/generate.py +++ b/fuzzers/039-hclk-config/generate.py @@ -25,8 +25,33 @@ def main(): with open('params.json') as f: params = json.load(f) + placed = {} + try: + with open('bufio_sites.txt') as f: + for line in f: + parts = line.split() + if len(parts) == 2: + placed[parts[0]] = parts[1] + except FileNotFoundError: + pass + used_sites = set(placed.values()) + for row in params: + if row.get('kind') == 'BUFIO_TILE': + sites = row['bufio_sites'] + ys = sorted(int(st.split('Y')[-1]) for st in sites) + y_min = ys[0] if ys else 0 + for st in sites: + y = int(st.split('Y')[-1]) - y_min + segmk.add_tile_tag( + row['tile'], 'BUFIO_Y{}.IN_USE'.format(y), 1 if st in used_sites else 0) + continue base_name = 'BUFR_Y{}'.format(row['y']) + # v5: regional-clock leaf into the IOI column, per RCLK index (slot rel-y -> RCLK {0:2,1:3,2:0,3:1}) + rclk = {0: 2, 1: 3, 2: 0, 3: 1}[row['y']] + segmk.add_tile_tag( + row['tile'], 'HCLK_IOI_RCLK2IO{}.HCLK_IOI_CK_BUFRCLK{}'.format(rclk, rclk), + 1 if (row['IN_USE'] and row.get('consumed')) else 0) segmk.add_tile_tag( row['tile'], '{}.IN_USE'.format(base_name), row['IN_USE']) diff --git a/fuzzers/039-hclk-config/generate.tcl b/fuzzers/039-hclk-config/generate.tcl index 60c93bb0..83155a78 100644 --- a/fuzzers/039-hclk-config/generate.tcl +++ b/fuzzers/039-hclk-config/generate.tcl @@ -17,6 +17,11 @@ proc run {} { place_design route_design + set fp [open bufio_sites.txt w] + foreach c [get_cells -quiet -hierarchical -filter {REF_NAME == BUFIO}] { + puts $fp "[get_property NAME $c] [get_sites -quiet -of_objects $c]" + } + close $fp write_checkpoint -force design.dcp write_bitstream -force design.bit } diff --git a/fuzzers/039-hclk-config/top.py b/fuzzers/039-hclk-config/top.py index 8a43d352..d9982e9f 100644 --- a/fuzzers/039-hclk-config/top.py +++ b/fuzzers/039-hclk-config/top.py @@ -21,6 +21,18 @@ def gen_sites(): xy_fun = util.create_xy_fun('BUFR_') db = Database(util.get_db_root(), util.get_part()) grid = db.grid() + # MMCME2_ADV site per clock region: an MMCM feeding a bank's BUFIOs must sit + # in that bank's own region (IOCLK_PLL is a region-local path). Only the + # MMCM's CLKOUT0..3 reach BUFIO -- a PLLE2's outputs are unroutable to BUFIO + # sites (measured), which is also why 047 registers only MMCM outputs as + # BUFIO-capable sources. + mmcm_by_region = {} + for tn in grid.tiles(): + gi = grid.gridinfo_at_loc(grid.loc_of_tilename(tn)) + for st, sty in gi.sites.items(): + if sty == 'MMCME2_ADV' and gi.clock_region is not None: + mmcm_by_region[str(gi.clock_region)] = st + for tile_name in sorted(grid.tiles()): loc = grid.loc_of_tilename(tile_name) gridinfo = grid.gridinfo_at_loc(loc) @@ -28,6 +40,8 @@ def gen_sites(): xs = [] ys = [] + bufio_sites = [] + bufio_xy = util.create_xy_fun('BUFIO_') for site, site_type in gridinfo.sites.items(): if site_type == 'BUFR': x, y = xy_fun(site) @@ -35,6 +49,9 @@ def gen_sites(): ys.append(y) sites.append((site, x, y)) + elif site_type == 'BUFIO': + bx, by = bufio_xy(site) + bufio_sites.append((site, bx, by)) if not sites: continue @@ -51,6 +68,8 @@ def gen_sites(): dx = -1 iobs = [] + iobs_s = [] + ilogics = [] for dy in (-1, -3, 2, 4): iob = grid.gridinfo_at_loc((loc.grid_x + dx, loc.grid_y + dy)) @@ -58,17 +77,28 @@ def gen_sites(): for site, site_type in iob.sites.items(): if site_type == 'IOB33M': iobs.append(site) + elif site_type == 'IOB33S': + iobs_s.append(site) - yield tile_name, min(xs), min(ys), sorted(sites), sorted(iobs) + ioi = grid.gridinfo_at_loc((loc.grid_x, loc.grid_y + dy)) + for site, site_type in sorted(ioi.sites.items()): + if site_type == 'ILOGICE3': + ilogics.append(site) + break + + mmcm_site = mmcm_by_region.get(str(gridinfo.clock_region)) + yield tile_name, min(xs), min(ys), sorted(sites), sorted(iobs), sorted(bufio_sites), ilogics, mmcm_site, sorted(iobs_s) def main(): params_list = [] num_clocks = 0 + num_outs = 0 outputs = [] luts = LutMaker() - for tile_name, x_min, y_min, sites, iobs in gen_sites(): + for tile_name, x_min, y_min, sites, iobs, bufio_sites, ilogics, mmcm_site, iobs_s in gen_sites(): + outs_used = 0 ioclks = [] for iob in iobs: ioclk = 'clk_{}'.format(iob) @@ -121,8 +151,36 @@ def main(): params['CE'] = luts.get_next_output_net() params['CLR'] = luts.get_next_output_net() - outputs.append( - ''' + params['consumed'] = 0 + if outs_used < len(iobs_s) and random.randint(0, 1): + params['consumed'] = 1 + params['obuf_site'] = iobs_s[outs_used] + params['out_idx'] = num_outs + num_outs += 1 + outs_used += 1 + if params['consumed']: + outputs.append( + ''' + wire {site}_o; + (* KEEP, DONT_TOUCH, LOC = "{site}" *) + BUFR #( + .BUFR_DIVIDE({BUFR_DIVIDE}) + ) buf_{site} ( + .CE({CE}), + .CLR({CLR}), + .I({I}), + .O({site}_o) + ); + wire {site}_q; + (* KEEP, DONT_TOUCH *) + ODDR #(.DDR_CLK_EDGE("SAME_EDGE")) oddr_{site} ( + .C({site}_o), .CE(1'b1), .D1(1'b1), .D2(1'b0), .R(1'b0), .S(1'b0), .Q({site}_q)); + (* KEEP, DONT_TOUCH, LOC = "{obuf_site}" *) + OBUF #(.IOSTANDARD("LVCMOS33")) obuf_{site} (.I({site}_q), .O(outs[{out_idx}])); + '''.format(**params)) + else: + outputs.append( + ''' (* KEEP, DONT_TOUCH, LOC = "{site}" *) BUFR #( .BUFR_DIVIDE({BUFR_DIVIDE}) @@ -131,14 +189,97 @@ def main(): .CLR({CLR}), .I({I}) ); - '''.format(**params)) + '''.format(**params)) params_list.append(params) + # --- BUFIO half (extension v4, fully LOC'd) --- + rec = {} + rec['tile'] = tile_name + rec['kind'] = 'BUFIO_TILE' + rec['bufio_sites'] = [site for site, _, _ in bufio_sites] + rec['bufio_insts'] = [] + rec['bufio_src'] = {} + rec['bufio_loc'] = {} + bys = sorted(by for _, _, by in bufio_sites) + by_min = bys[0] if bys else 0 + clkout_for_rel_y = {0: 0, 1: 1, 2: 2, 3: 3} + mmcm_declared = False + mmcm_name = 'mmcm_{}'.format(tile_name) + for k, (iob, ioclk) in enumerate(zip(iobs, ioclks)): + if k >= len(ilogics): + break + # A clock-capable IOB feeds ONE dedicated BUFIO of its bank: the k-th + # clock-capable IOB (by y) of the tile's neighbourhood drives the k-th + # BUFIO site (by y). Measured on every HCLK_IOI3 of xc7a100t (24/24, + # bijective); a CCIO-fed BUFIO LOC'd anywhere else fails placement. + bufio_by_y = sorted((st for st, _, _ in bufio_sites), key=lambda st: int(st.split('Y')[-1])) + iobs_by_y = sorted(iobs, key=lambda st: int(st.split('Y')[-1])) + rank = iobs_by_y.index(iob) + if rank >= len(bufio_by_y): + continue + bsite = bufio_by_y[rank] + state = random.choice(('unused', 'CCIO', 'MMCM')) if mmcm_site else random.choice(('unused', 'CCIO')) + if state == 'unused': + continue + inst = 'bufio_{}'.format(iob) + rec['bufio_insts'].append(inst) + rec['bufio_src'][inst] = state + rec['bufio_loc'][inst] = bsite + if state == 'MMCM': + if not mmcm_declared: + mmcm_declared = True + outputs.append( + ''' + wire {pll}_fb, {pll}_out0, {pll}_out1, {pll}_out2, {pll}_out3; + (* KEEP, DONT_TOUCH, LOC = "{mmcm_site}" *) + MMCME2_BASE #( + .CLKIN1_PERIOD(10.0), .CLKFBOUT_MULT_F(8.0), .DIVCLK_DIVIDE(1), + .CLKOUT0_DIVIDE_F(8.0), .CLKOUT1_DIVIDE(8), .CLKOUT2_DIVIDE(8), .CLKOUT3_DIVIDE(8) + ) {pll} ( + .CLKIN1({clkin}), .CLKFBIN({pll}_fb), .CLKFBOUT({pll}_fb), + .CLKOUT0({pll}_out0), .CLKOUT1({pll}_out1), .CLKOUT2({pll}_out2), .CLKOUT3({pll}_out3), + .RST(1'b0), .PWRDWN(1'b0) + );'''.format(pll=mmcm_name, clkin=ioclks[0], mmcm_site=mmcm_site)) + rel_y = int(bsite.split('Y')[-1]) - by_min + bufio_i = '{}_out{}'.format(mmcm_name, clkout_for_rel_y[rel_y]) + else: + bufio_i = ioclk + outputs.append( + ''' + wire {inst}_o; + (* KEEP, DONT_TOUCH, LOC = "{bsite}" *) + BUFIO {inst} ( + .I({bufio_i}), + .O({inst}_o) + ); + (* KEEP, DONT_TOUCH, LOC = "{ilogic}" *) + ISERDESE2 #( + .DATA_RATE("SDR"), + .DATA_WIDTH(4), + .INTERFACE_TYPE("OVERSAMPLE"), + .IOBDELAY("NONE"), + .NUM_CE(2), + .SERDES_MODE("MASTER") + ) iserdes_{inst} ( + .CLK({inst}_o), + .CLKB(), + .CLKDIV(), + .D(1'b0), + .DDLY(), + .OFB(), + .OCLKB(), + .RST(), + .SHIFTIN1(), + .SHIFTIN2() + ); + '''.format(inst=inst, bufio_i=bufio_i, bsite=bsite, ilogic=ilogics[k])) + params_list.append(rec) + print( ''' -module top(input [{n1}:0] clks); - '''.format(n1=num_clocks - 1)) +module top(input [{n1}:0] clks, output [{n2}:0] outs); + '''.format(n1=num_clocks - 1, n2=max(num_outs - 1, 0))) print(""" (* KEEP, DONT_TOUCH *)