| """ 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() |