| #!/usr/bin/env python3 | |
| # -*- coding: utf-8 -*- | |
| # | |
| # Copyright (C) 2019-2022 F4PGA Authors | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """ | |
| Convert a PCF file into a VPR io.place file. | |
| """ | |
| from argparse import ArgumentParser, FileType | |
| from pathlib import Path | |
| from csv import DictReader as csv_DictReader | |
| from sys import stdout, stderr, exit as sys_exit | |
| from json import dump as json_dump, load as json_load | |
| from f4pga.utils.vpr_io_place import IoPlace | |
| from f4pga.utils.pcf import parse_simple_pcf | |
| def p_main(blif, map, net, pcf=None, output=stdout, iostandard_defs_file=None, iostandard="LVCMOS33", drive=12): | |
| io_place = IoPlace() | |
| io_place.read_io_list_from_eblif(blif) | |
| io_place.load_block_names_from_net_file(net) | |
| # Map of pad names to VPR locations. | |
| pad_map = {} | |
| for pin_map_entry in csv_DictReader(map): | |
| pad_map[pin_map_entry["name"]] = ( | |
| ( | |
| int(pin_map_entry["x"]), | |
| int(pin_map_entry["y"]), | |
| int(pin_map_entry["z"]), | |
| ), | |
| pin_map_entry["is_output"], | |
| pin_map_entry["iob"], | |
| pin_map_entry["real_io_assoc"], | |
| ) | |
| iostandard_defs = {} | |
| # Load iostandard constraints. This is a temporary workaround that allows | |
| # to pass them into fasm2bels. As soon as there is support for XDC this | |
| # will not be needed anymore. | |
| # If there is a JSON file with the same name as the PCF file then it is | |
| # loaded and used as iostandard constraint source NOT for the design but | |
| # to be used in fasm2bels. | |
| iostandard_constraints = {} | |
| if pcf is not None: | |
| fname = Path(pcf.name.replace(".pcf", ".json")) | |
| if fname.is_file(): | |
| with fname.open("r") as fp: | |
| iostandard_constraints = json_load(fp) | |
| net_to_pad = io_place.net_to_pad | |
| if pcf is not None: | |
| net_to_pad |= set((constr.net, constr.pad) for constr in parse_simple_pcf(pcf)) | |
| # Check for conflicting pad constraints | |
| net_to_pad_map = dict() | |
| for (net, pad) in net_to_pad: | |
| if net not in net_to_pad_map: | |
| net_to_pad_map[net] = pad | |
| elif pad != net_to_pad_map[net]: | |
| print( | |
| f"ERROR: Conflicting pad constraints for net {net}:\n{pad}\n{net_to_pad_map[net]}", | |
| file=stderr, | |
| ) | |
| sys_exit(1) | |
| # Constrain nets | |
| for net, pad in net_to_pad: | |
| if not io_place.is_net(net): | |
| nets = "\n".join(io_place.get_nets()) | |
| print( | |
| f"ERROR: Constrained net {net} is not in available netlist:\n{nets}", | |
| file=stderr, | |
| ) | |
| sys_exit(1) | |
| if pad not in pad_map: | |
| pads = "\n".join(sorted(pad_map.keys())) | |
| print( | |
| f"ERROR: Constrained pad {pad} is not in available pad map:\n{pads}", | |
| file=stderr, | |
| ) | |
| sys_exit(1) | |
| loc, is_output, iob, real_io_assoc = pad_map[pad] | |
| io_place.constrain_net(net_name=net, loc=loc, comment="set_property LOC {} [get_ports {{{}}}]".format(pad, net)) | |
| if real_io_assoc == "True": | |
| iostandard_defs[iob] = ( | |
| iostandard_constraints[pad] | |
| if pad in iostandard_constraints | |
| else ({"DRIVE": drive, "IOSTANDARD": iostandard} if is_output else {"IOSTANDARD": iostandard}) | |
| ) | |
| io_place.output_io_place(output) | |
| # Write iostandard definitions | |
| if iostandard_defs_file is not None: | |
| with Path(iostandard_defs_file).open("w") as f: | |
| json_dump(iostandard_defs, f, indent=2) | |
| def main( | |
| blif, | |
| map, | |
| net, | |
| pcf=None, | |
| output=None, | |
| iostandard_defs_file=None, | |
| iostandard="LVCMOS33", | |
| drive=12, | |
| ): | |
| p_main( | |
| blif=Path(blif).open("r"), | |
| map=Path(map).open("r"), | |
| net=Path(net).open("r"), | |
| pcf=None if pcf is None else pcf, | |
| output=stdout if output is None else Path(output).open("w"), | |
| iostandard_defs_file=iostandard_defs_file, | |
| iostandard=iostandard, | |
| drive=drive, | |
| ) | |
| if __name__ == "__main__": | |
| parser = ArgumentParser(description="Convert a PCF file into a VPR io.place file.") | |
| parser.add_argument("--pcf", "-p", "-P", type=FileType("r"), required=False, help="PCF input file") | |
| parser.add_argument("--blif", "-b", type=FileType("r"), required=True, help="BLIF / eBLIF file") | |
| parser.add_argument("--map", "-m", "-M", type=FileType("r"), required=True, help="Pin map CSV file") | |
| parser.add_argument("--output", "-o", "-O", type=FileType("w"), default=stdout, help="The output io.place file") | |
| parser.add_argument("--iostandard_defs", help="(optional) Output IOSTANDARD def file") | |
| parser.add_argument( | |
| "--iostandard", | |
| default="LVCMOS33", | |
| help="Default IOSTANDARD to use for pins", | |
| ) | |
| parser.add_argument( | |
| "--drive", | |
| type=int, | |
| default=12, | |
| help="Default drive to use for pins", | |
| ) | |
| parser.add_argument("--net", "-n", type=FileType("r"), required=True, help="top.net file") | |
| args = parser.parse_args() | |
| p_main( | |
| blif=args.blif, | |
| map=args.map, | |
| net=args.net, | |
| pcf=args.pcf, | |
| output=args.output, | |
| iostandard_defs_file=args.iostandard_defs, | |
| iostandard=args.iostandard, | |
| drive=args.drive, | |
| ) |