Don't fear the bricking (unbricking Unmatched via JTAG)

Despite, as i think, this is quite obvious, some people miss the point.

So let me show you how to unbrick the HiFive Unmatched board, that’s useful in case of:

  • installing BSP without the need of preparing the SD card in advance (you still have to insert it through)
  • unbricking the board in case of deleting U-Boot
  • testing U-Boot + OpenSBI on fly hijacking the load addresses

The scheme works independently of selected MSEL value.

If we study the board boot process it will look like:

  • ZSBL starts on hartid 0 and initializes PCLK
  • ZSBL starts scanning partitions and looks for 5B193300-FC78-40CD-8002-E86C45580B47 GUID
  • ZSBL loads U-Boot’s SPL into L2 LIM (scratchpad TCM L2) at the start of 0x08000000
  • U-Boot SPL among other things initializes DDR and loads u-boot.bin to 0x80200000 and OpenSBI to 0x80000000

There is one interesting thing in original (meta-sifive SDK) U-boot config, as we can see CONFIG_SPL_LOAD_FIT_ADDRESS is set and it is used only for spl_ram loading.

The rest is out of our scope. As we can see - it is possible to replace U-Boot + OpenSBI by intercepting CONFIG_SPL_TEXT_BASE address, loading our RAM U-Boot SPL, allowing it to do all init works and then intercept CONFIG_SPL_LOAD_FIT_ADDRESS, load U-Boot into it and continue execution.

We need to patch U-Boot and produce our special RAM version (patch attached).

I used a fork of OpenOCD which resides here GitHub - riscv/riscv-openocd: Fork of OpenOCD that has RISC-V support (not sure if vanilla OpenOCD will work correctly) and the following config HiFive Unmatched + OpenOCD configuration (S7+U7 cores; no flash configured) · GitHub with a small function added:

