4.3. Quick start

There are four fundamental parts you need to implement for a minimal Nuclei C Runtime Library evaluation. And you can find the reference implementation in the latest Nuclei SDK release which support riscv64-unknown-elf-gcc (>=gcc13). The documentation for advanced features is in External function interface.

4.3.1. exit()

void exit(int fd);

4.3.2. Basic UART I/O functions (-lfileops_uart)

To simplify this tutorial, here you only need to implement 2 basic I/O functions:

int metal_tty_putc(int c); // UART output function
int metal_tty_getc(void);  // UART input function

4.3.3. Using Semihosting (-lfileops_semi)

If you want to use the semihosting function, simply link the ‘fileops_semi’ static library; there is no need to implement any additional stub functions.

4.3.4. Using RTT (-lfileops_rtt)

To use SEGGER’s RTT, you need to use J-LINK(>=v7.92f).

You can download the J-LINK installation package from the J-LINK page on the SEGGER official website, then unzip it. Next, use the SEGGER J-LINK GDB Server included in it to ensure that your J-LINK is connected.

Then, you need to compile and link the program with the ‘fileops_rtt’ static library, use the riscv64-unknown-elf-gdb to debug the program, and please note that you need to use monitor reset and monitor halt to reset the J-Link before loading the program.

Finally, open the J-LINK RTT Viewer to observe bi-directional communication with the target program. Please note that if nothing is displayed on the terminal, you may need to disconnect and then reconnect the terminal.

4.3.5. Thread-local storage

In the linker script, setup tdata, tbss, __tls_base and __tls_end:

.tdata           : ALIGN(8)
{
    PROVIDE( __tls_base = . );
    *(.tdata .tdata.* .gnu.linkonce.td.*)
} >RAM AT>DATA_LMA

.tbss (NOLOAD)   : ALIGN(8)
{
    *(.tbss .tbss.* .gnu.linkonce.tb.*)
    *(.tcommon)
    PROVIDE( __tls_end = . );
} >RAM AT>RAM

In startup.S, load __tls_base to tp register:

/* Initialize GP, TP and Stack Pointer SP */
.option push
.option norelax
la gp, __global_pointer$
la tp, __tls_base
.option pop
la sp, _sp

4.3.6. Heap

Specific ‘heapops’ static libraries can be chosen based on the API support. Here are the SEGGER heap API support conditions for the three ‘heapops’ static libraries:

4.3.6.1. SEGGER heap API support

heapops_realtime

heapops_basic

heapops_minimal

__SEGGER_RTL_alloc

yes

yes

yes

__SEGGER_RTL_aligned_alloc

yes

no

no

__SEGGER_RTL_realloc

yes

yes

no

__SEGGER_RTL_free

yes

yes

no

Before calling heap-related APIs such as malloc, you need to implement the initialization of the heap as shown in init_libncrt_heap(). You can call the function during startup such as in the startup.S file.

extern void __SEGGER_RTL_init_heap(void *ptr, size_t size);
extern char __heap_start[];
extern char __heap_end[];

void init_libncrt_heap(void)
{
    size_t heapsz = (size_t)__heap_end - (size_t)__heap_start;
    __SEGGER_RTL_init_heap((void *)__heap_start, heapsz);
}

In the linker script, setup __heap_start and __heap_end. You need to align the heap to a 16-byte boundary and reserve __HEAP_SIZE bytes for it.

.heap (NOLOAD)   : ALIGN(16)
{
    . = ALIGN(16);
    PROVIDE( __heap_start = . );
    . += __HEAP_SIZE;
    . = ALIGN(16);
    PROVIDE( __heap_limit = . );
} >RAM AT>RAM

.stack ORIGIN(RAM) + LENGTH(RAM) - __TOT_STACK_SIZE (NOLOAD) :
{
    . = ALIGN(16);
    PROVIDE( _heap_end = . );
    PROVIDE( __heap_end = . );
    PROVIDE( __StackLimit = . );
    PROVIDE( __StackBottom = . );
    . += __TOT_STACK_SIZE;
    . = ALIGN(16);
    PROVIDE( __StackTop = . );
    PROVIDE( _sp = . );
} >RAM AT>RAM