Skip to content

JTAG Operations

The JTAGController provides direct access to the JTAG interface: chain discovery, register scanning, TAP state machine control, and boundary scan file execution. It acts as a facade that delegates to specialized submodules (chain, scan, state, boundary).

Access it through the session:

# Async
session.jtag
# Sync
sync_session.jtag

scan_chain() queries OpenOCD for every TAP (Test Access Port) on the JTAG chain and returns them as TAPInfo dataclasses.

import asyncio
from openocd import Session
async def main():
async with await Session.connect() as session:
taps = await session.jtag.scan_chain()
for tap in taps:
print(f"TAP: {tap.name}")
print(f" Chip: {tap.chip}, TAP name: {tap.tap_name}")
print(f" IDCODE: 0x{tap.idcode:08X}")
print(f" IR length: {tap.ir_length} bits")
print(f" Enabled: {tap.enabled}")
asyncio.run(main())

A typical STM32 output:

TAP: stm32f1x.cpu
Chip: stm32f1x, TAP name: cpu
IDCODE: 0x3BA00477
IR length: 4 bits
Enabled: True

new_tap(chip, tap, ir_len, expected_id=None) declares a new TAP on the chain. This is typically done before scan chain initialization, but can be useful when dynamically configuring multi-device chains.

async with await Session.connect() as session:
# Declare a TAP with known IDCODE
await session.jtag.new_tap(
chip="fpga",
tap="bs",
ir_len=6,
expected_id=0x0362D093
)
# Declare a TAP without IDCODE verification
await session.jtag.new_tap(chip="cpld", tap="cpu", ir_len=8)

irscan(tap, instruction) shifts an instruction into a TAP’s instruction register (IR) and returns the value shifted out.

async with await Session.connect() as session:
# Select the IDCODE instruction (commonly 0x0E on ARM DAPs)
shifted_out = await session.jtag.irscan("stm32f1x.cpu", 0x0E)
print(f"IR shifted out: 0x{shifted_out:X}")

drscan(tap, bits, value) shifts a value of the specified bit width through the data register (DR) and returns the captured output.

async with await Session.connect() as session:
# Read IDCODE: shift 32 bits through DR after selecting IDCODE via IR
await session.jtag.irscan("stm32f1x.cpu", 0x0E)
idcode = await session.jtag.drscan("stm32f1x.cpu", 32, 0x0)
print(f"IDCODE: 0x{idcode:08X}")

runtest(cycles) clocks the specified number of TCK pulses while the TAP controller is in the Run-Test/Idle state. Some devices require idle clocking between operations.

# Clock 100 TCK cycles in Run-Test/Idle
await session.jtag.runtest(100)

The cycle count must be non-negative; passing a negative value raises JTAGError.

pathmove(states) walks the TAP controller through an explicit sequence of IEEE 1149.1 states. Each state must be a legal single-step transition from the previous one. OpenOCD validates the path and reports an error for illegal transitions.

from openocd import Session, JTAGState
async with await Session.connect() as session:
await session.jtag.pathmove([
JTAGState.DRSELECT,
JTAGState.DRCAPTURE,
JTAGState.DRSHIFT,
])

The list must contain at least one state. An empty list raises JTAGError.

The JTAGState enum defines all 16 IEEE 1149.1 TAP controller states:

StateDescription
RESETTest-Logic-Reset
IDLERun-Test/Idle
DRSELECTSelect-DR-Scan
DRCAPTURECapture-DR
DRSHIFTShift-DR
DREXIT1Exit1-DR
DRPAUSEPause-DR
DREXIT2Exit2-DR
DRUPDATEUpdate-DR
IRSELECTSelect-IR-Scan
IRCAPTURECapture-IR
IRSHIFTShift-IR
IREXIT1Exit1-IR
IRPAUSEPause-IR
IREXIT2Exit2-IR
IRUPDATEUpdate-IR

JTAGState is a str enum, so JTAGState.IDLE.value returns the string "IDLE".

svf(path, tap=None, quiet=False, progress=True) executes a Serial Vector Format file. SVF files describe JTAG test vectors and are commonly used for FPGA configuration, board-level test, and CPLD programming.

from pathlib import Path
async with await Session.connect() as session:
await session.jtag.svf(
path=Path("board_test.svf"),
tap="fpga.bs",
quiet=False,
progress=True,
)

Parameters:

  • path — path to the .svf file
  • tap — restrict operations to a specific TAP (optional; when None, OpenOCD applies vectors to the appropriate TAP)
  • quiet — suppress per-statement logging inside OpenOCD
  • progress — show a progress indicator (default True)

xsvf(tap, path) executes a Xilinx-extended SVF file against a specific TAP.

from pathlib import Path
await session.jtag.xsvf("cpld.bs", Path("config.xsvf"))
FieldTypeDescription
namestrFull TAP name (e.g. stm32f1x.cpu)
chipstrChip portion of the name
tap_namestrTAP portion of the name
idcodeintDetected IDCODE
ir_lengthintInstruction register length in bits
enabledboolWhether the TAP is enabled

All JTAG operations raise JTAGError on failure.

from openocd import JTAGError
try:
await session.jtag.irscan("nonexistent.tap", 0x0E)
except JTAGError as e:
print(f"JTAG error: {e}")
MethodReturn TypeDescription
scan_chain()list[TAPInfo]Enumerate TAPs on the chain
new_tap(chip, tap, ir_len, expected_id=None)NoneDeclare a new TAP
irscan(tap, instruction)intShift instruction into IR
drscan(tap, bits, value)intShift data through DR
runtest(cycles)NoneClock TCK in Run-Test/Idle
pathmove(states)NoneWalk TAP through state sequence
svf(path, tap=None, quiet=False, progress=True)NoneExecute SVF file
xsvf(tap, path)NoneExecute XSVF file