| """ Create segment definitions for prjxray interconnect. | |
| """ | |
| import argparse | |
| from prjxray.db import Database | |
| import re | |
| from collections import OrderedDict | |
| def add_segment_wires(db, tile, wires, segments): | |
| """ Adds to the set segment wires. """ | |
| tile = db.get_tile_type(tile) | |
| for pip in tile.get_pips(): | |
| # Ignore wires that sink to a site | |
| if 'GCLK' in pip.net_to: | |
| segments['CLKFEED'].add(pip.net_to) | |
| segments['HCLK_COLUMNS'].add(pip.net_from) | |
| continue | |
| elif 'IMUX' in pip.net_to or \ | |
| 'CTRL' in pip.net_to or \ | |
| 'CLK' in pip.net_to or \ | |
| re.match('BYP[0-7]', pip.net_to) or \ | |
| re.match('FAN[0-7]', pip.net_to): | |
| segments['INPINFEED'].add(pip.net_to) | |
| continue | |
| wires.add(pip.net_to) | |
| if not pip.is_directional: | |
| wires.add(pip.net_from) | |
| def reduce_wires_to_segments(wires, segments): | |
| """ Reduce wire names to segment definitions. | |
| For purposes of creating the routing heuristic, it is assumed that if two | |
| source wires share a prefix, they can be considered segments for the | |
| purposes of the routing heuristic. | |
| This is definitely true for wires like SR1BEG1 or LV18. | |
| This may apply to the local fanout wires like GFAN0 or FAN_BOUNCE0. | |
| """ | |
| WIRE_PARTS = re.compile('^(.*?)([0-9]+)$') | |
| for wire in wires: | |
| m = WIRE_PARTS.match(wire) | |
| assert m is not None | |
| segment = m.group(1) | |
| if segment not in segments: | |
| segments[segment] = set() | |
| segments[segment].add(wire) | |
| def get_segments(db): | |
| """ Return segment approximation for device. | |
| Returns | |
| ------- | |
| segments : dict of str to list of str | |
| Each key is a segment, with the elements of the values as the wires | |
| that belong to that segment type. | |
| """ | |
| wires = set() | |
| segments = OrderedDict() | |
| for segment in [ | |
| 'INPINFEED', | |
| 'CLKFEED', | |
| 'OUTPINFEED', | |
| 'BRAM_CASCADE', | |
| 'BUFG_CASCADE', | |
| 'GCLK', | |
| 'GCLK_OUTPINFEED', | |
| 'GCLK_INPINFEED', | |
| 'HCLK_CK_IN', | |
| 'BRAM_IMUX', | |
| 'HCLK_COLUMNS', | |
| 'HCLK_ROWS', | |
| 'HCLK_ROW_TO_COLUMN', | |
| 'CCIO_OUTPINFEED', | |
| 'CCIO_CLK_IN', | |
| 'PLL_OUTPINFEED', | |
| 'PLL_INPINFEED', | |
| ]: | |
| segments[segment] = set() | |
| for tile in ['INT_L', 'INT_R']: | |
| add_segment_wires(db, tile, wires, segments) | |
| reduce_wires_to_segments(wires, segments) | |
| return segments | |
| GCLK_MATCH = re.compile('GCLK_(L_)?B[0-9]+') | |
| LOGIC_OUT_MATCH = re.compile('LOGIC_OUTS') | |
| BRAM_CASCADE = re.compile('BRAM_CASC(OUT|IN|INBOT)_') | |
| HCLK_R2C_MATCH = re.compile('HCLK_CK_(OUTIN|INOUT)') | |
| HCLK_CK_IN = re.compile('HCLK_CK_IN[0-9]+') | |
| CCIO_CLK_IN = re.compile('HCLK_CCIO[0-3]') | |
| class SegmentWireMap(object): | |
| """ SegmentWireMap provides a way to map node wires to segments. | |
| The default segment should be used for non-routing wires, e.g. the wires | |
| that go from the interconnect switch box to sites. This default segment | |
| will generally be low delay extremely short wires. | |
| Routing segments (e.g. LH = 12-length horizontal wire) will have | |
| specialized lookahead entries, and should be given their own segment. | |
| """ | |
| def __init__(self, default_segment, db): | |
| self.default_segment = default_segment | |
| self.segments = get_segments(db) | |
| self.wire_to_segment = {} | |
| for segment, wires in self.segments.items(): | |
| for wire in wires: | |
| assert wire not in self.wire_to_segment | |
| self.wire_to_segment[wire] = segment | |
| def get_segment_for_wires(self, wires): | |
| wires = list(wires) | |
| segments = set() | |
| # BRAM_IMUX cannot use INPINFEED because it doesn't obey typically | |
| # connection box definitions. | |
| is_bram_imux = False | |
| for wire in wires: | |
| if 'BRAM_IMUX' in wire: | |
| is_bram_imux = True | |
| break | |
| for wire in wires: | |
| if wire in self.wire_to_segment and not is_bram_imux: | |
| segments.add(self.wire_to_segment[wire]) | |
| m = LOGIC_OUT_MATCH.match(wire) | |
| if m is not None: | |
| segments.add('OUTPINFEED') | |
| m = BRAM_CASCADE.search(wire) | |
| if m is not None: | |
| segments.add('BRAM_CASCADE') | |
| if 'CK_BUFG_CASC' in wire: | |
| segments.add('BUFG_CASCADE') | |
| m = HCLK_CK_IN.match(wire) | |
| if m is not None: | |
| segments.add('HCLK_CK_IN') | |
| if 'R_CK_GCLK' in wire: | |
| segments.add('GCLK') | |
| if 'INT_INTERFACE_LOGIC_OUTS' in wire: | |
| segments.add('OUTPINFEED') | |
| m = CCIO_CLK_IN.match(wire) | |
| if m is not None: | |
| segments.add('CCIO_CLK_IN') | |
| if wire.startswith('CLK_BUFG_CK_GCLK'): | |
| segments.add('GCLK') | |
| if 'CK_BUFHCLK' in wire: | |
| segments.add('HCLK_ROWS') | |
| if HCLK_R2C_MATCH.match(wire): | |
| segments.add('HCLK_ROW_TO_COLUMN') | |
| if wire.startswith('BRAM_IMUX'): | |
| segments.add('BRAM_IMUX') | |
| assert len(segments) <= 1, (wires, segments) | |
| if len(segments) == 1: | |
| return list(segments)[0] | |
| else: | |
| return self.default_segment | |
| def get_segments(self): | |
| return self.segments.keys() | |
| def main(): | |
| parser = argparse.ArgumentParser(description=__doc__) | |
| parser.add_argument('--db_root', required=True) | |
| parser.add_argument('--part', required=True) | |
| args = parser.parse_args() | |
| db = Database(args.db_root, args.part) | |
| segments = get_segments(db) | |
| for segment in sorted(segments): | |
| print('Segment = {}'.format(segment)) | |
| print('Wires:') | |
| for wire in sorted(segments[segment]): | |
| print(' {}'.format(wire)) | |
| if __name__ == '__main__': | |
| main() |