gccembeddedriscvlinker-scripts

Linker Script for RISCV CPU on FPGA, stack is not where I expect it to be?


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.


Solution

  • 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.