Skip to content

Commit

Permalink
Merge branch 'main' into new_fasm2bels
Browse files Browse the repository at this point in the history
  • Loading branch information
yonnorc42 authored Jul 19, 2024
2 parents 0da9055 + 5b34541 commit a4a3b4b
Show file tree
Hide file tree
Showing 5 changed files with 183 additions and 45 deletions.
4 changes: 3 additions & 1 deletion bfasst/flows/vivado_phys_netlist_cmp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -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

Expand Down
11 changes: 10 additions & 1 deletion bfasst/tools/compare/structural/structural.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,22 @@ 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"
self.log_name = log_name
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"

Expand All @@ -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 "",
},
)

Expand Down
Original file line number Diff line number Diff line change
@@ -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 }}


2 changes: 1 addition & 1 deletion bfasst/tools/compare/structural/structural_rules.ninja
Original file line number Diff line number Diff line change
@@ -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

209 changes: 167 additions & 42 deletions bfasst/utils/structural.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"""
Expand All @@ -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(
Expand All @@ -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"
)
Expand Down Expand Up @@ -74,6 +92,7 @@ def __set_cell_props(self):
"RAM32X1D_1",
"RAM256X1S",
"SRL16E",
"SRLC16E",
"SRLC32E",
"LDCE",
)
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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"""
Expand All @@ -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."
)
Expand Down Expand Up @@ -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

Expand All @@ -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

Expand Down Expand Up @@ -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()
Expand Down

0 comments on commit a4a3b4b

Please sign in to comment.