Skip to content
Lesson 13.8Physical Memory, Machines, and Hardware

JTAG Scan Chains and Debug Access

Shift registers, TAP states, IDCODE, and SWD packets show how a hardware debugger discovers and controls a chip.

Lesson 13.8
Read or listenPrint chapter or book
About 23 minutes

Keyboard shortcuts

?
Open this guide
Alt + B
Show or hide the lesson list
Alt + O
Show or fold the lesson outline
Alt + N
Open or close notes
Alt + H
Toggle semantic colours
Alt + D
Toggle page brightness
Alt + C
Toggle code brightness
← →
Move through a focused code trace
Home End
First or final step in a focused trace
Escape
Close this guide or an open panel

Reader shortcuts pause while you type in a text field.

Cheatsheet for Lesson 13.8Key ideas, reference and questions

A one-page summary of “JTAG Scan Chains and Debug Access” you can keep beside your notes. Plain Markdown.

Lesson 13.8 — JTAG Scan Chains and Debug Access: cheatsheet

  • Remember: Serial chain output begins with the chip nearest TDO, sending that selected register’s lowest bit first.
  • Also useful: Picture buckets moving toward the outlet together: each tick releases one old bit and takes one new bit at the far end.

Key terms

  • rising edge: The moment it rises from 0 to 1 is called the rising edge, and the moment it falls back to 0 is the falling edge.
  • falling edge: The moment it rises from 0 to 1 is called the rising edge, and the moment it falls back to 0 is the falling edge.
  • latch: So each cell has a second one-bit store, called a latch, that actually drives the pin, and the latch copies the shifted value only once the whole pattern is in place.
  • Vec: The four wires are function arguments, and the board is a Vec of chips.
  • TapState: Every chip sees the same TCK and TMS, so their controllers move in lockstep and one TapState describes them all.
  • match: The controller is a match with one row per state, giving the next state for TMS at 0 and at 1, exactly like the diagram.

Review

  • Q: A debugger resets the scan chain of a board it knows nothing about and scans out the data registers. How can it tell where each chip's answer begins, without knowing any chip's register lengths? A: An IDCODE chip starts with a 1 and then 31 more bits; a BYPASS chip sends just a 0
  • Q: The controller has just entered Shift-DR, and the debugger wants to shift exactly 8 bits through a data register and then stop shifting. How should it set TMS on the next clocks? A: 7 clocks with TMS at 0, then an 8th clock with TMS at 1
Download cheatsheet (.md)

Lesson 13.7 explained what JTAG is for and what each of its parts does. This lesson drives those parts one clock at a time, from a four-bit shift register to the packet that opens an SWD session, and ends with a lab that simulates a two-chip scan chain.

TCK is a clock: a wire that switches from 0 to 1 and back, over and over. The moment it rises from 0 to 1 is called the rising edge, and the moment it falls back to 0 is the falling edge. A JTAG chip reads TMS and TDI on the rising edge and changes TDO on the falling edge. Each wire is read on one edge and changed on the other, so no wire is ever read at the moment it changes.

The standard also requires TMS and TDI to read as 1 when nothing drives them. That small rule matters twice below.

Shift registers: many bits through one wire

Section titled “Shift registers: many bits through one wire”

JTAG’s shift registers always shift toward TDO: on each clock the cell next to TDO, bit 0, leaves first, every other bit moves down one place, and the bit arriving from TDI enters at the top.

Take a small chip with four pins and a 4-bit boundary-scan register, bits b3 to b0. The test captures the pins: pin 0 reads 1, pin 1 reads 0, pin 2 reads 1, and pin 3 reads 1. Each cell now holds its own pin’s level:

b3 · pin 31
b2 · pin 21
b1 · pin 10
b0 · pin 01
TDI enters at b3, TDO leaves from b0
After the capture, each cell holds its pin's level. b0 sits next to TDO, so pin 0's level will leave first.

Now the debugger runs four clocks, sending 1, 1, 0, 0 into TDI, first bit first. On each clock, TDO receives b0, then b0 takes b1’s value, b1 takes b2’s, b2 takes b3’s, and b3 takes the bit from TDI:

Clock TDI sends TDO receives b3 b2 b1 b0 after the clock
start 1 1 0 1
1 1 1, pin 0’s level 1 1 1 0
2 1 0, pin 1’s level 1 1 1 1
3 0 1, pin 2’s level 0 1 1 1
4 0 1, pin 3’s level 0 0 1 1

Check clock 1: TDO receives b0, which is 1. Then b0 takes b1’s 0, b1 takes b2’s 1, b2 takes b3’s 1, and b3 takes the incoming 1, giving 1 1 1 0. The other rows follow the same rule.

The same four clocks did two jobs. They read the pins out in order, pin 0 to pin 3, and they loaded a new pattern. The first bit sent travelled furthest and ended in b0; the last bit sent stopped in b3:

Clock a 4-bit shift registerOn each clock b0 leaves through TDO, every bit moves one place toward TDO, and the TDI bit enters at b3.

After 4 clocks

TDI sent

In order
1clock 1
1clock 2
0clock 3
0clock 4

Shift register

b3 b2 b1 b0
TDI enters at b3b0 leaves to TDO
0b3 · from TDI
0b2 · from TDI
1b1 · from TDI
1b0 · from TDI

TDO received

In order
1pin 0
0pin 1
1pin 2
1pin 3

TDO received 1, 0, 1, 1: pin 0’s level first, because b0 sits next to TDO. The register holds 0 0 1 1 (b3 to b0): the sent 1, 1, 0, 0, with the first bit in b0.

