| #!/usr/bin/env python3 | 
 | # -*- coding: utf-8 -*- | 
 | # | 
 | # Copyright (C) 2017-2020  The Project X-Ray Authors. | 
 | # | 
 | # Use of this source code is governed by a ISC-style | 
 | # license that can be found in the LICENSE file or at | 
 | # https://opensource.org/licenses/ISC | 
 | # | 
 | # SPDX-License-Identifier: ISC | 
 | """ Emits top.v's for various BUFHCE routing inputs. """ | 
 | import os | 
 | import random | 
 | random.seed(int(os.getenv("SEED"), 16)) | 
 | import re | 
 | from prjxray import util | 
 | from prjxray.lut_maker import LutMaker | 
 | from prjxray.db import Database | 
 | from io import StringIO | 
 |  | 
 | CMT_XY_FUN = util.create_xy_fun(prefix='') | 
 |  | 
 |  | 
 | def read_site_to_cmt(): | 
 |     """ Yields clock sources and which CMT they route within. """ | 
 |     with open(os.path.join(os.getenv('FUZDIR'), 'build', | 
 |                            'cmt_regions.csv')) as f: | 
 |         for l in f: | 
 |             site, cmt, tile = l.strip().split(',') | 
 |             yield (tile, site, cmt) | 
 |  | 
 |  | 
 | def make_ccio_route_options(): | 
 |  | 
 |     # Read the PIP lists | 
 |     piplist_path = os.path.join( | 
 |         os.getenv("FUZDIR"), "..", "piplist", "build", "hclk_cmt") | 
 |  | 
 |     pips = [] | 
 |     for fname in os.listdir(piplist_path): | 
 |         if not fname.endswith(".txt"): | 
 |             continue | 
 |  | 
 |         fullname = os.path.join(piplist_path, fname) | 
 |         with open(fullname, "r") as fp: | 
 |             pips += [l.strip() for l in fp.readlines()] | 
 |  | 
 |     # Get PIPs that mention FREQ_REFn wires. These are the ones that we want | 
 |     # force routing through. | 
 |     pips = [p for p in pips if "FREQ_REF" in p] | 
 |  | 
 |     # Sort by tile type | 
 |     options = {} | 
 |     for pip in pips: | 
 |         tile, dst, src = pip.split(".") | 
 |  | 
 |         for a, b in ((src, dst), (dst, src)): | 
 |             match = re.match(r".*FREQ_REF([0-3]).*", a) | 
 |             if match is not None: | 
 |                 n = int(match.group(1)) | 
 |  | 
 |                 if tile not in options: | 
 |                     options[tile] = {} | 
 |                 if n not in options[tile]: | 
 |                     options[tile][n] = set() | 
 |  | 
 |                 options[tile][n].add(b) | 
 |  | 
 |     return options | 
 |  | 
 |  | 
 | class ClockSources(object): | 
 |     """ Class for tracking clock sources. | 
 |  | 
 |     Some clock sources can be routed to any CMT, for these, cmt='ANY'. | 
 |     For clock sources that belong to a CMT, cmt should be set to the CMT of | 
 |     the source. | 
 |  | 
 |     """ | 
 |  | 
 |     def __init__(self): | 
 |         self.sources = {} | 
 |         self.source_to_cmt = {} | 
 |         self.used_sources_from_cmt = {} | 
 |  | 
 |     def add_clock_source(self, source, cmt): | 
 |         """ Adds a source from a specific CMT. | 
 |  | 
 |         cmt='ANY' indicates that this source can be routed to any CMT. | 
 |         """ | 
 |         if cmt not in self.sources: | 
 |             self.sources[cmt] = [] | 
 |  | 
 |         self.sources[cmt].append(source) | 
 |         self.source_to_cmt[source] = cmt | 
 |  | 
 |     def remove_clock_source(self, source, cmt="ANY"): | 
 |         """ | 
 |         Removes a clock source from the available clock sources list | 
 |         """ | 
 |         if source in self.source_to_cmt: | 
 |             del self.source_to_cmt[source] | 
 |  | 
 |         if cmt == "ANY": | 
 |             for sources in self.sources.values(): | 
 |                 if source in sources: | 
 |                     sources.remove(source) | 
 |         else: | 
 |             if source in self.sources[cmt]: | 
 |                 self.sources[cmt].remove(source) | 
 |  | 
 |     def get_random_source( | 
 |             self, cmt, uses_left_right_routing=False, no_repeats=False): | 
 |         """ Get a random source that is routable to the specific CMT. | 
 |  | 
 |         get_random_source will return a source that is either cmt='ANY', | 
 |         cmt equal to the input CMT, or the adjecent CMT. | 
 |  | 
 |         """ | 
 |  | 
 |         choices = [] | 
 |  | 
 |         if cmt in self.sources: | 
 |             choices.extend(self.sources[cmt]) | 
 |  | 
 |         if uses_left_right_routing: | 
 |             x, y = CMT_XY_FUN(cmt) | 
 |  | 
 |             if x % 2 == 0: | 
 |                 x += 1 | 
 |             else: | 
 |                 x -= 1 | 
 |  | 
 |             paired_cmt = 'X{}Y{}'.format(x, y) | 
 |  | 
 |             if paired_cmt in self.sources: | 
 |                 for source in self.sources[paired_cmt]: | 
 |                     if 'BUFHCE' not in source: | 
 |                         choices.append(source) | 
 |  | 
 |         random.shuffle(choices) | 
 |  | 
 |         if not uses_left_right_routing: | 
 |             return choices[0] | 
 |  | 
 |         for source in choices: | 
 |  | 
 |             source_cmt = self.source_to_cmt[source] | 
 |  | 
 |             if source_cmt not in self.used_sources_from_cmt: | 
 |                 self.used_sources_from_cmt[source_cmt] = set() | 
 |  | 
 |             if no_repeats and source in self.used_sources_from_cmt[source_cmt]: | 
 |                 continue | 
 |  | 
 |             if len(self.used_sources_from_cmt[source_cmt]) >= 14: | 
 |                 continue | 
 |  | 
 |             self.used_sources_from_cmt[source_cmt].add(source) | 
 |  | 
 |             return source | 
 |  | 
 |         return None | 
 |  | 
 |  | 
 | def get_paired_iobs(db, grid, tile_name): | 
 |     """ The two IOB33M's above and below the HCLK row have dedicate clock lines. | 
 |     """ | 
 |  | 
 |     gridinfo = grid.gridinfo_at_tilename(tile_name) | 
 |     loc = grid.loc_of_tilename(tile_name) | 
 |  | 
 |     if gridinfo.tile_type.endswith('_L'): | 
 |         inc = 1 | 
 |         lr = 'R' | 
 |     else: | 
 |         inc = -1 | 
 |         lr = 'L' | 
 |  | 
 |     idx = 1 | 
 |     while True: | 
 |         gridinfo = grid.gridinfo_at_loc((loc.grid_x + inc * idx, loc.grid_y)) | 
 |  | 
 |         if gridinfo.tile_type.startswith('HCLK_IOI'): | 
 |             break | 
 |  | 
 |         idx += 1 | 
 |  | 
 |     # A map of y deltas to CCIO wire indices | 
 |     CCIO_INDEX = {-1: 0, -3: 1, +2: 3, +4: 2} | 
 |  | 
 |     # Move from HCLK_IOI column to IOB column | 
 |     idx += 1 | 
 |  | 
 |     for dy in [-1, -3, 2, 4]: | 
 |         iob_loc = (loc.grid_x + inc * idx, loc.grid_y + dy) | 
 |         gridinfo = grid.gridinfo_at_loc(iob_loc) | 
 |         tile_name = grid.tilename_at_loc(iob_loc) | 
 |  | 
 |         assert gridinfo.tile_type.startswith(lr + 'IOB'), ( | 
 |             gridinfo, lr + 'IOB') | 
 |  | 
 |         for site, site_type in gridinfo.sites.items(): | 
 |             if site_type in ['IOB33M', 'IOB18M']: | 
 |                 yield tile_name, site, site_type[-3:-1], CCIO_INDEX[dy] | 
 |  | 
 |  | 
 | def check_allowed(mmcm_pll_dir, cmt): | 
 |     """ Check whether the CMT specified is in the allowed direction. | 
 |  | 
 |     This function is designed to bias sources to either the left or right | 
 |     input lines. | 
 |  | 
 |     """ | 
 |     if mmcm_pll_dir == 'BOTH': | 
 |         return True | 
 |     elif mmcm_pll_dir == 'ODD': | 
 |         x, y = CMT_XY_FUN(cmt) | 
 |         return (x & 1) == 1 | 
 |     elif mmcm_pll_dir == 'EVEN': | 
 |         x, y = CMT_XY_FUN(cmt) | 
 |         return (x & 1) == 0 | 
 |     else: | 
 |         assert False, mmcm_pll_dir | 
 |  | 
 |  | 
 | def main(): | 
 |     """ | 
 |     HCLK_CMT switch box has the follow inputs: | 
 |  | 
 |     4 IOBs above and below | 
 |     14 MMCM outputs | 
 |     8 PLL outputs | 
 |     4 PHASER_IN outputs | 
 |     2 INT connections | 
 |  | 
 |     and the following outputs: | 
 |  | 
 |     3 PLLE2 inputs | 
 |     2 BUFMR inputs | 
 |     3 MMCM inputs | 
 |     ~2 MMCM -> BUFR??? | 
 |     """ | 
 |  | 
 |     clock_sources = ClockSources() | 
 |     adv_clock_sources = ClockSources() | 
 |  | 
 |     tile_site_cmt = list(read_site_to_cmt()) | 
 |     site_to_cmt = {tsc[1]: tsc[2] for tsc in tile_site_cmt} | 
 |     cmt_to_hclk = { | 
 |         tsc[2]: tsc[0] | 
 |         for tsc in tile_site_cmt | 
 |         if tsc[0].startswith("HCLK_CMT_") | 
 |     } | 
 |  | 
 |     ccio_route_options = make_ccio_route_options() | 
 |  | 
 |     db = Database(util.get_db_root(), util.get_part()) | 
 |     grid = db.grid() | 
 |  | 
 |     def gen_sites(desired_site_type): | 
 |         for tile_name in sorted(grid.tiles()): | 
 |             loc = grid.loc_of_tilename(tile_name) | 
 |             gridinfo = grid.gridinfo_at_loc(loc) | 
 |             for site, site_type in gridinfo.sites.items(): | 
 |                 if site_type == desired_site_type: | 
 |                     yield tile_name, site | 
 |  | 
 |     hclk_cmts = set() | 
 |     ins = [] | 
 |     iobs = StringIO() | 
 |  | 
 |     hclk_cmt_tiles = set() | 
 |     for tile_name, site in gen_sites('BUFMRCE'): | 
 |         cmt = site_to_cmt[site] | 
 |         hclk_cmts.add(cmt) | 
 |         hclk_cmt_tiles.add(tile_name) | 
 |  | 
 |     mmcm_pll_only = random.randint(0, 1) | 
 |     mmcm_pll_dir = random.choice(('ODD', 'EVEN', 'BOTH')) | 
 |  | 
 |     print( | 
 |         '// mmcm_pll_only {} mmcm_pll_dir {}'.format( | 
 |             mmcm_pll_only, mmcm_pll_dir)) | 
 |  | 
 |     have_iob_clocks = random.random() > .1 | 
 |  | 
 |     iob_to_hclk = {} | 
 |     iob_clks = {} | 
 |     for tile_name in sorted(hclk_cmt_tiles): | 
 |         for _, site, volt, ccio in get_paired_iobs(db, grid, tile_name): | 
 |             iob_clock = 'clock_IBUF_{site}'.format(site=site) | 
 |  | 
 |             iob_to_hclk[site] = (tile_name, ccio) | 
 |             cmt = site_to_cmt[site] | 
 |  | 
 |             if cmt not in iob_clks: | 
 |                 iob_clks[cmt] = [''] | 
 |  | 
 |             iob_clks[cmt].append(iob_clock) | 
 |  | 
 |             ins.append('input clk_{site}'.format(site=site)) | 
 |  | 
 |             if have_iob_clocks: | 
 |                 if check_allowed(mmcm_pll_dir, cmt): | 
 |                     clock_sources.add_clock_source(iob_clock, cmt) | 
 |                 adv_clock_sources.add_clock_source(iob_clock, cmt) | 
 |  | 
 |             print( | 
 |                 """ | 
 |     (* KEEP, DONT_TOUCH, LOC = "{site}" *) | 
 |     wire clock_IBUF_{site}; | 
 |     IBUF #( .IOSTANDARD("LVCMOS{volt}") ) ibuf_{site} ( | 
 |         .I(clk_{site}), | 
 |         .O(clock_IBUF_{site}) | 
 |         ); | 
 |                     """.format(volt=volt, site=site), | 
 |                 file=iobs) | 
 |  | 
 |     print( | 
 |         ''' | 
 | module top({inputs}); | 
 |     (* KEEP, DONT_TOUCH *) | 
 |     LUT6 dummy(); | 
 |     '''.format(inputs=', '.join(ins))) | 
 |  | 
 |     print(iobs.getvalue()) | 
 |  | 
 |     luts = LutMaker() | 
 |     wires = StringIO() | 
 |     bufhs = StringIO() | 
 |  | 
 |     for _, site in gen_sites('MMCME2_ADV'): | 
 |         mmcm_clocks = [ | 
 |             'mmcm_clock_{site}_{idx}'.format(site=site, idx=idx) | 
 |             for idx in range(13) | 
 |         ] | 
 |  | 
 |         if check_allowed(mmcm_pll_dir, site_to_cmt[site]): | 
 |             for clk in mmcm_clocks: | 
 |                 clock_sources.add_clock_source(clk, site_to_cmt[site]) | 
 |  | 
 |         print( | 
 |             """ | 
 |     wire cin1_{site}, cin2_{site}, clkfbin_{site}, {c0}, {c1}, {c2}, {c3}, {c4}, {c5}; | 
 |     (* KEEP, DONT_TOUCH, LOC = "{site}" *) | 
 |     MMCME2_ADV pll_{site} ( | 
 |         .CLKIN1(cin1_{site}), | 
 |         .CLKIN2(cin2_{site}), | 
 |         .CLKFBIN(clkfbin_{site}), | 
 |         .CLKOUT0({c0}), | 
 |         .CLKOUT0B({c1}), | 
 |         .CLKOUT1({c2}), | 
 |         .CLKOUT1B({c3}), | 
 |         .CLKOUT2({c4}), | 
 |         .CLKOUT2B({c5}), | 
 |         .CLKOUT3({c6}), | 
 |         .CLKOUT3B({c7}), | 
 |         .CLKOUT4({c8}), | 
 |         .CLKOUT5({c9}), | 
 |         .CLKOUT6({c10}), | 
 |         .CLKFBOUT({c11}), | 
 |         .CLKFBOUTB({c12}) | 
 |     ); | 
 |         """.format( | 
 |                 site=site, | 
 |                 c0=mmcm_clocks[0], | 
 |                 c1=mmcm_clocks[1], | 
 |                 c2=mmcm_clocks[2], | 
 |                 c3=mmcm_clocks[3], | 
 |                 c4=mmcm_clocks[4], | 
 |                 c5=mmcm_clocks[5], | 
 |                 c6=mmcm_clocks[6], | 
 |                 c7=mmcm_clocks[7], | 
 |                 c8=mmcm_clocks[8], | 
 |                 c9=mmcm_clocks[9], | 
 |                 c10=mmcm_clocks[10], | 
 |                 c11=mmcm_clocks[11], | 
 |                 c12=mmcm_clocks[12], | 
 |             )) | 
 |  | 
 |     for _, site in gen_sites('PLLE2_ADV'): | 
 |         pll_clocks = [ | 
 |             'pll_clock_{site}_{idx}'.format(site=site, idx=idx) | 
 |             for idx in range(7) | 
 |         ] | 
 |  | 
 |         if check_allowed(mmcm_pll_dir, site_to_cmt[site]): | 
 |             for clk in pll_clocks: | 
 |                 clock_sources.add_clock_source(clk, site_to_cmt[site]) | 
 |  | 
 |         print( | 
 |             """ | 
 |     wire cin1_{site}, cin2_{site}, clkfbin_{site}, {c0}, {c1}, {c2}, {c3}, {c4}, {c5}, {c6}; | 
 |     (* KEEP, DONT_TOUCH, LOC = "{site}" *) | 
 |     PLLE2_ADV pll_{site} ( | 
 |         .CLKIN1(cin1_{site}), | 
 |         .CLKIN2(cin2_{site}), | 
 |         .CLKFBIN(clkfbin_{site}), | 
 |         .CLKOUT0({c0}), | 
 |         .CLKOUT1({c1}), | 
 |         .CLKOUT2({c2}), | 
 |         .CLKOUT3({c3}), | 
 |         .CLKOUT4({c4}), | 
 |         .CLKOUT5({c5}), | 
 |         .CLKFBOUT({c6}) | 
 |     ); | 
 |         """.format( | 
 |                 site=site, | 
 |                 c0=pll_clocks[0], | 
 |                 c1=pll_clocks[1], | 
 |                 c2=pll_clocks[2], | 
 |                 c3=pll_clocks[3], | 
 |                 c4=pll_clocks[4], | 
 |                 c5=pll_clocks[5], | 
 |                 c6=pll_clocks[6], | 
 |             )) | 
 |  | 
 |     for tile_name, site in gen_sites('BUFHCE'): | 
 |         print( | 
 |             """ | 
 |     wire I_{site}; | 
 |     wire O_{site}; | 
 |     (* KEEP, DONT_TOUCH, LOC = "{site}" *) | 
 |     BUFHCE buf_{site} ( | 
 |         .I(I_{site}), | 
 |         .O(O_{site}) | 
 |     ); | 
 |                     """.format(site=site, ), | 
 |             file=bufhs) | 
 |  | 
 |         if site_to_cmt[site] in hclk_cmts: | 
 |             if not mmcm_pll_only: | 
 |                 clock_sources.add_clock_source( | 
 |                     'O_{site}'.format(site=site), site_to_cmt[site]) | 
 |             adv_clock_sources.add_clock_source( | 
 |                 'O_{site}'.format(site=site), site_to_cmt[site]) | 
 |  | 
 |     hclks_used_by_cmt = {} | 
 |     for cmt in site_to_cmt.values(): | 
 |         hclks_used_by_cmt[cmt] = set() | 
 |  | 
 |     def check_hclk_src(src, src_cmt): | 
 |         if len(hclks_used_by_cmt[src_cmt] | 
 |                ) >= 12 and src not in hclks_used_by_cmt[src_cmt]: | 
 |             return None | 
 |         else: | 
 |             hclks_used_by_cmt[src_cmt].add(src) | 
 |             return src | 
 |  | 
 |     # Track used IOB sources | 
 |     used_iob_clks = set() | 
 |  | 
 |     if random.random() > .10: | 
 |         for tile_name, site in gen_sites('BUFHCE'): | 
 |  | 
 |             wire_name = clock_sources.get_random_source( | 
 |                 site_to_cmt[site], | 
 |                 uses_left_right_routing=True, | 
 |                 no_repeats=mmcm_pll_only) | 
 |  | 
 |             if wire_name is not None and 'BUFHCE' in wire_name: | 
 |                 # Looping a BUFHCE to a BUFHCE requires using a hclk in the | 
 |                 # CMT of the source | 
 |                 src_cmt = clock_sources.source_to_cmt[wire_name] | 
 |  | 
 |                 wire_name = check_hclk_src(wire_name, src_cmt) | 
 |  | 
 |             if wire_name is None: | 
 |                 continue | 
 |  | 
 |             if "IBUF" in wire_name: | 
 |                 used_iob_clks.add(wire_name) | 
 |                 clock_sources.remove_clock_source(wire_name) | 
 |                 adv_clock_sources.remove_clock_source(wire_name) | 
 |  | 
 |             print( | 
 |                 """ | 
 |         assign I_{site} = {wire_name};""".format( | 
 |                     site=site, | 
 |                     wire_name=wire_name, | 
 |                 ), | 
 |                 file=bufhs) | 
 |  | 
 |     for tile_name, site in gen_sites('BUFMRCE'): | 
 |         pass | 
 |  | 
 |     for l in luts.create_wires_and_luts(): | 
 |         print(l) | 
 |  | 
 |     print(wires.getvalue()) | 
 |     print(bufhs.getvalue()) | 
 |  | 
 |     for _, site in gen_sites('BUFR'): | 
 |  | 
 |         # Do not use BUFR always | 
 |         if random.random() < 0.50: | 
 |             continue | 
 |  | 
 |         available_srcs = set(iob_clks[site_to_cmt[site]]) - used_iob_clks | 
 |         if len(available_srcs) == 0: | 
 |             continue | 
 |  | 
 |         src = random.choice(list(available_srcs)) | 
 |  | 
 |         if src != "": | 
 |             used_iob_clks.add(src) | 
 |             clock_sources.remove_clock_source(src) | 
 |             adv_clock_sources.remove_clock_source(src) | 
 |  | 
 |         adv_clock_sources.add_clock_source( | 
 |             'O_{site}'.format(site=site), site_to_cmt[site]) | 
 |  | 
 |         print( | 
 |             """ | 
 |     wire O_{site}; | 
 |     (* KEEP, DONT_TOUCH, LOC = "{site}" *) | 
 |     BUFR bufr_{site} ( | 
 |         .I({I}), | 
 |         .O(O_{site}) | 
 |         );""".format(I=src, site=site)) | 
 |  | 
 |     route_file = open("routes.txt", "w") | 
 |  | 
 |     def fix_ccio_route(net): | 
 |  | 
 |         # Get the IOB site name | 
 |         match = re.match(r".*_IBUF_(.*)", net) | 
 |         assert match is not None, net | 
 |         iob_site = match.group(1) | 
 |  | 
 |         # Get associated HCLK_CMT tile and CCIO wire index | 
 |         hclk_tile_name, ccio = iob_to_hclk[iob_site] | 
 |  | 
 |         # Get HCLK_CMT tile type | 
 |         hclk_tile = hclk_tile_name.rsplit("_", maxsplit=1)[0] | 
 |  | 
 |         # Pick a random route option | 
 |         opts = list(ccio_route_options[hclk_tile][ccio]) | 
 |         route = random.choice(opts) | 
 |         route = "{}/{}".format(hclk_tile_name, route) | 
 |         route_file.write("{} {}\n".format(net, route)) | 
 |  | 
 |     for _, site in gen_sites('PLLE2_ADV'): | 
 |         for cin in ('cin1', 'cin2', 'clkfbin'): | 
 |             if random.random() > .2: | 
 |  | 
 |                 src = adv_clock_sources.get_random_source(site_to_cmt[site]) | 
 |  | 
 |                 src_cmt = adv_clock_sources.source_to_cmt[src] | 
 |  | 
 |                 if 'IBUF' not in src and 'BUFR' not in src: | 
 |                     # Clocks from input pins do not require HCLK's, all other | 
 |                     # sources route from a global row clock. | 
 |                     src = check_hclk_src(src, src_cmt) | 
 |  | 
 |                 if src is None: | 
 |                     continue | 
 |  | 
 |                 if "IBUF" in src: | 
 |                     clock_sources.remove_clock_source(src) | 
 |                     adv_clock_sources.remove_clock_source(src) | 
 |                     fix_ccio_route(src) | 
 |  | 
 |                 print( | 
 |                     """ | 
 |         assign {cin}_{site} = {csrc}; | 
 |             """.format(cin=cin, site=site, csrc=src)) | 
 |  | 
 |     for _, site in gen_sites('MMCME2_ADV'): | 
 |         for cin in ('cin1', 'cin2', 'clkfbin'): | 
 |             if random.random() > .2: | 
 |  | 
 |                 src = adv_clock_sources.get_random_source(site_to_cmt[site]) | 
 |  | 
 |                 src_cmt = adv_clock_sources.source_to_cmt[src] | 
 |                 if 'IBUF' not in src and 'BUFR' not in src: | 
 |                     # Clocks from input pins do not require HCLK's, all other | 
 |                     # sources route from a global row clock. | 
 |                     src = check_hclk_src(src, src_cmt) | 
 |  | 
 |                 if src is None: | 
 |                     continue | 
 |  | 
 |                 if "IBUF" in src: | 
 |                     clock_sources.remove_clock_source(src) | 
 |                     adv_clock_sources.remove_clock_source(src) | 
 |                     fix_ccio_route(src) | 
 |  | 
 |                 print( | 
 |                     """ | 
 |         assign {cin}_{site} = {csrc}; | 
 |             """.format(cin=cin, site=site, csrc=src)) | 
 |  | 
 |     print("endmodule") | 
 |  | 
 |  | 
 | if __name__ == '__main__': | 
 |     main() |