blob: e93e073247aa305135aa728efc740bfaffacb94d [file] [log] [blame] [edit]
#!/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()