1

In the last months I have implemented ad RV32I CPU on an FPGA. Until now I just tested it using some assembly code. This week I decided to try out a simple test program in C on it. The source code is as follows :

int main(){
        register unsigned int a = 1;
        register unsigned int b = 1;
        while(1){
                a = a << b;
                if(a == 0x80000000){
                        a = 1;
                }
        }
        return 0;
}

This is all plain and simple, so that I can see a walking one pattern in whichever register "a" gets assigned.

Doing an objdump I am still quite puzzled by the position in which the stack has been placed :

test.elf:     file format elf32-littleriscv


Disassembly of section .text:

00000000 <main>:
   0:   ff010113                add     sp,sp,-16
   4:   00112623                sw      ra,12(sp)
   8:   00812423                sw      s0,8(sp)
   c:   00912223                sw      s1,4(sp)
  10:   01010413                add     s0,sp,16
  14:   00100093                li      ra,1
  18:   00100493                li      s1,1
  1c:   009090b3                sll     ra,ra,s1
  20:   800007b7                lui     a5,0x80000
  24:   fef09ce3                bne     ra,a5,1c <main+0x1c>
  28:   00100093                li      ra,1
  2c:   ff1ff06f                j       1c <main+0x1c>

Given that I say in the linker-script where my memory is, I am not understanding why the compiler uses the highest possible address below the 4GB limit as the stack base address. This can be seen in the first four instructions, where we are saving values to the stack. My linker script is the following :

ENTRY(main)

BRAM_SIZE = 1024;

MEMORY{
        INSTR(RX)  : ORIGIN =0x00000000 , LENGTH = BRAM_SIZE 
        DATA(RWX)  : ORIGIN =0x01000000 , LENGTH = BRAM_SIZE
}

STACK_SIZE = 0x100;

/* Section Definitions */
SECTIONS
{
    .text :
    {
        KEEP(*(.vectors .vectors.*))
        *(.text*)
        *(.rodata*)
    } > INSTR

    /* .bss section which is used for uninitialized data */
    .bss (NOLOAD) :
    {
        *(.bss*)
        *(COMMON)
    } > DATA

    .data :
    {
        *(.data*);
    } > DATA AT >INSTR

    /* stack section */
    .stack (NOLOAD):
    {
        . = ALIGN(8);
        . = . + STACK_SIZE;
        . = ALIGN(8);
    } > DATA

    _end = . ;
}

I am compiling with the following commands and toolchains :

riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -ffreestanding -T  linkerscript_reduced.ld -o test.elf test.c

What am I doing wrong? Thanks in advance and sorry for the noob question.

5
  • It looks like the main function is assuming the stack pointer register has already been setup to point at the stack, rather than doing it itself. Theres nothing in what you have provided that suggests the stack pointer should be specifically populated. Presumably its set to all ones at reset. Commented Jan 27, 2024 at 20:49
  • Ok, I guess you are right. From an HW perspective, all registers are filled with zeros when the CPU starts operation. Now the question is, this running in bare-metal, how do I do that? Commented Jan 27, 2024 at 20:59
  • 1
    Usually an assembly function which your linker script marks as the entry-point, and then that assembly function calls your C entrypoint. Commented Jan 27, 2024 at 21:02
  • Sometimes this assembly entry point is called _start and found in crt0.o, but you can write your own. It should set up any registers needed, and the call main, and if that main returns this assembly code should terminate the process/simulation. Sometimes _start also passes argc, argv, and envp to main. Commented Jan 27, 2024 at 21:31
  • Looks like you have built the C code in standalone mode with no C runtime start up. The runtime start up is required to initialise the stack pointer, initialise static data objects (and for C++ support, call static object constructors). Commented Jan 28, 2024 at 8:04

1 Answer 1

4

Since you add the option "-nostdlib -ffreestanding", you need to provide each and any detail yourself. This includes the setup of the C environment as promised by the standard, and anything your specific target expects.

This says the documentation of the link options (emphasized by me):

-nostdlib

Do not use the standard system startup files or libraries when linking. No startup files and only the libraries you specify are passed to the linker, and options specifying linkage of the system libraries, such as -static-libgcc or -shared-libgcc, are ignored.

And this says the documentation of the C dialect options:

-ffreestanding

Assert that compilation targets a freestanding environment. This implies -fno-builtin. A freestanding environment is one in which the standard library may not exist, and program startup may not necessarily be at main.

Therefore, you need to add code that initializes the stack pointer to the value you want it to have, zeroes .bss, copies initialized data (INSTR memory) to the .data section (DATA memory), and finally calls main(). This is commonly written in assembler, but with a grain of inline assembler and in simple cases like yours, you can write a C source as well. Don't forget to think about what should happen, if main() returns.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.