#!/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)) 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 = l.strip().split(',') yield (site, cmt) class ClockSources(object): """ Class for tracking clock sources. """ def __init__(self, limit=14): self.sources = {} self.source_to_cmt = {} self.used_sources_from_cmt = {} self.limit = limit 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 get_random_source(self, cmt, 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]) random.shuffle(choices) 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]) >= self.limit: continue self.used_sources_from_cmt[source_cmt].add(source) return source return None def main(): """ HCLK_IOI has the following inputs: 12 (east) BUFH from the right side of the HROW 12 (west) Bounce PIPs from one BUFH to any of 6 GCLK_BOT and 6 GCLK_TOP 4 (east) PHSR_PERFCLK (IOCLK_PLL) from HCLK_CLB to input of BUFIO 8 (4 north and 4 south) BUFR CLR and CE 2 (south) I2IOCLK to input of BUFR 2 (north) I2IOCLK to input of BUFR 2 RCLK IMUX (IMUX0 and IMUX1) choosing input of BUFR outputs: 4 (east) BUFRCLK - from BUFR to HROW 4 (north) BUFR2IO - from BUFR 4 (north) IOCLK from BUFIO """ global_clock_sources = ClockSources() cmt_clock_sources = ClockSources() cmt_fast_clock_sources = ClockSources(4) bufr_clock_sources = ClockSources() bufio_clock_sources = ClockSources() site_to_cmt = dict(read_site_to_cmt()) clock_region_limit = dict() clock_region_serdes_location = dict() 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 def serdes_relative_location(tile, site): (serdes_loc_x, serdes_loc_y) = grid.loc_of_tilename(tile) serdes_clk_reg = site_to_cmt[site] for tile_name in sorted(grid.tiles()): if 'HCLK_IOI3' in tile_name: (hclk_tile_loc_x, hclk_tile_loc_y) = grid.loc_of_tilename(tile_name) if hclk_tile_loc_x == serdes_loc_x: gridinfo = grid.gridinfo_at_loc( (hclk_tile_loc_x, hclk_tile_loc_y)) random_site = next(iter(gridinfo.sites.keys())) hclk_clk_reg = site_to_cmt[random_site] if hclk_clk_reg == serdes_clk_reg: if serdes_loc_y < hclk_tile_loc_y: return "TOP" elif serdes_loc_y > hclk_tile_loc_y: return "BOTTOM" else: assert False clock_region_sites = set() def get_clock_region_site(site_type, clk_reg): for site_name, reg in site_to_cmt.items(): if site_name.startswith(site_type) and reg in clk_reg: if site_name not in clock_region_sites: clock_region_sites.add(site_name) return site_name print( ''' module top(); (* KEEP, DONT_TOUCH *) LUT6 dummy(); ''') luts = LutMaker() bufs = StringIO() for _, site in gen_sites('MMCME2_ADV'): mmcm_clocks = [ 'mmcm_clock_{site}_{idx}'.format(site=site, idx=idx) for idx in range(13) ] for idx, clk in enumerate(mmcm_clocks): if idx < 4: cmt_fast_clock_sources.add_clock_source(clk, site_to_cmt[site]) else: cmt_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({c4}), .CLKOUT1({c1}), .CLKOUT1B({c5}), .CLKOUT2({c2}), .CLKOUT2B({c6}), .CLKOUT3({c3}), .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) ] for clk in pll_clocks: cmt_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=bufs) global_clock_sources.add_clock_source( 'O_{site}'.format(site=site), site_to_cmt[site]) hclks_used_by_clock_region = {} for cmt in site_to_cmt.values(): hclks_used_by_clock_region[cmt] = set() def check_hclk_src(src, src_cmt): if len(hclks_used_by_clock_region[src_cmt] ) >= 12 and src not in hclks_used_by_clock_region[src_cmt]: return None else: hclks_used_by_clock_region[src_cmt].add(src) return src cmt_clks_used_by_clock_region = {} for cmt in site_to_cmt.values(): cmt_clks_used_by_clock_region[cmt] = list() def check_cmt_clk_src(src, src_clock_region): print( "//src: {}, clk_reg: {}, len {}".format( src, src_clock_region, len(cmt_clks_used_by_clock_region[src_clock_region]))) if len(cmt_clks_used_by_clock_region[src_clock_region]) >= 4: return None else: cmt_clks_used_by_clock_region[src_clock_region].append(src) return src #Add IDELAYCTRL idelayctrl_in_clock_region = {} for cmt in site_to_cmt.values(): idelayctrl_in_clock_region[cmt] = False for _, site in gen_sites('IDELAYCTRL'): if random.random() < 0.5: wire_name = global_clock_sources.get_random_source( site_to_cmt[site], no_repeats=False) if wire_name is None: continue src_cmt = global_clock_sources.source_to_cmt[wire_name] wire_name = check_hclk_src(wire_name, src_cmt) if wire_name is None: continue idelayctrl_in_clock_region[src_cmt] = True print( """ assign I_{site} = {clock_source}; (* KEEP, DONT_TOUCH, LOC = "{site}" *) IDELAYCTRL idelay_ctrl_{site} ( .RDY(), .REFCLK(I_{site}), .RST() );""".format(site=site, clock_source=wire_name)) # Add SERDES driven by BUFH or MMCM for tile, site in gen_sites('ILOGICE3'): wire_name = None clock_region = site_to_cmt[site] if clock_region not in clock_region_limit: # Select serdes limit and relative location per clock region serdes_location = random.choice(["TOP", "BOTTOM", "ANY"]) if serdes_location in "ANY": #We want TOP and BOTTOM IGCLK PIPs occupied but leave one slot for IDELAYCTRL if idelayctrl_in_clock_region[clock_region]: clock_region_limit[clock_region] = 0 if random.random( ) < 0.2 else 11 else: clock_region_limit[clock_region] = 0 if random.random( ) < 0.2 else 12 else: if idelayctrl_in_clock_region[clock_region]: clock_region_limit[clock_region] = 0 if random.random( ) < 0.2 else 5 else: clock_region_limit[clock_region] = 0 if random.random( ) < 0.2 else 6 clock_region_serdes_location[clock_region] = serdes_location # We reached the limit of hclks in this clock region if clock_region_limit[clock_region] == 0: continue # Add a serdes if it's located at the correct side from the HCLK_IOI tile if clock_region_serdes_location[clock_region] not in "ANY" and \ serdes_relative_location(tile, site) != clock_region_serdes_location[clock_region]: continue if random.random() > 0.1: wire_name = global_clock_sources.get_random_source( site_to_cmt[site], no_repeats=True) if wire_name is None: continue src_cmt = global_clock_sources.source_to_cmt[wire_name] wire_name = check_hclk_src(wire_name, src_cmt) if wire_name is None: print("//wire is None") continue clock_region_limit[clock_region] -= 1 print( """ assign serdes_clk_{site} = {clock_source};""".format( site=site, clock_source=wire_name)) else: wire_name = cmt_fast_clock_sources.get_random_source( site_to_cmt[site], no_repeats=False) if wire_name is None: continue src_cmt = cmt_fast_clock_sources.source_to_cmt[wire_name] wire_name = check_cmt_clk_src(wire_name, src_cmt) if wire_name is None: continue bufio_site = get_clock_region_site("BUFIO", clock_region) if bufio_site is None: continue print( """ assign serdes_clk_{serdes_loc} = O_{site}; assign I_{site} = {clock_source}; (* KEEP, DONT_TOUCH, LOC = "{site}" *) BUFIO bufio_{site} ( .O(O_{site}), .I(I_{site}) );""".format(site=bufio_site, clock_source=wire_name, serdes_loc=site)) print( "// clock_region: {} {}".format( clock_region, clock_region_serdes_location[clock_region])) print( """ (* KEEP, DONT_TOUCH, LOC = "{loc}" *) ISERDESE2 #( .DATA_RATE("SDR"), .DATA_WIDTH(4), .DYN_CLKDIV_INV_EN("FALSE"), .DYN_CLK_INV_EN("FALSE"), .INIT_Q1(1'b0), .INIT_Q2(1'b0), .INIT_Q3(1'b0), .INIT_Q4(1'b0), .INTERFACE_TYPE("OVERSAMPLE"), .IOBDELAY("NONE"), .NUM_CE(2), .OFB_USED("FALSE"), .SERDES_MODE("MASTER"), .SRVAL_Q1(1'b0), .SRVAL_Q2(1'b0), .SRVAL_Q3(1'b0), .SRVAL_Q4(1'b0) ) ISERDESE2_inst_{loc} ( .CLK(serdes_clk_{loc}), .CLKB(), .CLKDIV(), .D(1'b0), .DDLY(), .OFB(), .OCLKB(), .RST(), .SHIFTIN1(), .SHIFTIN2() ); """.format(loc=site, clock_source=wire_name)) # BUFRs for _, site in gen_sites('BUFR'): if random.random() < 0.6: if random.random() < 0.5: wire_name = luts.get_next_output_net() else: wire_name = cmt_fast_clock_sources.get_random_source( site_to_cmt[site], no_repeats=False) if wire_name is None: continue src_cmt = cmt_fast_clock_sources.source_to_cmt[wire_name] wire_name = check_cmt_clk_src(wire_name, src_cmt) if wire_name is None: continue bufr_clock_sources.add_clock_source( 'O_{site}'.format(site=site), site_to_cmt[site]) # Add DIVIDE divide = "BYPASS" if random.random() < 0.5: divide = "".format(random.randint(2, 8)) print( """ assign I_{site} = {clock_source}; (* KEEP, DONT_TOUCH, LOC = "{site}" *) BUFR #(.BUFR_DIVIDE("{divide}")) bufr_{site} ( .O(O_{site}), .I(I_{site}) );""".format(site=site, clock_source=wire_name, divide=divide), file=bufs) for _, site in gen_sites('MMCME2_ADV'): wire_name = bufr_clock_sources.get_random_source( site_to_cmt[site], no_repeats=True) if wire_name is None: continue print( """ assign cin1_{site} = {wire_name};""".format( site=site, wire_name=wire_name)) print(bufs.getvalue()) for l in luts.create_wires_and_luts(): print(l) print("endmodule") if __name__ == '__main__': main()