Look at the register after clock 2: 1 1 1 1, a pattern nobody asked for. While bits ripple through, the register passes through meaningless in-between values. If the pins followed the register directly, they would flicker through every one of them. So each cell has a second one-bit store, called a latch, that actually drives the pin, and the latch copies the shifted value only once the whole pattern is in place. That copy is the Update step of Lesson 13.7.

Lesson 13.7 followed the main path through the TAP controller: Capture, Shift, and Update. Here is the whole machine. At every rising edge of TCK it moves to one of two next states, chosen by whether TMS is 0 or 1. The diagram writes the 16 state names without hyphens; the standard spells them Test-Logic-Reset, Run-Test/Idle, Select-DR-Scan, Capture-DR, and so on.

power on
TMS 1
TMS 0
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TMS 0
TMS 1
TestLogicReset
RunTestIdle
SelectDRScan
CaptureDR
SelectIRScan
ShiftDR
Exit1DR
PauseDR
UpdateDR
Exit2DR
CaptureIR
ShiftIR
Exit1IR
PauseIR
UpdateIR
Exit2IR

The machine has two matching columns. States ending in DR work on a data register, such as the boundary-scan register; states ending in IR work on the instruction register. Down each column:

  • Select chooses a column. With TMS at 0 it enters that column’s Capture state; with TMS at 1 it moves on, from Select-DR-Scan to Select-IR-Scan, and from Select-IR-Scan to Test-Logic-Reset.
  • Capture loads the register’s shift stage from its inputs: the pin levels, for the boundary-scan register.
  • Shift moves one bit from TDI toward TDO on every clock, for as long as TMS stays 0.
  • Exit1, Pause, and Exit2 leave the shifting, wait if the debugger needs time to fetch more data, and resume without starting over.
  • Update copies the shifted-in value to the register’s outputs in one step.

Test-Logic-Reset is the state in which the chip ignores the test logic and works normally, and Run-Test/Idle is a resting place between operations. Six states can wait in place for as long as TMS holds steady: Test-Logic-Reset with TMS at 1, and Run-Test/Idle, Shift-DR, Pause-DR, Shift-IR, and Pause-IR with TMS at 0. Every other state lasts exactly one clock.

Each state does its work on the rising edge that leaves it. The capture happens on the clock that moves from Capture-DR to Shift-DR, and the last bit of a scan is shifted by the same clock that raises TMS to leave Shift-DR for Exit1-DR. To shift 64 bits, the debugger sends 63 clocks with TMS at 0 and then the 64th with TMS at 1. Getting from a fresh reset to the first shift takes four clocks:

Clock TMS From To
1 0 Test-Logic-Reset Run-Test/Idle
2 1 Run-Test/Idle Select-DR-Scan
3 0 Select-DR-Scan Capture-DR
4 0 Capture-DR, where the register loads Shift-DR

Lesson 13.7 said that five clocks with TMS at 1 reach Test-Logic-Reset from any state. Follow the arrows labelled TMS 1 from Shift-DR, one of the states furthest away: Exit1-DR, Update-DR, Select-DR-Scan, Select-IR-Scan, and Test-Logic-Reset, which is five clocks. The reset reference below checks every other state the same way. Extra clocks do no harm, because Test-Logic-Reset stays put while TMS is 1, so a debugger that is unsure of the state sends five 1s on TMS and starts from a known place. And because an undriven TMS reads as 1, a chip whose test port is not connected to anything sits in Test-Logic-Reset permanently and works normally.

Optional reference: every state's path to Test-Logic-Reset

To check the rule for all 16 states, hold TMS at 1 and work outward from Test-Logic-Reset, following the arrows labelled TMS 1:

Starting state Path with TMS at 1 Clocks
Select-IR-Scan straight to Test-Logic-Reset 1
Select-DR-Scan Select-IR-Scan, then as above 1 + 1 = 2
Run-Test/Idle, Update-DR, or Update-IR Select-DR-Scan, then as above 1 + 2 = 3
Exit1 or Exit2, in either column that column’s Update, then as above 1 + 3 = 4
Capture, Shift, or Pause, in either column Exit1 from Capture or Shift, Exit2 from Pause, then as above 1 + 4 = 5

The rows cover 1 + 1 + 3 + 4 + 6 = 15 states, and Test-Logic-Reset itself needs 0 clocks, so all 16 are accounted for and none needs more than five.

BYPASS, IDCODE, and a chain that fails safe

Section titled “BYPASS, IDCODE, and a chain that fails safe”

A few fixed rules about instruction codes shape how a debugger drives a chain.

BYPASS must be all ones, so a chip with a 4-bit instruction register uses 1111, and the one-bit register it selects always captures 0. A chip with an IDCODE register selects IDCODE on reset, so no instruction scan is needed before reading IDs.

Any code a chip does not define must behave as BYPASS. Together with the rule that an undriven TDI reads as 1, this means a broken chain fails safe: a chip whose TDI wire is cut receives nothing but ones, which select BYPASS, not a test mode that drives the pins against the chip’s own outputs.

Instructions are bits in the chain like any others. In one instruction scan the first bits shifted in travel furthest, so to give every chip its own instruction, the debugger sends the instruction for the chip nearest TDO first and the one for the chip nearest TDI last.

A test bit measures a chain of one-bit registers. This step-by-step trace needs JavaScript. Here is the code and where the run ends.

each chip contributes one zero bit
send one 1 through TDI, then zeros
sample the old last cell at TDO
shift every chip from its old neighbor
count zero outputs before the pulse
report the serial chain delay

