|
| 1 | +import json |
| 2 | +import logging |
| 3 | +import re |
| 4 | +import sys |
| 5 | + |
| 6 | +from amaranth import Shape |
| 7 | +from pathlib import Path |
| 8 | +from pprint import pformat |
| 9 | + |
| 10 | +from chipflow_lib.platforms.iostream import PORT_LAYOUT_SCHEMA |
| 11 | +from chipflow_lib.cli import _parse_config, _get_cls_by_reference |
| 12 | + |
| 13 | +logger = logging.getLogger(__name__) |
| 14 | + |
| 15 | +def has_consecutive_numbers(lst): |
| 16 | + if not lst: |
| 17 | + return False |
| 18 | + lst.sort() |
| 19 | + return all(lst[i] + 1 == lst[i + 1] for i in range(len(lst) - 1)) |
| 20 | + |
| 21 | +def strip_pin_suffix(name): |
| 22 | + """Strip _i, _o, and _oe suffixes from a pin name. |
| 23 | + |
| 24 | + Args: |
| 25 | + name: Pin name string |
| 26 | + |
| 27 | + Returns: |
| 28 | + Name with suffix removed |
| 29 | + """ |
| 30 | + return re.sub(r'(_i|_o|_oe)$', '', name) |
| 31 | + |
| 32 | +def find_consecutive_sequence(lst, n): |
| 33 | + """Find the next sequence of n consecutive numbers in a sorted list. |
| 34 | + |
| 35 | + Args: |
| 36 | + lst: Sorted list of numbers |
| 37 | + n: Length of consecutive sequence to find |
| 38 | + |
| 39 | + Returns: |
| 40 | + A slice indexing the first sequence of n consecutive numbers found within the given list |
| 41 | + or None if no such sequence exists |
| 42 | + """ |
| 43 | + if not lst or len(lst) < n: |
| 44 | + return None |
| 45 | + |
| 46 | + for i in range(len(lst) - n + 1): |
| 47 | + if all(lst[i + j] + 1 == lst[i + j + 1] for j in range(n - 1)): |
| 48 | + return slice(i,i + n) |
| 49 | + return None |
| 50 | + |
| 51 | +def signature_width(signature): |
| 52 | + width = 0 |
| 53 | + obj = signature.create() |
| 54 | + for a,b,c in signature.flatten(obj): |
| 55 | + shape = Shape.cast(b.shape) |
| 56 | + width += shape.width |
| 57 | + return width |
| 58 | + |
| 59 | +def member_width(member): |
| 60 | + if member.is_signature: |
| 61 | + return signature_width(member.signature) |
| 62 | + else: |
| 63 | + shape = Shape.cast(member.shape) |
| 64 | + return shape.width |
| 65 | + |
| 66 | +MATCH_TRIPLE = re.compile(r'(_i|_o|_oe)$') |
| 67 | + |
| 68 | +def coalesce_triples(sig: dict) -> None: |
| 69 | + if sig['type'] == 'port': |
| 70 | + pass |
| 71 | + |
| 72 | +def count_pins(port): |
| 73 | + width = 0 |
| 74 | + for _, v in port.items(): |
| 75 | + if type(v) is dict: |
| 76 | + width += count_pins(v) |
| 77 | + else: |
| 78 | + width += v[1] |
| 79 | + return width |
| 80 | + |
| 81 | + |
| 82 | +def allocate_pins(name, port, pins): |
| 83 | + pin_map = {} |
| 84 | + logger.debug(f"allocate_pins: name={name}, port={port}, pins={pins}") |
| 85 | + for k, v in port.items(): |
| 86 | + n = '_'.join([name,k]) |
| 87 | + logger.debug(f"{k},{v},{n}") |
| 88 | + if type(v) is dict: |
| 89 | + _map, pins = allocate_pins(n, v, pins) |
| 90 | + pin_map |= _map |
| 91 | + logger.debug(f"{pin_map},{_map}") |
| 92 | + else: |
| 93 | + direction, width = v |
| 94 | + if width == 1: |
| 95 | + pin_map[n] = {'pin':pins[0], 'type':direction} |
| 96 | + else: |
| 97 | + pin_map[n] = {'start':pins[0], |
| 98 | + 'end':pins[width-1], |
| 99 | + 'type':direction} |
| 100 | + logger.debug(f"pin_map[{n}]={pin_map[n]}") |
| 101 | + pins = pins[width:] |
| 102 | + return pin_map, pins |
| 103 | + |
| 104 | + |
| 105 | +def assign_pins(ports, old_lock, unallocated): |
| 106 | + old_ports = old_lock["ports"] if "ports" in old_lock else {} |
| 107 | + old_map = old_lock["map"]["ports"] if "map" in old_lock else {} |
| 108 | + pin_map = {} |
| 109 | + |
| 110 | + # we try to keep pins together for each port |
| 111 | + for k,v in ports.items(): |
| 112 | + logger.debug(f"Port {k}:\n{pformat(v)}") |
| 113 | + width = count_pins(v) |
| 114 | + logger.debug(f"member {k} total width = {width}") |
| 115 | + |
| 116 | + if k in old_ports: |
| 117 | + logger.debug(f"{k} already has pins defined") |
| 118 | + if width != count_pins(old_ports[k]): |
| 119 | + raise Exception("Port {k} has changed size. Use -c to allocate new pins non-contigously") |
| 120 | + _map = old_map[k] |
| 121 | + old_pins = [v['pin'] for v in old_map[k].values()] |
| 122 | + logger.debug("old pins = {old_pins}") |
| 123 | + unallocated = sorted(list(set(unallocated) - set(old_pins))) |
| 124 | + else: |
| 125 | + pins = find_consecutive_sequence(unallocated, width) |
| 126 | + logger.debug(f"allocated range: {pins}") |
| 127 | + if pins is None: |
| 128 | + raise Exception(f"Error allocating pins for {k},{v} in {ports}") |
| 129 | + |
| 130 | + |
| 131 | + newpins = unallocated[pins] |
| 132 | + unallocated[pins] = [] |
| 133 | + _map,_ = allocate_pins(k, v, newpins) |
| 134 | + |
| 135 | + pin_map[k] = _map |
| 136 | + return pin_map |
| 137 | + |
| 138 | + |
| 139 | +logging.basicConfig(level=logging.DEBUG, stream=sys.stdout) |
| 140 | +config = _parse_config() |
| 141 | +used_pins = set() |
| 142 | +pin_map = {} |
| 143 | +lockfile = Path('pins.lock') |
| 144 | +if lockfile.exists(): |
| 145 | + with open(lockfile) as f: |
| 146 | + old_lock = json.load(f) |
| 147 | + old_map = old_lock["map"] |
| 148 | +else: |
| 149 | + old_lock = {} |
| 150 | + old_map = {} |
| 151 | + |
| 152 | +for d, default in [("pads", "i"), ("power","pwr")]: |
| 153 | + logger.debug(f"Checking [chipflow.silicon.{d}]:") |
| 154 | + pin_map[d] = {} |
| 155 | + for k, v in config["chipflow"]["silicon"][d].items(): |
| 156 | + pin = int(v['loc']) |
| 157 | + used_pins.add(pin) |
| 158 | + if d in old_map and k in old_map[d] and old_map[d][k]['pin'] != pin: |
| 159 | + print(f"chipflow.toml conflicts with pins.lock: " |
| 160 | + f"{k} had pin {old_map[d][k]}, now {pin}.") |
| 161 | + exit(1) |
| 162 | + pin_map[d][k] = { |
| 163 | + 'pin': pin, |
| 164 | + 'type': v['type'] if 'type' in v else None} |
| 165 | + |
| 166 | + |
| 167 | +logger.info(f'Pins in use:\n{pformat(sorted(used_pins))}') |
| 168 | + |
| 169 | +unallocated = sorted(set(range(144)) - used_pins) |
| 170 | + |
| 171 | +ports = {} |
| 172 | +top_components = config["chipflow"]["top"].items() |
| 173 | +component_configs = {} |
| 174 | +top = {} |
| 175 | + |
| 176 | +for name, conf in top_components: |
| 177 | + if '.' in name: |
| 178 | + assert conf is dict |
| 179 | + logger.debug("Config found for {name}") |
| 180 | + component_configs[name.split('.')[0]] = conf |
| 181 | + |
| 182 | +for name, ref in top_components: |
| 183 | + cls = _get_cls_by_reference(ref, context=f"top component: {name}") |
| 184 | + if name in component_configs: |
| 185 | + top[name] = cls(component_configs[name]) |
| 186 | + else: |
| 187 | + top[name] = cls() |
| 188 | + metadata = top[name].metadata.as_json() |
| 189 | + logger.debug(f"{name}.metadata = {metadata}") |
| 190 | + ports |= metadata['interface']['annotations'][PORT_LAYOUT_SCHEMA]['ports'] |
| 191 | + |
| 192 | +logger.debug(f"All ports: {list(ports.keys())}") |
| 193 | + |
| 194 | +pin_map["ports"] = assign_pins(ports, old_lock, unallocated) |
| 195 | + |
| 196 | +with open('pins.lock', 'w') as f: |
| 197 | + newlock = {'map': pin_map, |
| 198 | + 'ports': ports} |
| 199 | + |
| 200 | + json.dump(newlock, f, indent=2, sort_keys=True) |
| 201 | +# |
| 202 | +# obj = soc_top.signature.create() |
| 203 | +# for a,b,c in soc_top.signature.flatten(obj): |
| 204 | +# pin_name = '_'.join(a) |
| 205 | +# iface = a[0] |
| 206 | +# shape = Shape.cast(b.shape) |
| 207 | +# count = 0 |
| 208 | +# for i in pin_map[iface]['pins']: |
| 209 | +# pin_name_i = f'{pin_name}_{count}' |
| 210 | +# count += 1 |
| 211 | +# # print(f'pin name {pin_name} {b.flow}') |
| 212 | +# pin_map[iface]['members'][pin_name_i]={'pin':i, 'dir':b.flow} |
| 213 | +# |
| 214 | +# # pprint(pin_map) |
0 commit comments