# Why program for MI-V bigger than 64 kB does not build properly?

I am woking with Microsemi Polarfire Splashkit evaluation board (Microchip's Polarfire MPF300T FPGA on board). My project has Mi-V RV32 Softcore processor (RISC-V ISA) and I am writing firmware for it. I set 256kB TCM as a memory for exeсutable program. When my programm's size is small it's all fine. Program compiled and executed in debug mode as expected. However if text + data sections of .elf exceed 64 kB then .elf builded but program doesn't work. Main program even doesn't loaded in debug mode. I realized that launched code has some odd arbitrary instructions in reset vector instead of boot code miv_rv32_entry.S from miv_rv32_hal. What's wrong?
On Libero SoC's side (Libero SoC v2021.1) I am using Mi-V RV32 3.0.100, IMC RISC-V extension. TCM is on, TCM APB slave (TAS) is off, TCM has address range 0x80000000 - 0x8003ffff. Reset vector is set to 0x80000000 also.
For making firmware I am using SoftConsole v2021.1. HAL is MIV_RV32 HAL version 3.0.109. In Properties->Target Processor pane Multiply and Compressed Extensions are on, align set to strict. -O0 optimization was choosen and "use newlib-nano" is off. For Linker Script I took miv-rv32-ram.ld from miv_rv32_hal and changed it to:

OUTPUT_ARCH( "riscv" )
ENTRY(_start)

MEMORY
{
ram (rwx) : ORIGIN = 0x80000000, LENGTH = 256k
}

RAM_START_ADDRESS   = 0x80000000;       /* Must be the same value MEMORY region ram ORIGIN above. */
RAM_SIZE            = 256k;              /* Must be the same value MEMORY region ram LENGTH above. */
STACK_SIZE          = 8k;               /* needs to be calculated for your application */
HEAP_SIZE           = 8k;               /* needs to be calculated for your application */

SECTIONS
{
.entry : ALIGN(0x10)
{
KEEP (*(SORT_NONE(.entry)))
. = ALIGN(0x10);
} > ram

.text : ALIGN(0x10)
{
*(.plt)
. = ALIGN(0x10);

KEEP (*crtbegin.o(.ctors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .ctors))
KEEP (*(SORT(.ctors.*)))
KEEP (*crtend.o(.ctors))
KEEP (*crtbegin.o(.dtors))
KEEP (*(EXCLUDE_FILE (*crtend.o) .dtors))
KEEP (*(SORT(.dtors.*)))
KEEP (*crtend.o(.dtors))

*(.gcc_except_table)
*(.eh_frame_hdr)
*(.eh_frame)

KEEP (*(.init))
KEEP (*(.fini))

PROVIDE_HIDDEN (__preinit_array_start = .);
KEEP (*(.preinit_array))
PROVIDE_HIDDEN (__preinit_array_end = .);
PROVIDE_HIDDEN (__init_array_start = .);
KEEP (*(SORT(.init_array.*)))
KEEP (*(.init_array))
PROVIDE_HIDDEN (__init_array_end = .);
PROVIDE_HIDDEN (__fini_array_start = .);
KEEP (*(.fini_array))
KEEP (*(SORT(.fini_array.*)))
PROVIDE_HIDDEN (__fini_array_end = .);
. = ALIGN(0x10);

} > ram

/* short/global data section */
.sdata : ALIGN(0x10)
{
__sdata_start = .;
PROVIDE( __global_pointer\$ = . + 0x800);
*(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2)
*(.srodata*)
. = ALIGN(0x10);
__sdata_end = .;
} > ram

/* data section */
.data : ALIGN(0x10)
{
__data_start = .;
*(.got.plt) *(.got)
*(.shdata)
. = ALIGN(0x10);
__data_end = .;
} > ram

/* sbss section */
.sbss : ALIGN(0x10)
{
__sbss_start = .;
*(.scommon)
. = ALIGN(0x10);
__sbss_end = .;
} > ram

/* sbss section */
.bss : ALIGN(0x10)
{
__bss_start = .;
*(.shbss)
*(COMMON)
. = ALIGN(0x10);
__bss_end = .;
} > ram

/* End of uninitialized data segment */
_end = .;

.heap : ALIGN(0x10)
{
__heap_start = .;
. += HEAP_SIZE;
__heap_end = .;
. = ALIGN(0x10);
_heap_end = __heap_end;
} > ram

.stack : ALIGN(0x10)
{
__stack_bottom = .;
. += STACK_SIZE;
__stack_top = .;
} > ram
}


Remaining part of linker script was unchanged. .map and .lst files seem ok, boot code has right place according to it.

I discovered that content of TCM memory differs from builded .hex file using SmartDebug. So, I think that problem is not in linker-script (not only, at least). Then I tried to boot from external SPI-flash (may be problem that binary loaded in debug mode). But result is the same (although memory content differs in different way for that case). If firmware is small then .hex content and loaded in TCM data match. Very strange situation. Why could it be?

• I would start from checking the link map. Could you post the full linker script. Map file would be to big to post, but will need eventually.
– jay
Aug 18, 2021 at 13:37

I have to explain this, for I would need understanding of the readers. I may not be able to answer it at once, since I have passed that part of process some time ago, even for the current project. I would be very happy to yield if someone right at the position come in.
You are in a serious area that EEs encounter when sofis complain hardware not working. Link script is always challenging due to it does not need to be touched once it has been settled, and forgotten. However, it is a critical part, especially when touching OS/RTOS, Bootloader, Configurable architecture, Multi-core system, and etc.

First answer, though you did not ask, my point to @ArchiMAD is, bite the bullet and understand the meaning of every line every word in the link script, and then put it under your complete control. You will come back to that, and feel easy to come back, when you run into problems like this, that often comes to Firmware and Embedded area of EE tasks.

As I explained why, I opened your script and trying to grapple it, but going to take some time. Thus, I have to just suggest what you to look at to find the ultimate answer. And, I will quickly run down the list. You will come back with either your own answer or a question.

1. I cannot tell where the core finds the very first code, looking at the script. Find where the reset goes. Locate the reset vector (not going to talk about other vectors) on the link script, may look at the map file. You may trace "instruction/assembly/core sequence" using debugger header (JTAG) from the "system reset". For an example, in convention, that is located in LOAD_ADDRESS, for it is expected to be non-volatile. It could be in something like ".vector", ".reset", or just at the ".text".

2. Figure out where and how the "binary (code)" gets "loaded" and "executed". That is sometimes called "load address" and "run address". The linker input (through the configurations) may supply it. Copy the linker output on the console into your favorite text editor, and analyze every word in that.

.entry : ALIGN(0x10) { KEEP (*(SORT_NONE(.entry))) . = ALIGN(0x10); } > ram

1. If the reset was to be at the beginning, check if ".entry" hosts space for the reset (vector).

2. ".stack" segment is too close to ".heap", in my opinion. You may want to place it to the end of the ">ram". I would say your core is "push down" & "pop up". If any part of your code requires the stack size known, it is either for sanity check or a "kernel" to detect stack usage. Otherwise, you can just make it "1" sized at the top of the memory.

3. Read the c_start (or similar, likely in assembly) how it prepares it's "run" environment (.ctor, .dtor, .vtab (those of c++) .init, .const, etc.) along with the "relocation of dynamic vectors" and the heap, stack & variables initialization before calling "main()".

When you feel like to start writing a C/C++ compiler, your are doing it right.

Sorry that I could not give you an immediate answer.

It seems that I found solution. I customized configuration of Mi-V: I have set boxes "Internal MTIME" and "Internal MTIME IRQ" (despite that I don't use internal timer). And then I saw expected boot code in the reset vector's location. Also I noted that checking "Internal MTIME", "Internal MTIME IRQ" and "GPR Registers" facilitates place and route significantly reducing time. Linker script remained unchanged.