@ -1,9 +1,9 @@
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
@ -21,6 +21,10 @@ class LogicSim2VCone:
self . c = base_sim . c . copy ( )
self . c = base_sim . c . copy ( )
self . c_dirty = np . full ( self . c_len , 1 , dtype = np . uint8 )
self . c_dirty = np . full ( self . c_len , 1 , dtype = np . uint8 )
self . _full_mask = base_sim . _full_mask
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 ) :
def c_prop ( self , fault_line = - 1 , fault_model = 2 , fault_mask = None ) :
if fault_mask is None :
if fault_mask is None :
@ -33,22 +37,56 @@ class LogicSim2VCone:
c_prop_2v_cpu ( self . ops , self . c_locs , self . c , self . c_dirty , int ( fault_line ) , fault_mask , int ( fault_model ) )
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 ) ,
@ -109,14 +147,20 @@ class SAFSimPPSFP:
fclass_DS = set ( )
fclass_DS = set ( )
nbatches = cdiv ( patterns . shape [ 1 ] , self . sim . sims )
nbatches = cdiv ( patterns . shape [ 1 ] , self . sim . sims )
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 )
obs_mask = np . packbits ( np . full ( patterns . shape [ 1 ] , 1 , dtype = np . uint8 ) , bitorder = ' little ' )
obs_mask = np . zeros ( self . sim . c . shape [ 2 ] , dtype = np . uint8 )
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 ( )
# collect necessary stem s
# collate all necessary stems into partition s
ffr_stems = set ( )
part2stems = defaultdict ( set )
for fault in faults :
for fault in faults :
fault_site = fault / / 2
fault_site = fault / / 2
ffr_stems . add ( self . lines2ffr_stem [ fault_site ] )
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 ) ) :
@ -125,24 +169,13 @@ 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 ]
# compute all necessary FFR observability vectors in ffr_obs
for part , ffr_stems in part2stems . items ( ) :
n_stems = len ( ffr_stems )
ffr_idxs = [ self . ffr_stem2idx [ stem ] for stem in ffr_stems ]
for stem_progress , ffr_stem in enumerate ( ffr_stems ) :
sim = self . sim_parts [ part ]
ffr_idx = self . ffr_stem2idx [ ffr_stem ]
if isinstance ( sim , LogicSim2VCone ) :
if len ( ffr_stem . outs ) > 1 :
sim . c_from_base ( self . sim )
self . sim . c_dirty [ . . . ] = 0
ffr_obs [ ffr_idxs ] = compute_ffr_obs ( sim , ffr_stems , obs_mask , self . timers )
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 :
for fault in faults :
fault_site = fault / / 2
fault_site = fault / / 2