Result: 2 zero samples precede the pulse, showing 2 serial BYPASS cells in this sampling convention.

Final values: chips = 2; zero_outputs = 2; clock = 3; incoming = 0; tdo = 1; inferred_chips = 2.

Read every step of the lesson’s example
  1. Step 1, line 1. Start with the selected inputs. Follow the highlighted operation and the changing state below.

    chips = 2.

  2. Step 2, line 1. The cells start at zero; chip 1 is nearest TDI.

    chips = 2; zero_outputs = 0.

    Memory: chip 1 = 0; chip 2 = 0 (nearest TDO).

  3. Step 3, line 2. Clock 1 drives TDI=1.

    chips = 2; zero_outputs = 0; clock = 1; incoming = 1.

    Memory: chip 1 = 0; chip 2 = 0 (nearest TDO).

  4. Step 4, line 3. Sample the old last cell at the output.

    chips = 2; zero_outputs = 0; clock = 1; incoming = 1; tdo = 0.

    Memory: chip 1 = 0; chip 2 = 0 (nearest TDO).

  5. Step 5, line 4. Every cell takes its old neighbor, so the pulse advances one chip.

    chips = 2; zero_outputs = 0; clock = 1; incoming = 1; tdo = 0.

    Memory: chip 1 = 1; chip 2 = 0 (nearest TDO).

  6. Step 6, line 5. Count the zero outputs before the first 1.

    chips = 2; zero_outputs = 1; clock = 1; incoming = 1; tdo = 0.

    Memory: chip 1 = 1; chip 2 = 0 (nearest TDO).

  7. Step 7, line 2. Clock 2 drives TDI=0.

    chips = 2; zero_outputs = 1; clock = 2; incoming = 0; tdo = 0.

    Memory: chip 1 = 1; chip 2 = 0 (nearest TDO).

  8. Step 8, line 3. Sample the old last cell at the output.

    chips = 2; zero_outputs = 1; clock = 2; incoming = 0; tdo = 0.

    Memory: chip 1 = 1; chip 2 = 0 (nearest TDO).

  9. Step 9, line 4. Every cell takes its old neighbor, so the pulse advances one chip.

    chips = 2; zero_outputs = 1; clock = 2; incoming = 0; tdo = 0.

    Memory: chip 1 = 0; chip 2 = 1 (nearest TDO).

  10. Step 10, line 5. Count the zero outputs before the first 1.

    chips = 2; zero_outputs = 2; clock = 2; incoming = 0; tdo = 0.

    Memory: chip 1 = 0; chip 2 = 1 (nearest TDO).

  11. Step 11, line 2. Clock 3 drives TDI=0.

    chips = 2; zero_outputs = 2; clock = 3; incoming = 0; tdo = 0.

    Memory: chip 1 = 0; chip 2 = 1 (nearest TDO).

  12. Step 12, line 3. Sample the old last cell at the output.

    chips = 2; zero_outputs = 2; clock = 3; incoming = 0; tdo = 1.

    Memory: chip 1 = 0; chip 2 = 1 (nearest TDO).

  13. Step 13, line 4. Every cell takes its old neighbor, so the pulse advances one chip.

    chips = 2; zero_outputs = 2; clock = 3; incoming = 0; tdo = 1.

    Memory: chip 1 = 0; chip 2 = 0 (nearest TDO).

  14. Step 14, line 5. Count the zero outputs before the first 1.

    chips = 2; zero_outputs = 2; clock = 3; incoming = 0; tdo = 1.

    Memory: chip 1 = 0; chip 2 = 0 (nearest TDO).

  15. Step 15, line 6. Result: 2 zero samples precede the pulse, showing 2 serial BYPASS cells in this sampling convention.

    chips = 2; zero_outputs = 2; clock = 3; incoming = 0; tdo = 1; inferred_chips = 2.

    Memory: chip 1 = 0; chip 2 = 0 (nearest TDO).

Worked example: count the chips on a chain

Section titled “Worked example: count the chips on a chain”

A debugger attached to an unfamiliar board can count its chips in two scans.

  1. Reset, then shift plenty of ones through the instruction registers. The debugger does not know how long each register is, and it does not need to: every register ends up full of ones, which is BYPASS, and the spare ones fall out of TDO.
  2. Scan the data registers. Each chip now contributes one bypass bit, which captured 0. Send a single 1 followed by 0s, and count the clocks until the 1 comes back.

Here is the second scan on a board with two chips, A nearest TDI and B nearest TDO. On each clock, TDO receives B’s bit, B takes A’s old bit, and A takes the bit from TDI:

Clock TDI sends TDO receives A’s bit after B’s bit after
start 0 0
1 1 0 1 0
2 0 0 0 1
3 0 1 0 0
TDI0
chip A0
chip B1
TDO0
the whole data chain: one bypass bit per chip
After clock 2, the 1 sent on clock 1 sits in chip B's bypass bit. On clock 3 it reaches TDO.

The 1 went in on clock 1 and came out on clock 3, so the chain held it for 3 − 1 = 2 clocks. Each one-bit register holds a bit for exactly one clock, so there are 2 chips.

After reset, every chip with an IDCODE register has it selected, so one data scan reads every ID on the chain. For two chips that is 32 + 32 = 64 bits. They come out lowest bit first, and the chip nearest TDO answers first: the first 32 bits belong to chip B.

Bit 0 of every IDCODE is 1, and that is deliberate. A bypass register captures 0, so the first bit from each chip tells the debugger what kind of register it is reading. A 1 means a 32-bit IDCODE follows; a 0 means a chip with no IDCODE, sitting in BYPASS, which contributes that single bit and nothing more.

