Faultflow¶
Retargeting a real faultflow --export-patterns export through an inserted network; see
Faultflow pattern retargeting for a full example.
warptap.faultflow_retarget ¶
faultflow pattern retargeting (implementation_plan.md ยง7 Stage 14, built on Stage 13's
PulsePin/STIL emission). Consumes faultflow's real --export-patterns JSON (a flat array
of {"load_seqs": {chain_id: [bool,...]}, "capture_pi_values": {port: bool},
"expected_unload": {chain_id: [bool,...]}} records, faultflow/scan/pattern_export.py's
own scan_pattern_to_dict) and remaps each pattern through warptap's own SIB/TAP network
model, driving a real :class:~warptap.pdl_interpreter.PDLInterpreter -- never faultflow's own
SoC chain-offset table, and never faultflow's own retargeting algorithm (this project's
established "port understanding, not code" boundary with that project).
A faultflow chain id carries no inherent name (a bare int assigned during block-level scan
stitching) -- chain_to_instrument is a REQUIRED caller input, since no shared naming
convention exists between the two tools. The separate, structurally different
faultflow_soc_access_v1 manifest (a flat, always-present SoC chain addressed by offset, not
a TAP-instruction-selected network) is not consumed here.
Bit order -- independently re-derived and verified twice this session (once by hand with a
non-palindromic worked example, once by an independent review given the same primitives): both
tools define physical "position 0" as nearest-SI identically, but faultflow's own exported
sequence is index-reversed relative to position (faultflow/retarget/transform.py's own
s[k] == V[L-1-k]), while warptap's own payload_value bit k IS position k
directly (:mod:warptap.sib_layout's compose_bits). This means
payload_bit[k] == sequence[width-1-k] -- the exported bit list must be REVERSED before
packing into an int via :func:warptap.tap_ir.bits_to_int. Not a new convention: tap_ir.py's
own module docstring already states the identical rule for composing sib_layout's
position-ordered bit lists into a chronological int.
Padding, confirmed directly against the real source (faultflow/scan/protocol.py's
serialize_vector): when a pattern's chains differ in length, load_seqs is padded at the
FRONT and expected_unload at the BACK to a shared max_chain_length -- neither the
padding amount nor max_chain_length itself is present in the exported JSON, so padding is
stripped here using each chain's REAL width (from chain_to_instrument -> graph ->
InstrumentNode.width): the last width elements of load_seqs, the first width
elements of expected_unload.
One PulsePin per pattern, not per chain -- a real correctness requirement, not a
simplification: faultflow's ScanPattern represents ONE fault-detection attempt where every
referenced chain is loaded, a SINGLE functional capture edge is applied (with
capture_pi_values held throughout), and every chain is then unloaded/compared against that
SAME capture. Interleaving load/pulse/unload per chain would compare a chain's response before
other chains in the same pattern were even loaded, or before any capture happened at all --
this module loads every chain first, pulses once, then unloads every chain.
clock_port/clock_pulse_count are REQUIRED caller inputs: faultflow's own schema carries
no clock/timing field at all (confirmed directly against transform.py/emit.py -- zero
references to any clock concept in the real SoC-pattern-emission code). Making the caller
supply a real period for that pin is standard STIL authoring, not a burden unique to this
module -- see :mod:warptap.tap_ir_stil's own pulse_periods requirement.
FaultflowRetargetError ¶
Bases: WarptapError
Raised when a pattern references a faultflow chain id absent from
chain_to_instrument, or that mapping names an instrument not present in graph.
retarget_faultflow_patterns ¶
retarget_faultflow_patterns(patterns: List[Dict[str, Any]], chain_to_instrument: Dict[int, str], graph: PhysicalGraph, root: ModuleInstance, *, clock_port: str, clock_pulse_count: int) -> List[_IrOp]
Retarget every pattern in patterns (the parsed --export-patterns JSON array)
through a real :class:~warptap.pdl_interpreter.PDLInterpreter built against
graph/root, returning the accumulated :mod:warptap.tap_ir ops -- feed straight
into :func:warptap.tap_ir_stil.to_stil (the only backend that can render the
:class:~warptap.tap_ir.PulsePin ops this function inserts).
Raises :class:FaultflowRetargetError naming the offending chain id if
chain_to_instrument doesn't cover every chain a pattern references, or names an
instrument absent from graph.
Continuous-flow decompressor¶
A faultflow design whose [compression] entry says architecture = "continuous" loads its
chains from a ring generator that takes num_channels fresh channel bits on every shift,
not one seed. retarget_decompressed_faultflow_patterns picks the retarget from the
manifest's architecture (an entry without one is the older one-shot decompressor);
solve_pattern_channels solves a pattern's load over every free bit (num_channels x
warmup + max_chain_length) strictly, and the retarget writes one channel word per shift
to the channel instrument, with one clock pulse each.
warptap.faultflow_continuous ¶
faultflow's continuous-flow scan decompressor: its model and pattern retargeting (W1, next
to faultflow_compression.py's one-shot port).
The decompressor (faultflow/scan/ring_generator.py and compression.py in faultflow;
ported here as understanding, not as an import): a Galois ring generator of poly.width
stages, cleared on the scan enable's rise (the first shift of a load, after a clock with
scan enable low) and fed num_channels channel bits on every shift -- channel c is
XORed into ring stage c * width // num_channels -- and a phase shifter driving each chain's
scan-in from the ring. Shift t's scan-ins read the ring as shift t - 1 left it (shift
0's: the cleared ring, 0). A load is warmup + max_chain_length shifts, so a pattern has
num_channels x shifts free bits instead of one seed's: the first warmup shifts only
fill the ring (their scan-ins fall off the chains' far ends), and a pattern's load_seqs[chain][t]
is the scan-in of shift warmup + t.
A free bit is named by its shift and channel: bit t * num_channels + c is channel c of
the word applied at shift t. :func:continuous_scan_in_rows gives every scan-in as a GF(2)
mask over them; :func:solve_pattern_channels solves a pattern's load over all of them at
once, strictly -- a don't-care is a load_care the pattern names, nothing else -- and
returns each channel's bit for each shift. :func:retarget_continuous_faultflow_patterns drives
them through the JTAG network: one clock pulse with scan enable low, then for each shift a
write of that shift's channel word and one pulse with scan enable high, then the capture and
the unload as in the one-shot flow.
manifest["compression"] describes the decompressor: architecture ("continuous";
absent or "one_shot" is the older decompressor, which
:func:retarget_decompressed_faultflow_patterns hands to faultflow_compression),
num_channels, polynomial ({"width", "taps"}), phase_shifter_taps, warmup
and, as a check, injectors.
Not retargeted here: a transition (launch) pattern, as in the one-shot flow.
ContinuousDecompressor
dataclass
¶
What a pattern's load is made of: the ring (poly), the phase shifter (one tap
list of ring stages per chain), the channel count and the warm-up shifts.
shifts ¶
shifts(max_chain_length: int) -> int
How many shifts a load takes: the warm-up and the longest chain's.
continuous_from_manifest ¶
continuous_from_manifest(compression: dict) -> ContinuousDecompressor
The decompressor manifest["compression"] describes (see this module's docstring);
a missing field, or injectors that are not the ones this model computes, is
refused.
continuous_scan_in_rows ¶
continuous_scan_in_rows(poly: LfsrPolynomial, phase_shifter_taps: List[List[int]], num_channels: int, cycles: int) -> List[List[int]]
rows[t][chain]: chain's scan-in at shift t, for t in range(cycles),
as a GF(2) bitmask over the channel bits (bit t * num_channels + c: channel c of
the word applied at shift t) -- ported from
faultflow/scan/ring_generator.py::continuous_scan_in_rows.
injector_stages ¶
injector_stages(width: int, num_channels: int) -> List[int]
The ring stage each channel bit XORs into on every shift: channel c into stage
c * width // num_channels, spread evenly -- ported from
faultflow/scan/ring_generator.py::injector_stages.
solve_pattern_channels ¶
solve_pattern_channels(load_seqs: dict, decompressor: ContinuousDecompressor, max_chain_length: int, pattern_index: int, load_care: 'Optional[set[tuple[int, int]]]' = None, launch_scan_in: 'Optional[dict[int, bool]]' = None) -> dict
One joint GF(2) system over every free bit of the load (num_channels x shifts),
solved once: {channel: [bit for each shift]} driving the chains to load_seqs.
load_seqs[chain][t] is shift warmup + t's scan-in; load_care, when not None,
keeps only the (chain, t) positions it names; launch_scan_in ({chain: bit}, a
launch-on-shift pattern) constrains the scan-in of the shift after the load.
Unconstrained bits are 0.
retarget_continuous_faultflow_patterns ¶
retarget_continuous_faultflow_patterns(patterns: List[dict], chain_to_instrument: dict, compression_channel_instrument: str, graph: PhysicalGraph, root: ModuleInstance, *, decompressor: ContinuousDecompressor, max_chain_length: int, clock_port: str, scan_enable_port: str, clock_pulse_count: int = 1) -> list
Retarget every pattern through a continuous-decompressor composed netlist's JTAG
network: solve the pattern's channel words (:func:solve_pattern_channels), pulse the
clock once with scan enable low (so the next edge's scan-enable rise clears the ring),
then for each shift write that shift's channel word to compression_channel_instrument
(a num_channels-bit WRITE instrument on the composed netlist's channel port) and pulse
the clock once with scan enable high. The unload side is
:mod:warptap.faultflow_compression's: capture pulse (scan enable low, the pattern's
capture_pi_values), then a read of each raw scan_out_N chain.
retarget_decompressed_faultflow_patterns ¶
retarget_decompressed_faultflow_patterns(patterns: List[dict], chain_to_instrument: dict, compression_channel_instrument: str, graph: PhysicalGraph, root: ModuleInstance, *, compression: dict, max_chain_length: int, clock_port: str, scan_enable_port: str, clock_pulse_count: int = 1) -> list
Retarget through whichever decompressor manifest["compression"] says is on the
chip: architecture == "continuous" the continuous one, anything else (absent: the
older one) :func:~warptap.faultflow_compression.retarget_compressed_faultflow_patterns.