| #!/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() |