| #!/usr/bin/env python3 | |
| """ | |
| This script processes an SDC constraint file and replaces all references to | |
| pin names in place of net names with actual net names that are mapped to those | |
| pins. Pin-to-net mapping is read from a PCF constraint file. | |
| Note that this script does not check whether the net names in the input PCF | |
| file are actually present in a design and whether the pin names are valid. | |
| """ | |
| import argparse | |
| import re | |
| import csv | |
| from lib.parse_pcf import parse_simple_pcf, PcfIoConstraint | |
| from eblif import parse_blif | |
| # ============================================================================= | |
| RE_INDICES = re.compile(r"(?P<name>\S+)\[(?P<i0>[0-9]+):(?P<i1>[0-9]+)\]") | |
| def collect_eblif_nets(eblif): | |
| """ | |
| Collects all net names that are present in the given parsed BLIF/EBLIF | |
| netlist. Returns a set of net names | |
| """ | |
| nets = set() | |
| # First add all input and output nets | |
| for key in ["inputs", "outputs", "clocks"]: | |
| nets |= set(eblif.get(key, [])) | |
| # Look for cell-to-cell connections | |
| for cell in eblif.get("subckt", []): | |
| for conn in cell["args"][1:]: | |
| port, net = conn.split("=") | |
| nets.add(net) | |
| for cell in eblif.get("names", []): | |
| nets |= set(cell["args"]) | |
| for cell in eblif.get("latch", []): | |
| args = cell["args"] | |
| assert len(args) >= 2 | |
| nets.add(args[0]) | |
| nets.add(args[1]) | |
| if len(args) >= 4: | |
| nets.add(args[3]) | |
| return nets | |
| def expand_indices(items): | |
| """ | |
| Expands index ranges for each item in the given iterable. For example | |
| converts a single item like: "pin[1:0]" into two "pin[1]" and "pin[0]". | |
| """ | |
| new_items = [] | |
| # Process each item | |
| for item in items: | |
| # Match using regex. If there is no match then pass the item through | |
| match = RE_INDICES.fullmatch(item) | |
| if not match: | |
| new_items.append(item) | |
| continue | |
| name = match.group("name") | |
| i0 = int(match.group("i0")) | |
| i1 = int(match.group("i1")) | |
| # Generate individual indices | |
| if i0 == i1: | |
| indices = [i0] | |
| elif i0 < i1: | |
| indices = [i for i in range(i0, i1 + 1)] | |
| elif i0 > i1: | |
| indices = [i for i in range(i1, i0 + 1)] | |
| # Generate names | |
| for i in indices: | |
| new_items.append("{}[{}]".format(name, i)) | |
| return new_items | |
| def process_get_ports(match, pad_to_net, valid_pins=None, valid_nets=None): | |
| """ | |
| Used as a callback in re.sub(). Responsible for substition of net names | |
| for pin names. | |
| When the valid_pin list is provided the function checks if a name specified | |
| in SDC refers to any of them. If it is so and the pin name is not present | |
| in the PCF mapping an error is thrown - it is not possible to map the pin | |
| to a net. | |
| When no valid_pin list is known then there is no possibility to check if | |
| a given name refers to a pin or net. Hence if it is present in the PCF | |
| mapping it is considered a pin name and gets remapped to a net accordingly. | |
| Otherwise it is just passed through. | |
| Lastly, if the valid_nets list is provided the function checks if the | |
| final net name is valid and throws an error if it is not. | |
| """ | |
| # Strip any spurious whitespace chars | |
| arg = match.group("arg").strip() | |
| # A helper mapping func. | |
| def map_pad_to_net(pad): | |
| # Unescape square brackets | |
| pad = pad.replace("\\[", "[") | |
| pad = pad.replace("\\]", "]") | |
| # If we have a valid pins list and the pad is in the map then re-map it | |
| if valid_pins and pad in valid_pins: | |
| assert pad in pad_to_net, \ | |
| "The pin '{}' is not associated with any net in PCF".format(pad) | |
| net = pad_to_net[pad].net | |
| # If we don't have a valid pins list then just look up in the PCF | |
| # mapping. | |
| elif not valid_pins and pad in pad_to_net: | |
| net = pad_to_net[pad].net | |
| # Otherwise it looks like its a direct reference to a net so pass it | |
| # through. | |
| else: | |
| net = pad | |
| # If we have a valit net list then validate the net name | |
| if valid_nets: | |
| assert net in valid_nets, \ | |
| "The net '{}' is not present in the netlist".format(net) | |
| # Escape square brackets | |
| net = net.replace("[", "\\[") | |
| net = net.replace("]", "\\]") | |
| return net | |
| # We have a list of ports, map each of them individually | |
| if arg[0] == "{" and arg[-1] == "}": | |
| arg = arg[1:-1].split() | |
| nets = ", ".join([map_pad_to_net(p) for p in arg]) | |
| new_arg = "{{{}}}".format(nets) | |
| # We have a single port, map it directly | |
| else: | |
| new_arg = map_pad_to_net(arg) | |
| # Format the new statement | |
| return "[get_ports {}]".format(new_arg) | |
| # ============================================================================= | |
| def main(): | |
| # Parse arguments | |
| parser = argparse.ArgumentParser( | |
| description=__doc__, | |
| formatter_class=argparse.RawDescriptionHelpFormatter | |
| ) | |
| parser.add_argument( | |
| "--sdc-in", type=str, required=True, help="Input SDC file" | |
| ) | |
| parser.add_argument( | |
| "--pcf", type=str, required=True, help="Input PCF file" | |
| ) | |
| parser.add_argument( | |
| "--sdc-out", type=str, required=True, help="Output SDC file" | |
| ) | |
| parser.add_argument( | |
| "--eblif", type=str, default=None, help="Input EBLIF netlist file" | |
| ) | |
| parser.add_argument( | |
| "--pin-map", type=str, default=None, help="Input CSV pin map file" | |
| ) | |
| args = parser.parse_args() | |
| # Read the input PCF file | |
| with open(args.pcf, "r") as fp: | |
| pcf_constraints = list(parse_simple_pcf(fp)) | |
| # Build a pad-to-net map | |
| pad_to_net = {} | |
| for constr in pcf_constraints: | |
| if isinstance(constr, PcfIoConstraint): | |
| assert constr.pad not in pad_to_net, \ | |
| "Multiple nets constrained to pin '{}'".format(constr.pad) | |
| pad_to_net[constr.pad] = constr | |
| # Read the input SDC file | |
| with open(args.sdc_in, "r") as fp: | |
| sdc = fp.read() | |
| # Read the input EBLIF file, extract all valid net names from it | |
| valid_nets = None | |
| if args.eblif is not None: | |
| with open(args.eblif, "r") as fp: | |
| eblif = parse_blif(fp) | |
| valid_nets = collect_eblif_nets(eblif) | |
| # Reat the input pinmap CSV file, extract valid pin names from it | |
| valid_pins = None | |
| if args.pin_map is not None: | |
| with open(args.pin_map, "r") as fp: | |
| reader = csv.DictReader(fp) | |
| csv_data = list(reader) | |
| valid_pins = [line["mapped_pin"] for line in csv_data] | |
| valid_pins = set(expand_indices(valid_pins)) | |
| # Process the SDC | |
| def sub_cb(match): | |
| return process_get_ports(match, pad_to_net, valid_pins, valid_nets) | |
| sdc_lines = sdc.splitlines() | |
| for i in range(len(sdc_lines)): | |
| if not sdc_lines[i].strip().startswith("#"): | |
| sdc_lines[i] = re.sub( | |
| r"\[\s*get_ports\s+(?P<arg>.*)\]", sub_cb, sdc_lines[i] | |
| ) | |
| # Write the output SDC file | |
| sdc = "\n".join(sdc_lines) + "\n" | |
| with open(args.sdc_out, "w") as fp: | |
| fp.write(sdc) | |
| # ============================================================================= | |
| if __name__ == "__main__": | |
| main() |