Writing assembly for HiFive1

Hey there!
I just got my HiFive1 and right now I am reading the documentation in preparation to start developing for the board. I’ve come up with the following questions, which I have not been able to answer on my own.

This is a very simple program I’ve written, which is supposed to enable the blue led and turn it on:

.section .text
.globl _start

_start:
    li t0, 0x10012000
    li t1, 0x200000
    sw t1, 0x08(t0)
    sw t1, 0x0C(t0)
    sw t1, 0x40(t0)

It assembles without errors using the riscv64-unknown-elf-as with no additional parameters.

  1. The assembled file is rather big with 864 bytes. The instructions themselves should only take up 20 bytes, so where is all that other data coming from?

    a.out:     file format elf64-littleriscv
    Disassembly of section .text:
    
    0000000000000000 <_start>:
       0:   100122b7                lui     t0,0x10012
       4:   00200337                lui     t1,0x200
       8:   0062a423                sw      t1,8(t0) # 10012008 <_start+0x10012008>
       c:   0062a623                sw      t1,12(t0)
      10:   0462a023                sw      t1,64(t0)
    
  2. Do I really need to specify the .text segment, if I am writing a privileged program?

  3. When I try to upload to the board I get the following error message:

    $RISCV_OPENOCD_PATH/bin/openocd -f freedom-e-sdk/bsp/sifive-hifive1/openocd.cfg -c "program ./a.out verify reset exit"
    
    Error: invalid ELF file, only 32bits files are supported
    

    Is this an error on my side? And if yes, how would I go about fixing it?

  4. Is there a specific memory address, where the program is loaded from the flash on bootup? I have not found a answer in the manual yet

Thanks in advance!

  1. Output file is in ELF format, with all its headers. If you objcopy it to the bin format, it should be 20 bytes or so.
  2. .text section represents code section, regardless of target privilege level.
  3. Seems like riscv64-unknown-elf-as generates 64-bit binaries by default. Try riscv64-unknown-elf-as -m32.
  4. It’s executed from the flash directly. Details are platform-specific: if you do not have a bootloader, then it’s flash base address (0x20000000), if you have one, then the load address is different and depends on the bootloader used. Default bootloader uses 0x20400000 on HiFive1 (old) and 0x20010000 on HiFive1 Rev B.

The assembler options for 32-bit code are the same as the compiler, -march=X and -mabi=Y to specify the architecture and the abi. So something like -march=rv32imac -mabi=ilp32 if you want 32-bit soft-float code with the multiply, atomic, and compressed extensions. rv64gc/lp64d is probably the default arch/abi.

Thank you to both of you!

Following your advice I assembled it as a 32-bit binary and converted it to an binary file.
I just had to flash it to the HiFive1 and it’s working perfectly fine.

I am a little confused on how though, so I’d be grateful if you could help me once more:

  1. Is there an option to generate an object(binary) file directly with the assembler? I found no such option.

  2. What is the difference between flashing an .elf or and .bin to the HiFive1? Both run fine so apparently the processor can handle .elf files just fine. Is the overhead from the ELF file useless to the processor?

  3. If the code is executed from flash directly, does that mean the processor fetches the first instruction from 0x2000000 inside the flash assuming, that I have no bootloader? What’s up with 0x0-0x1FFFFFFF?
    When flashing to the board I need to specify an address and unless I use 0x20000000 the code is not going to be executed. So I can assume, that the first instruction the CPU executes lies at address 0x20000000 in the flash?

Again thank you, you really helped me out!

Assembler output contains relocations, which can not be represented in binary files, so the assembler output must always be ELF, which is the only structured object file format supported for the RISC-V assembler port. The linker has an option to emit binary files directly, but the RISC-V port does not support this. The RISC-V linker port uses some special fields in the ELF info while linking, and hence it can only produce working ELF binaries. You have to create ELF output and then use a program like objcopy to convert to a binary file. Some other linker ports have the same problem, so this isn’t a RISC-V specific problem.

Binary files are just a raw block of memory to be copied to an address, where the target address has to be specified elsewhere. An ELF file contains headers that describe the info in the file. It can have multiple segments, where each segment has a file offset, memory address to copy to, file size, and memory size. The file size and memory size can be different for bss sections. An ELF file can also contain a symbol table, debugging info, and other stuff useful for software developers.

