---
title: JTAG and SWD Protocols
description: Wire-level protocol details for JTAG and SWD debug interfaces
sidebar:
  order: 1
---

JTAG and SWD are the two wire protocols that connect a debug probe to a target chip. mcjtag abstracts both behind the same set of tools, but understanding the protocol layer helps when things go wrong or when you need raw access via `jtag_shift()`.

---

## JTAG Fundamentals

JTAG (IEEE 1149.1) is a serial protocol originally designed for boundary scan testing. It uses four wires: TDI (data in), TDO (data out), TMS (mode select), and TCK (clock). An optional fifth wire, TRST, resets the TAP controller.

### TAP State Machine

The Test Access Port controller is a 16-state finite state machine. TMS is sampled on each rising edge of TCK to determine the next state. The key states for debug work:

```
                  TMS=1
              .-----------.
              |           |
              v           |
       Test-Logic-Reset --'
              |
              | TMS=0
              v
        Run-Test/Idle <-------.
              |               |
              | TMS=1         | TMS=0
              v               |
        Select-DR-Scan        |
         /          \         |
   TMS=1             TMS=0   |
        |             |       |
        v             v       |
  Select-IR-Scan  Capture-DR  |
        |             |       |
   TMS=0 |       TMS=0|       |
        v             v       |
   Capture-IR     Shift-DR    |
        |          /     \    |
   TMS=0|    TMS=0/  TMS=1\   |
        v        /         v  |
    Shift-IR    '    Exit1-DR |
     /    \              |    |
TMS=0  TMS=1        TMS=1|    |
   |      |              v    |
   '  Exit1-IR      Update-DR-'
          |
     TMS=1|
          v
     Update-IR ----------'
```

The two paths that matter most:

- **Shift-DR**: Data is shifted through the data register (DR). This is how memory reads, register access, and IDCODE extraction happen.
- **Shift-IR**: An instruction is shifted into the instruction register (IR) to select which DR the next Shift-DR operation will access.

Five TMS=1 transitions from any state will return to Test-Logic-Reset. This is how probes recover from unknown states.

### Scan Chains: IR and DR

JTAG moves data through two serial registers:

- **Instruction Register (IR)**: Selects the active function. Common IR values on an ARM DAP: `0x0E` (IDCODE), `0x0A` (DPACC -- Debug Port Access), `0x0B` (APACC -- Access Port Access).
- **Data Register (DR)**: The payload register whose meaning depends on the current IR value. When IR=IDCODE, the DR is 32 bits containing the chip's identity. When IR=DPACC, the DR carries debug port read/write transactions.

Data is shifted LSB-first through TDI, and the previous register contents exit through TDO simultaneously. This is why `jtag_shift()` returns a `value_out` -- you always get back what was in the register before your data arrived.

### IDCODE Register Format

After reset, the default DR is the 32-bit IDCODE register. Its format:

| Bits | Field | Description |
|------|-------|-------------|
| 31:28 | Version | Silicon revision |
| 27:12 | Part Number | Chip identifier |
| 11:1 | Manufacturer ID | JEDEC JEP106 code (11 bits) |
| 0 | Fixed | Always 1 |

Example: an STM32F103 reports IDCODE `0x1BA01477`:

- Version: `0x1`
- Part Number: `0xBA01` -- ARM Cortex-M3 DAP
- Manufacturer: `0x23B` -- ARM Ltd (JEDEC bank 4, 0x3B)
- Bit 0: `1`

### How `jtag_scan()` Works

`jtag_scan()` reads the IDCODE of every TAP on the chain:

1. The TAP state machine is driven to Test-Logic-Reset (5x TMS=1)
2. After reset, the IDCODE instruction is loaded by default
3. The state machine moves to Shift-DR
4. 32 bits are shifted out of each TAP in the chain
5. Each IDCODE is parsed and returned as a `TAPResult` with name, IDCODE, IR length, and enabled status

