Is there C boot code (source) for RevB02 hardware?

I’m brand new to Risc-V, just received my HiFive1-RevB02 board.
I like to start things off by building and installing the boot firmware, so can someone point me to it? I’ve downloaded everything I could from the HiFive1 webpage, but don’t see anything that appears to be bare-metal boot code.
Thanks in advance for any pointers to code or docs related to this.

Welcome @edsut Here is a very detailed discussion of the FE310 (HiFive) Boot Process which is illustrated and summarized below.

Admittedly, the boot process entry point is a little bit circuitous. Table 5 of Chapter 5 of the Manual shows how the boot vector address depends on the state of the MSEL pins (earlier Manual version v19p05, available elsewhere, has Table 7 called “Target of the reset vector” which explains the MSEL settings).

Unfortunately, the MSEL pins are not accessible, but are hard-wired (presumably floating up) to 11’b which is mentioned as the hardware default condition (and determined empirically).

This causes a jump to the Mask ROM at 0x1 0000.

Section 5.1.1 of the Manual says that the Mask ROM has instructions which further jump to OTP at 0x2 0000.

Now, the “Boot Code” section at the beginning of Chapter 5 of the Datasheet gives us the default OTP program which is a jump to SPI-Flash at 0x2000 0000. Quoting the Datasheet:

As shipped, the OTP memory at the boot location is preprogrammed to jump immediately to the end of the OTP memory, which contains the following code to jump to the beginning of the SPI-Flash at 0x2000 0000:

fence 0,0
li t0, 0x20000000
jr t0

fence 0,0 is encoded as 0x0000 000F, and the instruction may be modified by burning additional bits to transform it into a JAL instruction ( opcode 0x6F ) to execute arbitrary code rather than jumping directly to the beginning of the SPI-Flash.

A little clarification to the above:

In general, note that li t0, 0x20000000 is a pseudo-instruction which is actually the two instructions lui t0, %hi(0x20000000), to load a 20-bit long immediate value, and addi t0, t0, %lo(0x20000000), to load a 12-bit short immediate value. Specifically, the Datasheet is mis-stated here: the immediate value loaded is 0x20010 (OTP_BOOT), not 0x20000000 as printed.

Within the OTP memory and not described in the Datasheet is a proprietary library which performs an SPI flash recovery mechanism. This library counts how many recovery attempts to read the SPI flash ID code after power cycling, and goes into a tight loop after the 5th failure, in order to prevent further execution into a non-existent or defective SPI memory device…

Thus, at power-up, program execution will jump to 0x2000_0000 where, in case of the HiFive board is a “double-tap” bootloader which helps get back to a happy state in case of runaway user code. It is completely optional, and all of its code is described well and available as double_tap_dontboot.c with its related discussion (and binaries) available here . User code then starts at 0x2001_0000.

In contrast to the “double tap” bootloader, a really lean start-up and minimalist example is in start.s and of RISC Five Easy As PI.

You can either map/link and load your code to 0x8000 0000, for RAM operation; or to 0x2000 0000, for Flash/ROM operation, and all will be fine.

Don’t worry if you erase, re-flash, and wipe out what’s there; it’s easy to put back to the original state.

1 Like

@pds Thanks much for the details. I’m looking forward to digging into this, and a warning… I’ll probably have more questions.

So I realize now that the MK22FN128VLH10 is actually a SEGGER (JTAG) programmer built in; but then I don’t quite get why there is a need for the doubletap bootloader on this board…
The purpose (AFAIK) of the doubletap bootloaders I’ve used on other boards is to provide the ability to recover from an otherwise unrecoverable firmware install when the only way to update the firmware is through the bootloader… If the bootloader automatically jumps to code that will lock the processor, then all bets are off because resetting the board just restarts the bootloader which then automatically jumps into the bad code. The “double-tap” reset just pulls the bootloader out of automatically jumping into the bad code; thus allowing it to reload new (preferably fixed) firmware.
Having said that, since the HiFive1B uses the on-board JTAG programmer to load the firmware (and also has the ability to pull the CPU out of any bad state), what is the purpose of the double tap bootloader?