Chip A in this lesson’s lab contains Arm’s JTAG debug port, found in many microcontrollers, and it answers 0x4BA00477. The 32 bits split into three fields plus the fixed 1:

31–284
27–24B
23–20A
19–160
15–120
11–84
7–47
3–07
version
part number 0xBA00
manufacturer and the fixed 1
Each box is one hex digit, four bits, labelled with the bit numbers it covers.
  • Bits 31 to 28 are the first hex digit: version 4.
  • Bits 27 to 12 are the next four digits: part number 0xBA00.
  • Bits 11 to 0 are 0x477, which is 4 × 256 + 7 × 16 + 7 = 1,143. Its lowest bit is the fixed 1. Removing that bit means subtracting 1 and halving: (1,143 − 1) ÷ 2 = 571. In hex, 571 = 2 × 256 + 3 × 16 + 11 = 0x23B, the manufacturer code that JEDEC, the industry body that hands out these numbers, assigned to Arm.

Arm’s Cortex-M processors keep their debug controls at fixed addresses in the memory map, and their debug logic includes a memory access port that performs bus reads and writes for the debugger. So halting the core takes one ordinary memory write through the debug port, and reading RAM afterwards takes one memory read. The register the halt writes is DHCSR, the Debug Halting Control and Status Register, at address 0xE000EDF0, described in Arm’s public architecture manual. Its low three bits are:

Bit Name Value of the bit Effect when written as 1
0 C_DEBUGEN 1 allow halting debug
1 C_HALT 2 stop the core
2 C_STEP 4 while C_HALT is 0, run one instruction and stop

Every write must also carry 0xA05F in the upper 16 bits, or the processor ignores it. That key is printed in the public manual, so it is not a password. It stops a stray write from buggy software halting the core by accident.

A 32-bit value has eight hex digits, four bits each, so its upper 16 bits are the first four digits, and putting 0xA05F there gives 0xA05F0000. Adding bit values to it:

  • halt: 0xA05F0000 + 2 + 1 = 0xA05F0003;
  • step one instruction while halted: 0xA05F0000 + 4 + 1 = 0xA05F0005;
  • resume: 0xA05F0000 + 1 = 0xA05F0001.

The write travels through that memory access port. The port has an address register and a data register, called TAR and DRW in Arm’s documentation: write an address to the first, then read or write the second, and the port performs that bus access.

Core debug logicMemory access portDebug port on the chipDebugger on a PCCore debug logicMemory access portDebug port on the chipDebugger on a PCthe core stops before its next instructionselect the memory access portaddress register = 0xE000EDF0data register = 0xA05F0003bus write to DHCSRaddress register = 0x20000000read the data registerthe 32-bit word stored at 0x20000000

The second read fetches the first word of the chip’s RAM, which starts at 0x20000000 on Cortex-M. Core registers come through two more memory-mapped registers, one to choose a register by number and one to carry its value.

Lesson 13.7 showed the shape of an SWD read: an 8-bit request, a turnaround, a 3-bit acknowledgement, 32 data bits, and a parity bit. The request’s eight bits, in the order they are sent, are:

  • Start: always 1.
  • APnDP: 0 for a debug port register, 1 for an access port register.
  • RnW: 1 to read, 0 to write.
  • A2 and A3: two address bits that choose one of four registers.
  • Parity: makes the count of 1s among APnDP, RnW, A2, A3, and the parity bit itself even.
  • Stop: always 0. Park: always 1.

The first request a debugger usually sends reads the debug port’s ID register, register 0. That needs APnDP = 0, RnW = 1, A2 = 0, and A3 = 0. Those four bits contain one 1, an odd count, so the parity bit is 1, which makes two:

Start1
APnDP0
RnW1
A20
A30
Parity1
Stop0
Park1
read debug port register 0
evens out the 1s
Sent left to right, one bit per SWCLK tick. As a byte with the first bit as bit 0, it is 1 + 4 + 32 + 128 = 165 = 0xA5.

The bits in the order sent are 1, 0, 1, 0, 0, 1, 0, 1. Treating the first as bit 0, the 1s sit in bits 0, 2, 5, and 7, worth 1, 4, 32, and 128, for a total of 165. In hex that is 10 × 16 + 5 = 0xA5.

The ID comes back in the same format as a JTAG IDCODE. For many Cortex-M chips it is 0x2BA01477. Split the same way as above, that is version 2, part number 0xBA01 for the serial-wire version of the port, and the low three digits 0x477 once again, so the same Arm manufacturer code, 0x23B.

The portable lab jtag_tap_lab.rs simulates the board from this lesson: chip A with Arm’s JTAG debug port, which answers 0x4BA00477, and a made-up sensor as chip B. No hardware is involved. The four wires are function arguments, and the board is a Vec of chips. Every chip sees the same TCK and TMS, so their controllers move in lockstep and one TapState describes them all.

jtag_tap_lab.rs

rust-labs/src/bin/ · Rust, 573 lines

Show the codeHide the code
//! JTAG TAP controller lab for Lesson 14.10.
//!
//! Everything here is simulated. The four JTAG wires are function arguments
//! and return values, and the board is two chips wired into one scan chain:
//! chip A contains an Arm JTAG debug port, and chip B is a made-up sensor.
//! The lab models the 16-state TAP controller, each chip's 4-bit instruction
//! register, the 1-bit bypass register, and the 32-bit IDCODE register. It
//! then drives them the way a debugger does: reset the chain, read every
//! chip's ID, and count the chips by timing a single 1 through their bypass
//! registers.