In a multi-device chain (e.g., an FPGA + CPU on the same board), `jtag_scan()` enumerates all TAPs. SWD does not support chaining -- see below.

### Raw Access with `jtag_shift()`

`jtag_shift()` provides direct access to the IR and DR scan operations. It shifts arbitrary bit patterns through the selected register on a named TAP:

```python
# Load the IDCODE instruction into the IR
jtag_shift(tap="stm32f1x.cpu", operation="irscan", value="0x0E")

# Shift out the 32-bit IDCODE from the DR
jtag_shift(tap="stm32f1x.cpu", operation="drscan", value="0x00000000", bits=32)
```

This is useful for protocols not covered by the higher-level tools -- custom boundary scan sequences, vendor-specific debug registers, or TAPs that OpenOCD does not natively support.

---

## SWD Fundamentals

SWD (Serial Wire Debug) is a two-wire alternative to JTAG designed by ARM for Cortex-M. It uses SWDIO (bidirectional data) and SWCLK (clock), reducing pin count from 4+ to 2. Most Cortex-M dev boards use SWD by default.

### Packet Structure

A single SWD transaction has three phases:

**Host request (8 bits):**

| Bit(s) | Field | Description |
|--------|-------|-------------|
| 0 | Start | Always 1 |
| 1 | APnDP | 0 = Debug Port, 1 = Access Port |
| 2 | RnW | 0 = Write, 1 = Read |
| 3:4 | Addr\[2:3\] | Register address bits (selects register within DP or AP) |
| 5 | Parity | Even parity over APnDP + RnW + Addr |
| 6 | Stop | Always 0 |
| 7 | Park | Always 1 |

**Turnaround:** The bus direction flips. The probe releases SWDIO and the target drives it.

**Target acknowledge (3 bits):**

| Value | Meaning |
|-------|---------|
| `001` | OK -- transaction accepted |
| `010` | WAIT -- target busy, retry |
| `100` | FAULT -- error, check status register |

**Data phase (33 bits):** 32 data bits + 1 parity bit. Direction depends on RnW: target drives data on reads, host drives on writes.

### DPIDR -- SWD's IDCODE

The Debug Port Identification Register (DPIDR) is the SWD equivalent of JTAG's IDCODE. It is read automatically when OpenOCD connects to the target. `probe_diagnostics()` reports this value as part of its health checks.

The DPIDR uses the same 32-bit format as the JTAG IDCODE (version, part number, manufacturer ID, fixed bit 0).

### No Daisy-Chaining

SWD is point-to-point: one probe, one target. There is no equivalent of JTAG's scan chain. If you have multiple devices on a board, each needs its own SWD connection or you need to use JTAG.

SWD v2 (ARM ADIv6) introduced multi-drop SWD, which allows multiple targets on a single SWD connection using target selection sequences. Support for this is still uncommon in both probes and OpenOCD.

---

## Debug Port and Access Port Architecture

Both JTAG and SWD converge on the same abstraction: the ARM Debug Interface Architecture (ADI). This defines a two-layer model that is protocol-independent.

### Debug Port (DP)

The Debug Port is the protocol-facing side. There are two variants:

- **JTAG-DP**: Accessed via JTAG IR/DR scans (IR values DPACC and APACC)
- **SWD-DP**: Accessed via SWD packet transactions (APnDP=0)

Both expose the same logical registers:

| Register | Address | Description |
|----------|---------|-------------|
| DPIDR | 0x0 (read) | Debug Port Identification Register |
| CTRL/STAT | 0x4 | Control and status (power-up, error flags) |
| SELECT | 0x8 | Selects which AP and AP register bank to access |
| RDBUFF | 0xC (read) | Read buffer for AP read results |

The DP is the gateway to all Access Ports. To talk to an AP, the host writes the AP index and register bank to SELECT, then performs AP read/write transactions.

### Access Port (AP)

Access Ports sit between the DP and the target's internal buses. A chip can have multiple APs:

