diff --git a/bfasst/flows/vivado_bit_analysis.py b/bfasst/flows/vivado_bit_analysis.py index 4c8b3a681..5757ff260 100644 --- a/bfasst/flows/vivado_bit_analysis.py +++ b/bfasst/flows/vivado_bit_analysis.py @@ -13,9 +13,10 @@ class VivadoBitAnalysis(Flow): """Flow to reverse a netlist from a bitstream using x-ray.""" - def __init__(self, design, synth_options=""): + def __init__(self, design, synth_options="", logging_level="INFO"): # pylint: disable=duplicate-code super().__init__(design) + self.logging_level = logging_level self.vivado_synth_tool = VivadoSynth(self, design, synth_options=synth_options) self.vivado_impl_tool = VivadoImpl( self, @@ -30,7 +31,10 @@ def __init__(self, design, synth_options=""): bitstream=self.vivado_impl_tool.outputs["bitstream"], ) self.netlist_cleanup_tool = NetlistCleanup( - self, design, rev_netlist=self.xrev_tool.outputs["rev_netlist"] + self, + design, + rev_netlist=self.xrev_tool.outputs["rev_netlist"], + logging_level=self.logging_level, ) self.netlist_phys_to_logical = NetlistPhysToLogical( self, design, cleaned_netlist=self.netlist_cleanup_tool.outputs["netlist_cleaned_path"] diff --git a/bfasst/flows/vivado_phys_netlist.py b/bfasst/flows/vivado_phys_netlist.py index 5da9b15a5..c3bc3019f 100644 --- a/bfasst/flows/vivado_phys_netlist.py +++ b/bfasst/flows/vivado_phys_netlist.py @@ -10,11 +10,12 @@ class VivadoPhysNetlist(Flow): """Creates a Vivado netlist that has only physical primitives.""" - def __init__(self, design, synth_options=""): + def __init__(self, design, synth_options="", logging_level="INFO"): # pylint: disable=duplicate-code super().__init__(design) self.synth_options = VivadoPhysNetlist.add_required_synth_options(synth_options) + self.logging_level = logging_level self.vivado_synth_tool = VivadoSynth(self, design, synth_options=self.synth_options) self.vivado_impl_tool = VivadoImpl( @@ -28,6 +29,7 @@ def __init__(self, design, synth_options=""): design, impl_checkpoint=self.vivado_impl_tool.outputs["impl_dcp"], impl_edf=self.vivado_impl_tool.outputs["impl_edf"], + logging_level=self.logging_level, ) # pylint: enable=duplicate-code diff --git a/bfasst/flows/vivado_phys_netlist_cmp.py b/bfasst/flows/vivado_phys_netlist_cmp.py index bdb87bb76..3025d1a95 100644 --- a/bfasst/flows/vivado_phys_netlist_cmp.py +++ b/bfasst/flows/vivado_phys_netlist_cmp.py @@ -13,13 +13,13 @@ class VivadoPhysNetlistCmp(Flow): """Structural Comparison of physical netlist and reversed netlist""" - def __init__(self, design, synth_options="", debug=False): + def __init__(self, design, synth_options="", debug=False, logging_level="INFO"): # pylint: disable=duplicate-code super().__init__(design) self.synth_options = VivadoPhysNetlist.add_required_synth_options(synth_options) self.debug = debug - + self.logging_level = logging_level self.vivado_synth_tool = VivadoSynth(self, design, synth_options=self.synth_options) self.vivado_impl_tool = VivadoImpl( self, @@ -32,6 +32,7 @@ def __init__(self, design, synth_options="", debug=False): design, impl_edf=self.vivado_impl_tool.outputs["impl_edf"], impl_checkpoint=self.vivado_impl_tool.outputs["impl_dcp"], + logging_level=self.logging_level, ) self.xray_tool = Xray( self, @@ -46,6 +47,7 @@ def __init__(self, design, synth_options="", debug=False): golden_netlist=self.phys_netlist_tool.outputs["viv_impl_physical_v"], rev_netlist=self.xray_tool.outputs["rev_netlist"], debug=self.debug, + logging_level=self.logging_level, ) # pylint: enable=duplicate-code diff --git a/bfasst/flows/vivado_structural_error_injection.py b/bfasst/flows/vivado_structural_error_injection.py index abab257ce..afbf5a284 100644 --- a/bfasst/flows/vivado_structural_error_injection.py +++ b/bfasst/flows/vivado_structural_error_injection.py @@ -18,12 +18,13 @@ class VivadoStructuralErrorInjection(Flow): """Inject an error into a xrev netlist and run a structural compare to detect it.""" - def __init__(self, design, num_runs=100, seed=None, synth_options=""): + def __init__(self, design, num_runs=100, seed=None, synth_options="", logging_level="INFO"): # pylint: disable=duplicate-code super().__init__(design) self.design = design self.num_runs = num_runs self.seed = seed + self.logging_level = logging_level if self.seed is not None: random.seed(self.seed) @@ -41,6 +42,7 @@ def __init__(self, design, num_runs=100, seed=None, synth_options=""): design, impl_checkpoint=self.vivado_impl_tool.outputs["impl_dcp"], impl_edf=self.vivado_impl_tool.outputs["impl_edf"], + logging_level=self.logging_level, ) self.xrev_tool = Xray( self, @@ -49,7 +51,7 @@ def __init__(self, design, num_runs=100, seed=None, synth_options=""): bitstream=self.vivado_impl_tool.outputs["bitstream"], ) self.default_comparison_tool = Structural(self, design) - self.default_injection_tool = ErrorInjector(self, design) + self.default_injection_tool = ErrorInjector(self, design, logging_level) # pylint: enable=duplicate-code def create_build_snippets(self): @@ -68,6 +70,7 @@ def create_build_snippets(self): num=i, multiplier=random_seed_multiplier, reversed_netlist=self.xrev_tool.outputs["rev_netlist"], + logging_level=self.logging_level, ) error_injector_tool.create_build_snippets() @@ -79,6 +82,7 @@ def create_build_snippets(self): golden_netlist=error_injector_tool.outputs["corrupt_netlist"], rev_netlist=self.xrev_tool.outputs["rev_netlist"], expect_fail=True, + logging_level=self.logging_level, ).create_build_snippets() def get_top_level_flow_path(self) -> str: diff --git a/bfasst/tools/compare/structural/structural.py b/bfasst/tools/compare/structural/structural.py index 8f7cdc605..98d231bce 100644 --- a/bfasst/tools/compare/structural/structural.py +++ b/bfasst/tools/compare/structural/structural.py @@ -16,6 +16,7 @@ def __init__( rev_netlist=None, expect_fail=False, debug=False, + logging_level=None, ): super().__init__(flow, design) self.build_path = self.design_build_path / "struct_cmp" @@ -24,6 +25,7 @@ def __init__( self.rev_netlist = rev_netlist self.expect_fail = expect_fail self.debug = debug + self.logging_level = logging_level self._init_outputs() self.rule_snippet_path = COMPARE_TOOLS_PATH / "structural" / "structural_rules.ninja" @@ -37,6 +39,7 @@ def create_build_snippets(self): "compare_script_path": str(BFASST_UTILS_PATH / "structural.py"), "expect_fail": "--expect_fail" if self.expect_fail else "", "debug": "--debug True" if self.debug else "", + "logging_level": f"--logging_level {self.logging_level}", }, ) diff --git a/bfasst/tools/compare/structural/structural_build.ninja.mustache b/bfasst/tools/compare/structural/structural_build.ninja.mustache index 5b1e337c0..ca59e9043 100644 --- a/bfasst/tools/compare/structural/structural_build.ninja.mustache +++ b/bfasst/tools/compare/structural/structural_build.ninja.mustache @@ -1,5 +1,6 @@ build {{ log_path }}: compare {{ netlist_a }} {{ netlist_b }} | {{ compare_script_path }} bfasst/utils/sdn_helpers.py expect_fail = {{ expect_fail }} debug = {{ debug }} + logging_level = {{ logging_level }} diff --git a/bfasst/tools/compare/structural/structural_rules.ninja b/bfasst/tools/compare/structural/structural_rules.ninja index 9d671007b..711ab241d 100644 --- a/bfasst/tools/compare/structural/structural_rules.ninja +++ b/bfasst/tools/compare/structural/structural_rules.ninja @@ -1,4 +1,4 @@ rule compare - command = python bfasst/utils/structural.py --netlists $in --log_path $out $expect_fail $debug + command = python bfasst/utils/structural.py --netlists $in --log_path $out $expect_fail $debug $logging_level description = structurally compare $in diff --git a/bfasst/tools/impl/vivado_impl.py b/bfasst/tools/impl/vivado_impl.py index 97d47306d..f37f2b780 100644 --- a/bfasst/tools/impl/vivado_impl.py +++ b/bfasst/tools/impl/vivado_impl.py @@ -83,7 +83,7 @@ def _init_outputs(self): self.outputs["impl_verilog"] = self.build_path / "viv_impl.v" self.outputs["impl_edf"] = self.build_path / "viv_impl.edf" self.outputs["impl_dcp"] = self.build_path / "impl.dcp" - self.outputs["utilization"] = self.build_path / "utiliztion.txt" + self.outputs["utilization"] = self.build_path / "utilization.txt" self.outputs["timing"] = self.build_path / "timing_summary.txt" self.outputs["journal"] = self.build_path / "vivado.jou" self.outputs["log"] = self.build_path / "vivado.log" diff --git a/bfasst/tools/transform/error_injector.py b/bfasst/tools/transform/error_injector.py index 1befa9321..c2f8eb4fa 100644 --- a/bfasst/tools/transform/error_injector.py +++ b/bfasst/tools/transform/error_injector.py @@ -10,13 +10,21 @@ class ErrorInjector(Tool): """Create the rule and build snippets for error injection into an xray netlist.""" def __init__( - self, flow, design, error_type=None, num=None, multiplier=None, reversed_netlist=None + self, + flow, + design, + logging_level, + error_type=None, + num=None, + multiplier=None, + reversed_netlist=None, ): super().__init__(flow, design) self.error_type = error_type self.num = num self.multiplier = multiplier self.reversed_netlist = reversed_netlist + self.logging_level = logging_level self.build_path = self.design_build_path / "error_injection" if error_type is not None and num is not None: self.injection_log = self.build_path / f"{self.error_type.name.lower()}_{self.num}.log" @@ -40,6 +48,7 @@ def create_build_snippets(self): "seed": self.num * self.multiplier, "error_injector_script_path": str(BFASST_UTILS_PATH / "error_injector.py"), "reversed_netlist": self.reversed_netlist, + "logging_level": f"--logging_level {self.logging_level}", }, ) diff --git a/bfasst/tools/transform/error_injector_build.ninja.mustache b/bfasst/tools/transform/error_injector_build.ninja.mustache index be13a09cd..1fa357945 100644 --- a/bfasst/tools/transform/error_injector_build.ninja.mustache +++ b/bfasst/tools/transform/error_injector_build.ninja.mustache @@ -4,4 +4,5 @@ build {{ log_path }} {{ corrupt_netlist_path }}: inject | {{ build_dir }}/xray/{ error_type = {{ error_type }} log_path = {{ log_path }} reversed_netlist = {{ reversed_netlist }} + logging_level = {{ logging_level }} diff --git a/bfasst/tools/transform/error_injector_rules.ninja b/bfasst/tools/transform/error_injector_rules.ninja index 24e662008..27adab738 100644 --- a/bfasst/tools/transform/error_injector_rules.ninja +++ b/bfasst/tools/transform/error_injector_rules.ninja @@ -1,4 +1,4 @@ rule inject - command = python bfasst/utils/error_injector.py --build_dir $build_dir --log_path $log_path --seed $seed --error_type $error_type --reversed_netlist $reversed_netlist + command = python bfasst/utils/error_injector.py --build_dir $build_dir --log_path $log_path --seed $seed --error_type $error_type --reversed_netlist $reversed_netlist $logging_level description = inject a $error_type into xrev netlist for $build_dir diff --git a/bfasst/tools/transform/netlist_cleanup.py b/bfasst/tools/transform/netlist_cleanup.py index 93130a2ea..e4c24626f 100644 --- a/bfasst/tools/transform/netlist_cleanup.py +++ b/bfasst/tools/transform/netlist_cleanup.py @@ -9,10 +9,11 @@ class NetlistCleanup(Tool): """Create rule and build snippets for phys netlist creation.""" - def __init__(self, flow, design, rev_netlist): + def __init__(self, flow, design, rev_netlist, logging_level): super().__init__(flow, design) self.rev_netlist = rev_netlist + self.logging_level = logging_level self.build_path = self.design_build_path / "netlist_cleanup" self._init_outputs() @@ -34,5 +35,6 @@ def create_build_snippets(self): "netlist_cleanup_output": self.build_path, "netlist_in": self.rev_netlist, "netlist_out": self.outputs["netlist_cleaned_path"], + "logging_level": f"--logging_level {self.logging_level}", }, ) diff --git a/bfasst/tools/transform/netlist_cleanup_build.ninja.mustache b/bfasst/tools/transform/netlist_cleanup_build.ninja.mustache index c201ad76d..15ae2711c 100644 --- a/bfasst/tools/transform/netlist_cleanup_build.ninja.mustache +++ b/bfasst/tools/transform/netlist_cleanup_build.ninja.mustache @@ -2,3 +2,4 @@ build {{ netlist_out }} {{ netlist_cleanup_output }}/log.txt: netlist_cleanup {{ build_path = {{ netlist_cleanup_output }} netlist_in = {{ netlist_in }} netlist_out = {{ netlist_out }} + logging_level = {{ logging_level }} diff --git a/bfasst/tools/transform/netlist_cleanup_rules.ninja b/bfasst/tools/transform/netlist_cleanup_rules.ninja index 17a63bf71..d82c86c5d 100644 --- a/bfasst/tools/transform/netlist_cleanup_rules.ninja +++ b/bfasst/tools/transform/netlist_cleanup_rules.ninja @@ -1,3 +1,3 @@ rule netlist_cleanup - command = python bfasst/utils/netlist_cleanup.py $build_path $netlist_in $netlist_out + command = python bfasst/utils/netlist_cleanup.py $build_path $netlist_in $netlist_out $logging_level description = netlist cleanup for $in diff --git a/bfasst/tools/transform/phys_netlist.py b/bfasst/tools/transform/phys_netlist.py index 0e5ea4b90..b67de6c50 100644 --- a/bfasst/tools/transform/phys_netlist.py +++ b/bfasst/tools/transform/phys_netlist.py @@ -15,11 +15,12 @@ class PhysNetlist(Tool): """Create rule and build snippets for phys netlist creation.""" - def __init__(self, flow, design, impl_checkpoint, impl_edf): + def __init__(self, flow, design, impl_checkpoint, impl_edf, logging_level): super().__init__(flow, design) self.impl_checkpoint = impl_checkpoint self.impl_edf = impl_edf + self.logging_level = logging_level self.build_path = self.design_build_path / "vivado_phys_netlist" @@ -53,6 +54,7 @@ def __append_build_snippets(self): "phys_netlist_output": self.build_path, "phys_netlist_library": NINJA_TRANSFORM_TOOLS_PATH, "build_dir": self.build_path.parent, + "logging_level": f"--logging_level {self.logging_level}", "impl_dcp": self.impl_checkpoint, "impl_edf": self.impl_edf, }, diff --git a/bfasst/tools/transform/phys_netlist_build.ninja.mustache b/bfasst/tools/transform/phys_netlist_build.ninja.mustache index 88bc3f3bd..475f3f726 100644 --- a/bfasst/tools/transform/phys_netlist_build.ninja.mustache +++ b/bfasst/tools/transform/phys_netlist_build.ninja.mustache @@ -2,6 +2,7 @@ build {{ phys_netlist_output }}/checkpoint_to_v.tcl: template {{ phys_netlist_ou build {{ phys_netlist_output }}/viv_impl_physical.edf {{ phys_netlist_output }}/phys_netlist.dcp {{ phys_netlist_output }}/log.txt {{ phys_netlist_output }}/rapidwright_stdout.log: phys_netlist | bfasst/utils/rw_phys_netlist.py {{ impl_edf }} {{ impl_dcp }} build_dir = {{ build_dir }} + logging_level = {{ logging_level }} impl_dcp = {{ impl_dcp }} impl_edf = {{ impl_edf }} diff --git a/bfasst/tools/transform/phys_netlist_rules.ninja b/bfasst/tools/transform/phys_netlist_rules.ninja index 15f2e56fd..4711b7d9a 100644 --- a/bfasst/tools/transform/phys_netlist_rules.ninja +++ b/bfasst/tools/transform/phys_netlist_rules.ninja @@ -1,4 +1,4 @@ rule phys_netlist - command = python bfasst/utils/rw_phys_netlist.py --build_dir $build_dir --impl_dcp $impl_dcp --impl_edf $impl_edf + command = python bfasst/utils/rw_phys_netlist.py --build_dir $build_dir --impl_dcp $impl_dcp --impl_edf $impl_edf $logging_level description = generate physical netlist for $build_dir diff --git a/bfasst/utils/error_injector.py b/bfasst/utils/error_injector.py index 81e360294..7673ce685 100644 --- a/bfasst/utils/error_injector.py +++ b/bfasst/utils/error_injector.py @@ -26,13 +26,16 @@ class ErrorInjectorException(Exception): class ErrorInjector: """Inject errors into an xrev netlist""" - def __init__(self, build_dir, log_path, seed, error_type, reversed_netlist): + def __init__( + self, build_dir, log_path, seed, error_type, reversed_netlist, logging_level=logging.DEBUG + ): self.build_dir = Path(build_dir) self.stage_dir = self.build_dir / "error_injection" self.log_path = self.stage_dir / log_path + self.logging_level = logging_level logging.basicConfig( filename=self.log_path, - level=logging.DEBUG, + level=self.logging_level, format="%(asctime)s %(message)s", datefmt="%Y%m%d%H%M%S", ) @@ -264,6 +267,14 @@ def __log_wire_swap(self, selected_input, selected_input2, two_instances): parser.add_argument("--seed", help="Seed for random number generator") parser.add_argument("--error_type", type=str, help="Type of error to inject") parser.add_argument("--reversed_netlist", type=str, help="Path to reversed netlist") + parser.add_argument("--logging_level", help="Decides what levels of logs to display") args = parser.parse_args() - ErrorInjector(args.build_dir, args.log_path, args.seed, args.error_type, args.reversed_netlist) + ErrorInjector( + args.build_dir, + args.log_path, + args.seed, + args.error_type, + args.reversed_netlist, + args.logging_level, + ) diff --git a/bfasst/utils/netlist_cleanup.py b/bfasst/utils/netlist_cleanup.py index 520583043..8b57f9203 100644 --- a/bfasst/utils/netlist_cleanup.py +++ b/bfasst/utils/netlist_cleanup.py @@ -5,6 +5,7 @@ import argparse import logging import pathlib +import time import spydrnet as sdn @@ -14,10 +15,11 @@ class NetlistCleaner: """Clean a netlist.""" - def __init__(self, build_path, netlist_in_path, netlist_out_path): + def __init__(self, build_path, netlist_in_path, netlist_out_path, logging_level): self.build_path = build_path self.netlist_in = netlist_in_path self.netlist_out = netlist_out_path + self.logging_level = logging_level self.log_path = self.build_path / "log.txt" self.log_path.unlink(missing_ok=True) @@ -25,15 +27,21 @@ def __init__(self, build_path, netlist_in_path, netlist_out_path): logging.basicConfig( filename=self.log_path, format="%(asctime)s %(message)s", - level=logging.DEBUG, + level=self.logging_level, datefmt="%Y-%m-%d %H:%M:%S", ) # Read netlist with spydrnet netlist_ir = sdn.parse(self.netlist_in) top = netlist_ir.top_instance - - # Find all ASSIGN instances and remove them + self.remove_assign_instances(top) + self.remove_unused_instances(top) + self.write_netlist(netlist_ir) + + def remove_assign_instances(self, top): + """Remove all ASSIGN instances""" + logging.info("Finding and removing all ASSIGN instances") + t_begin = time.perf_counter() for instance in top.get_instances(): if instance.reference.name.startswith("SDN_VERILOG_ASSIGNMENT"): pin_out = None @@ -44,24 +52,25 @@ def __init__(self, build_path, netlist_in_path, netlist_out_path): else: pin_out = pin - assert pin_out is not None for pin in pin_out.wire.pins: if pin == pin_out: continue raise NotImplementedError - - logging.info("Removing instance: %s", instance) top.reference.remove_child(instance) + logging.info("Total time to remove ASSIGN instances: %s", time.perf_counter() - t_begin) - # Remove unused instances + def remove_unused_instances(self, top): + """Remove unused instances""" + logging.info("Removing unused instances") unused_instance_types = {"LUT6_2": ("O5", "O6"), "IBUF": ("O",)} netlist_wrapper = SdnNetlistWrapper(top) - + t_begin = time.perf_counter() for instance_type, pin_names in unused_instance_types.items(): - for instance_wrapper in netlist_wrapper.instances: - if instance_wrapper.instance.reference.name != instance_type: - continue - logging.info("Processing %s %s", instance_type, instance_wrapper.name) + for instance_wrapper in [ + instance_wrapper + for instance_wrapper in netlist_wrapper.instances + if instance_wrapper.instance.reference.name == instance_type + ]: connected_pins = ( netlist_wrapper.wire_to_net[ @@ -71,9 +80,13 @@ def __init__(self, build_path, netlist_in_path, netlist_out_path): ) if not any(connected_pins): top.reference.remove_child(instance_wrapper.instance) + logging.info("Total time to remove unused instances: %s", time.perf_counter() - t_begin) - # Write out netlist + def write_netlist(self, netlist_ir): + logging.info("Writing out netlist") + t_begin = time.perf_counter() sdn.compose(netlist_ir, self.netlist_out, write_blackbox=False) + logging.info("Total time to write out netlist: %s", time.perf_counter() - t_begin) if __name__ == "__main__": @@ -81,6 +94,9 @@ def __init__(self, build_path, netlist_in_path, netlist_out_path): parser.add_argument("build_path", type=pathlib.Path, help="Path to build directory") parser.add_argument("netlist_in", type=pathlib.Path, help="Path to input netlist") parser.add_argument("netlist_out", type=pathlib.Path, help="Path to output netlist") + parser.add_argument("--logging_level", help="Decides what levels of logs to display") args = parser.parse_args() - netlist_cleaner = NetlistCleaner(args.build_path, args.netlist_in, args.netlist_out) + netlist_cleaner = NetlistCleaner( + args.build_path, args.netlist_in, args.netlist_out, args.logging_level + ) diff --git a/bfasst/utils/rw_phys_netlist.py b/bfasst/utils/rw_phys_netlist.py index 76d851e87..63321efde 100644 --- a/bfasst/utils/rw_phys_netlist.py +++ b/bfasst/utils/rw_phys_netlist.py @@ -35,14 +35,15 @@ class PhysNetlistTransformError(Exception): class RwPhysNetlist: """Creates a xilinx netlist that has only physical primitives""" - def __init__(self, build_dir): + def __init__(self, build_dir, logging_level): self.build_dir = Path(build_dir) self.stage_dir = self.build_dir / "vivado_phys_netlist" (self.stage_dir / "log.txt").unlink(missing_ok=True) + self.logging_level = logging_level logging.basicConfig( filename=self.stage_dir / "log.txt", format="%(asctime)s %(message)s", - level=logging.DEBUG, + level=self.logging_level, datefmt="%Y%m%d%H%M%S", ) @@ -251,14 +252,6 @@ def __process_all_luts(self, cells_already_visited): if site_inst.getSiteTypeEnum() not in (SiteTypeEnum.SLICEL, SiteTypeEnum.SLICEM): continue - # if ( - # "X6Y107" in site_inst.getName() - # and site_inst.getSiteTypeEnum() == SiteTypeEnum.SLICEM - # ): - # import code - - # code.interact(local=dict(globals(), **locals())) - gnd_nets = site_inst.getSiteWiresFromNet(self.rw_design.getGndNet()) vcc_nets = site_inst.getSiteWiresFromNet(self.rw_design.getVccNet()) @@ -991,8 +984,9 @@ def __create_lut_routethru_net(self, cell, is_lut5, new_lut_cell): required=True, help="The implementation edf file to use for the netlist.", ) + parser.add_argument("--logging_level", help="Decides what levels of logs to display") args = parser.parse_args() - netlist_generator = RwPhysNetlist(args.build_dir) + netlist_generator = RwPhysNetlist(args.build_dir, args.logging_level) try: netlist_generator.run(args.impl_dcp, args.impl_edf) except jpype.JException as e: diff --git a/bfasst/utils/sdn_helpers.py b/bfasst/utils/sdn_helpers.py index 8d75ffc29..7394ec2f0 100644 --- a/bfasst/utils/sdn_helpers.py +++ b/bfasst/utils/sdn_helpers.py @@ -23,13 +23,6 @@ def __init__(self, top_instance) -> None: instances = [SdnInstanceWrapper(i, self) for i in top_instance.get_instances()] self.instances = instances - self.instances_to_map = { - i - for i in self.instances - if i.cell_type not in ("GND", "VCC") - and not i.cell_type.startswith("SDN_VERILOG_ASSIGNMENT_") - } - SdnNetlistWrapper.GND_NAMES = {i.name for i in self.instances if i.cell_type == "GND"} SdnNetlistWrapper.VCC_NAMES = {i.name for i in self.instances if i.cell_type == "VCC"} @@ -335,6 +328,10 @@ def __init__(self, instance, netlist): pin_spydernet.inner_pin.port.pins.index(pin_spydernet.inner_pin), ) ] = pin + self.pins.sort( + key=lambda p: (p.net.is_vdd if p.net is not None else False) + or (p.net.is_gnd if p.net is not None else False) + ) def init_connectivity(self): """Initialize connectivity for this instance""" @@ -355,7 +352,4 @@ def properties(self): return self.instance.data.get("VERILOG.Parameters") def get_pin(self, name, index=0): - try: - return self.pins_by_name_and_index[(name, index)] - except KeyError: - return SdnInstanceWrapper.GND_PIN + return self.pins_by_name_and_index.get((name, index), SdnInstanceWrapper.GND_PIN) diff --git a/bfasst/utils/structural.py b/bfasst/utils/structural.py index d42559cc4..c8dc4e46d 100644 --- a/bfasst/utils/structural.py +++ b/bfasst/utils/structural.py @@ -30,23 +30,37 @@ class StructuralCompareError(Exception): class StructuralCompare: """Structural compare and map""" - def __init__(self, named_netlist_path, reversed_netlist_path, log_path, debug) -> None: + def __init__( + self, + named_netlist_path, + reversed_netlist_path, + log_path, + debug, + logging_level, + ) -> None: self.reversed_netlist_path = reversed_netlist_path self.named_netlist_path = named_netlist_path self.named_netlist = None + self.reversed_instance_map = None + self.named_instance_map = None self.reversed_netlist = None self.debug = debug + self.logging_level = logging_level self.log_path = log_path logging.basicConfig( filename=self.log_path, filemode="w", format="%(asctime)s %(message)s", - level=logging.DEBUG, + level=self.logging_level, datefmt="%Y%m%d%H%M%S", ) assert str(log_path).endswith("_cmp.log") + self.possible_matches_cache_path = os.path.join( + os.path.dirname(self.log_path), "possible_matches.pkl" + ) + if self.debug: build_folder = os.path.dirname(os.path.dirname(self.log_path)) dcp_path = os.path.join(build_folder, "vivado_impl/impl.dcp") @@ -79,7 +93,7 @@ def __init__(self, named_netlist_path, reversed_netlist_path, log_path, debug) - jpype_jvm.start() - def __set_cell_props(self): + def __set_cell_props(self) -> None: init_only = ( "LUT6_2", "FDSE", @@ -151,11 +165,11 @@ def __set_cell_props(self): self._cell_props = _cell_props - def reset_mappings(self): + def reset_mappings(self) -> None: self.block_mapping = bidict() self.net_mapping = bidict() - def load_mappings(self): + def load_mappings(self) -> None: """Init mapping data structures from pkl files""" with open(self.block_mapping_pkl, "rb") as f: block_map = pickle.load(f) @@ -179,9 +193,9 @@ def load_mappings(self): rh_net = [n for n in self.reversed_netlist.nets if n.name == rh][0] self.net_mapping[lh_net] = rh_net - def init_netlists(self): + def init_netlists(self) -> None: """Load both netlists from spydrnet and build wrapper objects""" - log_with_banner("Building netlist A %s", self.reversed_netlist_path) + log_with_banner(f"Building reversed netlist {self.reversed_netlist_path}") # Loads the first netlist as intermediate representation (ir1) ir_a = sdn.parse(str(self.reversed_netlist_path)) @@ -189,7 +203,7 @@ def init_netlists(self): netlist_a = self.get_netlist(library_a) logging.info("Netlist A size: %s", len(netlist_a.instances)) - log_with_banner("Building netlist B %s", self.named_netlist_path) + log_with_banner(f"Building named netlist B {self.named_netlist_path}") # Loads the second netlist as intermediate representation (ir2) ir_b = sdn.parse(str(self.named_netlist_path)) @@ -201,8 +215,14 @@ def init_netlists(self): self.reversed_netlist = netlist_a self.named_netlist = netlist_b + self.named_instance_map = { + instance.name: instance for instance in self.named_netlist.instances + } + self.reversed_instance_map = { + instance.name: instance for instance in self.reversed_netlist.instances + } - def compare_netlists(self): + def compare_netlists(self) -> None: """Map the golden and reversed netlists through automated block mapping""" self.start_time = time.time() @@ -212,36 +232,52 @@ def compare_netlists(self): logging.info("Time after init_netlists: %s".ljust(35), str(time.perf_counter() - t_begin)) # self.load_mappings() + + # a set of tuples with the first element being the instance name + # and second element being which mapping function to use + self.named_netlist.instances_to_map = { + ( + i.name, + ( + self.check_for_potential_mapping + if i.cell_type not in {"RAMB36E1", "RAMB18E1"} + else self.check_for_potential_bram_mapping + ), + ) + for i in self.named_netlist.instances + if i.cell_type not in ("GND", "VCC") + } + # Structurally map the rest of the netlists self.perform_mapping() - t_end = time.perf_counter() log_with_banner("Mapping (Instances)") - block_map = {k.name: v.name for k, v in self.block_mapping.items()} + block_map = dict(self.block_mapping.items()) for key, val in self.block_mapping.items(): - logging.info("%s -> %s", key.name, val.name) - logging.info("") - # log_with_banner("Mapping (Nets)") + logging.debug("%s -> %s", key, val) + logging.debug("") + log_with_banner("Mapping (Nets)") net_map = {k.name: v.name for k, v in self.net_mapping.items()} for key, val in self.net_mapping.items(): - logging.info("%s -> %s", key.name, val.name) + logging.debug("%s -> %s", key.name, val.name) log_with_banner("Finalizing") - self.named_netlist.instances_to_map = { - i for i in self.named_netlist.instances if i.cell_type not in ("GND", "VCC") + + should_be_mapped = { + instance.name + for instance in self.named_netlist.instances + if instance.cell_type not in ("GND", "VCC") } + logging.info( "Number of mapped blocks: %s of %s", len(self.block_mapping), - len(self.named_netlist.instances_to_map), + len(should_be_mapped), ) - logging.info(" Unmapped blocks:") - for block in [ - block - for block in self.named_netlist.instances_to_map - if block not in self.block_mapping - ]: - logging.info(" %s", block.name) + logging.error(" Unmapped blocks:") + + for block in [block for block in should_be_mapped if block not in self.block_mapping]: + logging.error(" %s", block) num_mapped_nets = ( len([net for net in self.net_mapping if net.is_connected]) @@ -251,7 +287,7 @@ def compare_netlists(self): num_total_nets = len(self.named_netlist.get_connected_nets()) logging.info("Number of mapped nets: %s of %s", num_mapped_nets, num_total_nets) - logging.info(" Unmapped nets:") + logging.error(" Unmapped nets:") for net in [ net for net in self.named_netlist.get_connected_nets() @@ -262,9 +298,9 @@ def compare_netlists(self): # if net.is_vdd or net.is_gnd: # num_total_nets -= 1 # continue - logging.info(" %s", net.name) + logging.error(" %s", net.name) - if len(self.block_mapping) != len(self.named_netlist.instances_to_map): + if len(self.block_mapping) != len(should_be_mapped): raise StructuralCompareError("Could not map all blocks") if num_mapped_nets != num_total_nets: raise StructuralCompareError("Could not map all nets") @@ -280,14 +316,13 @@ def compare_netlists(self): with open(self.net_mapping_pkl, "wb") as f: pickle.dump(net_map, f) - mtime = round(t_end - t_begin, 1) + mtime = round(time.perf_counter() - t_begin, 1) logging.info("Mapping time: %s seconds", mtime) # After establishing mapping, verify equivalence t_begin = time.perf_counter() self.verify_equivalence() - t_end = time.perf_counter() - vtime = round(t_end - t_begin, 1) + vtime = round(time.perf_counter() - t_begin, 1) logging.info("Equivalence verification time: %s seconds", vtime) logging.info("Total time: %s seconds", mtime + vtime) @@ -297,7 +332,7 @@ def compare_netlists(self): with open(self.comparison_time_log, "w") as f: f.write(f"{self.end_time - self.start_time}\n") - def map_ports(self): + def map_ports(self) -> None: """Map top-level ports""" log_with_banner("Mapping top-level ports") for pin in self.named_netlist.pins: @@ -321,148 +356,133 @@ def map_ports(self): + "signal a reserved HDL keyword?" ) from err - def init_matching_instances(self): + def init_matching_instances(self) -> None: """Init possible_matches dict with all instances that match by cell type and properties""" - log_with_banner("Initializing possible matches based on cell type and properties") - - possible_matches_cache_path = os.path.join( - os.path.dirname(self.log_path), "possible_matches.pkl" - ) - if self.debug and os.path.exists(possible_matches_cache_path): - logging.info("Loading possible matches from cache") - with open(possible_matches_cache_path, "rb") as f: - pickle_dump = pickle.load(f) - self.import_possible_matches(pickle_dump) + if self.debug and os.path.exists(self.possible_matches_cache_path): + log_with_banner("Loading possible matches from cache") + with open(self.possible_matches_cache_path, "rb") as f: + self.possible_matches = pickle.load(f) else: + log_with_banner("Initializing possible matches based on cell type and properties") + all_instances = [ - i - for i in self.reversed_netlist.instances - if not i.cell_type.startswith("SDN_VERILOG") + i for i in self.reversed_netlist.instances if not i.cell_type.startswith("SD") ] + grouped_by_cell_type = defaultdict(list) + grouped_by_cell_type_and_const = defaultdict(list) for instance in all_instances: + num_const = self.count_num_const(instance.pins) properties = set() for prop in self.get_properties_for_type(instance.cell_type): properties.add( f"{prop}{convert_verilog_literal_to_int(instance.properties[prop])}" ) - # properties[prop] = convert_verilog_literal_to_int(instance.properties[prop]) + + grouped_by_cell_type_and_const[ + (instance.cell_type, hash(frozenset(properties)), num_const) + ].append(instance.name) grouped_by_cell_type[(instance.cell_type, hash(frozenset(properties)))].append( - instance + instance.name ) - for named_instance in self.named_netlist.instances_to_map: + for instance_name, _ in self.named_netlist.instances_to_map: ############################################################### # First find all instances of the same type and same properties ############################################################### # Compute a hash of this instance's properties + instance = self.named_instance_map[instance_name] + num_const = self.count_num_const(instance.pins) properties = set() - for prop in self.get_properties_for_type(named_instance.cell_type): + for prop in self.get_properties_for_type(instance.cell_type): properties.add( - f"{prop}{convert_verilog_literal_to_int(named_instance.properties[prop])}" + f"{prop}{convert_verilog_literal_to_int(instance.properties[prop])}" ) my_hash = hash(frozenset(properties)) - instances_matching = grouped_by_cell_type[(named_instance.cell_type, my_hash)] + instances_matching = grouped_by_cell_type_and_const[ + (instance.cell_type, my_hash, num_const) + ] if not instances_matching: - logging.info( - "No property matches for cell %s of type %s. Properties:", - named_instance.name, - named_instance.cell_type, - ) - for prop in self.get_properties_for_type(named_instance.cell_type): - logging.info(" %s: %s", prop, named_instance.properties[prop]) - raise StructuralCompareError( - f"Not equivalent. {named_instance.name} \ - has no possible match in the netlist." - ) - - self.possible_matches[named_instance] = instances_matching.copy() - possible_matches = {} - for named_instance, possibilities in self.possible_matches.items(): - possible_matches[named_instance.name] = [i.name for i in possibilities] - with open(possible_matches_cache_path, "wb") as f: - pickle.dump(possible_matches, f) - - def import_possible_matches(self, pickle_dump): - all_instances = { - i.name: i - for i in self.reversed_netlist.instances - if not i.cell_type.startswith("SDN_VERILOG") - } - for named_instance in self.named_netlist.instances_to_map: - matches = pickle_dump[named_instance.name] - tmp = [all_instances[i] for i in matches] - self.possible_matches[named_instance] = tmp - - def potential_mapping_wrapper(self, instance): - """Wrap check_for_potential_mapping some inital checks/postprocessing""" + instances_matching = grouped_by_cell_type[(instance.cell_type, my_hash)] + if not instances_matching: + logging.error( + "No property matches for cell %s of type %s. Properties:", + instance_name, + instance.cell_type, + ) + for prop in self.get_properties_for_type(instance.cell_type): + logging.error(" %s: %s", prop, instance.properties[prop]) + raise StructuralCompareError( + f"Not equivalent. {instance_name} \ + has no possible match in the netlist." + ) - # Skip assign statements (named netlist shouldn't have them) - # Update: just let the code break if this happens - # assert not instance.cell_type.startswith("SDN_VERILOG_ASSIGNMENT") + self.possible_matches[instance_name] = set(instances_matching) + with open(self.possible_matches_cache_path, "wb") as f: + pickle.dump(self.possible_matches, f) - logging.info("Considering %s (%s)", instance.name, instance.cell_type) + def count_num_const(self, pins) -> int: + return sum(1 for pin in pins if pin.net and (pin.net.is_gnd or pin.net.is_vdd)) - # Get the implemented potential instance to map - if not instance.cell_type.startswith("RAMB"): - instances_matching = self.check_for_potential_mapping(instance) - else: - instances_matching = self.check_for_potential_bram_mapping(instance) + def potential_mapping_wrapper(self, instance_tuple: tuple) -> bool: + """Wrap check_for_potential_mapping some inital checks/postprocessing""" + instance = self.named_instance_map[instance_tuple[0]] + logging.info("Considering %s (%s)", instance_tuple[0], instance.cell_type) + instances_matching = instance_tuple[1](instance_tuple[0]) if not instances_matching: if not self.debug: raise StructuralCompareError( - f"Not equivalent. {instance.name} has no possible match in the netlist." + f"Not equivalent. {instance_tuple[0]} has no possible match in the netlist." ) - cell = self.design.getCell(instance.name) - if not cell: - # often, the cell name in vivado is a little bit different than in the netlist, - # so if it's not an exact match, I used difflib to get the closest match - # this has worked for me so far, but it might not always - cells = list(self.design.getCells()) - actual_cell_name = str( - difflib.get_close_matches( - instance.name, [cell.getName() for cell in cells], n=1 - )[0] + try: + cell = self.design.getCell(instance_tuple[0]) + if not cell: + # often, the cell name in vivado is a little bit different than in the netlist, + # so if it's not an exact match, I used difflib to get the closest match + # this has worked for me for the most part, but it might not always + cells = list(self.design.getCells()) + actual_cell_name = str( + difflib.get_close_matches( + instance_tuple[0], [cell.getName() for cell in cells], n=1 + )[0] + ) + cell = [cell for cell in cells if cell.getName() == actual_cell_name][0] + + logging.error( + "%s should map to %s_%s_%s, but has no possible match in the netlist", + instance_tuple[0], + cell.getTile(), + cell.getSite(), + cell.getType(), ) - # now that we have the cell's actual name, - # we can use that to access the cell object - cell = [cell for cell in cells if cell.getName() == actual_cell_name][0] - - site = cell.getSite() - tile = cell.getTile() - cell_type = cell.getType() - logging.info( - "%s should map to %s_%s_%s, but has no possible match in the netlist", - instance.name, - tile, - site, - cell_type, - ) + except IndexError: + logging.error("%s has no possible match in the netlist", instance_tuple[0]) + self.named_netlist.instances_to_map.remove(instance_tuple) return False if len(instances_matching) > 1: logging.info(" %s matches, skipping for now:", len(instances_matching)) if len(instances_matching) < 10: for matched_instance in instances_matching: - logging.info(" %s", matched_instance.name) + logging.info(" %s", matched_instance) return False assert len(instances_matching) == 1 - matched_instance = instances_matching[0] + matched_instance = instances_matching.pop() - logging.info(" Mapped to %s", matched_instance.name) + logging.info(" Mapped to %s", matched_instance) - self.add_block_mapping(instance, matched_instance) + self.add_block_mapping(instance_tuple, matched_instance) return True - def perform_mapping(self): + def perform_mapping(self) -> None: """Maps netlists based on their cells and nets""" # First map top-level nets @@ -485,21 +505,24 @@ def perform_mapping(self): + len(self.gnd_mappings) ) num_total_nets = len(self.named_netlist.get_connected_nets()) - cell_type = {i.cell_type for i in self.named_netlist.instances_to_map} + cell_type = { + self.named_instance_map[i].cell_type + for i, _ in self.named_netlist.instances_to_map + } if len(cell_type) == 1 and num_mapped_nets == num_total_nets: reversed_remaining = [ - self.possible_matches[i] for i in self.named_netlist.instances_to_map + self.possible_matches[i] for i, _ in self.named_netlist.instances_to_map ] remaining = set() for i in reversed_remaining: for j in i: - remaining.add(j.name) + remaining.add(j) if len(remaining) == len(reversed_remaining[0]): - for named, rev in zip( + for named_tuple, rev in zip( set(self.named_netlist.instances_to_map), reversed_remaining[0] ): # Make a copy of instances_to_map since python won't let you # change the size of a set during iteration - self.add_block_mapping(named, rev) + self.add_block_mapping(named_tuple, rev) break logging.info("No more progress can be made. Failed at iteration %s.", iteration) @@ -509,7 +532,13 @@ def perform_mapping(self): logging.info("===== Mapping Iteration %s =====", iteration) # Loop through reversed netlist blocks - instance_iter = iter(set(self.named_netlist.instances_to_map)) + # Sort the instances based on the length of their possible matches list + sorted_instances = sorted( + set(self.named_netlist.instances_to_map), + key=lambda instance_tuple: len(self.possible_matches[instance_tuple[0]]), + ) + # Create an iterator from the sorted instances + instance_iter = iter(sorted_instances) try: while not overall_progress: overall_progress = self.potential_mapping_wrapper(next(instance_iter)) @@ -519,7 +548,7 @@ def perform_mapping(self): pass iteration += 1 - def verify_equivalence(self): + def verify_equivalence(self) -> None: """Verify equivalence by looping through all mapped instances and checking that for each pin, the connected nets are also mapped to each other.""" @@ -527,15 +556,15 @@ def verify_equivalence(self): # Loop through all instances and check for equivalence log_with_banner("Verifying equivalence") warnings = [] - for instance in self.named_netlist.instances_to_map: - mapped_instance = self.block_mapping.get(instance) + for instance, _ in self.named_netlist.instances_to_map: + mapped_instance = self.reversed_instance_map[self.block_mapping.get(instance)] if mapped_instance is None: raise StructuralCompareError( - f"Not equivalent. Instance {instance.name} is not mapped to anything." + f"Not equivalent. Instance {instance} is not mapped to anything." ) # Loop through all pins on instance and compare nets - for pin_a in instance.pins: + for pin_a in self.named_instance_map[instance].pins: if pin_a.ignore_net_equivalency: continue pin_b = mapped_instance.get_pin(pin_a.name, pin_a.index) @@ -545,22 +574,15 @@ def verify_equivalence(self): net_a_empty = net_a is None or not net_a.is_connected net_b_empty = net_b is None or not net_b.is_connected - if net_a_empty and not net_b_empty: - warnings.append( - ( - f"Not equivalent. Pin {pin_b.name_with_index} of " - f"{mapped_instance.name} is connected to net {net_b.name}," - f" but no connection on mapped instance {instance.name}." - ) - ) - continue - - if net_b_empty and not net_a_empty: + if net_a_empty != net_b_empty: warnings.append( ( - f"Not equivalent. Pin {pin_a.name_with_index} of {instance.name}" - f" is connected to net {net_a.name}," - f" but no connection on mapped instance {mapped_instance.name}." + f"Not equivalent. Pin " + f"{pin_a.name_with_index if not net_a_empty else pin_b.name_with_index}" + f" of {instance if not net_a_empty else mapped_instance.name}" + f" is connected to net {net_a.name if not net_a_empty else net_b.name}," + f" but no connection on mapped instance " + f"{mapped_instance.name if net_a_empty else instance}." ) ) continue @@ -585,14 +607,17 @@ def verify_equivalence(self): if not warnings: logging.info("Equivalence verified") else: - logging.info("Equivalence questionable") + for warning in warnings: + logging.warning(" %s", warning) raise StructuralCompareError("Warnings during equivalence verification") - def add_block_mapping(self, instance, matched_instance): + def add_block_mapping(self, instance_tuple: tuple, matched_instance_name: str) -> None: """Add mapping point between two Instances""" - self.block_mapping[instance] = matched_instance - self.named_netlist.instances_to_map.remove(instance) + instance = self.named_instance_map[instance_tuple[0]] + matched_instance = self.reversed_instance_map[matched_instance_name] + self.block_mapping[instance_tuple[0]] = matched_instance_name + self.named_netlist.instances_to_map.remove(instance_tuple) for pin in instance.pins: # Some pins should not be used to establish net mapping @@ -620,7 +645,7 @@ def add_block_mapping(self, instance, matched_instance): if pin_b is None: continue net_b = pin_b.net - assert net_b, f"{pin_b.name} of {matched_instance.name} is not connected" + assert net_b, f"{pin_b.name} of {matched_instance_name} is not connected" if net_b is None and net_a.is_gnd: continue assert isinstance(net_b, SdnNet), f"{net_b} is not a net" @@ -643,7 +668,7 @@ def add_block_mapping(self, instance, matched_instance): ) self.add_net_mapping(net_a, net_b) - def add_net_mapping(self, net1, net2): + def add_net_mapping(self, net1, net2) -> None: """Add mapping point between two Nets""" assert net1 not in self.net_mapping if net2 in self.net_mapping.inverse: @@ -661,8 +686,39 @@ def add_net_mapping(self, net1, net2): self.net_mapping[net1] = net2 - def check_for_potential_bram_mapping(self, named_instance): + def eliminate_redundant_matches(self, instance_name: str) -> set[str]: + return self.possible_matches[instance_name] - set(self.block_mapping.inverse) + + def make_matches_by_nets( + self, instances_matching_connections, other_net, name, idx + ) -> set[str]: + """Helper function for creating matches based off of net equivalence""" + + matches = { + instance + for instance in instances_matching_connections + if self.reversed_instance_map[instance].get_pin(name, idx).net == other_net + } + + if not matches: + if other_net.is_gnd: + matches = { + instance + for instance in instances_matching_connections + if self.reversed_instance_map[instance].get_pin(name, idx).net.is_gnd + } + elif other_net.is_vdd: + matches = { + instance + for instance in instances_matching_connections + if self.reversed_instance_map[instance].get_pin(name, idx).net.is_vdd + } + + return matches + + def check_for_potential_bram_mapping(self, instance_name: str) -> set[str]: """Special mapping checker for BRAMs""" + named_instance = self.named_instance_map[instance_name] bram_do = False bram_a_only = False bram_do = named_instance.properties["DOA_REG"] == "0" @@ -691,11 +747,9 @@ def check_for_potential_bram_mapping(self, named_instance): if not expected_properties: if not self.debug: raise StructuralCompareError("Unexpected BRAM CASCADE Configuration") - logging.info("Unexpected BRAM CASCADE Configuration for %s", named_instance.name) + logging.error("Unexpected BRAM CASCADE Configuration for %s", instance_name) - instances_matching_connections = [ - i for i in self.possible_matches[named_instance] if i not in self.block_mapping.inverse - ] + instances_matching_connections = self.eliminate_redundant_matches(instance_name) for pin in named_instance.pins: # For RAMB18E1, "REGCEAREGCE" and "REGCEB" only depend on DOA_REG and DOB_REG @@ -725,7 +779,6 @@ def check_for_potential_bram_mapping(self, named_instance): continue if pin.net not in self.net_mapping: - # Skip pin that is not yet mapped continue # Otherwise pin connected to a mapped net, and filter based on instances that are @@ -734,33 +787,14 @@ def check_for_potential_bram_mapping(self, named_instance): logging.info(" Filtering on pin %s, %s", pin.name_with_index, other_net.name) - idx = pin.index - - tmp = [ - instance - for instance in instances_matching_connections - if instance.get_pin(pin.name, idx).net == other_net - ] - - if not tmp: - if other_net.is_gnd: - tmp = [ - instance - for instance in instances_matching_connections - if instance.get_pin(pin.name, idx).net is None - or instance.get_pin(pin.name, idx).net.is_gnd - ] - elif other_net.is_vdd: - tmp = [ - instance - for instance in instances_matching_connections - if instance.get_pin(pin.name, idx).net.is_vdd - ] - instances_matching_connections = tmp + temp_matches = self.make_matches_by_nets( + instances_matching_connections, other_net, pin.name, pin.index + ) + instances_matching_connections = temp_matches num_instances = len(instances_matching_connections) info = ( - ": " + ",".join(i.name for i in instances_matching_connections) + ": " + ",".join(i for i in instances_matching_connections) if num_instances <= 10 else "" ) @@ -772,19 +806,19 @@ def check_for_potential_bram_mapping(self, named_instance): self.possible_matches[named_instance] = instances_matching_connections return instances_matching_connections - def check_for_potential_mapping(self, named_instance): + def check_for_potential_mapping(self, instance_name: str) -> set[str]: """Returns cells that could map to the named_instance""" ############################################################### # Now look at connections ############################################################### - instances_matching_connections = [ - i for i in self.possible_matches[named_instance] if i not in self.block_mapping.inverse - ] + instance = self.named_instance_map[instance_name] + + instances_matching_connections = self.eliminate_redundant_matches(instance_name) + + for pin in instance.pins: - for pin in named_instance.pins: - # Skip pin that is not yet mapped if pin.net not in self.net_mapping: continue @@ -794,24 +828,48 @@ def check_for_potential_mapping(self, named_instance): logging.info(" Filtering on pin %s, %s", pin.name_with_index, other_net.name) + name = pin.name + # Extra step for LUTRAMS - if named_instance.cell_type != "RAM32M" or not pin.name.startswith("D"): + if instance.cell_type != "RAM32M" or not name.startswith("D"): # for some reason RW sets DI* to the wrong bit, and SDN reads # DO* from the wrong bit for f2b netlist idx = pin.index else: idx = 0 if pin.index == 1 else 1 - name = pin.name + if pin.ignore_net_equivalency: + continue - if named_instance.cell_type == "DSP48E1" and name in { - "ALUMODE", - "OPMODE", - "INMODE", - "CLK", - "CARRYIN", - }: - for instance in instances_matching_connections: + temp_matches = self.make_matches_by_nets( + instances_matching_connections, other_net, name, idx + ) + + if not temp_matches and instance.cell_type == "BUFGCTRL" and name[0] == "I": + # sometimes f2b routes the clk net to both inputs + other_pin = f"I{'1' if name[1] == '0' else '0'}" + temp_matches = { + inst + for inst in instances_matching_connections + if self.reversed_instance_map[inst].get_pin(name, idx).net + == instance.get_pin(other_pin, idx).net + } + pin.ignore_net_equivalency = True + + if ( + not temp_matches + and instance.cell_type == "DSP48E1" + and name + in { + "ALUMODE", + "OPMODE", + "INMODE", + "CLK", + "CARRYIN", + } + ): + for inst in instances_matching_connections: + instance = self.reversed_instance_map[inst] # [3:] : gets rid of the "{# of bits}'b" at the beginning of the prop # [::-1] : reverses the string since the pins index the opposite as strings reversed_prop = instance.properties[f"IS_{name}_INVERTED"][3:][::-1] @@ -820,45 +878,15 @@ def check_for_potential_mapping(self, named_instance): or (other_net.is_vdd and instance.get_pin(name, idx).net.is_gnd) ): pin.ignore_net_equivalency = True + break if pin.ignore_net_equivalency: continue - tmp = [ - instance - for instance in instances_matching_connections - if instance.get_pin(name, idx).net == other_net - ] - - if named_instance.cell_type == "BUFGCTRL" and pin.name[0] == "I" and not tmp: - # sometimes f2b routes the clk net to both inputs - other_pin = f"I{'1' if name[1] == '0' else '0'}" - tmp = [ - inst - for inst in instances_matching_connections - if inst.get_pin(name, idx).net == inst.get_pin(other_pin, idx).net - ] - pin.ignore_net_equivalency = True - - if not tmp: - if other_net.is_gnd: - tmp = [ - instance - for instance in instances_matching_connections - if instance.get_pin(name, idx).net is None - or instance.get_pin(name, idx).net.is_gnd - ] - elif other_net.is_vdd: - tmp = [ - instance - for instance in instances_matching_connections - if instance.get_pin(name, idx).net.is_vdd - ] - instances_matching_connections = tmp - + instances_matching_connections = temp_matches num_instances = len(instances_matching_connections) info = ( - ": " + ",".join(i.name for i in instances_matching_connections) + ": " + ",".join(i for i in instances_matching_connections) if num_instances <= 10 else "" ) @@ -867,10 +895,10 @@ def check_for_potential_mapping(self, named_instance): logging.info( " %s instance(s) after filtering on connections", len(instances_matching_connections) ) - self.possible_matches[named_instance] = instances_matching_connections + self.possible_matches[instance_name] = instances_matching_connections return instances_matching_connections - def get_properties_for_type(self, cell_type): + def get_properties_for_type(self, cell_type) -> tuple[str]: """Return the list of properties that must match for a given cell type for the cell to be considered equivalent.""" try: @@ -878,7 +906,7 @@ def get_properties_for_type(self, cell_type): except KeyError as err: raise StructuralCompareError(f"Unhandled properties for type {cell_type}") from err - def get_netlist(self, library): + def get_netlist(self, library) -> SdnNetlistWrapper: return SdnNetlistWrapper(library) @@ -893,12 +921,14 @@ def get_netlist(self, library): parser.add_argument("--log_path", type=str, help="The log file path to use as output") parser.add_argument("--expect_fail", action="store_true", help="Expect the comparison to fail") parser.add_argument("--debug", help="Utilize debugging functionality") + parser.add_argument("--logging_level", help="Decides what levels of logs to display") args = parser.parse_args() struct_cmp = StructuralCompare( named_netlist_path=args.netlists[0], reversed_netlist_path=args.netlists[1], log_path=args.log_path, debug=args.debug, + logging_level=args.logging_level, ) try: struct_cmp.compare_netlists() diff --git a/tests/weekly/phys_netlist_paper.yaml b/tests/weekly/phys_netlist_paper.yaml index 082ae52ab..6667cf733 100644 --- a/tests/weekly/phys_netlist_paper.yaml +++ b/tests/weekly/phys_netlist_paper.yaml @@ -37,7 +37,7 @@ designs: - ooc/a25_wishbone - ooc/basicrsa - ooc/bcd_adder - # - ooc/bubblesort + - ooc/bubblesort - ooc/control_unit - ooc/cpu8080 - ooc/data_path @@ -50,16 +50,20 @@ designs: - ooc/pid # - ooc/quadratic_func - ooc/random_pulse_generator - # - ooc/simon_core + - ooc/simon_core - ooc/tiny_encryption_algorithm - ooc/uart2spi - ooc/wb_lcd - vtr_benchmarks/mkPktMerge - # - vtr_benchmarks/mkSMAdapter4B - # - vtr_benchmarks/raygentop + - vtr_benchmarks/mkSMAdapter4B + - vtr_benchmarks/raygentop - vtr_benchmarks/sha - vtr_benchmarks/stereovision1 - vtr_benchmarks/stereovision2 - vtr_benchmarks/stereovision3 flow: vivado_phys_netlist_cmp + + +synth_options: + synth_design: "-flatten_hierarchy full -max_dsp 0" \ No newline at end of file