prepare partitioning for multi-thread ppsfp
This commit is contained in:
1 parent
6b99576f0b
commit
2928b5129c
5 files changed
+244
-28
No files matched your search
@@ -0,0 +1,74 @@
|
|||||||
|
# general
|
||||||
|
mode=direct
|
||||||
|
objective=km1
|
||||||
|
seed=-1
|
||||||
|
cmaxnet=1000
|
||||||
|
vcycles=0
|
||||||
|
# main -> preprocessing -> min hash sparsifier
|
||||||
|
p-use-sparsifier=true
|
||||||
|
p-sparsifier-min-median-he-size=28
|
||||||
|
p-sparsifier-max-hyperedge-size=1200
|
||||||
|
p-sparsifier-max-cluster-size=10
|
||||||
|
p-sparsifier-min-cluster-size=2
|
||||||
|
p-sparsifier-num-hash-func=5
|
||||||
|
p-sparsifier-combined-num-hash-func=100
|
||||||
|
# main -> preprocessing -> community detection
|
||||||
|
p-detect-communities=true
|
||||||
|
p-detect-communities-in-ip=true
|
||||||
|
p-reuse-communities=false
|
||||||
|
p-max-louvain-pass-iterations=100
|
||||||
|
p-min-eps-improvement=0.0001
|
||||||
|
p-louvain-edge-weight=hybrid
|
||||||
|
p-large-he-threshold=1000
|
||||||
|
# main -> preprocessing -> large he removal
|
||||||
|
p-smallest-maxnet-threshold=50000
|
||||||
|
p-maxnet-removal-factor=0.01
|
||||||
|
# main -> coarsening
|
||||||
|
c-type=ml_style
|
||||||
|
c-s=1
|
||||||
|
c-t=160
|
||||||
|
# main -> coarsening -> rating
|
||||||
|
c-rating-score=heavy_edge
|
||||||
|
c-rating-use-communities=true
|
||||||
|
c-rating-heavy_node_penalty=no_penalty
|
||||||
|
c-rating-acceptance-criterion=best_prefer_unmatched
|
||||||
|
c-fixed-vertex-acceptance-criterion=fixed_vertex_allowed
|
||||||
|
# main -> initial partitioning
|
||||||
|
i-mode=recursive
|
||||||
|
i-technique=multi
|
||||||
|
# initial partitioning -> coarsening
|
||||||
|
i-c-type=ml_style
|
||||||
|
i-c-s=1
|
||||||
|
i-c-t=150
|
||||||
|
# initial partitioning -> coarsening -> rating
|
||||||
|
i-c-rating-score=heavy_edge
|
||||||
|
i-c-rating-use-communities=true
|
||||||
|
i-c-rating-heavy_node_penalty=no_penalty
|
||||||
|
i-c-rating-acceptance-criterion=best_prefer_unmatched
|
||||||
|
i-c-fixed-vertex-acceptance-criterion=fixed_vertex_allowed
|
||||||
|
# initial partitioning -> initial partitioning
|
||||||
|
i-algo=pool
|
||||||
|
i-runs=20
|
||||||
|
# initial partitioning -> bin packing
|
||||||
|
i-bp-algorithm=worst_fit
|
||||||
|
i-bp-heuristic-prepacking=false
|
||||||
|
i-bp-early-restart=true
|
||||||
|
i-bp-late-restart=true
|
||||||
|
# initial partitioning -> local search
|
||||||
|
i-r-type=twoway_fm
|
||||||
|
i-r-runs=-1
|
||||||
|
i-r-fm-stop=simple
|
||||||
|
i-r-fm-stop-i=50
|
||||||
|
# main -> local search
|
||||||
|
r-type=kway_fm_hyperflow_cutter_km1
|
||||||
|
r-runs=-1
|
||||||
|
r-fm-stop=adaptive_opt
|
||||||
|
r-fm-stop-alpha=1
|
||||||
|
r-fm-stop-i=350
|
||||||
|
# local_search -> flow scheduling and heuristics
|
||||||
|
r-flow-execution-policy=exponential
|
||||||
|
# local_search -> hyperflowcutter configuration
|
||||||
|
r-hfc-size-constraint=mf-style
|
||||||
|
r-hfc-scaling=16
|
||||||
|
r-hfc-distance-based-piercing=true
|
||||||
|
r-hfc-mbc=true
|
||||||
+1
-1
Submodule kyupy updated: ed9b5c6cd6...f0c10633bf.
@@ -2,14 +2,13 @@
|
|||||||
import argparse
|
import argparse
|
||||||
import subprocess
|
import subprocess
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import time
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from kyupy import verilog, bench, log, logic, batchrange, atalanta, stil
|
from kyupy import verilog, bench, log, logic, batchrange, atalanta, stil
|
||||||
from kyupy.techlib import techlib_by_name, KYUPY
|
from kyupy.techlib import techlib_by_name, KYUPY
|
||||||
|
|
||||||
from fsim.static import LineRoles, FaultSet
|
from fsim.static import LineRoles, FaultSet, CircuitPartition
|
||||||
from fsim.simple import SAFSimSimple
|
from fsim.simple import SAFSimSimple
|
||||||
from fsim.incremental import SAFSimIncremental
|
from fsim.incremental import SAFSimIncremental
|
||||||
from fsim.ppsfp import SAFSimPPSFP
|
from fsim.ppsfp import SAFSimPPSFP
|
||||||
@@ -23,7 +22,8 @@ def main():
|
|||||||
parser.add_argument('-t', '--tlib', default='SKY130', help=f'Techlib for verilog circuit. Default: SKY130, available: {sorted(techlib_by_name.keys())}.')
|
parser.add_argument('-t', '--tlib', default='SKY130', help=f'Techlib for verilog circuit. Default: SKY130, available: {sorted(techlib_by_name.keys())}.')
|
||||||
parser.add_argument('-p', '--patterns', default='1024', help='Pattern file or number of random patterns to simulate. Default: 1024.')
|
parser.add_argument('-p', '--patterns', default='1024', help='Pattern file or number of random patterns to simulate. Default: 1024.')
|
||||||
parser.add_argument('-a', '--algorithm', default='simple', help=f'Fault simulation algorithm. Default: simple, available: {sorted(algorithms.keys())}.')
|
parser.add_argument('-a', '--algorithm', default='simple', help=f'Fault simulation algorithm. Default: simple, available: {sorted(algorithms.keys())}.')
|
||||||
parser.add_argument('-o', '--output', default=None, help='')
|
parser.add_argument('-o', '--output', default=None, help='Write fault list to given file')
|
||||||
|
parser.add_argument('-k', '--partitions', default=1, help='Partition circuit into partitions')
|
||||||
parser.add_argument('--seed', type=int, default=42, help='Random seed for reproducibility. Default: 42.')
|
parser.add_argument('--seed', type=int, default=42, help='Random seed for reproducibility. Default: 42.')
|
||||||
parser.add_argument('circuit', help='Gate-level verilog, bench, or nix package to import. See available packages: "nix flake show github:s-holst/benchmark-circuits".')
|
parser.add_argument('circuit', help='Gate-level verilog, bench, or nix package to import. See available packages: "nix flake show github:s-holst/benchmark-circuits".')
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
@@ -43,9 +43,7 @@ def main():
|
|||||||
|
|
||||||
stats = {k.replace('__',''): v for k, v in c.stats(args.tlib).items() if k.startswith('__') or k.endswith('put')}
|
stats = {k.replace('__',''): v for k, v in c.stats(args.tlib).items() if k.startswith('__') or k.endswith('put')}
|
||||||
log.info(f'circuit {stats=}')
|
log.info(f'circuit {stats=}')
|
||||||
|
log.info(f'line role stats={LineRoles(c, args.tlib).stats}')
|
||||||
lr = LineRoles(c, args.tlib)
|
|
||||||
log.info(f'line role stats={lr.stats}')
|
|
||||||
|
|
||||||
c_resolved = c.copy()
|
c_resolved = c.copy()
|
||||||
c_resolved.resolve_tlib_cells(args.tlib)
|
c_resolved.resolve_tlib_cells(args.tlib)
|
||||||
@@ -55,13 +53,8 @@ def main():
|
|||||||
log.info(f'uncollapsed stuck-at fault count: {len(fs.saf_set)}')
|
log.info(f'uncollapsed stuck-at fault count: {len(fs.saf_set)}')
|
||||||
log.info(f'collapsed stuck-at fault count: {len(fs.saf_equiv_classes)}')
|
log.info(f'collapsed stuck-at fault count: {len(fs.saf_equiv_classes)}')
|
||||||
|
|
||||||
ffr_stems = []
|
partitions = CircuitPartition(c_resolved, int(args.partitions))
|
||||||
for stem, _ in c_resolved.fanout_free_regions(KYUPY):
|
partitions.print_stats()
|
||||||
if len(stem.outs) > 0 and stem.outs[0] is not None:
|
|
||||||
ffr_stems.append(stem.outs[0])
|
|
||||||
ffr_stems = np.array(ffr_stems, dtype=np.uint32)
|
|
||||||
|
|
||||||
log.info(f'FFR count: {len(ffr_stems)}')
|
|
||||||
|
|
||||||
rng = np.random.default_rng(args.seed)
|
rng = np.random.default_rng(args.seed)
|
||||||
|
|
||||||
|
|||||||
+41
-13
@@ -7,6 +7,31 @@ from kyupy import log, batchrange, cdiv, Timers, logic
|
|||||||
from kyupy.circuit import Circuit, Node
|
from kyupy.circuit import Circuit, Node
|
||||||
from kyupy.logic_sim import LogicSim2V
|
from kyupy.logic_sim import LogicSim2V
|
||||||
from kyupy.techlib import KYUPY
|
from kyupy.techlib import KYUPY
|
||||||
|
from kyupy.logic_sim import LogicSim2V, c_prop_2v_cpu
|
||||||
|
|
||||||
|
|
||||||
|
class LogicSim2VCone:
|
||||||
|
def __init__(self, base_sim: LogicSim2V, origin_lines: set[int]):
|
||||||
|
self.sims = base_sim.sims
|
||||||
|
|
||||||
|
self.ops = base_sim.cone_ops(origin_lines)
|
||||||
|
self.c_locs = base_sim.c_locs
|
||||||
|
self.c_len = len(self.c_locs)
|
||||||
|
|
||||||
|
self.c = base_sim.c.copy()
|
||||||
|
self.c_dirty = np.full(self.c_len, 1, dtype=np.uint8)
|
||||||
|
self._full_mask = base_sim._full_mask
|
||||||
|
|
||||||
|
def c_prop(self, fault_line=-1, fault_model=2, fault_mask=None):
|
||||||
|
if fault_mask is None:
|
||||||
|
fault_mask = self._full_mask # default: full mask
|
||||||
|
else:
|
||||||
|
if len(fault_mask) < self.c.shape[-1]: # pad mask with 0's if necessary
|
||||||
|
fault_mask2 = np.full(self.c.shape[-1], 0, dtype=np.uint8)
|
||||||
|
fault_mask2[:len(fault_mask)] = fault_mask
|
||||||
|
fault_mask = fault_mask2
|
||||||
|
c_prop_2v_cpu(self.ops, self.c_locs, self.c, self.c_dirty, int(fault_line), fault_mask, int(fault_model))
|
||||||
|
|
||||||
|
|
||||||
class SAFSimPPSFP:
|
class SAFSimPPSFP:
|
||||||
"""A stuck-at fault simulator that uses explicit simulations only at roots of fan-out free regions.
|
"""A stuck-at fault simulator that uses explicit simulations only at roots of fan-out free regions.
|
||||||
@@ -83,12 +108,15 @@ class SAFSimPPSFP:
|
|||||||
c_golden = np.zeros_like(self.sim.c)
|
c_golden = np.zeros_like(self.sim.c)
|
||||||
fclass_DS = set()
|
fclass_DS = set()
|
||||||
nbatches = cdiv(patterns.shape[1], self.sim.sims)
|
nbatches = cdiv(patterns.shape[1], self.sim.sims)
|
||||||
nfaults = len(faults)
|
|
||||||
ffr_obs = np.zeros((len(self.ffr_stem2idx), self.sim.c.shape[2]), dtype=np.uint8)
|
ffr_obs = np.zeros((len(self.ffr_stem2idx), self.sim.c.shape[2]), dtype=np.uint8)
|
||||||
ffr_obs_valid = np.zeros(len(self.ffr_stem2idx), dtype=np.uint8)
|
|
||||||
obs_mask = np.packbits(np.full(patterns.shape[1], 1, dtype=np.uint8), bitorder='little')
|
obs_mask = np.packbits(np.full(patterns.shape[1], 1, dtype=np.uint8), bitorder='little')
|
||||||
fault_to_stem_obs = obs_mask.copy()
|
fault_to_stem_obs = obs_mask.copy()
|
||||||
fault_act = obs_mask.copy()
|
fault_act = obs_mask.copy()
|
||||||
|
# collect necessary stems
|
||||||
|
ffr_stems = set()
|
||||||
|
for fault in faults:
|
||||||
|
fault_site = fault//2
|
||||||
|
ffr_stems.add(self.lines2ffr_stem[fault_site])
|
||||||
|
|
||||||
with self.timers['sim'], log.progress() as p:
|
with self.timers['sim'], log.progress() as p:
|
||||||
for bidx, (bo, bs) in enumerate(batchrange(patterns.shape[1], self.sim.sims)):
|
for bidx, (bo, bs) in enumerate(batchrange(patterns.shape[1], self.sim.sims)):
|
||||||
@@ -99,17 +127,11 @@ class SAFSimPPSFP:
|
|||||||
self.sim.c_prop()
|
self.sim.c_prop()
|
||||||
c_golden[...] = self.sim.c
|
c_golden[...] = self.sim.c
|
||||||
c_golden_poppo = c_golden[self.sim.poppo_c_locs]
|
c_golden_poppo = c_golden[self.sim.poppo_c_locs]
|
||||||
ffr_obs_valid[...] = 0
|
|
||||||
|
|
||||||
for fidx, fault in enumerate(faults):
|
# compute all necessary FFR observability vectors in ffr_obs
|
||||||
fault_site = fault//2
|
n_stems = len(ffr_stems)
|
||||||
fault_polarity = fault&1
|
for stem_progress, ffr_stem in enumerate(ffr_stems):
|
||||||
ffr_stem = self.lines2ffr_stem[fault_site]
|
|
||||||
assert ffr_stem is not None
|
|
||||||
ffr_idx = self.ffr_stem2idx[ffr_stem]
|
ffr_idx = self.ffr_stem2idx[ffr_stem]
|
||||||
|
|
||||||
# compute FFR observability vector ffr_obs[ffr_idx] if necessary
|
|
||||||
if not ffr_obs_valid[ffr_idx]:
|
|
||||||
if len(ffr_stem.outs) > 1:
|
if len(ffr_stem.outs) > 1:
|
||||||
self.sim.c_dirty[...] = 0
|
self.sim.c_dirty[...] = 0
|
||||||
with self.timers['sim_ffr_prop']:
|
with self.timers['sim_ffr_prop']:
|
||||||
@@ -120,7 +142,14 @@ class SAFSimPPSFP:
|
|||||||
self.sim.c[...] = c_golden # clear fault
|
self.sim.c[...] = c_golden # clear fault
|
||||||
else: # primary output, completely observable
|
else: # primary output, completely observable
|
||||||
ffr_obs[ffr_idx] = obs_mask
|
ffr_obs[ffr_idx] = obs_mask
|
||||||
ffr_obs_valid[ffr_idx] = 1
|
p.update(((stem_progress+1) / n_stems) * ((bidx+1) / nbatches))
|
||||||
|
|
||||||
|
for fault in faults:
|
||||||
|
fault_site = fault//2
|
||||||
|
fault_polarity = fault&1
|
||||||
|
ffr_stem = self.lines2ffr_stem[fault_site]
|
||||||
|
assert ffr_stem is not None
|
||||||
|
ffr_idx = self.ffr_stem2idx[ffr_stem]
|
||||||
|
|
||||||
# observability of the fault site at the FFR stem by tracing
|
# observability of the fault site at the FFR stem by tracing
|
||||||
# the (unique, fan-out free) path from the site to the stem
|
# the (unique, fan-out free) path from the site to the stem
|
||||||
@@ -141,6 +170,5 @@ class SAFSimPPSFP:
|
|||||||
|
|
||||||
if np.bitwise_or.reduce(ffr_obs[ffr_idx] & fault_to_stem_obs & fault_act) != 0:
|
if np.bitwise_or.reduce(ffr_obs[ffr_idx] & fault_to_stem_obs & fault_act) != 0:
|
||||||
fclass_DS.add(fault)
|
fclass_DS.add(fault)
|
||||||
p.update(((fidx+1) / nfaults) * ((bidx+1) / nbatches))
|
|
||||||
|
|
||||||
return {'DS': fclass_DS, 'NO': set(faults) - fclass_DS}
|
return {'DS': fclass_DS, 'NO': set(faults) - fclass_DS}
|
||||||
+122
-1
@@ -2,12 +2,15 @@
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from collections.abc import Iterable
|
||||||
from itertools import product
|
from itertools import product
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
import kahypar
|
||||||
|
|
||||||
|
from kyupy import log
|
||||||
from kyupy.circuit import Circuit, Node
|
from kyupy.circuit import Circuit, Node
|
||||||
from kyupy.techlib import TechLib
|
from kyupy.techlib import TechLib, KYUPY
|
||||||
|
|
||||||
class LineRoles:
|
class LineRoles:
|
||||||
"""Computes signal line roles of a circuit depending on the structural reach of each signal.
|
"""Computes signal line roles of a circuit depending on the structural reach of each signal.
|
||||||
@@ -193,3 +196,121 @@ class FaultSet:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def fault_type_str(fault: int) -> str:
|
def fault_type_str(fault: int) -> str:
|
||||||
return 'sa1' if fault&1 else 'sa0'
|
return 'sa1' if fault&1 else 'sa0'
|
||||||
|
|
||||||
|
|
||||||
|
def collect_ancestors(key: int, predecessors: dict[int,set[int]], ancestors: dict[int,set[int]]) -> set[int]:
|
||||||
|
if key not in ancestors:
|
||||||
|
anc: set[int] = {key}
|
||||||
|
if key in predecessors:
|
||||||
|
for pred in predecessors[key]:
|
||||||
|
anc.update(collect_ancestors(pred, predecessors, ancestors))
|
||||||
|
ancestors[key] = anc
|
||||||
|
return ancestors[key]
|
||||||
|
|
||||||
|
|
||||||
|
def collect_reachable(key: int, successors: dict[int,set[int]], reachable: dict[int,set[int]]) -> set:
|
||||||
|
if key not in reachable:
|
||||||
|
suc = {key}
|
||||||
|
if key in successors:
|
||||||
|
for s in successors[key]:
|
||||||
|
suc.update(collect_reachable(s, successors, reachable))
|
||||||
|
reachable[key] = suc
|
||||||
|
return reachable[key]
|
||||||
|
|
||||||
|
|
||||||
|
def ffr_set_stats(stems: Iterable[int], reachable: dict[int,set[int]]):
|
||||||
|
all_reachable = set()
|
||||||
|
propagation_sum = 0
|
||||||
|
stem_count = 0
|
||||||
|
for n in stems:
|
||||||
|
stem_count += 1
|
||||||
|
r = reachable[n]
|
||||||
|
all_reachable.update(r)
|
||||||
|
propagation_sum += len(r)
|
||||||
|
total_reachable_stems = len(all_reachable)
|
||||||
|
propagation_avg = propagation_sum / stem_count
|
||||||
|
propagation_avg_portion = propagation_avg / total_reachable_stems
|
||||||
|
return f'{stem_count:6d} {total_reachable_stems:7d} {propagation_avg_portion:.3f}'
|
||||||
|
|
||||||
|
|
||||||
|
class CircuitPartition:
|
||||||
|
|
||||||
|
def __init__(self, c_resolved: Circuit, k: int = 1):
|
||||||
|
line2fo_node = np.full(len(c_resolved.lines), -1, dtype=np.int32)
|
||||||
|
fo_nodes = []
|
||||||
|
for fo_node, region_nodes in c_resolved.fanout_free_regions(KYUPY):
|
||||||
|
fo_nodes.append(fo_node)
|
||||||
|
for n in [fo_node] + region_nodes:
|
||||||
|
for il in n.ins.without_nones():
|
||||||
|
line2fo_node[il] = fo_node
|
||||||
|
|
||||||
|
log.info(f'FFR count: {len(fo_nodes)}')
|
||||||
|
|
||||||
|
predecessors = defaultdict(set)
|
||||||
|
successors = defaultdict(set)
|
||||||
|
for fo_node in fo_nodes:
|
||||||
|
if len(fo_node.outs) > 1:
|
||||||
|
for ol in fo_node.outs.without_nones():
|
||||||
|
assert line2fo_node[ol] >= 0, f'unknown {ol}'
|
||||||
|
predecessors[int(line2fo_node[ol])].add(fo_node.index)
|
||||||
|
successors[fo_node.index].add(int(line2fo_node[ol]))
|
||||||
|
|
||||||
|
ancestors = dict()
|
||||||
|
for fo_node in fo_nodes:
|
||||||
|
collect_ancestors(fo_node.index, predecessors, ancestors)
|
||||||
|
|
||||||
|
reachable = dict()
|
||||||
|
for fo_node in fo_nodes:
|
||||||
|
collect_reachable(fo_node.index, successors, reachable)
|
||||||
|
|
||||||
|
if k <= 1:
|
||||||
|
return {0: {n.index for n in fo_nodes}}
|
||||||
|
|
||||||
|
stem2hnode = {s.index: i for i, s in enumerate(fo_nodes)}
|
||||||
|
|
||||||
|
num_hnodes = len(fo_nodes)
|
||||||
|
hnode_weights = [len(reachable[n.index]) for n in fo_nodes]
|
||||||
|
|
||||||
|
num_hnets = len(fo_nodes)
|
||||||
|
hnets = [[] for _ in range(num_hnodes)]
|
||||||
|
for stem, anc in ancestors.items():
|
||||||
|
hnets[stem2hnode[stem]] = sorted(stem2hnode[n] for n in anc)
|
||||||
|
|
||||||
|
hyperedge_indices = []
|
||||||
|
hyperedges = []
|
||||||
|
for hnet in hnets:
|
||||||
|
hyperedge_indices.append(len(hyperedges))
|
||||||
|
hyperedges.extend(hnet)
|
||||||
|
hyperedge_indices.append(len(hyperedges))
|
||||||
|
|
||||||
|
edge_weights = []
|
||||||
|
|
||||||
|
hypergraph = kahypar.Hypergraph(num_hnodes, num_hnets, hyperedge_indices, hyperedges, k, edge_weights, hnode_weights)
|
||||||
|
|
||||||
|
context = kahypar.Context()
|
||||||
|
context.suppressOutput(True)
|
||||||
|
context.loadINIconfiguration("km1_kKaHyPar_sea20.ini")
|
||||||
|
|
||||||
|
context.setK(k)
|
||||||
|
context.setEpsilon(0.1)
|
||||||
|
|
||||||
|
log.info('Partitioning...')
|
||||||
|
|
||||||
|
kahypar.partition(hypergraph, context)
|
||||||
|
|
||||||
|
log.info('Partitioning finished.')
|
||||||
|
|
||||||
|
self.partitions = defaultdict(set)
|
||||||
|
for n in hypergraph.nodes():
|
||||||
|
self.partitions[hypergraph.blockID(n)].add(fo_nodes[n].index)
|
||||||
|
self.fo_nodes = fo_nodes
|
||||||
|
self.reachable = reachable
|
||||||
|
|
||||||
|
|
||||||
|
def print_stats(self):
|
||||||
|
log.info('FFR set stats:')
|
||||||
|
log.info('[part] [size] [reach] [prop avg.]')
|
||||||
|
log.info(f'[all] {ffr_set_stats([n.index for n in self.fo_nodes], self.reachable)}')
|
||||||
|
|
||||||
|
for i, stems in sorted(self.partitions.items()):
|
||||||
|
log.info(f'[{i:3d}] {ffr_set_stats(stems, self.reachable)}')
|
||||||
Reference in new issue
Block a user