Debugging using PDI

Getting started

Debug has its own key value that must be sent to enable it. You can check if this needs doing by issuing ldcs status to the PDI processor.

The debug key uses the constant 0x3a212dd49f7c8121 to unlock the debug part of the controller.

Initiating a debug session

Put the processor into reset

stcs reset 0x59
ldcs status

The LDCS should return 0x00 if these are the first instructions run in a session.

Turn the debug interface on

key debug
ldcs status

The LDCS should return 0x04 if key nvm has not been run yet, and 0x06 if it has.

Take the reset processor and put it in debug pause state

stcs r4 1
ldcs r3
stcs reset 0x00
ldcs r3
ldcs r3

Writing bit 1 of r4 puts the processor into debug-based pause once reset is released the first LDCS to r3 is used to verify that r3 is presently 0x10 (processor held in reset, debugging not active). Once reset is released, r3 will initially be 0x14, which should be observed with the second LDCS. Once debug setup is completed, r4 will then read as 0x04, which should be observed with the final LDCS.

Running the processor to an address

The following uses the hardware breakpoint unit, which contains 2 breakpoint address registers and associated control machinery.

Set up run-to-address

sts.u32 0x00000020 0x29 0x01 0x00 0x00
sts.u32 0x00000024 0x00 0x00 0x00 0x00
sts.u8 0x00000040 0x00
sts.u8 0x00000040 0x00
sts.u16 0x00000028 0x00 0x01
sts.u8 0x00000048 0x00

This sequence performs the following operations (some of them need more figuring out how things work to understand):

  • Stores 0x00000129 (as the target program address to run the processor to) into the hardware breakpoint unit’s first break address register at 0x00000020. Program addresses are in words which is why they are half what they should be going by objdump output.

  • Stores 0x00000000 to the hardware breakpoint unit’s second break address register at 0x00000024

  • Stores 0x00 to a byte register at 0x00000040 (twice) - the purpose for this is not well understood yet and is essential to the proper functioning of the breakpoint unit. Without this, the unit will not engage correctly.

  • Stores 0x0100 to the hardware breakpoint unit’s breakpoint counter and configuration (16-bit) register at 0x00000028

  • Stores 0x00 to a byte register at 0x00000048 - the purpose for this is not well understood yet and is essential to the proper functioning of the breakpoint unit. Without this, the unit will not engage correctly.

Set the program counter to a specific address

sts.u32 0x00000004 0x00 0x00 0x00 0x00

This writes the program counter, exposed at 0x00000004, to its POR value

Run the program to breakpoint

stcs reset 0x00
sts.u8 0x0000000a 0x00
stcs r4 0x01
ldcs r3
ldcs r3

This sequence does the following:

  • With r4 poked, releases reset so that at this stage enters the processor into a debugger-supervised state

  • Stores 0x00 to the debug register 0x0000000a which appears to arm the processor resuming execution

  • Ensures r4 holds the value 0x01 which ensures we stay in debugger-supervised state is set

  • Loads the status of the processor from r3 (should read as 0x14 the first time)

  • Loads the status of the processor again (should read as 0x04 the second time indicating the breakpoint is hit)

Clean up

sts.u16 0x00000028 0x00 0x00
sts.u8 0x00000048 0x00

Cleans up after the debug run by clearing all set breakpoints by setting the control and counter value to 0

Reading processor state

Read program counter (PC+1)

lds.u32 0x00000004

Reads the program counter from its fixed register address of 0x00000004

Clean up verification

lds.u8 0x00000050
lds.u8 0x0000000b

Reads an unknown special register in the breakpoint unit and the upper half of the control register

Read back Stack Pointer + SREG

st.u32 ptr 0x0100003d
repeat 0x02
ld *(ptr++)

Directly reads from the PDI bus location for the SPL, SPH and SREG registers in peripheral space

Verify state

ldcs r3
lds.u8 0x010001ca
lds.u8 0x010001c4

Verify that the NVM controller in peripheral space is in an idle state

Reading back the AVR registers

sts.u32 0x00000004 0x00 0x00 0x00 0x00
sts.u8 0x0000000a 0x11
sts.u32 0x00000000 0x20 0x00 0x00 0x00
stcs r4 1
st ptr 0x0000000c
repeat 31
ld *ptr

This sequence does the following:

  • Sets the program counter to 0x0 (is this actually necessary?)

  • Loads the debug control register with special value 0x11 which turns the multifunction register into a read counter

  • Loads the multifunction register (now a read counter) with the number of registers to read (32)

  • Tells the PDI part of the debug controller to exec the action (STCS to r4)

  • Loads the PDI pointer register with the address of the I/O FIFO (0x0000000c)

  • Tells the PDI controller to repeat for the number of AVR registers minus 1 (due to how the repeat register works, this makes the load run 32 times)

  • Reads the registers back from the read FIFO (r0..r31)

Single Stepping

The debug control system has a tempory (single-stepping) breakpoint available too which can be used to implement one-use breakpoints without touching the main two.

Setting up the breakpoint

sts.u8 0x0000000a 0x04
sts.u32 0x00000000 0x55 0x03 0x00 0x00
sts.u32 0x00000004 0x54 0x03 0x00 0x00

This sequence does the following:

  • Loads the debug control register with special value 0x04 which turns the multifunction register into a temporary breakpoint

  • Loads the now breakpoint register with the address to break on (0x0355 in this example)

  • Loads the program counter with the resumption address (0x0354 in this address, making this a single-step run)