- **AP 0**: Usually a MEM-AP connected to the AHB system bus
- **AP 1**: Sometimes a second MEM-AP for a different bus (APB, AXI)
- **AP 255**: ROM table / CoreSight discovery

Each AP has its own register space. The DP's SELECT register determines which AP and which register bank within that AP is being addressed.

### MEM-AP

The Memory Access Port is the most important AP type. It provides access to the target's memory bus -- the same bus the CPU uses. This is what makes `read_memory()`, `write_memory()`, `read_registers()`, and `flash_program()` possible.

Key MEM-AP registers:

| Register | Offset | Description |
|----------|--------|-------------|
| CSW | 0x00 | Control/Status Word -- sets transfer size (8/16/32 bit), auto-increment mode, and access permissions |
| TAR | 0x04 | Transfer Address Register -- the target address for the next read or write |
| DRW | 0x0C | Data Read/Write -- writing DRW initiates a write to the address in TAR; reading DRW initiates a read |

### Protocol-Level Walkthrough: `read_memory("0x20000000", 4, 32)`

Here is what happens on the wire when you call `read_memory(address="0x20000000", count=4, width=32)`:

1. **mcjtag** parses the hex address, validates count and width, calls `openocd-python`'s `memory.read_u32(0x20000000, 4)`
2. **OpenOCD** configures the MEM-AP CSW register for 32-bit transfers with auto-increment
3. **OpenOCD** writes `0x20000000` to the TAR register
4. **OpenOCD** reads DRW four times -- each read returns one 32-bit word and TAR auto-increments by 4
5. Each of those AP register accesses is a DP transaction:
   - **SWD**: one SWD write packet to SELECT (to address the AP), then one SWD read/write packet for the AP register
   - **JTAG**: one IR scan to load APACC, then one DR scan to perform the read/write
6. The data path from target to host: **Target memory bus** -- AHB interconnect -- MEM-AP -- DP -- probe -- USB -- OpenOCD -- openocd-python -- mcjtag

The auto-increment in CSW is what makes sequential reads efficient. Without it, each word would require a separate TAR write.

---

## CoreSight Component Table

ARM CoreSight is a debug and trace infrastructure built into Cortex-M (and Cortex-A/R) chips. The DAP is the entry point. Other components are accessible through the MEM-AP at fixed addresses in the system region (`0xE0000000`+).

mcjtag currently focuses on DAP-level access: memory, registers, flash, and execution control. Trace and breakpoint components are listed here for reference.

| Component | Full Name | Purpose | mcjtag Support |
|-----------|-----------|---------|----------------|
| DAP | Debug Access Port | DP + AP, memory and register access | Full -- core functionality |
| ITM | Instrumentation Trace Macrocell | printf-style debug output via SWO pin | Not yet |
| DWT | Data Watchpoint and Trace | Hardware watchpoints, cycle counter, PC sampling | Not yet |
| FPB | Flash Patch and Breakpoint | Hardware breakpoints (up to 8), flash patching | Not yet |
| TPIU | Trace Port Interface Unit | Formats trace output for external capture | Not yet |
| ETM | Embedded Trace Macrocell | Full instruction trace (requires trace probe) | Not yet |

These components live at well-known addresses and can be read with `read_memory()` today:

```python
# DWT cycle counter control register
read_memory(address="0xE0001000", count=1, width=32)

# FPB control register (breakpoint unit)
read_memory(address="0xE0002000", count=1, width=32)

# ITM stimulus port 0
read_memory(address="0xE0000000", count=1, width=32)
```

Reading these registers is safe and non-destructive. Writing to them changes debug behavior and should be done with care.

---

## How mcjtag Maps to Protocols

Each mcjtag tool translates to specific protocol operations. This table shows what happens on the wire for each tool call.

