@ -1,29 +1,92 @@
import time
from collections import defaultdict
from collections . abc import Collection
from collections . abc import Collection
import numpy as np
import numpy as np
from kyupy import log , batchrange , cdiv , Timers , logic
from kyupy import log , batchrange , cdiv , Timers
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
self . poppo_c_locs = base_sim . poppo_c_locs
def c_from_base ( self , base_sim ) :
self . c [ . . . ] = base_sim . c
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 ) )
def compute_ffr_obs ( sim : LogicSim2V | LogicSim2VCone , ffr_stems : Collection [ Node ] , obs_mask , timers ) :
ffr_obs = [ ]
c_golden = sim . c . copy ( )
c_golden_poppo = c_golden [ sim . poppo_c_locs ]
for ffr_stem in ffr_stems :
if len ( ffr_stem . outs ) > 1 :
sim . c_dirty [ . . . ] = 0
with timers [ ' sim_ffr_prop ' ] :
sim . c_prop ( fault_line = ffr_stem . ins [ 0 ] . index , fault_model = 2 )
with timers [ ' sim_ffr_out_reduce ' ] :
ffr_obs . append ( np . bitwise_or . reduce ( c_golden_poppo ^ sim . c [ sim . poppo_c_locs ] , axis = 0 ) [ 0 ] & obs_mask )
with timers [ ' sim_ffr_reset ' ] :
sim . c [ . . . ] = c_golden # clear fault
else : # primary output, completely observable
ffr_obs . append ( obs_mask )
return np . stack ( ffr_obs )
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.
"""
"""
def __init__ ( self , circuit_resolved : Circuit , batch_size : int ) :
def __init__ ( self , circuit_resolved : Circuit , batch_size : int , partitioning : dict [ int , set [ int ] ] | None = None ) :
self . partitioning = partitioning
self . sim = LogicSim2V ( circuit_resolved , sims = batch_size )
self . sim = LogicSim2V ( circuit_resolved , sims = batch_size )
self . timers = Timers ( )
self . timers = Timers ( )
self . ffr_stem2idx = { }
self . ffr_stem2idx = { }
self . lines2ffr_stem : list [ Node | None ] = [ None ] * len ( circuit_resolved . lines )
self . lines2ffr_stem : list [ Node | None ] = [ None ] * len ( circuit_resolved . lines )
# origin line (the stem's driving line, where faults are injected in
# compute_ffr_obs) keyed by stem *node* index -- as stored in partitioning.
stem_idx2origin_line = { }
for stem , nodes in circuit_resolved . fanout_free_regions ( KYUPY ) :
for stem , nodes in circuit_resolved . fanout_free_regions ( KYUPY ) :
self . ffr_stem2idx [ stem ] = len ( self . ffr_stem2idx )
self . ffr_stem2idx [ stem ] = len ( self . ffr_stem2idx )
if len ( stem . ins ) > 0 : # PI-fork stems have no driving line / no fault site
stem_idx2origin_line [ stem . index ] = stem . ins [ 0 ] . index
for n in nodes + [ stem ] :
for n in nodes + [ stem ] :
for il in n . ins . without_nones ( ) :
for il in n . ins . without_nones ( ) :
self . lines2ffr_stem [ il . index ] = stem
self . lines2ffr_stem [ il . index ] = stem
self . sim_parts = { 0 : self . sim }
if self . partitioning is not None :
self . ffr_stem2part = { s : p for p , ss in self . partitioning . items ( ) for s in ss }
if len ( self . partitioning ) > 1 :
self . sim_parts = {
p : LogicSim2VCone ( self . sim , { stem_idx2origin_line [ s ] for s in stems
if s in stem_idx2origin_line } )
for p , stems in self . partitioning . items ( ) }
def _gate_sensitivity ( self , node : Node , pin : int ) - > np . ndarray :
def _gate_sensitivity ( self , node : Node , pin : int ) - > np . ndarray :
""" Patterns (as a packed bit-vector) for which `node` ' s output is
""" Patterns (as a packed bit-vector) for which `node` ' s output is
sensitive to a change on input ` pin ` ( the Boolean difference d_o / d_pin ) ,
sensitive to a change on input ` pin ` ( the Boolean difference d_o / d_pin ) ,
@ -83,12 +146,21 @@ 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 . zeros ( self . sim . c . shape [ 2 ] , dtype = np . uint8 )
obs_mask = np . packbits ( np . full ( patterns . shape [ 1 ] , 1 , dtype = np . uint8 ) , bitorder = ' little ' )
obs_mask_start = np . packbits ( np . full ( patterns . shape [ 1 ] , 1 , dtype = np . uint8 ) , bitorder = ' little ' )
obs_mask [ : len ( obs_mask_start ) ] = obs_mask_start
fault_to_stem_obs = obs_mask . copy ( )
fault_to_stem_obs = obs_mask . copy ( )
fault_act = obs_mask . copy ( )
fault_act = obs_mask . copy ( )
# collate all necessary stems into partitions
part2stems = defaultdict ( set )
for fault in faults :
fault_site = fault / / 2
stem = self . lines2ffr_stem [ fault_site ]
assert stem is not None
part = 0 if self . partitioning is None else self . ffr_stem2part [ stem . index ]
part2stems [ part ] . add ( stem )
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 ) ) :
@ -97,31 +169,21 @@ class SAFSimPPSFP:
self . sim . c_dirty [ . . . ] = 1
self . sim . c_dirty [ . . . ] = 1
with self . timers [ ' sim_full_prop ' ] :
with self . timers [ ' sim_full_prop ' ] :
self . sim . c_prop ( )
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 ) :
for part , ffr_stems in part2stems . items ( ) :
ffr_idxs = [ self . ffr_stem2idx [ stem ] for stem in ffr_stems ]
sim = self . sim_parts [ part ]
if isinstance ( sim , LogicSim2VCone ) :
sim . c_from_base ( self . sim )
ffr_obs [ ffr_idxs ] = compute_ffr_obs ( sim , ffr_stems , obs_mask , self . timers )
for fault in faults :
fault_site = fault / / 2
fault_site = fault / / 2
fault_polarity = fault & 1
fault_polarity = fault & 1
ffr_stem = self . lines2ffr_stem [ fault_site ]
ffr_stem = self . lines2ffr_stem [ fault_site ]
assert ffr_stem is not None
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 :
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
# 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
# and requiring every gate along it to be sensitised.
# and requiring every gate along it to be sensitised.
@ -141,6 +203,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 }