Skip to content

JTAG On Chip Debugging Support

Jeff Bush edited this page May 7, 2019 · 16 revisions

JTAG On-Chip Debugging Support

This is currently experimental and a work in progress. The external JTAG (IEEE 1149) interface allows limited software debugging support. It supports a few JTAG instruction types:

Instructions

Code Name Width Description
0000 IDCODE 32 Device type identification (standard JTAG instruction)
0011 CONTROL 7 Debug Control register
0100 INJECT_INST 32 When this is selected, the shifted in value will be sent into the pipeline as an instruction, running on the thread and core indicated in the control register.
0101 TRANSFER_DATA 32 Allows bi-directional data transfer between host and target. There is a control register visible to all threads called "JTAG data." This instruction will shift the current value out of that register and shift a new value into it.
0110 STATUS 2 Status of injected instruction
1111 BYPASS 1 Pass data through with no side effects (standard JTAG instruction)

The debug control register consists of the following fields:

6 5 5 3 2 1 0
core thread halt

The core and thread fields control which thread the INJECT_INST will execute in the context of. When the halt field is 1, the processor will stop fetching instructions. This must be true to execute the INJECT_INST instruction.

The status register can have the following values:

Value Meaning
00 READY - This will be the state if an instruction has completed successfully
01 ISSUED - An instruction has been issued, but has not completed yet
10 ROLLED_BACK - The instruction did not complete, but was rolled back. This is normal for a branch instruction (which can be used to change the program counter), but for a memory instruction, indicates a cache miss occurred, and that the debugger should reissue the instruction

Examples

These instructions are sufficient to inspect processor state. Here are a few examples of how they can be used:

Read Register

To read a register (assuming processor is already halted), first transfer it to the control register, then read out:

INJECT_INST  0xac0000d2  (getcr s6, 18)
TRANSFER_DATA out: <data value>

Write Register:

TRANSFER_DATA in: <data value>
INJECT_INST 0x8c0000f2  (setcr s7, 18)

The next instructions require scratchpad registers, which can be stored on the host using the read register operation, then restored before resuming the program with the write register operation.

Read Memory

TRANSFER_DATA in: <address>
INJECT_INST   getcr s0, 18
INJECT_INST   load_32 s0, (s0)
INJECT_INST   setcr s0, 18
TRANSFER_DATA out: data

May need to retry, as described in the section above about the status register.

Write Memory

TRANSFER_DATA in: <address>
INJECT_INST  getcr s0, 18
TRANSFER_DATA in: <data>
INJECT_INST  getcr s1, 18
INJECT_INST  store_32 s1, (s0)

Read program counter

The program counter is not directly accessible as a register, but can be read by using a call instruction. This will clobber the ra register, so that must have already been saved on the host:

INJECT_INST call 0        (will stay on same instruction)
INJECT_INST getcr ra, 18
TRANSFER_DATA  out: program counter

Write program counter

TRANSFER_DATA  in: <dest addr>
INJECT_INST getcr s0, 18
INJECT_INST b s0

Limitations

Information about limitations is documented in the file hardware/core/on_chip_debugger.sv

See https://github.com/jbush001/NyuziProcessor/issues/149

Alternative Approaches

  • Instrumenting register file and caches with bypasses that allow direct manipulation.
  • Creating a small RAM that can be selectively enabled and loaded by JTAG, and adding special interrupts to jump to it.