Can't upload with J-Link to SiFive FE310 when framework is empty

Hello!

I’m doing experiments with SparkFun RED-V thing plus and PlatformIO. For example, with this code: Running-dot-all-GPIO_asm.

It works fine when framework = freedom-e-sdk is set in platformio.ini:

[env:sparkfun_thing_plus_v]
platform = sifive
board = sparkfun_thing_plus_v
framework = freedom-e-sdk

When I comment out framework in platformio.ini, I couldn’t upload a binary to the board. Error message is “Writing target memory failed”:

 % pio run --target upload
…
J-Link>loadfile .pio/build/sparkfun_thing_plus_v/firmware.hex
'loadfile': Performing implicit reset & halt of MCU.
RISC-V: Performing reset via <ndmreset>
Downloading file [.pio/build/sparkfun_thing_plus_v/firmware.hex]...
Writing target memory failed.
…
======================== [SUCCESS] Took 3.20 seconds ========================

Full CLI output for both cases:

Probably I need to copy some upload_command and/or upload_flags from the framework configuration, but I don’t know where to get them.

Why I want to use empty framework — because when I run disassemble in J-Link, I see a lot of code I didn’t write, from the framework. I want to try to create a bare metal program.

Or, maybe, there is another way to not include framework files into the final build?

When you do so, are you adding and setting a linker script to use? If not, it might not place the firmware code at the right address (i.e., where the start of flash is).

Thanks for lightning fast response!
No, I don’t use a linker script.
Just running pio run --target upload in the project directory.

I did pio run --target envdump and I found

  'LINKFLAGS': [ '-T',
                 'metal.default.lds',
                 '-march=rv32imac',
                 '-mabi=ilp32',
                 '-mcmodel=medlow',
                 '-Os',
                 '-ffunction-sections',
                 '-fdata-sections',
                 '-nostartfiles',
                 '--specs=nano.specs',
                 '-Wl,--gc-sections',
                 '-nostdlib'],

Probably I can use /.platformio/packages/framework-freedom-e-sdk/bsp/sifive-hifive1-revb/metal.default.lds, these boards are quite similar.

Well when I disassemble the firmware built with the regular framework line, I get

> C:\Users\Max\.platformio\packages\toolchain-riscv\bin\riscv64-unknown-elf-objdump.exe -d .\.pio\build\sparkfun_thing_plus_v\firmware.elf

.\.pio\build\sparkfun_thing_plus_v\firmware.elf:     file format elf32-littleriscv        


Disassembly of section .init:

20010000 <_enter>:
20010000:       5fff1197                auipc   gp,0x5fff1
20010004:       80018193                addi    gp,gp,-2048 # 80000800 <__global_pointer$>
20010008:       00000297                auipc   t0,0x0
2001000c:       01828293                addi    t0,t0,24 # 20010020 <_enter+0x20>       
20010010:       30529073                csrw    mtvec,t0
20010014:       00100293                li      t0,1
20010018:       00028463                beqz    t0,20010020 <_enter+0x20>
2001001c:       7c105073                csrwi   0x7c1,0
20010020:       00000297                auipc   t0,0x0
20010024:       15828293                addi    t0,t0,344 # 20010178 <early_trap_vector>
20010028:       30529073                csrw    mtvec,t0
2001002c:       dfff0117                auipc   sp,0xdfff0
20010030:       fd410113                addi    sp,sp,-44 # 0 <__metal_boot_hart>
20010034:       00011463                bnez    sp,2001003c <_enter+0x3c>
20010038:       c0018113                addi    sp,gp,-1024 # 80000400 <_sp>
2001003c:       f1402573                csrr    a0,mhartid
20010040:       4281                    li      t0,0
20010042:       40000313                li      t1,1024
20010046:       ff017113                andi    sp,sp,-16
2001004a:       00a28563                beq     t0,a0,20010054 <_enter+0x54>
2001004e:       911a                    add     sp,sp,t1
20010050:       0285                    addi    t0,t0,1
20010052:       bfd5                    j       20010046 <_enter+0x46>
20010054:       00000097                auipc   ra,0x0
20010058:       04a08093                addi    ra,ra,74 # 2001009e <__metal_before_start>
2001005c:       00008363                beqz    ra,20010062 <_enter+0x62>
20010060:       9082                    jalr    ra
20010062:       f1402573                csrr    a0,mhartid
20010066:       4581                    li      a1,0
20010068:       4601                    li      a2,0
2001006a:       2a39                    jal     20010188 <_start>
2001006c:       dfff0097                auipc   ra,0xdfff0
20010070:       f9408093                addi    ra,ra,-108 # 0 <__metal_boot_hart>
20010074:       00008363                beqz    ra,2001007a <_enter+0x7a>
20010078:       9082                    jalr    ra
2001007a:       00000297                auipc   t0,0x0
2001007e:       00c28293                addi    t0,t0,12 # 20010086 <_enter+0x86>
20010082:       30529073                csrw    mtvec,t0
20010086:       00002303                lw      t1,0(zero) # 0 <__metal_boot_hart>
2001008a:       bff5                    j       20010086 <_enter+0x86>
2001008c:       0000                    unimp
        ...

