diff --git a/bfasst/flows/vivado_phys_netlist_cmp.py b/bfasst/flows/vivado_phys_netlist_cmp.py index e5f9d51c1..bdb87bb76 100644 --- a/bfasst/flows/vivado_phys_netlist_cmp.py +++ b/bfasst/flows/vivado_phys_netlist_cmp.py @@ -13,11 +13,12 @@ class VivadoPhysNetlistCmp(Flow): """Structural Comparison of physical netlist and reversed netlist""" - def __init__(self, design, synth_options=""): + def __init__(self, design, synth_options="", debug=False): # pylint: disable=duplicate-code super().__init__(design) self.synth_options = VivadoPhysNetlist.add_required_synth_options(synth_options) + self.debug = debug self.vivado_synth_tool = VivadoSynth(self, design, synth_options=self.synth_options) self.vivado_impl_tool = VivadoImpl( @@ -44,6 +45,7 @@ def __init__(self, design, synth_options=""): "struct_cmp.log", golden_netlist=self.phys_netlist_tool.outputs["viv_impl_physical_v"], rev_netlist=self.xray_tool.outputs["rev_netlist"], + debug=self.debug, ) # pylint: enable=duplicate-code diff --git a/bfasst/tools/compare/structural/structural.py b/bfasst/tools/compare/structural/structural.py index 5f421c829..8f7cdc605 100644 --- a/bfasst/tools/compare/structural/structural.py +++ b/bfasst/tools/compare/structural/structural.py @@ -8,7 +8,14 @@ class Structural(Tool): """Create the rule and build snippets for structural comparison.""" def __init__( - self, flow, design, log_name=None, golden_netlist=None, rev_netlist=None, expect_fail=False + self, + flow, + design, + log_name=None, + golden_netlist=None, + rev_netlist=None, + expect_fail=False, + debug=False, ): super().__init__(flow, design) self.build_path = self.design_build_path / "struct_cmp" @@ -16,6 +23,7 @@ def __init__( self.golden_netlist = golden_netlist self.rev_netlist = rev_netlist self.expect_fail = expect_fail + self.debug = debug self._init_outputs() self.rule_snippet_path = COMPARE_TOOLS_PATH / "structural" / "structural_rules.ninja" @@ -28,6 +36,7 @@ def create_build_snippets(self): "log_path": str(self.outputs["structural_log"]), "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 "", }, ) diff --git a/bfasst/tools/compare/structural/structural_build.ninja.mustache b/bfasst/tools/compare/structural/structural_build.ninja.mustache index aa7adc4a9..5b1e337c0 100644 --- a/bfasst/tools/compare/structural/structural_build.ninja.mustache +++ b/bfasst/tools/compare/structural/structural_build.ninja.mustache @@ -1,3 +1,5 @@ build {{ log_path }}: compare {{ netlist_a }} {{ netlist_b }} | {{ compare_script_path }} bfasst/utils/sdn_helpers.py expect_fail = {{ expect_fail }} + debug = {{ debug }} + diff --git a/bfasst/tools/compare/structural/structural_rules.ninja b/bfasst/tools/compare/structural/structural_rules.ninja index 168994dea..9d671007b 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 + command = python bfasst/utils/structural.py --netlists $in --log_path $out $expect_fail $debug description = structurally compare $in diff --git a/bfasst/utils/structural.py b/bfasst/utils/structural.py index d6acb56a0..9adff33a9 100644 --- a/bfasst/utils/structural.py +++ b/bfasst/utils/structural.py @@ -6,14 +6,22 @@ import pickle import sys import time +import os +import difflib from bidict import bidict import spydrnet as sdn + from bfasst import jpype_jvm from bfasst.utils import convert_verilog_literal_to_int from bfasst.utils.general import log_with_banner from bfasst.utils.sdn_helpers import SdnNetlistWrapper, SdnInstanceWrapper, SdnNet, SdnPinWrapper +# pylint: disable=wrong-import-order +from com.xilinx.rapidwright.design import Design + +# pylint: enable=wrong-import-order + class StructuralCompareError(Exception): """Exception for structural comparison errors""" @@ -22,11 +30,12 @@ class StructuralCompareError(Exception): class StructuralCompare: """Structural compare and map""" - def __init__(self, named_netlist_path, reversed_netlist_path, log_path) -> None: + def __init__(self, named_netlist_path, reversed_netlist_path, log_path, debug) -> None: self.reversed_netlist_path = reversed_netlist_path self.named_netlist_path = named_netlist_path self.named_netlist = None self.reversed_netlist = None + self.debug = debug self.log_path = log_path logging.basicConfig( @@ -38,6 +47,15 @@ def __init__(self, named_netlist_path, reversed_netlist_path, log_path) -> None: ) assert str(log_path).endswith("_cmp.log") + 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") + edf_path = os.path.join(build_folder, "vivado_impl/viv_impl.edf") + self.design = Design.readCheckpoint( + dcp_path, + edf_path, + ) + self.block_mapping_pkl = ( str(log_path).split("_cmp.log", maxsplit=1)[0] + "_block_mapping.pkl" ) @@ -74,6 +92,7 @@ def __set_cell_props(self): "RAM32X1D_1", "RAM256X1S", "SRL16E", + "SRLC16E", "SRLC32E", "LDCE", ) @@ -102,6 +121,33 @@ def __set_cell_props(self): "PRESELECT_I0", "PRESELECT_I1", ) + _cell_props["DSP48E1"] = ( + "ACASCREG", + "ADREG", + "A_INPUT", + "ALUMODEREG", + "AREG", + "AUTORESET_PATDET", + "BCASCREG", + "B_INPUT", + "BREG", + "CARRYINREG", + "CARRYINSELREG", + "CREG", + "DREG", + "INMODEREG", + "MASK", + "MREG", + "OPMODEREG", + "PATTERN", + "PREG", + "SEL_MASK", + "SEL_PATTERN", + "USE_DPORT", + "USE_MULT", + "USE_PATTERN_DETECT", + "USE_SIMD", + ) self._cell_props = _cell_props @@ -277,47 +323,82 @@ def map_ports(self): def init_matching_instances(self): """Init possible_matches dict with all instances that match by cell type and properties""" - all_instances = [ - i for i in self.reversed_netlist.instances if not i.cell_type.startswith("SDN_VERILOG") - ] - grouped_by_cell_type = defaultdict(list) - for instance in all_instances: - 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]) + log_with_banner("Initializing possible matches based on cell type and properties") - grouped_by_cell_type[(instance.cell_type, hash(frozenset(properties)))].append(instance) + 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) - for named_instance 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 - properties = set() - for prop in self.get_properties_for_type(named_instance.cell_type): - properties.add( - f"{prop}{convert_verilog_literal_to_int(named_instance.properties[prop])}" + else: + all_instances = [ + i + for i in self.reversed_netlist.instances + if not i.cell_type.startswith("SDN_VERILOG") + ] + grouped_by_cell_type = defaultdict(list) + for instance in all_instances: + 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[(instance.cell_type, hash(frozenset(properties)))].append( + instance ) - my_hash = hash(frozenset(properties)) - instances_matching = grouped_by_cell_type[(named_instance.cell_type, my_hash)] + for named_instance in self.named_netlist.instances_to_map: + ############################################################### + # First find all instances of the same type and same properties + ############################################################### - if not instances_matching: - logging.info( - "No property matches for cell %s of type %s. Properties:", - named_instance.name, - named_instance.cell_type, - ) + # Compute a hash of this instance's properties + properties = set() 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." - ) + properties.add( + f"{prop}{convert_verilog_literal_to_int(named_instance.properties[prop])}" + ) + my_hash = hash(frozenset(properties)) + + instances_matching = grouped_by_cell_type[(named_instance.cell_type, my_hash)] + + 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() + 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""" @@ -334,9 +415,28 @@ def potential_mapping_wrapper(self, instance): else: instances_matching = self.check_for_potential_bram_mapping(instance) - # self.log(f" {instances_matching} matches") - if not instances_matching: + if self.debug: + 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] + ) + # 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", instance.name, tile, site, cell_type) + raise StructuralCompareError( f"Not equivalent. {instance.name} has no possible match in the netlist." ) @@ -693,19 +793,42 @@ def check_for_potential_mapping(self, named_instance): idx = pin.index else: idx = 0 if pin.index == 1 else 1 + + name = pin.name + + if named_instance.cell_type == "DSP48E1" and name in { + "ALUMODE", + "OPMODE", + "INMODE", + "CLK", + "CARRYIN", + }: + for instance in instances_matching_connections: + # [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] + if int(reversed_prop[idx]) == 1 and ( + (other_net.is_gnd and instance.get_pin(name, idx).net.is_vdd) + or (other_net.is_vdd and instance.get_pin(name, idx).net.is_gnd) + ): + pin.ignore_net_equivalency = True + + if pin.ignore_net_equivalency: + continue + tmp = [ instance for instance in instances_matching_connections - if instance.get_pin(pin.name, idx).net == other_net + 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 pin.name[1] == '0' else '0'}" + other_pin = f"I{'1' if name[1] == '0' else '0'}" tmp = [ inst for inst in instances_matching_connections - if inst.get_pin(pin.name, idx).net == inst.get_pin(other_pin, idx).net + if inst.get_pin(name, idx).net == inst.get_pin(other_pin, idx).net ] pin.ignore_net_equivalency = True @@ -714,14 +837,14 @@ def check_for_potential_mapping(self, named_instance): 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 + 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(pin.name, idx).net.is_vdd + if instance.get_pin(name, idx).net.is_vdd ] instances_matching_connections = tmp @@ -761,11 +884,13 @@ 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") 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, ) try: struct_cmp.compare_netlists()