use std::fmt;

/// Every chip on this board has a 4-bit instruction register.
const IR_LENGTH: usize = 4;

/// Capture-IR always loads `01` into the two lowest bits of an instruction
/// register. The other bits are the chip designer's choice; these chips
/// capture zeros there.
const IR_CAPTURE: u8 = 0b0001;

/// The standard fixes BYPASS as all ones: `1111` in a 4-bit register.
const BYPASS: u8 = 0b1111;

/// The 16 states of the TAP controller, named as in IEEE 1149.1.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum TapState {
    TestLogicReset,
    RunTestIdle,
    SelectDrScan,
    CaptureDr,
    ShiftDr,
    Exit1Dr,
    PauseDr,
    Exit2Dr,
    UpdateDr,
    SelectIrScan,
    CaptureIr,
    ShiftIr,
    Exit1Ir,
    PauseIr,
    Exit2Ir,
    UpdateIr,
}

impl TapState {
    const ALL: [Self; 16] = [
        Self::TestLogicReset,
        Self::RunTestIdle,
        Self::SelectDrScan,
        Self::CaptureDr,
        Self::ShiftDr,
        Self::Exit1Dr,
        Self::PauseDr,
        Self::Exit2Dr,
        Self::UpdateDr,
        Self::SelectIrScan,
        Self::CaptureIr,
        Self::ShiftIr,
        Self::Exit1Ir,
        Self::PauseIr,
        Self::Exit2Ir,
        Self::UpdateIr,
    ];

    /// The state after one rising edge of TCK, given the level of TMS.
    ///
    /// Each row gives the next state for TMS low, then for TMS high. Several
    /// rows share a pair, but one row per state keeps the table easy to
    /// check against the lesson's diagram.
    #[expect(
        clippy::match_same_arms,
        reason = "one row per state mirrors the state diagram"
    )]
    const fn next(self, tms: bool) -> Self {
        let (tms_low, tms_high) = match self {
            Self::TestLogicReset => (Self::RunTestIdle, Self::TestLogicReset),
            Self::RunTestIdle => (Self::RunTestIdle, Self::SelectDrScan),
            Self::SelectDrScan => (Self::CaptureDr, Self::SelectIrScan),
            Self::CaptureDr => (Self::ShiftDr, Self::Exit1Dr),
            Self::ShiftDr => (Self::ShiftDr, Self::Exit1Dr),
            Self::Exit1Dr => (Self::PauseDr, Self::UpdateDr),
            Self::PauseDr => (Self::PauseDr, Self::Exit2Dr),
            Self::Exit2Dr => (Self::ShiftDr, Self::UpdateDr),
            Self::UpdateDr => (Self::RunTestIdle, Self::SelectDrScan),
            Self::SelectIrScan => (Self::CaptureIr, Self::TestLogicReset),
            Self::CaptureIr => (Self::ShiftIr, Self::Exit1Ir),
            Self::ShiftIr => (Self::ShiftIr, Self::Exit1Ir),
            Self::Exit1Ir => (Self::PauseIr, Self::UpdateIr),
            Self::PauseIr => (Self::PauseIr, Self::Exit2Ir),
            Self::Exit2Ir => (Self::ShiftIr, Self::UpdateIr),
            Self::UpdateIr => (Self::RunTestIdle, Self::SelectDrScan),
        };
        if tms { tms_high } else { tms_low }
    }
}

impl fmt::Display for TapState {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        formatter.write_str(match self {
            Self::TestLogicReset => "Test-Logic-Reset",
            Self::RunTestIdle => "Run-Test/Idle",
            Self::SelectDrScan => "Select-DR-Scan",
            Self::CaptureDr => "Capture-DR",
            Self::ShiftDr => "Shift-DR",
            Self::Exit1Dr => "Exit1-DR",
            Self::PauseDr => "Pause-DR",
            Self::Exit2Dr => "Exit2-DR",
            Self::UpdateDr => "Update-DR",
            Self::SelectIrScan => "Select-IR-Scan",
            Self::CaptureIr => "Capture-IR",
            Self::ShiftIr => "Shift-IR",
            Self::Exit1Ir => "Exit1-IR",
            Self::PauseIr => "Pause-IR",
            Self::Exit2Ir => "Exit2-IR",
            Self::UpdateIr => "Update-IR",
        })
    }
}

/// How many clocks with TMS high it takes to reach Test-Logic-Reset from
/// `start`. A walk that has not arrived after 16 clocks is going round a
/// loop that never visits it, so the search stops there.
fn clocks_to_reset(start: TapState) -> Option<u32> {
    let mut state = start;
    for clocks in 0..16 {
        if state == TapState::TestLogicReset {
            return Some(clocks);
        }
        state = state.next(true);
    }
    None
}

/// One chip's test logic, apart from its TAP controller, which the chain
/// keeps for all chips at once.
#[derive(Clone, Debug)]
struct Chip {
    name: &'static str,
    idcode: u32,
    /// Which instruction selects IDCODE is the chip designer's choice.
    idcode_instruction: u8,
    /// The instruction in force. Only Update-IR and reset change it.
    instruction: u8,
    /// The instruction register's shift stage.
    ir_shift: u8,
    /// The shift stage of the data register the instruction selects.
    dr_shift: u32,
}