I.e. there is an _enter section at 0x20010000 that does a lot of initialization before even calling into _start. That also matches

When I comment out the framework, I get a disassembly of

PS C:\Users\Max\temp\native-blink_asm_sparkfun_red-v_thing_plus\Blink-all-GPIO_asm> C:\Users\Max\.platformio\packages\toolchain-riscv\bin\riscv64-unknown-elf-objdump.exe -D .\.pio\build\sparkfun_thing_plus_v\firmware.elf

.\.pio\build\sparkfun_thing_plus_v\firmware.elf:     file format elf32-littleriscv


Disassembly of section .text:

00010054 <_start>:
   10054:       100122b7                lui     t0,0x10012
   10058:       00828293                addi    t0,t0,8 # 10012008 <GPIO_ADDR_OUTPUT_ENABLE>
   1005c:       00fd0337                lui     t1,0xfd0
   10060:       fff30313                addi    t1,t1,-1 # fcffff <__global_pointer$+0xfbe735>
   10064:       0062a023                sw      t1,0(t0)
   10068:       100122b7                lui     t0,0x10012
   1006c:       03828293                addi    t0,t0,56 # 10012038 <GPIO_ADDR_OUTPUT_SPECIAL>
   10070:       0002a023                sw      zero,0(t0)

and there it’s at the completely wrong address and missing all the init code that the previous framework had.

Once you disable framework = .., you are expected to supply all that yourself.

1 Like

If you just want a basic example, you could add this as src/entry.S: (just entry.S with two function calls / references commented out)

/* Copyright 2018 SiFive, Inc */
/* SPDX-License-Identifier: Apache-2.0 */

/* This code executes before _start, which is contained inside the C library.
 * In embedded systems we want to ensure that _enter, which contains the first
 * code to be executed, can be loaded at a specific address.  To enable this
 * feature we provide the '.text.metal.init.enter' section, which is
 * defined to have the first address being where execution should start. */
.section .text.metal.init.enter
.global _enter
_enter:
    .cfi_startproc

    /* Inform the debugger that there is nowhere to backtrace past _enter. */
    .cfi_undefined ra

    /* The absolute first thing that must happen is configuring the global
     * pointer register, which must be done with relaxation disabled because
     * it's not valid to obtain the address of any symbol without GP
     * configured.  The C environment might go ahead and do this again, but
     * that's safe as it's a fixed register. */
.option push
.option norelax
#ifdef __riscv_cmodel_compact
1:
    auipc a0, %pcrel_hi(__global_pointer__)
    addi a0, a0, %pcrel_lo(1b)
    ld   gp, 0(a0)
    add  gp, gp, a0
#else
    la gp, __global_pointer$
#endif
.option pop

    /* trap over the chicken bit register clearing, aloe & fe310 dont have it */
    la t0, 1f
    csrw mtvec, t0
    /* chicken bit is enable if core are sifive series. */
    la t0, __metal_chicken_bit
    beqz t0, 1f
    /* If set, always clear the feature disable register for all cores series */
    csrwi 0x7C1, 0
.align 4
1:
    /* Set up a simple trap vector to catch anything that goes wrong early in
     * the boot process. */
     // don't do that
    //la t0, early_trap_vector
    //csrw mtvec, t0

    /* There may be pre-initialization routines inside the MBI code that run in
     * C, so here we set up a C environment. First we set up a stack pointer,
     * which is left as a weak reference in order to allow initialization
     * routines that do not need a stack to be set up to transparently be
     * called. */
    .weak __metal_stack_pointer
    la sp, __metal_stack_pointer

   /* The METAL is designed for a bare-metal environment and therefore is expected
    * to define its own stack pointer. We also align the stack pointer here
    * because the only RISC-V ABI that's currently defined, mandates 16-byte
    * stack alignment. */

    bne sp, zero, 1f