UPDATE: I also just noticed that the doubletap bootloader that comes with the SDK jumps to 0x20400000; but as far as I can tell, the sdk examples are loaded at 0x20010000. That tells me that isn’t the bootloader running on my board (or that the sdk is not up-to-date).

1 Like

Good thinking @edsut. the U3 (Segger) chip is optional; you can bypass it by not connecting the U5 (micro-USB) and instead supplying power to J7 (barrel connector). Your JTAG signals are available on J1 (10-pin, 2x5, might not be populated) in the standard fashion. I recommend OpenOCD with an Olimex or similar FT(2)232-based device, or even bitbang from a Raspberry PI.

Don’t confuse the doubletap bootloader (DTB) in the FLASH area (0x2000 0000) with the flash recovery mechanism (FRM) library in the OTP area (0x2 000). The former is optional and helpful but not necessary for system recovery. Early versions of DTB made their final jump to user code at 0x2040 0000, while later versions of DTB made their final jump to user code at 0x2001 0000. If you erase your FLASH, or otherwise replace your DTB, you’ll want your code to start at the base, 0x2000 0000, which is where the FRM makes its final jump.

See illustration above.

It is the FRM, not the DTB, which might “lock the processor”. What makes this processor lock recoverable is a bit subtle. The FRM holds persistent state of its power-on retry counter in the always-on backup1 register of the Power Management Unit (PMU) block. It’s value is preserved even when you reset or power-cycle your board. However, if you power-off your board long enough, the PMU registers are reset to their default state – and, thus, the FRM begins anew and does not lock the processor in its tight loop anymore.

Look at R32 and C22 on Sheet 2 your HiFive1B Schematic. That’s about a 106mS time constant, for the 150K Ohm and 10 uF components installed. If you leave the board unplugged for a few seconds (or momentarily bridge C22 with a tweezer or needle-nosed pliers) you’ll be back in business after even the most hideous code mistake – such as accidentally disabling the master clock (which I’ve done many times, by accident and on purpose).

Since the FRM programs the PMU_OUT_0 to pulse low only for about 400 uS upon reset, pressing the reset button might not be sufficient to disable U8 and power-off the board long enough to reset the state of PMU register backup1. On HiFive1-RevB the RT9080 has somewhat lower input current on its EN pin, and you might get lucky with enough reset button presses; on LoFive-R1 the SPX3819M5 is not as good, and it’s EN pin draws enough power to make the voltage drop through that 150K resistor significant–the board never resets, and needs either tweezer across the 10 uF or to be unplugged for awhile.

In short, don’t worry about erasing your entire FLASH and putting your desired code from scratch at 0x2000 0000. You’ll never brick your board, I promise.

PS–if you really want to simplify things and bypass that FRM entirely, you can re-program the OTP at its 0x2 1ff0 location, by changing that fence 0,0 instruction as noted in the Application Notes of the FE310-G002 Datasheet to be an unconditional j 0x2000 0000. I have not tried this, and probably never will. It is possible to brick the chip(*) if something goes wrong during the OTP Programming Procedure outlined in Chapter 6. You might, thus, want to experiment on a very small and inexpensive board, first.

(*) Actually, you’ll never brick the chip, even in case of unfortunate OTP programming failure, because you can always use JTAG with reset and halt, attach gdb, and single-step around a corrupted OTP program; for example, manually jumping from 0x1004 (the power-up reset point), over the MASK ROM and OTP areas, directly to 0x2000 0000 which is the start of external user-programmable FLASH or other memory on the SPI interface. The chip remains fully functional and never bricked; however, you’d have to use JTAG and gdb every time you reset or power-up–it could still be used as a bench test device for prototype and development work.

1 Like