impl Chip {
    /// A chip as it comes out of reset, with IDCODE selected.
    const fn new(name: &'static str, idcode: u32, idcode_instruction: u8) -> Self {
        Self {
            name,
            idcode,
            idcode_instruction,
            instruction: idcode_instruction,
            ir_shift: 0,
            dr_shift: 0,
        }
    }

    /// IDCODE selects the 32-bit ID register. BYPASS, and every instruction
    /// the chip does not define, select the 1-bit bypass register.
    const fn selects_idcode(&self) -> bool {
        self.instruction == self.idcode_instruction
    }

    fn capture_dr(&mut self) {
        // The bypass register always captures 0.
        self.dr_shift = if self.selects_idcode() {
            self.idcode
        } else {
            0
        };
    }

    /// Shifts one bit in from the TDI side and returns the bit that leaves on
    /// the TDO side: bit 0 leaves, every other bit moves down one place, and
    /// the new bit enters at the top.
    fn shift_dr(&mut self, bit_in: bool) -> bool {
        let top = if self.selects_idcode() { 31 } else { 0 };
        let bit_out = (self.dr_shift & 1) == 1;
        self.dr_shift = (self.dr_shift >> 1) | (u32::from(bit_in) << top);
        bit_out
    }

    fn capture_ir(&mut self) {
        self.ir_shift = IR_CAPTURE;
    }

    fn shift_ir(&mut self, bit_in: bool) -> bool {
        let bit_out = (self.ir_shift & 1) == 1;
        self.ir_shift = (self.ir_shift >> 1) | (u8::from(bit_in) << (IR_LENGTH - 1));
        bit_out
    }

    fn update_ir(&mut self) {
        self.instruction = self.ir_shift;
    }

    fn reset(&mut self) {
        self.instruction = self.idcode_instruction;
    }
}

/// Which set of registers a scan goes through.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Register {
    Instruction,
    Data,
}

/// A board's scan chain: TDI -> `chips[0]` -> `chips[1]` -> ... -> TDO.
///
/// Every chip sees the same TCK and TMS, so their TAP controllers move in
/// lockstep and one state describes them all.
struct Chain {
    state: TapState,
    chips: Vec<Chip>,
}

impl Chain {
    const fn new(chips: Vec<Chip>) -> Self {
        Self {
            state: TapState::TestLogicReset,
            chips,
        }
    }

    /// One cycle of TCK. On the rising edge the current state's action runs
    /// (capture or shift) and the controller moves on. Update and reset act
    /// on the falling edge, in the state just entered.
    ///
    /// Returns the bit TDO carried at the rising edge, or `None` when no Shift
    /// state is active and nothing drives TDO.
    fn clock(&mut self, tms: bool, tdi: bool) -> Option<bool> {
        let bit_out = match self.state {
            TapState::CaptureDr => {
                self.chips.iter_mut().for_each(Chip::capture_dr);
                None
            }
            TapState::CaptureIr => {
                self.chips.iter_mut().for_each(Chip::capture_ir);
                None
            }
            TapState::ShiftDr => Some(self.shift_chain(tdi, Chip::shift_dr)),
            TapState::ShiftIr => Some(self.shift_chain(tdi, Chip::shift_ir)),
            _ => None,
        };
        self.state = self.state.next(tms);
        match self.state {
            TapState::UpdateIr => self.chips.iter_mut().for_each(Chip::update_ir),
            TapState::TestLogicReset => self.chips.iter_mut().for_each(Chip::reset),
            _ => {}
        }
        bit_out
    }

    /// Moves every register in the chain one place toward TDO. `tdi` enters
    /// the first chip, each chip's outgoing bit enters the next chip, and the
    /// last chip's outgoing bit is the one that appears on TDO.
    fn shift_chain(&mut self, tdi: bool, shift: fn(&mut Chip, bool) -> bool) -> bool {
        self.chips
            .iter_mut()
            .fold(tdi, |bit, chip| shift(chip, bit))
    }

    /// Five clocks with TMS high reach Test-Logic-Reset from any state. One
    /// more with TMS low parks the chain in Run-Test/Idle, ready to scan.
    fn reset(&mut self) {
        for _ in 0..5 {
            self.clock(true, false);
        }
        self.clock(false, false);
    }

    /// From Run-Test/Idle, shifts `bits_in` through the instruction or data
    /// registers and returns to Run-Test/Idle. Returns the bits that came out
    /// of TDO, first bit first.
    fn scan(&mut self, register: Register, bits_in: &[bool]) -> Vec<bool> {
        assert_eq!(
            self.state,
            TapState::RunTestIdle,
            "scans start in Run-Test/Idle"
        );
        assert!(!bits_in.is_empty(), "a scan shifts at least one bit");

        self.clock(true, false); // to Select-DR-Scan
        if register == Register::Instruction {
            self.clock(true, false); // to Select-IR-Scan
        }
        self.clock(false, false); // to Capture
        self.clock(false, false); // capture happens now; to Shift

        let mut bits_out = Vec::with_capacity(bits_in.len());
        for (index, &bit) in bits_in.iter().enumerate() {
            // TMS rises with the last bit: that clock shifts it and leaves
            // for Exit1.
            let last = index + 1 == bits_in.len();
            bits_out.push(
                self.clock(last, bit)
                    .expect("TDO is driven in a Shift state"),
            );
        }

        self.clock(true, false); // to Update
        self.clock(false, false); // to Run-Test/Idle
        bits_out
    }
}

/// `count` bits of `value`, lowest bit first: the order JTAG shifts them.
fn bits_lsb_first(value: u32, count: usize) -> Vec<bool> {
    (0..count)
        .map(|index| ((value >> index) & 1) == 1)
        .collect()
}