#ifdef __riscv_cmodel_compact
    lla.gprel sp, _sp
#else
    la sp, _sp
#endif
1:
    /* Increment by hartid number of stack sizes */
    csrr a0, mhartid
    li t0, 0
    la t1, __stack_size
1:
    andi sp, sp, -16
    beq t0, a0, 1f
    add sp, sp, t1
    addi t0, t0, 1
    j 1b
1:

    /* Check for an initialization routine and call it if one exists, otherwise
     * just skip over the call entirely.   Note that __metal_initialize isn't
     * actually a full C function, as it doesn't end up with the .bss or .data
     * segments having been initialized.  This is done to avoid putting a
     * burden on systems that can be initialized without having a C environment
     * set up. */
    // don't do that
    //la ra, __metal_before_start
    //beqz ra, 1f
    //jalr ra
1:

    /* At this point we can enter the C runtime's startup file.  The arguments
     * to this function are designed to match those provided to the SEE, just
     * so we don't have to write another ABI. */
    csrr a0, mhartid
    li a1, 0
    li a2, 0
    call _start

    /* If we've made it back here then there's probably something wrong.  We
     * allow the METAL to register a handler here. */
    .weak __metal_after_main
    la ra, __metal_after_main
    beqz ra, 1f
    jalr ra
1:

    /* If that handler returns then there's not a whole lot we can do.  Just
     * try to make some noise. */
     la t0, 1f
     csrw mtvec, t0
1:
     lw t1, 0(x0)
     j 1b

    .cfi_endproc

/* The GCC port might not emit a __register_frame_info symbol, which eventually
 * results in a weak undefined reference that eventually causes crash when it
 * is dereference early in boot.  We really shouldn't need to put this here,
 * but to deal with what I think is probably a bug in the linker script I'm
 * going to leave this in for now.  At least it's fairly cheap :) */
.weak __register_frame_info
.section .text.metal.init.__register_frame_info
__register_frame_info:
    .cfi_startproc
    ret
    .cfi_endproc

And this file as src/link.ld.

Then have

[env:sparkfun_thing_plus_v]
platform = sifive
board = sparkfun_thing_plus_v
board_build.ldscript = src/link.ld

as platformio.ini. The _entry should now be at the correct place, the firmware should be uploadable.

> C:\Users\Max\.platformio\packages\toolchain-riscv\bin\riscv64-unknown-elf-objdump.exe -D .\.pio\build\sparkfun_thing_plus_v\firmware.elf

.\.pio\build\sparkfun_thing_plus_v\firmware.elf:     file format elf32-littleriscv


Disassembly of section .init:

20010000 <_enter>:
20010000:       5fff1197                auipc   gp,0x5fff1
20010004:       80018193                addi    gp,gp,-2048 # 80000800 <__global_pointer$>
20010008:       00000297                auipc   t0,0x0
..
1 Like

I found my code works even without entry.S and with small file https://github.com/psherman42/Demystifying-OpenOCD/blob/main/fe310-g002-rom.lds (MIT license). Linker config there has address 0x20000000 vs 0x20010000 in metal.default.lds, so it rewrites doubletap bootloader, which I don’t need.

@maxgerhardt , thank you very much for the support!

Just be aware that not by not using the code in _entry, you

  1. don’t have setup the stack pointer (sp). This will lead to an immediate crash when any instruction accesses the stack, such push, pop, jump to /return from subroutine (jalr, ret), etc.
  2. don’t setup the global pointer, code that uses that (e.g. call into C library or other stuff) will fail
  3. don’t do some setup stuff for vector tables

Since your blinky code does not use that, you’re fine. But with more complex code, kaboom.

1 Like

Actually this is the reason I want to get rid of “unwanted” code: I’m trying to figure out why my timer interrupt code doesn’t work SiFive FE310-G002: Using WFI together with MTIMECMP - HiFive1 Rev B - SiFive Forums . I saw there was some code in entry.S which changes MSTATUS, MTVEC, e.t.c., so now I’m playing with true bare metal code.