Browse Source

prepare partitioning for multi-thread ppsfp

main
stefan 5 days ago
parent
commit
2928b5129c
  1. 74
      km1_kKaHyPar_sea20.ini
  2. 2
      kyupy
  3. 19
      main.py
  4. 66
      src/fsim/ppsfp.py
  5. 123
      src/fsim/static.py

74
km1_kKaHyPar_sea20.ini

@ -0,0 +1,74 @@ @@ -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

2
kyupy

@ -1 +1 @@ @@ -1 +1 @@
Subproject commit ed9b5c6cd67e78876c105643cd107ca9f32dcee2
Subproject commit f0c10633bf1d4a2dbef7d83e494386b87ad93c07

19
main.py

@ -2,14 +2,13 @@ @@ -2,14 +2,13 @@
import argparse
import subprocess
from pathlib import Path
import time
import numpy as np
from kyupy import verilog, bench, log, logic, batchrange, atalanta, stil
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.incremental import SAFSimIncremental
from fsim.ppsfp import SAFSimPPSFP
@ -23,7 +22,8 @@ def main(): @@ -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('-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('-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('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()
@ -43,9 +43,7 @@ def main(): @@ -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')}
log.info(f'circuit {stats=}')
lr = LineRoles(c, args.tlib)
log.info(f'line role stats={lr.stats}')
log.info(f'line role stats={LineRoles(c, args.tlib).stats}')
c_resolved = c.copy()
c_resolved.resolve_tlib_cells(args.tlib)
@ -55,13 +53,8 @@ def main(): @@ -55,13 +53,8 @@ def main():
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)}')
ffr_stems = []
for stem, _ in c_resolved.fanout_free_regions(KYUPY):
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)}')
partitions = CircuitPartition(c_resolved, int(args.partitions))
partitions.print_stats()
rng = np.random.default_rng(args.seed)

66
src/fsim/ppsfp.py

@ -7,6 +7,31 @@ from kyupy import log, batchrange, cdiv, Timers, logic @@ -7,6 +7,31 @@ from kyupy import log, batchrange, cdiv, Timers, logic
from kyupy.circuit import Circuit, Node
from kyupy.logic_sim import LogicSim2V
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:
"""A stuck-at fault simulator that uses explicit simulations only at roots of fan-out free regions.
@ -83,12 +108,15 @@ class SAFSimPPSFP: @@ -83,12 +108,15 @@ class SAFSimPPSFP:
c_golden = np.zeros_like(self.sim.c)
fclass_DS = set()
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_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')
fault_to_stem_obs = 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:
for bidx, (bo, bs) in enumerate(batchrange(patterns.shape[1], self.sim.sims)):
@ -99,29 +127,30 @@ class SAFSimPPSFP: @@ -99,29 +127,30 @@ class SAFSimPPSFP:
self.sim.c_prop()
c_golden[...] = self.sim.c
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
n_stems = len(ffr_stems)
for stem_progress, ffr_stem in enumerate(ffr_stems):
ffr_idx = self.ffr_stem2idx[ffr_stem]
if len(ffr_stem.outs) > 1:
self.sim.c_dirty[...] = 0
with self.timers['sim_ffr_prop']:
self.sim.c_prop(fault_line=ffr_stem.ins[0].index, fault_model=2)
with self.timers['sim_ffr_out_reduce']:
ffr_obs[ffr_idx] = np.bitwise_or.reduce(c_golden_poppo ^ self.sim.c[self.sim.poppo_c_locs], axis=0) & obs_mask
with self.timers['sim_ffr_reset']:
self.sim.c[...] = c_golden # clear fault
else: # primary output, completely observable
ffr_obs[ffr_idx] = obs_mask
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]
# compute FFR observability vector ffr_obs[ffr_idx] if necessary
if not ffr_obs_valid[ffr_idx]:
if len(ffr_stem.outs) > 1:
self.sim.c_dirty[...] = 0
with self.timers['sim_ffr_prop']:
self.sim.c_prop(fault_line=ffr_stem.ins[0].index, fault_model=2)
with self.timers['sim_ffr_out_reduce']:
ffr_obs[ffr_idx] = np.bitwise_or.reduce(c_golden_poppo ^ self.sim.c[self.sim.poppo_c_locs], axis=0) & obs_mask
with self.timers['sim_ffr_reset']:
self.sim.c[...] = c_golden # clear fault
else: # primary output, completely observable
ffr_obs[ffr_idx] = obs_mask
ffr_obs_valid[ffr_idx] = 1
# observability of the fault site at the FFR stem by tracing
# the (unique, fan-out free) path from the site to the stem
# and requiring every gate along it to be sensitised.
@ -141,6 +170,5 @@ class SAFSimPPSFP: @@ -141,6 +170,5 @@ class SAFSimPPSFP:
if np.bitwise_or.reduce(ffr_obs[ffr_idx] & fault_to_stem_obs & fault_act) != 0:
fclass_DS.add(fault)
p.update(((fidx+1) / nfaults) * ((bidx+1) / nbatches))
return {'DS': fclass_DS, 'NO': set(faults) - fclass_DS}

123
src/fsim/static.py

@ -2,12 +2,15 @@ @@ -2,12 +2,15 @@
"""
from collections import defaultdict
from collections.abc import Iterable
from itertools import product
import numpy as np
import kahypar
from kyupy import log
from kyupy.circuit import Circuit, Node
from kyupy.techlib import TechLib
from kyupy.techlib import TechLib, KYUPY
class LineRoles:
"""Computes signal line roles of a circuit depending on the structural reach of each signal.
@ -193,3 +196,121 @@ class FaultSet: @@ -193,3 +196,121 @@ class FaultSet:
@staticmethod
def fault_type_str(fault: int) -> str:
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)}')
Loading…
Cancel
Save