/// Rebuilds a number from bits that arrived lowest bit first.
fn word_lsb_first(bits: &[bool]) -> u32 {
    bits.iter()
        .rev()
        .fold(0, |word, &bit| (word << 1) | u32::from(bit))
}

/// The three fields of a 32-bit IDCODE, whose bit 0 is always 1.
#[derive(Debug, PartialEq, Eq)]
struct IdCode {
    version: u32,
    part: u32,
    manufacturer: u32,
}

/// Splits an IDCODE into its fields, or returns `None` when bit 0 is 0,
/// which no IDCODE can have.
fn decode_idcode(value: u32) -> Option<IdCode> {
    ((value & 1) == 1).then_some(IdCode {
        version: value >> 28,
        part: (value >> 12) & 0xFFFF,
        manufacturer: (value >> 1) & 0x7FF,
    })
}

/// Loads one instruction into every chip. `instructions` is in chain order,
/// chip nearest TDI first. The first bits shifted in travel furthest, so the
/// bits for the chip nearest TDO go in first.
fn load_instructions(chain: &mut Chain, instructions: &[u8]) {
    let bits: Vec<bool> = instructions
        .iter()
        .rev()
        .flat_map(|&instruction| bits_lsb_first(u32::from(instruction), IR_LENGTH))
        .collect();
    chain.scan(Register::Instruction, &bits);
}

/// Counts the chips on a chain without knowing anything about them.
///
/// BYPASS is all ones, so 64 ones leave every chip in BYPASS whatever its
/// instruction register's length, as long as the lengths add up to 64 or
/// less; the spare ones fall out of TDO. Each bypass register holds one bit
/// and captures 0, so a single 1 fed in on the first clock comes out one
/// clock later for every chip it passes through.
fn count_chips(chain: &mut Chain, max_chips: usize) -> Option<usize> {
    chain.scan(Register::Instruction, &[true; 64]);
    let mut probe = vec![false; max_chips + 1];
    probe[0] = true;
    chain
        .scan(Register::Data, &probe)
        .iter()
        .position(|&bit| bit)
}

/// The lesson's board: TDI -> chip A -> chip B -> TDO.
fn board() -> Chain {
    Chain::new(vec![
        Chip::new("chip A, an Arm JTAG debug port", 0x4BA0_0477, 0b1110),
        Chip::new("chip B, a made-up sensor", 0x15EE_D001, 0b0010),
    ])
}