| mcjtag Tool | Protocol Operation | What Happens on the Wire |
|-------------|-------------------|--------------------------|
| `jtag_scan()` | DR scan (IDCODE) | Resets TAP state machine, shifts out IDCODE from each TAP in the chain |
| `probe_diagnostics()` | MEM-AP read of `0xE000ED00` | Reads CPUID register via DP -- MEM-AP -- AHB bus |
| `read_memory()` | MEM-AP read | Writes address to TAR, reads DRW (auto-increments for sequential reads) |
| `write_memory()` | MEM-AP write | Writes address to TAR, writes DRW (auto-increments for sequential writes) |
| `read_registers()` | CoreSight debug register reads | Reads via debug register interface at `0xE000EDF0`+ (DCRSR/DCRDR) |
| `write_register()` | CoreSight debug register write | Writes register selector to DCRSR, value to DCRDR at `0xE000EDF0`+ |
| `target_control(halt)` | Debug halt request | Writes C_HALT + C_DEBUGEN to DHCSR at `0xE000EDF0` with debug key |
| `target_control(step)` | Debug step request | Writes C_STEP + C_DEBUGEN to DHCSR |
| `target_control(reset_halt)` | Reset + halt | Asserts reset via AIRCR or nRST pin, catches at reset vector using vector catch |
| `jtag_shift()` | Raw IR/DR scan | Shifts arbitrary bit patterns through the selected register on the named TAP |
| `flash_program()` | MEM-AP writes + flash controller | Writes flash control registers to unlock, erase sectors, program words, and verify |
| `search_memory()` | Repeated MEM-AP reads | Reads memory in 4 KB chunks, compares against the search pattern |

### Register Access Detail

CPU register reads and writes do not access registers directly. The Cortex-M debug interface uses two registers in the Debug Control Block at `0xE000EDF0`:

| Register | Address | Purpose |
|----------|---------|---------|
| DHCSR | `0xE000EDF0` | Debug Halting Control and Status Register -- halt, step, debug enable |
| DCRSR | `0xE000EDF4` | Debug Core Register Selector -- which CPU register to read/write |
| DCRDR | `0xE000EDF8` | Debug Core Register Data -- the value read from or written to the selected register |

To read R0, for example:

1. Write `0x00000000` to DCRSR (register selector = R0, read mode)
2. Poll DHCSR until S_REGRDY is set
3. Read DCRDR -- it now contains the value of R0

`read_registers()` repeats this sequence for each requested register. The target must be halted for this to work.

### Flash Programming Detail

`flash_program()` is the most complex wire operation. The general sequence for STM32F1:

1. Unlock flash by writing the key sequence to FLASH_KEYR (`0x40022004`)
2. Set the PG (programming) bit in FLASH_CR (`0x40022010`)
3. Write 16-bit half-words to the target flash address via MEM-AP
4. Poll FLASH_SR (`0x4002200C`) for BSY flag after each write
5. Verify by reading back and comparing
6. Lock flash by setting the LOCK bit in FLASH_CR

OpenOCD handles this internally through chip-specific flash drivers. mcjtag delegates entirely to OpenOCD's `program` command, which handles erase, write, and verify as an atomic operation.

---

## JTAG vs SWD: When to Use Which

| Consideration | JTAG | SWD |
|---------------|------|-----|
| Pin count | 4+ (TDI, TDO, TMS, TCK) | 2 (SWDIO, SWCLK) |
| Chain scanning | Yes -- multiple devices on one chain | No -- point-to-point only |
| Cortex-M support | Yes | Yes (native) |
| FPGA support | Yes | No |
| RISC-V support | Yes | Varies by implementation |
| Throughput | Comparable | Comparable |
| Probe support | Universal | ARM-specific probes |

mcjtag works with both transports. The transport is selected in the OpenOCD configuration file, not in mcjtag itself. See [OpenOCD Configurations](/reference/openocd-configs/) for shipped configs.

Most Cortex-M work uses SWD because it requires fewer pins and every Cortex-M chip supports it. Use JTAG when you need chain scanning, when working with FPGAs, or when the target board only exposes a JTAG header.
