Pattern Export¶
The shared, format-independent TAP-transaction op vocabulary (warptap.tap_ir), and the
SVF/STAPL/STIL text emitters that render it.
A tester-ready STIL file¶
to_stil always declares TCK, TMS, TDI and TDO. Declare the DUT's other pins as well:
inputs with the value each one holds, outputs (never compared), one signal per bit of a
bus. Start the pattern with a reset lead-in that clocks TCK while TRST is low:
from warptap import GotoState, SetPins, select_instruction, to_stil
from warptap.tap_fsm import TapState
from warptap.tap_model import OPCODE_EXTEST
lead_in = [
SetPins((("trst_n", 0), ("rst_n", 0))), # assert TRST and the chip reset
GotoState(TapState.TEST_LOGIC_RESET), # five TCK cycles with TMS=1
SetPins((("trst_n", 1), ("rst_n", 1))), # release both
GotoState(TapState.RUN_TEST_IDLE), # one TMS=0 cycle
]
stil = to_stil(
lead_in + select_instruction(OPCODE_EXTEST) + pdl.program,
jtag_period="50ns",
pulse_periods={"clk": "10ns"},
inputs={"trst_n": 1, "rst_n": 1, "clk": 0, "func_addr[0]": 0, "func_addr[1]": 1},
outputs=["func_dout[0]", "func_dout[1]"],
)
In each TCK cycle TMS and TDI change at 0, TDO is compared at a quarter period, and TCK rises
at half and falls at three quarters; to_stil below says why. A Runtest or PulsePin of
many cycles is written as one Loop. Releases up to 0.0.3 compared TDO after the rising edge,
so on warptap's own TAP every compare checked the next bit.
warptap.tap_ir ¶
TAP-transaction IR (implementation_plan.md §3.4, §7 Stage 5). One internal representation
for a scan-test sequence, independent of output format -- built now, not deferred to Stage 7,
because §3.4's own stated justification ("reduces header+payload+trailer composition from the
SIB-tree retargeting result to shift/OR/mask arithmetic") describes exactly the composition
problem pdl_interpreter.PDLInterpreter.iApply already has to solve. A Stage-5-private
alternative would need the same fields anyway; Stage 7's SVF/STAPL emitters and Stage 5's own
tap_ir_play.play() become two stateless backends walking the same list.
Scan data is an arbitrary-precision Python int with an explicit bits width and an
optional mask (which bits of tdo to actually compare), not a bit-list -- matches SVF/
STAPL's own LSB-first hex conventions and keeps composition to shift/OR/mask arithmetic.
Bit-encoding convention for tdi/tdo/mask: bit i is the value shifted on
cycle i + 1 of that op -- chronological/feed order, matching how this project's own tests
already represent an observed shift-out sequence as an int
(sum(bit << i for i, bit in enumerate(observed_bits))). Any code composing these ints from
:mod:warptap.sib_layout's position-ordered bit lists (index 0 = the chain position nearest
TDI, i.e. what that position ends up holding once the whole shift completes) must reverse the
list first -- the two orders are exact reverses of each other, since the last bit fed ends up
at the position nearest TDI (the same "last fed bit lands at position 0" rule every prior
stage's directed tests already rely on).
GotoState/Runtest reuse :class:~warptap.tap_fsm.TapState directly rather than a
parallel enum -- the same "one canonical table, never two that could drift apart" reasoning
implementation_plan.md §7 Stage 2 already gives for sharing the FSM table between the RTL
codegen and the Python model.
GotoState ¶
Bases: NamedTuple
ShiftIR ¶
Bases: NamedTuple
ShiftDR ¶
Bases: NamedTuple
Runtest ¶
Bases: NamedTuple
PulsePin ¶
Bases: NamedTuple
Pulse a named signal OTHER than TCK/TMS/TDI/TDO (implementation_plan.md §7 Stage 13)
-- e.g. a DUT's functional/system clock -- count times, independent of TCK's own
timing domain. TAP pins (TCK/TMS/TDI) stay static throughout. hold_pins optionally
drives other named signals (e.g. a faultflow pattern's capture_pi_values) to fixed
values for the same duration -- both only ever meaningful together, at the moment of
forcing a functional capture edge, hence one op rather than two.
Exists because gating this pulse from TCK itself (an earlier design considered and
rejected) is unsound for a real class of external ATPG patterns: a transition/delay fault
needs an at-speed capture edge, which TCK -- externally, slowly, ATE-driven -- cannot
provide. Deliberately NOT expressible via SVF/STAPL (a JTAG-pins-only vocabulary, no
concept of a second clock domain) -- both emitters raise their own existing
TapIrSvfError/TapIrStaplError for it; only :mod:warptap.tap_ir_stil can render
it, since STIL's own WaveformTable mechanism lets an independently-timed signal
coexist with the JTAG pins in one pattern.
SetPins ¶
Bases: NamedTuple
Drive DUT pins other than TCK/TMS/TDI/TDO to new values from the next cycle on, until
another SetPins changes them: e.g. assert trst_n for a reset lead-in, clock TCK,
then release it. Unlike hold_pins, the values stay after the op. Only
:mod:warptap.tap_ir_stil renders it, and each pin must be one of its inputs; the SVF
and STAPL emitters and :mod:warptap.tap_ir_play raise their unsupported-op errors for it,
as they do for :class:PulsePin.
bits_from_int ¶
bits_from_int(value: int, width: int) -> list[int]
Inverse of :func:bits_to_int: unpack value into a width-long
chronological-order bit list.
bits_to_int ¶
bits_to_int(bits: list[int]) -> int
Pack a chronological-order bit list (index 0 = cycle 1) into this module's LSB-first int convention.
warptap.tap_ir_svf ¶
SVF (Serial Vector Format) text emitter (implementation_plan.md §3.4, §6, §7 Stage 7).
Walks the same :mod:warptap.tap_ir ops list :func:warptap.tap_ir_play.play/to_cycles
already walk, producing real SVF text -- the second of §3.4's "stateless pretty-printers over
one IR." Grounded directly in the SVF Specification, Revision E (ASSET InterTech / Texas
Instruments, 8 March 1999) -- freely copyable/quotable per its own copyright notice -- and
cross-checked against OpenOCD's real svf.c parser/executor source.
Bit-encoding: SVF's own bit-order rule ("the least significant bit... is the first bit
scanned into the hardware for TDI... and is the first bit scanned out for TDO", spec p.5) is
identical to tap_ir.py's own chronological/LSB-first int convention -- confirmed by
direct comparison against the spec's own worked example (SIR 8 TDI (41); shifts LSB-first,
cycle 1 = bit 0 of 0x41, exactly matching bits_from_int(0x41, 8)). No bit reversal is
needed anywhere in this module -- op.tdi/op.tdo/op.mask format directly as hex.
GotoState ops are dropped, not translated. SVF's own SIR/SDR statements
implicitly handle the entire "navigate from the current stable state into Shift-IR/DR, shift,
return to whatever ENDIR/ENDDR currently specifies" sequence -- confirmed directly by
the spec's own worked example, which never precedes a scan with an explicit STATE command.
Since every GotoState this project's own ops ever emit only ever brackets a shift (matching
tap_ir_play.py's own narrow navigation model), an explicit STATE translation would be
redundant, not merely an optimization -- this module emits a single ENDIR IDLE;/
ENDDR IDLE; header instead (SVF's own default, per spec p.8, but stated explicitly here to
match this stage's "always emit every field, no sticky-default optimization yet" discipline),
which is exactly what every GotoState(RUN_TEST_IDLE) in this project's own ops already
wants. The one exception is GotoState(TEST_LOGIC_RESET), a reset that SIR/SDR
navigation never performs: it becomes STATE RESET;.
Deliberately emits every TDI/TDO/MASK field on every SIR/SDR -- SVF's
own field "stickiness" (reusing the last value for an omitted field) is a real, legal
compression this module doesn't use, matching §7 Stage 7's explicit instruction.
HDR/HIR/TDR/TIR (multi-device chain header/trailer padding) are omitted
entirely -- confirmed safe by both the spec's own semantics ("A header can be removed by
setting length to 0") and OpenOCD's own svf_para_init, which statically zeroes them before
any file is even read. TRST is likewise never emitted -- warptap's own tap_ir ops have
no TRST-equivalent op type, matching tap_ir_play.play()/to_cycles()'s own "neither
function drives a reset itself" precedent (Stage 5).
TapIrSvfError ¶
Bases: WarptapError
Raised when to_svf is given an op it can't render.
to_svf ¶
to_svf(ir_ops: List[_IrOp]) -> str
Render ir_ops as SVF text, one statement per line, prefixed by an explicit
ENDIR IDLE;/ENDDR IDLE; header. Raises :class:TapIrSvfError for an op this
emitter can't represent (currently none of the ops this project's own callers
produce trigger this -- GotoState is unconditionally droppable, see module
docstring -- but a genuinely unsupported future op type would land here rather than
silently emitting something wrong).
warptap.tap_ir_stapl ¶
STAPL (JEDEC JESD71 / IEEE 1532) text emitter (implementation_plan.md §3.4, §6, §7 Stage 7).
Walks the same :mod:warptap.tap_ir ops list the SVF backend (:mod:warptap.tap_ir_svf) and
:mod:warptap.tap_ir_play already walk -- the third "stateless pretty-printer over one IR"
§3.4 promises. Grounded in the full JESD71-1999 spec text and Altera/Actel's real reference
STAPL-player source (jamcrc.c), fetched and read directly during Stage 7's own
implementation session.
File shape: NOTE fields, one ACTION, one PROCEDURE containing the lowered ops,
ENDPROC, then a CRC statement computed over everything before it
(:mod:warptap.stapl_crc16). STAPL mandates the CRC statement be the file's last
statement, computed over the whole file's ASCII source text up to that point -- this module
therefore builds the file in two passes: render everything through ENDPROC;, compute the
CRC over that text, then append the CRC <hex4>; line.
GotoState ops are dropped, matching the SVF backend's own reasoning: STAPL's
IRSCAN/DRSCAN statements implicitly handle the full "navigate from the current stable
state into the shift state, shift, return to whatever IRSTOP/DRSTOP currently
specifies" sequence -- IRSTOP/DRSTOP are STAPL's own sticky-end-state directives,
directly analogous to SVF's ENDIR/ENDDR, confirmed to default to IDLE the same
way. This module emits an explicit IRSTOP IDLE;/DRSTOP IDLE; header, matching every
GotoState(RUN_TEST_IDLE) this project's own ops already target. As in SVF,
GotoState(TEST_LOGIC_RESET) is the exception and becomes STATE RESET;.
Bit-encoding: STAPL's own array-index convention (JESD71 §6.4/§8.8 -- "array index 0
corresponds to the rightmost character... shifted in increasing order of the array index," an
explicit "least significant bit to most significant bit" framing in Annex A's own worked
example) matches tap_ir.py's chronological/LSB-first int convention exactly, the same as
SVF's -- no bit reversal needed; a scan literal is a plain positional hex string
($<hex>), MSB on the left, LSB on the right.
COMPARE clause, for ops carrying tdo/mask. Grammar confirmed this session
against five independently-converging real sources -- the full JESD71-1999 spec text (read
directly, §8.8 DRSCAN/§8.18 IRSCAN), real vendor-generated .jam/.stp files
(Altera Quartus Prime, Altera's own "Chain Interrogation" utility, Microsemi FlashPro), and
two independent copies of the Altera "Jam STAPL Player" reference C source
(jamexec.c, sibling of the jamcrc.c already used for :mod:warptap.stapl_crc16) --
all agreeing on the same grammar, unlike an earlier, lower-confidence secondary summary that
wrongly implied a separate MASK keyword:
DRSCAN <length>, <data> [,COMPARE <compare array>, <mask array>, <result>];
IRSCAN <length>, <data> [,COMPARE <compare array>, <mask array>, <result>];
COMPARE is not a standalone statement -- it is a trailing clause on DRSCAN/IRSCAN,
taking exactly three positional (unlabeled) arguments: the expected value, a mask (bit 1 =
compare this position, 0 = don't-care), and a previously-declared scalar BOOLEAN
variable that receives the result. <compare array>/<mask array> may be bare hex
literals (confirmed in a real Microsemi FlashPro file: DRSCAN 8, $00,COMPARE $00,$80,
PASS;) -- this emitter uses that form, avoiding any need to also declare/populate BOOLEAN
array variables for the expected/mask data, matching this module's existing bare-hex-literal
TDI convention.
Per JESD71's own text, a failed COMPARE sets <result> FALSE but does not abort
execution by itself -- every real file this session found follows a COMPARE with an
explicit check (IF <result> == 0 THEN ...) to actually act on failure, exactly the pattern
this emitter reproduces (IF <result_var> == 0 THEN EXIT (1);, real syntax confirmed
directly in Altera's IDCODE.JAM: IF (stuck_tdo_flag == 1) THEN EXIT (1);). Without this,
STAPL's COMPARE would silently NOT enforce what SVF's TDO/MASK enforces
automatically (SVF's own player checks and reports a mismatch with no extra statement needed
in the file, confirmed directly against a real OpenOCD svf run this session) -- the two
emitted formats would otherwise diverge in behavior for identical ir_ops input. Each
COMPARE-bearing op gets its own uniquely-named result variable
(compare_result_<n>), declared with a plain BOOLEAN <name>; statement immediately
before its first use, directly inside PROCEDURE DO_RUN -- the Jam 1.1 language spec's own
inline example (BOOLEAN verify_error; DRSCAN ..., COMPARE ..., verify_error;) shows a
local declaration immediately preceding use with no surrounding DATA/USES block, which
this module follows since its own PROCEDURE DO_RUN has no such block either; real
vendor-generated files instead declare variables in a separate DATA block referenced via
USES on the PROCEDURE line, an alternative, more elaborate structure not adopted here.
TapIrStaplError ¶
Bases: WarptapError
Raised when to_stapl is given an op it can't render -- an unsupported op type, or
a ShiftIR/ShiftDR with exactly one of tdo/mask set (STAPL's COMPARE
clause needs both together, see module docstring).
to_stapl ¶
to_stapl(ir_ops: List[_IrOp], *, device: str, date: str, creator: str = 'warptap', stapl_version: str = '1.01', alg_version: str = '1', stack_depth: str = '1', target: str = '1', max_freq: str = '10000000') -> str
Render ir_ops as a complete, single-PROCEDURE STAPL file, ending in a real,
computed CRC statement. device/date are required (this project doesn't
invent device-identifying data, matching e.g. Stage 4's InstrumentNode discipline).
target (JESD71 §10.2, Table 13) is not free text: it's a comma-separated list of
1-based chain-position numbers, counted from the TDI side toward TDO, naming which
device(s) in the physical scan chain this file's operations address. It defaults to
"1" (the first, and in warptap's v1 scope the only, device in the chain) -- a real,
meaningful default, not an "unspecified" placeholder, since warptap only ever targets a
standalone TAP in v1, never a multi-device daisy chain. The remaining NOTE fields
default to explicitly-labeled placeholder values -- this project has no real
frequency/version data to report either, and says so via these defaults rather than
fabricating plausible-looking ones.
warptap.tap_ir_stil ¶
STIL (IEEE Std 1450, "Standard Test Interface Language") text emitter (implementation_plan.md
§7 Stage 13). The fourth "stateless pretty-printer over one IR" (:mod:warptap.tap_ir_svf,
:mod:warptap.tap_ir_stapl, and this module all walk the same :mod:warptap.tap_ir ops list),
built specifically to carry :class:~warptap.tap_ir.PulsePin -- a functional/system-clock
pulse independent of TCK's own timing domain, which SVF/STAPL (a JTAG-pins-only vocabulary)
cannot express at all. See :class:~warptap.tap_ir.PulsePin's own docstring for why this
capability exists and why it isn't modeled as TCK gating a functional clock instead.
Grounded in real primary sources fetched directly this session: the IEEE 1450 working
group's own "D03" STIL Reference Guide and a real worked 1450-1999 example file (both hosted at
grouper.ieee.org/groups/1450) -- the actual ratified standard text is paywalled and was not
independently read. Live-validated against a real, independent, pip-installable parser,
Semi-ATE-STIL (github.com/Semi-ATE/STIL, Lark-based) -- despite its own "not yet ready
for production" disclaimer, it both syntax- and semantic-validated real STIL text this session,
including the exact multi-WaveformTable mechanism this emitter depends on.
Real, empirically-discovered requirements this module's output follows (found by actually
feeding files through Semi-ATE-STIL, not derived from documentation alone -- the Reference
Guide's own condensed worked example omits several of these):
- A
WaveformTable's per-signal event schedules must be wrapped in aWaveforms { ... }block -- the real worked 1450-1999 example this session's research found omits this wrapper entirely (an older/simplified presentation), butSemi-ATE-STIL's own grammar requires it. - Block order is significant and was not documented anywhere this session's research found:
SignalGroupsmust precedeTiming;PatternBurstandPatternExecmust both precede anyPatternblock they reference. - Every signal referenced in ANY vector under a given
WaveformTablemust have its OWN WFC (waveform character) defined in THAT table -- switching tables mid-pattern (this module's own mechanism for letting the TCK domain and a functional-clock domain coexist) means every signal needs an entry in EVERY table the pattern ever selects, even if that entry is just "hold" (P) or "don't-compare" (X) for a signal irrelevant to that table's own phase. STILParser.parse_semantic()'s return value is alwaysNoneregardless of outcome -- real success isparser.is_parsing_done is Truewithparser.err_msg == ""(confirmed by reading its own source directly; a naiveif parser.parse_semantic():would silently treat every file, valid or not, as failing).
No real, independent-tool-confirmed convention exists for encoding a JTAG SIR/SDR-style scan
operation as STIL vectors -- this session's own research found no public example (an EDA
vendor's own IJTAG-to-STIL translator was cited as existing, but its actual per-cycle
convention isn't public). This emitter's own convention -- one V{} per TCK cycle (a
Runtest or PulsePin run is one V{} in a Loop); TMS/TDI driven via
:func:warptap.tap_ir_play.navigation_tms/:func:~warptap.tap_ir_play.shift_tms; TDO
compared (H/L) when a bit falls within a ShiftIR/ShiftDR's own mask,
don't-compared (X) otherwise -- is a reasoned construction from STIL's own confirmed
primitives, not an established industry idiom, stated here rather than presented as settled
convention.
Where each edge and strobe sits in a period is :func:to_stil's docstring. Semi-ATE-STIL's
syntax and semantic checks cannot see it, so tests/test_tap_ir_stil_replay.py replays
emitted files literally against rtl/tap_core.v and inserted designs in Icarus and counts
TDO mismatches; it fails on the 0.0.3 strobe, which compared TDO after the rising TCK edge.
One WaveformTable per distinct :class:~warptap.tap_ir.PulsePin target port, each
declaring hold/force/don't-compare WFCs for every OTHER known signal (TCK/TMS/TDI/TDO, every
other pulse port, every hold_pins key seen anywhere in ir_ops, every declared input and
output) -- the mechanism,
confirmed empirically against Semi-ATE-STIL, that lets an independently-timed functional
clock pulse coexist with the TAP's own JTAG-speed WaveformTable in one Pattern,
switched via a plain W <name>; statement.
TapIrStilError ¶
Bases: WarptapError
Raised when to_stil is given an op it can't render, a period it can't read, a
pulse_periods mapping missing an entry for some :class:~warptap.tap_ir.PulsePin
target this ir_ops list actually uses, or a pin it can't declare or drive as asked.
to_stil ¶
to_stil(ir_ops: List[_IrOp], *, jtag_period: str = '100ns', pulse_periods: Optional[Dict[str, str]] = None, inputs: Optional[Mapping[str, int]] = None, outputs: Iterable[str] = ()) -> str
Render ir_ops as a complete STIL file: Signals/SignalGroups/Timing
(one WaveformTable for ordinary JTAG cycles, one more per distinct
:class:~warptap.tap_ir.PulsePin target port)/PatternBurst/PatternExec/
Pattern (block order matches this module's own empirically-confirmed requirement,
see module docstring). jtag_period/pulse_periods are real caller inputs -- STIL
always requires explicit timing, this project has no real frequency data to invent
(matches :mod:warptap.tap_ir_stapl's own NOTE field discipline). Each is a positive
number with a unit (fs, ps, ns, us, ms or s), e.g. '50ns'; the
edge times are written in the same unit. Raises :class:TapIrStilError for an
unsupported op, a period it can't read, or a PulsePin target port missing from
pulse_periods.
Pins. TCK, TMS, TDI (In) and TDO (Out) are always declared. inputs
declares the DUT's other inputs, each with the value (0 or 1) it holds: it is driven in
every vector, except where a PulsePin pulses it or lists it in hold_pins.
outputs declares the DUT's other outputs: Out, and never compared. A tester-ready
file declares every DUT pin this way. A PulsePin port or hold_pins key left out of
inputs is still declared In, but only a pulse that names it ever drives it: JTAG
cycles and other pulses hold whatever it had (P), and nothing drives it before. Up to
0.0.3 a PulsePin drove every such pin it did not list to 0, which asserts an
active-low reset. Naming a TAP pin, a pin in both, or an output in a PulsePin raises
:class:TapIrStilError. A bus is one signal per bit, named like func_addr[0]; a name
that is not a plain identifier is written in double quotes, as STIL requires, in every
block and in a PulsePin's WaveformTable name.
Reset lead-in. :class:~warptap.tap_ir.SetPins changes declared inputs from the next
vector on, so a pattern can reset the TAP itself, e.g. with inputs={"trst_n": 1, ...}::
[SetPins((("trst_n", 0),)), # assert TRST
GotoState(TapState.TEST_LOGIC_RESET), # five TCK cycles with TMS=1
SetPins((("trst_n", 1),)), # release TRST
GotoState(TapState.RUN_TEST_IDLE)] # one TMS=0 cycle
TCK runs while TRST is low, which matters: tap_core and the network cells reset on
negedge trst_n or on a TCK edge while it is low, so a TRST that is low from the start
(in simulation, or a tester pin that powers up low) has no falling edge and needs those TCK
edges. Without a TRST pin the two GotoState still reset the TAP through TMS, but not
the SIB network, which only TRST resets. Without either, the ops must start where the TAP
already is: in Run-Test/Idle.
Loops. A Runtest or PulsePin with count above 1 is one V inside
Loop count { ... }, so an MBIST run loop of thousands of cycles is one statement. When
such a run opens the pattern, its first cycle is written as a plain V and the rest
loop: Semi-ATE-STIL's compiler, like a tester that loads the loop count on the vector
before the loop, needs a vector first.
Timing. One jtag_wft vector is one TCK cycle of period T:
- TMS and TDI change at 0;
- TDO is compared at T/4;
- TCK rises at T/2 and falls at 3T/4, so it idles low between cycles.
A vector's expected TDO bit is the one the TAP shows before that vector's rising edge,
the edge that shifts it out: :meth:~warptap.tap_model.TapModel.tick returns it before it
clocks, and ShiftIR/ShiftDR's tdo holds those bits. rtl/tap_core.v changes
TDO on the rising edge, so that bit is valid from the previous rise (-T/2) to this one
(T/2). A TAP that changes TDO on the falling edge, as IEEE 1149.1 requires, shows it from
the previous fall (-T/4) to this cycle's fall (3T/4). A strobe at T/4 lies inside both
windows and is at least T/4 from any TDO change in either. TMS and TDI get T/2 of setup
before the rise and T/2 of hold after it. Up to 0.0.3, TCK rose at 1ns and TDO was compared
at 2ns, after the rising edge, so on tap_core every compare checked the next bit.
A PulsePin's port has the same shape in its own period: low at 0, rising at T/2,
falling at 3T/4. Its hold_pins change at 0, half a period before the rising edge.