fn main() {
    println!("1. Five clocks with TMS high reach Test-Logic-Reset from any state");
    let mut state = TapState::ShiftDr;
    let mut path = vec![state.to_string()];
    for _ in 0..5 {
        state = state.next(true);
        path.push(state.to_string());
    }
    println!("  {}", path.join(" -> "));
    let slowest = TapState::ALL
        .into_iter()
        .filter_map(clocks_to_reset)
        .max()
        .unwrap_or_default();
    println!("  the slowest of the 16 states needs {slowest} clocks");

    println!("\n2. Reset selects IDCODE in every chip, so 64 bits read both IDs");
    let mut chain = board();
    chain.reset();
    let bits = chain.scan(Register::Data, &[false; 64]);
    // The chip nearest TDO answers first.
    for (chunk, chip) in bits.chunks(32).zip(chain.chips.iter().rev()) {
        let value = word_lsb_first(chunk);
        match decode_idcode(value) {
            Some(id) => println!(
                "  {value:#010X} from {}: version {}, part {:#06X}, manufacturer {:#05X}",
                chip.name, id.version, id.part, id.manufacturer
            ),
            None => println!("  {value:#010X} from {}: not an IDCODE", chip.name),
        }
    }

    println!("\n3. Count the chips by timing one bit through BYPASS");
    match count_chips(&mut chain, 8) {
        Some(chips) => println!(
            "  the 1 went in on clock 1 and came out on clock {}: {chips} chips",
            chips + 1
        ),
        None => println!("  the 1 never came out: more than 8 chips, or a broken chain"),
    }

    println!("\n4. The instruction picks the register: chip A on IDCODE, chip B on BYPASS");
    chain.reset();
    load_instructions(&mut chain, &[0b1110, BYPASS]);
    let bits = chain.scan(Register::Data, &[false; 33]);
    println!(
        "  32 + 1 = 33 bits: chip B's bypass bit ({}) first, then chip A's ID {:#010X}",
        u8::from(bits[0]),
        word_lsb_first(&bits[1..])
    );
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn five_clocks_with_tms_high_reset_every_state() {
        let clocks: Vec<u32> = TapState::ALL
            .into_iter()
            .map(|state| clocks_to_reset(state).unwrap())
            .collect();
        assert!(clocks.iter().all(|&count| count <= 5));
        assert_eq!(clocks.iter().max(), Some(&5));
        assert_eq!(clocks_to_reset(TapState::ShiftDr), Some(5));
        assert_eq!(clocks_to_reset(TapState::RunTestIdle), Some(3));
    }

    #[test]
    fn exactly_six_states_can_wait_in_place() {
        let waiting: Vec<(TapState, bool)> = TapState::ALL
            .into_iter()
            .flat_map(|state| [(state, false), (state, true)])
            .filter(|&(state, tms)| state.next(tms) == state)
            .collect();
        assert_eq!(
            waiting,
            [
                (TapState::TestLogicReset, true),
                (TapState::RunTestIdle, false),
                (TapState::ShiftDr, false),
                (TapState::PauseDr, false),
                (TapState::ShiftIr, false),
                (TapState::PauseIr, false),
            ]
        );
    }

    #[test]
    fn tdo_is_driven_only_in_shift_states() {
        let mut chain = board();
        chain.reset();
        assert_eq!(chain.clock(false, false), None);
    }

    #[test]
    fn idcodes_arrive_nearest_chip_first_and_lowest_bit_first() {
        let mut chain = board();
        chain.reset();
        let bits = chain.scan(Register::Data, &[false; 64]);
        assert!(bits[0], "bit 0 of every IDCODE is 1");
        assert_eq!(word_lsb_first(&bits[..32]), 0x15EE_D001);
        assert_eq!(word_lsb_first(&bits[32..]), 0x4BA0_0477);
    }

    #[test]
    fn the_arm_idcode_splits_into_the_lesson_fields() {
        assert_eq!(
            decode_idcode(0x4BA0_0477),
            Some(IdCode {
                version: 4,
                part: 0xBA00,
                manufacturer: 0x23B,
            })
        );
        assert_eq!(decode_idcode(0x4BA0_0476), None);
    }

    #[test]
    fn instruction_capture_starts_every_register_with_01() {
        let mut chain = board();
        chain.reset();
        let bits = chain.scan(Register::Instruction, &[true; 8]);
        assert_eq!(bits, [true, false, false, false, true, false, false, false]);
        assert!(chain.chips.iter().all(|chip| chip.instruction == BYPASS));
    }

    #[test]
    fn a_bit_is_delayed_one_clock_per_chip_in_bypass() {
        let mut chain = board();
        chain.reset();
        assert_eq!(count_chips(&mut chain, 8), Some(2));

        let mut longer = board();
        longer.chips.push(Chip::new(
            "chip C, another made-up chip",
            0x0000_0001,
            0b0011,
        ));
        longer.reset();
        assert_eq!(count_chips(&mut longer, 8), Some(3));
    }

    #[test]
    fn undefined_instructions_select_the_bypass_register() {
        let mut chain = board();
        chain.reset();
        load_instructions(&mut chain, &[0b0101, 0b0101]);
        assert!(chain.chips.iter().all(|chip| chip.instruction == 0b0101));
        let probe = [true, false, false, false];
        assert_eq!(
            chain.scan(Register::Data, &probe),
            [false, false, true, false]
        );
    }

    #[test]
    fn instructions_land_in_chain_order() {
        let mut chain = board();
        chain.reset();
        load_instructions(&mut chain, &[0b1110, BYPASS]);
        assert_eq!(chain.chips[0].instruction, 0b1110);
        assert_eq!(chain.chips[1].instruction, BYPASS);

        // The data chain is now 32 + 1 = 33 bits long. Chip B's bypass bit
        // is nearest TDO, so its captured 0 leaves first.
        let bits = chain.scan(Register::Data, &[false; 33]);
        assert!(!bits[0]);
        assert_eq!(word_lsb_first(&bits[1..]), 0x4BA0_0477);
    }

    #[test]
    fn reset_restores_idcode_from_any_state() {
        let mut chain = board();
        chain.reset();
        load_instructions(&mut chain, &[BYPASS, BYPASS]);
        for tms in [true, true, false, true, false] {
            chain.clock(tms, true);
        }
        assert_eq!(chain.state, TapState::PauseIr);

        chain.reset();
        assert_eq!(chain.state, TapState::RunTestIdle);
        assert!(
            chain
                .chips
                .iter()
                .all(|chip| chip.instruction == chip.idcode_instruction)
        );
    }
}

The controller is a match with one row per state, giving the next state for TMS at 0 and at 1, exactly like the diagram. The shift register is five lines:

fn shift_dr(&mut self, bit_in: bool) -> bool {
let top = if self.selects_idcode() { 31 } else { 0 };
let bit_out = (self.dr_shift & 1) == 1;
self.dr_shift = (self.dr_shift >> 1) | (u32::from(bit_in) << top);
bit_out
}

& 1 keeps only bit 0, the bit leaving toward TDO. >> 1 moves every bit down one place. << top puts the incoming bit at the top: bit 31 for the IDCODE register, or bit 0 for the one-bit bypass register, where the top and the bottom are the same cell. The chain passes each chip’s outgoing bit into the next chip:

self.chips.iter_mut().fold(tdi, |bit, chip| shift(chip, bit))

Build it, then run it from the rust-labs folder:

Terminal window
cargo run --bin jtag_tap_lab

The lab walks from Shift-DR to Test-Logic-Reset in five clocks and confirms that no state needs more. It reads both IDCODEs, chip B’s first because chip B is nearest TDO, and splits Arm’s into the fields above. It counts the chips by timing a single 1 through their bypass bits. Finally, it puts chip A on IDCODE and chip B on BYPASS, which makes the data chain 32 + 1 = 33 bits long. The tests check that every state resets within five clocks, that exactly six states can wait in place, that undefined instructions behave as BYPASS, and that instructions land in chain order.

Then add a third chip to board(). Before you run it, predict what count_chips reports and how many bits the IDCODE scan in main now needs.

Knowledge check

Reading a board you know nothing about

A debugger resets the scan chain of a board it knows nothing about and scans out the data registers. How can it tell where each chip's answer begins, without knowing any chip's register lengths?

Finished Lesson 13.8?

Mark it done and the lesson list shows a beside it. A chapter whose lessons are all done is marked done too. Progress is saved in this browser only.

Chapter 13: 10 lessons

This book grew out of the original Game Hacking Academy by attilathedud and has been rewritten as a beginner-first course. Like the original repository, its source is published under the Apache License 2.0.