#!/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 import re random.seed(int(os.getenv("SEED"), 16)) from prjxray import util from prjxray import verilog from prjxray.grid_types import GridLoc from prjxray.db import Database from prjxray.lut_maker import LutMaker from io import StringIO import csv import sys CMT_XY_FUN = util.create_xy_fun(prefix='') BUFGCTRL_XY_FUN = util.create_xy_fun('BUFGCTRL_') BUFHCE_XY_FUN = util.create_xy_fun('BUFHCE_') def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) def gen_sites(desired_site_type): db = Database(util.get_db_root(), util.get_part()) grid = db.grid() 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 loc, gridinfo.tile_type, site def gen_bufhce_sites(): db = Database(util.get_db_root(), util.get_part()) grid = db.grid() for tile_name in sorted(grid.tiles()): loc = grid.loc_of_tilename(tile_name) gridinfo = grid.gridinfo_at_loc(loc) sites = [] for site, site_type in gridinfo.sites.items(): if site_type == 'BUFHCE': sites.append(site) if sites: yield tile_name, sorted(sites) def get_cmt_loc(cmt_tile_name): db = Database(util.get_db_root(), util.get_part()) grid = db.grid() return grid.loc_of_tilename(cmt_tile_name) 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) def read_pss_clocks(): with open(os.path.join(os.getenv('FUZDIR'), 'build', 'pss_clocks.csv')) as f: for l in csv.DictReader(f): yield l 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.merged_sources = {} self.source_to_cmt = {} self.used_sources_from_cmt = {} self.sources_by_loc = {} self.active_cmt_ports = {} def add_clock_source(self, source, cmt, loc=None): """ 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) assert source not in self.source_to_cmt or self.source_to_cmt[ source] == cmt, source self.source_to_cmt[source] = cmt self.add_bufg_clock_source(source, cmt, loc) def add_bufg_clock_source(self, source, cmt, loc): if loc not in self.sources_by_loc: self.sources_by_loc[loc] = [] self.sources_by_loc[loc].append((cmt, source)) def get_random_source(self, cmt): """ 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. """ if cmt not in self.merged_sources: choices = [] if 'ANY' in self.sources: choices.extend(self.sources['ANY']) if cmt in self.sources: choices.extend(self.sources[cmt]) 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: choices.extend(self.sources[paired_cmt]) self.merged_sources[cmt] = choices if self.merged_sources[cmt]: source = random.choice(self.merged_sources[cmt]) 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() self.used_sources_from_cmt[source_cmt].add(source) if source_cmt != 'ANY' and len( self.used_sources_from_cmt[source_cmt]) > 14: self.used_sources_from_cmt[source_cmt].remove(source) return None else: return source def get_bufg_source(self, loc, tile_type, site, todos, i_wire, used_only): bufg_sources = [] top = '_TOP_' in tile_type bottom = '_BOT_' in tile_type assert top ^ bottom, tile_type if top: for src_loc, cmt_sources in self.sources_by_loc.items(): if src_loc is None: continue if src_loc.grid_y <= loc.grid_y: bufg_sources.extend(cmt_sources) elif bottom: for src_loc, cmt_sources in self.sources_by_loc.items(): if src_loc is None: continue if src_loc.grid_y > loc.grid_y: bufg_sources.extend(cmt_sources) # CLK_HROW_TOP_R_CK_BUFG_CASCO0 -> CLK_BUFG_BUFGCTRL0_I0 # CLK_HROW_TOP_R_CK_BUFG_CASCO22 -> CLK_BUFG_BUFGCTRL11_I0 # CLK_HROW_TOP_R_CK_BUFG_CASCO23 -> CLK_BUFG_BUFGCTRL11_I1 # CLK_HROW_BOT_R_CK_BUFG_CASCO27 -> CLK_BUFG_BUFGCTRL13_I1 x, y = BUFGCTRL_XY_FUN(site) assert x == 0 y = y % 16 assert i_wire in [0, 1], i_wire casco_wire = '{tile_type}_CK_BUFG_CASCO{casco_idx}'.format( tile_type=tile_type.replace('BUFG', 'HROW'), casco_idx=(y * 2 + i_wire)) if casco_wire not in todos: return None target_wires = [] need_bufr = False for src_wire in todos[casco_wire]: if 'BUFRCLK' in src_wire: need_bufr = True break for cmt, wire in bufg_sources: if 'BUFR' in wire: if need_bufr: target_wires.append((cmt, wire)) else: target_wires.append((cmt, wire)) random.shuffle(target_wires) for cmt, source in target_wires: if cmt == 'ANY': return source else: # Make sure to not try to import move than 14 sources from # the CMT, there is limited routing. if cmt not in self.used_sources_from_cmt: self.used_sources_from_cmt[cmt] = set() if source in self.used_sources_from_cmt[cmt]: return source elif used_only: continue if len(self.used_sources_from_cmt[cmt]) < 14: self.used_sources_from_cmt[cmt].add(source) return source else: continue return None 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 elif mmcm_pll_dir == 'NONE': return False else: assert False, mmcm_pll_dir def read_todo(): dsts = {} with open(os.path.join('..', 'todo_all.txt')) as f: for l in f: tile_type, dst, src = l.strip().split('.') if dst not in dsts: dsts[dst] = set() dsts[dst].add(src) return dsts def need_int_connections(todos): for srcs in todos.values(): for src in srcs: if re.search('INT_._.', src): return True return False def bufhce_in_todo(todos, site): if 'BUFHCE' in site: # CLK_HROW_CK_MUX_OUT_R9 -> X1Y9 # CLK_HROW_CK_MUX_OUT_L11 -> X0Y35 x, y = BUFHCE_XY_FUN(site) y = y % 12 if x == 0: lr = 'L' elif x == 1: lr = 'R' else: assert False, x return 'CLK_HROW_CK_MUX_OUT_{lr}{y}'.format(lr=lr, y=y) in todos else: return True def need_gclk_connection(todos, site): x, y = BUFGCTRL_XY_FUN(site) assert x == 0 src_wire = 'CLK_HROW_R_CK_GCLK{}'.format(y) for srcs in todos.values(): if src_wire in srcs: return True return False def only_gclk_left(todos): for srcs in todos.values(): for src in srcs: if 'GCLK' not in src: return False return True def main(): """ BUFHCE's can be driven from: MMCME2_ADV PLLE2_ADV BUFGCTRL Local INT connect PS7 (Zynq) """ print(''' // SEED={} module top(); '''.format(os.getenv('SEED'))) is_zynq = os.getenv('XRAY_DATABASE') == 'zynq7' clock_sources = ClockSources() site_to_cmt = dict(read_site_to_cmt()) if is_zynq: pss_clocks = list(read_pss_clocks()) # To ensure that all left or right sources are used, sometimes only MMCM/PLL # sources are allowed. The force of ODD/EVEN/BOTH further biases the # clock sources to the left or right column inputs. mmcm_pll_only = random.randint(0, 1) mmcm_pll_dir = random.choice(('ODD', 'EVEN', 'BOTH', 'NONE')) todos = read_todo() if only_gclk_left(todos): mmcm_pll_dir = 'NONE' if not mmcm_pll_only: if need_int_connections(todos): for _ in range(10): clock_sources.add_clock_source('one', 'ANY') clock_sources.add_clock_source('zero', 'ANY') print(""" wire zero = 0; wire one = 1;""") for loc, _, 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], loc) print( """ wire {c0}, {c1}, {c2}, {c3}, {c4}, {c5}; (* KEEP, DONT_TOUCH, LOC = "{site}" *) MMCME2_ADV pll_{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 loc, _, site in gen_sites('PLLE2_ADV'): pll_clocks = [ 'pll_clock_{site}_{idx}'.format(site=site, idx=idx) for idx in range(6) ] 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], loc) print( """ wire {c0}, {c1}, {c2}, {c3}, {c4}, {c5}; (* KEEP, DONT_TOUCH, LOC = "{site}" *) PLLE2_ADV pll_{site} ( .CLKOUT0({c0}), .CLKOUT1({c1}), .CLKOUT2({c2}), .CLKOUT3({c3}), .CLKOUT4({c4}), .CLKOUT5({c5}) ); """.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], )) for loc, _, site in gen_sites('BUFR'): clock_sources.add_bufg_clock_source( 'O_{site}'.format(site=site), site_to_cmt[site], loc) print( """ wire O_{site}; (* KEEP, DONT_TOUCH, LOC = "{site}" *) BUFR bufr_{site} ( .O(O_{site}) );""".format(site=site)) if is_zynq: # FCLK clocks. Those are generated by the PS and go directly to one of # the CLK_HROW tile. clocks = [ "PSS_FCLKCLK0", "PSS_FCLKCLK1", "PSS_FCLKCLK2", "PSS_FCLKCLK3", ] loc, _, site = next(gen_sites('PS7')) print("") # Add clock sources and generate wires for wire in clocks: clock_info = [d for d in pss_clocks if d["pin"] == wire][0] # CMT tile cmt_tile = clock_info["tile"] cmt_loc = get_cmt_loc(cmt_tile) # Add only if the input wire is in the todo list dsts = [k for k, v in todos.items() if clock_info["wire"] in v] if len(dsts) > 0: # Wire source clock region. The PS7 is always left of the # CLK_HROW tile, but it does not matter here. regions = clock_info["clock_regions"].split() regions = sorted([(int(r[1]), int(r[3])) for r in regions]) # Add the clock source cmt = "X{}Y{}".format(regions[0][0], regions[0][1]) clock_sources.add_clock_source(wire, cmt, cmt_loc) print(" wire {};".format(wire)) print( """ (* KEEP, DONT_TOUCH, LOC = "{site}" *) PS7 ps7_{site} ( .FCLKCLK({{{fclk3}, {fclk2}, {fclk1}, {fclk0}}}) ); """.format( site=site, fclk0=clocks[0], fclk1=clocks[1], fclk2=clocks[2], fclk3=clocks[3])) luts = LutMaker() bufhs = StringIO() bufgs = StringIO() gclks = [] for _, _, site in sorted(gen_sites("BUFGCTRL"), key=lambda x: BUFGCTRL_XY_FUN(x[2])): wire_name = 'gclk_{}'.format(site) gclks.append(wire_name) include_source = True if mmcm_pll_only: include_source = False elif only_gclk_left(todos): include_source = need_gclk_connection(todos, site) if include_source: clock_sources.add_clock_source(wire_name, 'ANY') print(""" wire {wire_name}; """.format(wire_name=wire_name)) print( """ wire I1_{site}; wire I0_{site}; (* KEEP, DONT_TOUCH, LOC = "{site}" *) BUFGCTRL bufg_{site} ( .O({wire_name}), .S1({s1net}), .S0({s0net}), .IGNORE1({ignore1net}), .IGNORE0({ignore0net}), .I1(I1_{site}), .I0(I0_{site}), .CE1({ce1net}), .CE0({ce0net}) ); """.format( site=site, wire_name=wire_name, s1net=luts.get_next_output_net(), s0net=luts.get_next_output_net(), ignore1net=luts.get_next_output_net(), ignore0net=luts.get_next_output_net(), ce1net=luts.get_next_output_net(), ce0net=luts.get_next_output_net(), ), file=bufgs) any_bufhce = False for tile_name, sites in gen_bufhce_sites(): for site in sites: if not bufhce_in_todo(todos, site): continue any_bufhce = True print( """ wire I_{site}; (* KEEP, DONT_TOUCH, LOC = "{site}" *) BUFHCE buf_{site} ( .I(I_{site}) ); """.format(site=site, ), file=bufhs) if random.random() > .05: wire_name = clock_sources.get_random_source(site_to_cmt[site]) if wire_name is None: continue print( """ assign I_{site} = {wire_name};""".format( site=site, wire_name=wire_name, ), file=bufhs) if not any_bufhce: for tile_name, sites in gen_bufhce_sites(): for site in sites: print( """ (* KEEP, DONT_TOUCH, LOC = "{site}" *) BUFHCE #( .INIT_OUT({INIT_OUT}), .CE_TYPE({CE_TYPE}), .IS_CE_INVERTED({IS_CE_INVERTED}) ) buf_{site} ( .I({wire_name}) ); """.format( INIT_OUT=random.randint(0, 1), CE_TYPE=verilog.quote( random.choice(('SYNC', 'ASYNC'))), IS_CE_INVERTED=random.randint(0, 1), site=site, wire_name=gclks[0], )) break break for l in luts.create_wires_and_luts(): print(l) print(bufhs.getvalue()) print(bufgs.getvalue()) used_only = random.random() < .25 for loc, tile_type, site in sorted(gen_sites("BUFGCTRL"), key=lambda x: BUFGCTRL_XY_FUN(x[2])): if random.randint(0, 1): wire_name = clock_sources.get_bufg_source( loc, tile_type, site, todos, 1, used_only) if wire_name is not None: print( """ assign I1_{site} = {wire_name};""".format( site=site, wire_name=wire_name, )) if random.randint(0, 1): wire_name = clock_sources.get_bufg_source( loc, tile_type, site, todos, 0, used_only) if wire_name is not None: print( """ assign I0_{site} = {wire_name};""".format( site=site, wire_name=wire_name, )) print("endmodule") if __name__ == '__main__': main()