If you have a smart program for flashing, it can parse the ELF headers and write the appropriate sized blocks to the appropriate target memory addresses. If you have a dumb program for flashing, then maybe all it can do is take one file and write it to one address, in which case it has to be a binary file.

I don’t have a HiFive1 so I don’t know some of the details of using it. I do know that the most of the address space is reserved for specific purposes, some addresses are reserved for memory mapped I/O devices, some are reserved for the debug/trace module, some are reserved for rom/flash/ram, etc. if you look at the sifive documentation web site
https://www.sifive.com/documentation
and open the Freedom E310-G0002 manual, Chapter 4 is a memory map showing exactly what each address range is reserved for. This is the chip used in the HiFive1 RevB. The memory map for the original HiFve1 is a little different.

I believe the main purpose of the boot loader is for recovery purposes. if you accidentally flash a program that puts the core into an unrecoverable state, then you can still flash a new program over top of it by interrupting the boot loader to avoid running the program. But if you write such a program over top the boot loader, then every time you turn it on it goes directly into an unrecoverable state, and you may not be able to flash a new program. If this happens, you then have to use some special process to reflash the boot loader. I don’t know exactly how that works, but you want to avoid that if possible. So it is best not to overwrite the boot loader. This is why by default we don’t write programs at the start of flash, but rather after the boot loader.

1 Like

I don’t have a HiFive1 RevB, but for the first release the bootloader was there to allow Arduino flash programming.

Arduino is a toy technology, with a very simple development environment, which does not provide debugging features and does not use JTAG or similar hardware technologies to program the on board flash. Instead, Arduino uses a serial protocol that must be known by the programmed board, thus the need for the bootloader.

But bootloaders are not specific to the Arduino world, many microcontrollers use them, mostly to allow in-place firmware updates, without the need to take the unit to a lab and use an external JTAG probe.

Thank you Jim, that has been very helpful! I have also found the appropriate documentation for what is executed.

In the FE310-G000 Datasheet, Chapter 5.1 Boot Code it says the following:

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 0x20000000

So this is now cleared up for me, thanks again!

Although the memory map you’ve pointed out leaves me with even more questions. According to it, the highest address is 0xFFFF_FFFF, which is equivalent to 4GiB of RAM. The only thing regarding memory I found was, that the FE300-G000 has 16KB of SRAM. Does this mean, that I’ve had 4GiB of (mapped and used) RAM lying under my nose without me noticing it? This is confusing.

Second, the Memory Map specifies that the SPI Flash is mapped to 512MiB of Space, but the flash itself is only is 16MiB big. There must be something, that I am misinterpreting, as it does not make sense at all.

I am mainly asking this, because I want to implement a software stack and I was looking for an appropriate memory region. With what I know I figure, that the DTIM at memory location
0x8000_0000 - 0x8000_3FFF is the most appropriate for containing the stack.

For reference I disassembled the example hello program and it uses the following address for the stack pointer: 0x000D_FC00, which is in a ‘Reserved’ region. What does ‘Reserved’ mean in this context?

Thanks again!

Is this a HiFve1 or a HiFive1 Rev B? They are different boards. We don’t sell the HiFive1 anymore as far as I know, so if you just got it, I’d assume it is a HiFive1 Rev B.

The memory map just specifies where something is if you have it. Not every board has every peripheral or rom/flash/ram region, and if they do have them, they don’t necessarily have the max size. Also, some of the on chip devices have sizes that can be configured, so different parts in the same series don’t necessarily have the same DTIM sizes for instance.

Anyways, you can see examples of linker scripts for the boards in freedom-e-sdk, which will show where the memory regions are. A HiFive1 Rev B example is here


and a HiFive1 example is here

Are you sure? Was the example compiled for the right target hardware?

Oh I see, the code is:

20010020:       dfff0117                auipc   sp,0xdfff0
20010024:       fe010113                addi    sp,sp,-32 # 0 <__metal_boot_hart>

The instruction here is auipc (not lui), which adds 0xdfff0<<12 to the current PC. So the address becomes 0x20010000 + (0x5fff1<<12) + 648 or 0x80001288, which is in the RAM area.

‘Reserved’, in this context, means that the address region is reserved for future use, and should not be used.

You are correct! Seems like I misinterpreted that instruction, thanks for clearing that one up!

It is a HiFive1. Thank you for elaborating!