proc load_uboot {} {
        reset init
        reg pc 0x00010000
        bp 0x08000000 0x1000 hw
        after 10000
        regexp {(0x[a-fA-F0-9]*)} [reg pc] pc
        echo "PC=$pc"
        load_image u-boot-spl.bin 0x8000000 bin
        rbp all
        wp 0x84000000 0x1000
        after 5000
        regexp {(0x[a-fA-F0-9]*)} [reg pc] pc
        echo "PC=$pc"
        rwp 0x84000000
        load_image u-boot.itb 0x84000000 bin
        verify_image u-boot.itb 0x84000000 bin

The reason for after is that bp and wp hits are not reported and we have to “poll” the pc reg - once reg pc output is not empty - we have hit the breakpoint.
The reason for rbp all, rwp 0x84000000 is that currently bp and wp behaves strangely on Unmatched board with current OpenOCD version.

Works even if MSEL is in debug mode, because we simply jump to ZSBL on start, and even if it doesn’t find the required partition to boot, it simply waits in some loop after the init done.

Expected result:

U-Boot SPL 2021.01-00053-g8afbe20524-dirty (Jul 21 2021 - 04:00:02 +0000)
Trying to boot from RAM
U-Boot 2021.01-00053-g8afbe20524-dirty (Jul 21 2021 - 04:00:02 +0000)

Now you can load anything from network, for example a kernel with initramfs to deploy the BSP. Most distribution would fit into 16GB memory, i advise against those that don’t.


Very nice idea here, @Maquefel
To expand a bit on the idea let me suggest a few more lines to add, immediately following the target create step. These will give a very useful log of all progress steps that OpenOCD goes through. ToDo: Expand the idea for multiple harts.

riscv.cpu.0 configure -event examine-start {
  echo "examine start"

  # prevent the dreaded 'Error: unable to halt hart 0' message
  if {[catch {riscv.cpu.0 arp_examine} err] != 0} {
    riscv.cpu.0 arp_examine

proc asic_reset { } {
  echo "proc asic_reset"

  # pulse the reset line just in case h/w was locked
  #!!!CRITICAL -- to avoid dreaded 'Error: unable to halt hart 0' message during init ...

  echo "asic_reset: pulsing reset line"
  reset_config trst_only trst_open_drain separate
  jtag_ntrst_assert_width 0
  jtag_ntrst_delay 0
  adapter assert trst  ;#  ftdi_set_signal nTRST 0  ;# assert RST
  adapter deassert trst  ;#  ftdi_set_signal nTRST 1  ;# deassert RST
  reset_config none

  jtag arp_init-reset  ;# this thing takes a long time to come back

  echo "asic_reset: wait for target get into reset state (prevent impatient scan retries)"
  sleep 1500  ;# ASIC target AON reset block delay, typical default 2^8 cycles of core clock

proc jtag_init { } {  ;# overrides openocd/src/jtag/startup.tcl
  echo "proc jtag_init"
  if {[catch {jtag arp_init} err] != 0} {

proc init_reset { mode } {  ;# overrides openocd/src/jtag/startup.tcl
  echo "proc init_reset"
  if {[catch {jtag arp_init} err] != 0} {

riscv.cpu.0 configure -event examine-end { echo "examine end" }
riscv.cpu.0 configure -event examine-fail { echo "examine fail ... OOPS!" }

riscv.cpu.0 configure -event reset-start { echo "reset start" }
riscv.cpu.0 configure -event reset-assert-pre { echo "reset assert pre" }
riscv.cpu.0 configure -event reset-assert { echo "reset assert" }
riscv.cpu.0 configure -event reset-assert-post { echo "reset assert post" }
riscv.cpu.0 configure -event reset-deassert-pre { echo "reset deassert pre" }
riscv.cpu.0 configure -event reset-deassert-post { echo "reset deassert post" }
riscv.cpu.0 configure -event reset-end { echo "reset end" }

riscv.cpu.0 configure -event debug-halted { echo "debug halted" }
riscv.cpu.0 configure -event debug-resumed { echo "debug resumed" }
riscv.cpu.0 configure -event resume-start { echo "resume start" }
riscv.cpu.0 configure -event resume-end { echo "resume end" }

riscv.cpu.0 configure -event step-start { echo "step start" }
riscv.cpu.0 configure -event step-end { echo "step end" }

riscv.cpu.0 configure -event gdb-attach { echo "gdb attach" }
riscv.cpu.0 configure -event gdb-detach { echo "gdb detach" }

riscv.cpu.0 configure -event gdb-halt { echo "gdb halt" }
riscv.cpu.0 configure -event gdb-start { echo "gdb start" }
riscv.cpu.0 configure -event gdb-end { echo "gdb end" }

riscv.cpu.0 configure -event gdb-flash-erase-start { echo "gdb flash erase start" }
riscv.cpu.0 configure -event gdb-flash-erase-end { echo "gdb flash erase end" }
riscv.cpu.0 configure -event gdb-flash-write-start { echo "gdb flash write start" }
riscv.cpu.0 configure -event gdb-flash-write-end { echo "gdb flash write end" }

For lightweight operation when a debugger interactive session is not needed, you can add the following three tcl statements somewhere before the transport selection; then optionally specify one or more at the command line level (with -c option) when interactivity is desired.

gdb_port disabled  ;# 3333
tcl_port disabled  ;# 6666
telnet_port disabled  ;# 4444

For some more tips dealing with FTDI adapter configuration and the tiny glitch bug, see also Progress getting OpenOCD usable with RevB

Hello @pds, these looks like a really nice additions, thank you for sharing them!

That is this “dreaded ‘Error: unable to halt hart 0’ message”, i can’t say that i used “halt” very often, but never encountered it on Unmatched ?

Thanks, Nikita!

The “Unable to halt …” message typically arises when there is a noisy connection between debugger/emulator hardware and target. Such as long wires, poor or missing ground connections (between emulator and target), or in case of Rasperry Pi: low voltage power packs (recommend the new power adaptor from Adafruit, 5.25V, 20 AWG or larger cable wires, etc).

Having messages displayed at each possible event of the multi-threaded OpenOCD tool is a God-send.

Don’t be afraid to “halt” in order to check “regs …”, or even “reset init” once in a while … and watch the output from my cfg file!