Experiments with RISC-V

I thought I ought to start up something new & challenging, so – try extending my Cortex GNAT RTS to use a RISC-V chip† (the inexpensive ESP32-H2).

So far,

  • built the cross-compiler. I targeted riscv-elf, others have used riscv32-elf, riscv64-elf; since the 64-bit version can generate 32-bit code, what’s the difference? (I suppose the 32-bit version won’t generate 64-bit code??)
  • that means that as far as gprbuild is concerned, the compiler won’t generate libraries. This is down to a regexp in the gprconfig database which insists on seeing 32 or 64.
  • the SVD for the ESP32-H2 includes the %s symbol for arrays and lists which svd2ada doesn’t handle - @Irvise has already come across this.
  • there are some assembly files in the FreeRTOS portability layer which the compiler complains about.
  • trouble getting gdb to work. I guess I need to try actually creating a binary of my own first. Also, re-re-reading the documentation.

† that will of course mean changing the name of the project! I understand that Github retains links under the old name, here’s hoping


I experimented with ESP32-C3 a few years ago and ran into similar issues. I posted my notes on Reddit then, copied below for posterity.

ESP32C3 has a USB port on GPIO pins 18 and 19 that exposes a CDC-ACM UART and JTAG for the RISC-V cores. Neither of the dev boards I bought had these pins connected to a USB port, so I had to hack one together by cutting up a USB cable. Once I had that connected, I compiled Espressif’s OpenOCD fork and got gdb running.

⁠You can only use hardware breakpoints in gdb with the hbreak command. Software breakpoints don’t work because the program memory is mapped to different addresses on the instruction and data buses. When gdb tries to insert break instructions, it does so on the data bus, using an address it calculated from the instruction bus. I have no idea how to fix that or force gdb to always use hardware breakpoints, but it probably requires patching gdb requires a change to the OpenOCD config, which is included in their fork now.

⁠bb-runtimes needs to be patched to generate a runtime for -march=rv32imc_zicsr

⁠The riscv64-elf toolchain in Alire supports 32-bit targets as well, the naming is counter-intuitive.

⁠The .sdata linker section stands for “small data”, which needs to be linked in the drom address space, same as .data.

⁠The RMT peripheral (IR photodiode demodulator, I think?) in the SVD file uses some fancy format string patterns that aren’t supported by svd2ada. svd2ada is a few versions behind the SVD XML schema and would need some significant patching to support this. I worked around it by just removing this section from the SVD for now.

I found the espressif/esp32c3-direct-boot-example code to be enlightening.

My proof of concept is on GitHub at JeremyGrosser/learnesp32.

I recorded nine hours of screen capture of me fiddling with this chip. I’ve got about a third of it edited for YouTube, but I’m not sure it’s going to be interesting for the casual viewer.


Thanks very much for that!

The ESP32 H2 is very similar, so far (different pins for the debugging connection, but they’re all at one end of one side of the board (bar +5, but so far it doesn’t seem to be needed?)).

I guess I’ll rebuild the compiler to riscv64-elf to go along with the majority.

I’ve got to -march=rv32imac_zicsr -mabi=ilp32.

My FreeRTOS issues were to do with (a) not including zicsr, (b) not updating the config file.

The RMT (Remote Control) peripheral is for things like implementing a TV remote. I agree about just removing it (I think I can tell gprbuild to exclude it?)

All that’s for another day!


“Progress”: raised GCC PR 115591. Now to work on a workround.

There’s a fix for the PR, but I found a simple workaround (implement my own Ada version of memcpy(), memset()).

I’ve now managed to make a test executable. Some outstanding puzzles: I don’t think I understand the difference between irom and drom - the same area of flash, mapped to two different addresses, presumably to allow different memory access paths for instructions and read-only data? I see that the ESP32C3 example in the Direct Boot example (which has moved) uses them, but the ESP32H2 example doesn’t. If it’s only for performance, I can postpone further thought!

What I can’t do yet is load and run the executable.

The executable is quite small (7981 bytes), but when I use riscv64-elf-objcopy -O binary the resulting file is 1.1GB!!!. I used esptool.py elf2image hoping to create something more to openocd’s liking, but failed: openocd still says

Info : Flash mapping 0: 0x10020 -> 0x42018020, 37 KB
Info : Flash mapping 1: 0x20020 -> 0x42000020, 93 KB
Info : Auto-detected flash bank 'esp32h2.flash' size 4096 KB
Info : Using flash bank 'esp32h2.flash' size 4096 KB
Info : Flash mapping 0: 0x10020 -> 0x42018020, 37 KB
Info : Flash mapping 1: 0x20020 -> 0x42000020, 93 KB
Info : Using flash bank 'esp32h2.irom' size 96 KB
Info : Flash mapping 0: 0x10020 -> 0x42018020, 37 KB
Info : Flash mapping 1: 0x20020 -> 0x42000020, 93 KB
Info : Using flash bank 'esp32h2.drom' size 40 KB

(irom/drom again?) but then goes on to say

Info : PROF: Wrote 5188 bytes in 6614.95 ms (data transfer time included)
PROF: Wrote 5188 bytes in 6614.95 ms (data transfer time included)

I patched openocd - the write was to esp32h2.flash.

There’s nothing at the start address of irom, and no code at main:

(gdb) Breakpoint 1 at 0x420001b6: file b__testbed.adb, line 21.
(gdb) set lang c
(gdb) p/x *(int *)0x420001b6
$1 = 0x0

so trying to execute the program is futile!

Any thoughts?

That’s correct on ESP32-C3. It looks like they simplified things on ESP32-H2 and just made the instruction and data buses share the same address space. I’m not sure why they chose to use separate address spaces on ESP32-C3. It seems like something borrowed from a machine with a more complex MMU, so maybe it had something to do with the RISC-V implementation they chose.

I’m not sure what those 37 and 93 KB flash mappings are. All of those addresses are within the address space mapped to external flash. .irom and .drom being different sizes seems odd as well.

There is a cache in front of the flash, so maybe it’s not flushed after write? What happens if you print that address after a power cycle?

I still can’t write to flash, but I can build the image for ram and have it execute! That must mean that the FreeRTOS/riscv support for tasking is good, because the Ada program runs in the environment task. Next thing: getting the on-board LEDs working.

A major issue was that Espressif’s OpenOCD fork works with their FreeRTOS fork. When it sees a standard FreeRTOS it thinks there are millions of priority levels, and it complains after every instruction. You can turn off the RTOS stuff, but it took some time to work out where to say configure -rtos none.

What may be an issue is that I got a postcondition failure in Ada.Numerics.Elementary_Functions.Sqrt (2.0) - but I took time off to watch @irvise’s excellent & challenging Ada Community Advocacy video. Tomorrow is another day :sleeping:


Actually, I think that getting delays working might come first.

As a side issue, while investigating the problem with Sqrt I found that the riscv64-elf C compiler supports long double, but the Ada compiler doesn’t support Long_Long_Float (well, it does, but it’s the same as Long_Float, i.e. C double). Being a purist, I wanted to raise a PR at Bugzilla, but you may think that (a) there are more important issues, (b) the chance of such a PR getting anywhere are slender at best!