diff --git a/CHANGELOG.md b/CHANGELOG.md index 187e5aff4..709806a1d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ The version number is globally defined by the `hw_version_c` constant in the mai * :bug: = bug-fix * :sparkles: = new feature +* :test_tube: = new (experimental) feature * :warning: = (major) change that might impact compatibility with previous versions * :lock: = security issue * :rocket: = release @@ -32,11 +33,12 @@ The version number is globally defined by the `hw_version_c` constant in the mai | Date (*dd.mm.yyyy*) | Version | Comment | |:----------:|:-------:|:--------| +| 11.02.2022 | 1.6.7.8 | :test_tube: added newlib's system calls (stubs) and linker script symbols for heap memory to support **dynamic memory allocation** (e.g. `malloc`) and even **standard IO functions** like `printf`; see [PR #275](https://github.com/stnolting/neorv32/pull/275) | | 10.02.2022 | 1.6.7.7 | :sparkles: added **RISC-V hardware trigger module** to CPU - allows to set _hardware breakpoints_ (via gdb's `hb`/`hbreak` command) to debug code from ROM; see [PR #274](https://github.com/stnolting/neorv32/pull/274); :bug: minor bug fix in `ebreak` instruction's `dcsr.cause` value (was 0b010 but has to be 0b001) | | 08.02.2022 | 1.6.7.6 | :warning: renamed default branch of repository to `main` | | 07.02.2022 | 1.6.7.5 | removed default values for bi-directional top entity ports `twi_sda_io` and `twi_scl_io` | | 05.02.2022 | 1.6.7.4 | added `err_o` signal to **IMEM** module; if the IMEM is implemented as true ROM any write attempt will raise a _store access fault_ exception (with a `[DEVICE_ERR]` error); see [PR #273](https://github.com/stnolting/neorv32/pull/273) | -| 03.02.2022 | 1.6.7.3 | using `LTO` (link-time-optimization) option for **bootloader**; improved bootloader user console; see [PR #268](https://github.com/stnolting/neorv32/pull/268) | +| 03.02.2022 | 1.6.7.3 | :test_tube: using `LTO` (link-time-optimization) option for **bootloader**; improved bootloader user console; see [PR #268](https://github.com/stnolting/neorv32/pull/268) | | 31.01.2022 | 1.6.7.2 | :bug: fixed minor bug in **bootloader's MTIME handling** (bootloader crashed if `Zicntr` ISA extension not enabled), fixed minor issues in MTIME and `time` CSRs handling; added MTIME example program; see [PR #267](https://github.com/stnolting/neorv32/pull/267) | | 30.01.2022 | 1.6.7.1 | :sparkles: added **`Zxcfu` ISA extension for user-defined custom RISC-V instructions**; see [PR #264](https://github.com/stnolting/neorv32/pull/264) | | 28.01.2022 |[**:rocket:1.6.7**](https://github.com/stnolting/neorv32/releases/tag/v1.6.7) | **New release** | diff --git a/docs/datasheet/soc.adoc b/docs/datasheet/soc.adoc index 4e48d1265..a17f1ac47 100644 --- a/docs/datasheet/soc.adoc +++ b/docs/datasheet/soc.adoc @@ -1184,6 +1184,11 @@ internal _bootloader memory_ (BOOTLDROM). .NEORV32 processor - address space (default configuration) image::address_space.png[900] +.RAM Layout - Usage of the Data Address Space +[TIP] +The actual usage of the data address space by the software/executables (stack, heap, ...) is +illustrated in section <<_ram_layout>>. + :sectnums: ==== CPU Data and Instruction Access diff --git a/docs/datasheet/software.adoc b/docs/datasheet/software.adoc index c437df129..afcbe824f 100644 --- a/docs/datasheet/software.adoc +++ b/docs/datasheet/software.adoc @@ -101,7 +101,6 @@ The documentation is automatically built and deployed to GitHub pages by the CI - <<< // #################################################################################################################### :sectnums: @@ -282,19 +281,16 @@ MEMORY ---- Each memory section provides a _base address_ `ORIGIN` and a _size_ `LENGTH`. The base address and size of the `iodev` section is -fixed and must not be altered. The base addresses and sizes of the `ram` and `rom` regions correspond to the total available instruction -and data memory address space (see section <<_address_space_layout>>). +fixed and should not be altered. The base addresses and sizes of the `ram` and `rom` regions correspond to the total available instruction +and data memory address space (see section <<_address_space_layout>>) as defined in `rtl/core/neorv32_package.vhd`. [IMPORTANT] -`ORIGIN` of the `ram` section has to be always identical to the processor's `dspace_base_c` hardware configuration. Additionally, +`ORIGIN` of the `ram` section has to be always identical to the processor's `dspace_base_c` hardware configuration. + + + `ORIGIN` of the `rom` section has to be always identical to the processor's `ispace_base_c` hardware configuration. -The sizes of `ram` section has to be equal to the size of the **physical available data instruction memory**. For example, if the processor -setup only uses processor-internal DMEM (<<_mem_int_dmem_en>> = _true_ and no external data memory attached) the `LENGTH` parameter of -this memory section has to be equal to the size configured by the <<_mem_int_dmem_size>> generic. - The sizes of `rom` section is a little bit more complicated. The default linker script configuration assumes a _maximum_ of 2GB _logical_ -memory space, which is also the default configuration of the processor's hardware instruction memory address space. This size _does not_ have +memory space, which is also the default configuration of the processor's hardware instruction memory address space. This size does not have to reflect the _actual_ physical size of the instruction memory (internal IMEM and/or processor-external memory). It just provides a maximum limit. When uploading new executable via the bootloader, the bootloader itself checks if sufficient _physical_ instruction memory is available. If a new executable is embedded right into the internal-IMEM the synthesis tool will check, if the configured instruction memory size @@ -308,8 +304,8 @@ The `ram` region also uses a conditional assignment (via the `make_bootloader` s (`make_bootloader` symbol is set) the generated bootloader will only use the _first_ 512 bytes of the data address space. This is a fall-back to ensure the bootloader can operate independently of the actual _physical_ data memory size. -The linker maps all the regions from the compiled object files into four final sections: `.text`, `.rodata`, `.data` and `.bss`. -These four regions contain everything required for the application to run: +The linker maps all the regions from the compiled object files into five final sections: `.text`, `.rodata`, `.data`, `.bss` and `.heap`. +These regions contain everything required for the application to run: .Linker memory regions [cols="<1,<9"] @@ -320,13 +316,45 @@ These four regions contain everything required for the application to run: | `.rodata` | Constants (like strings) from the application; also the initial data for initialized variables. | `.data` | This section is required for the address generation of fixed (= global) variables only. | `.bss` | This section is required for the address generation of dynamic memory constructs only. +| `.heap` | This section is required for the address generation of dynamic memory constructs only. |======================= -The `.text` and `.rodata` sections are mapped to processor's instruction memory space and the `.data` and -`.bss` sections are mapped to the processor's data memory space. Finally, the `.text`, `.rodata` and `.data` +The `.text` and `.rodata` sections are mapped to processor's instruction memory space and the `.data`, +`.bss` and `heap` sections are mapped to the processor's data memory space. Finally, the `.text`, `.rodata` and `.data` sections are extracted and concatenated into a single file `main.bin`. +:sectnums: +==== RAM Layout + +The default NEORV32 linker script uses all of the defined RAM (linker script memory section `ram`) to create four areas. +Note that depending on the application some areas might not be existent at all. + +.Default RAM Layout +image::ram_layout.png[400] + +[start=1] +. **Constant data (`.data`)**: The constant data section is placed right at the beginning of the RAM. For example, this section +contains _explicitly initialized_ global variables. This section is initialized by the executable. +. **Dynamic data (`.bss`)**: The constant data section is followed by the dynamic data section, which contains _uninitialized_ data +like global variables without explicit initialization. This section is cleared by the start-up code `crt0.S`. +. **Heap (`.heap`)**: The heap is used for dynamic memory that is managed by functions like `malloc()` and `free()`. The heap +grows upwards. This section is not initialized at all. +. **Stack**: The stack starts at the very end of the RAM at address `ORIGIN(ram) + LENGTH(ram) - 4`. The stack grows downwards. + +There is _no explicit limit_ for the maximum stack size as this is hard to check. However, a physical memory protection rule could +be used to configure a maximum size by adding a "protection area" between stack and heap (a PMP region without any access rights). + +The maximum size of the heap is defined by the linker script's `__heap_size` symbol. This symbol can be overridden at any time. +By default, the maximum heap size is 1/4 of the total RAM size. + +.Heap-Stack Collisions +[WARNING] +Take care when using dynamic memory to avoid collision of the heap and stack memory areas. There is no compile-time protection +mechanism available as the actual heap and stack size are defined by _runtime_ data. Also beware of fragmentation when +using dynamic memory allocation. + + :sectnums: ==== Executable Image Generator @@ -409,12 +437,14 @@ int __neorv32_crt0_after_main(int32_t return_code) { ---- + <<< // #################################################################################################################### include::software_bootloader.adoc[] + <<< // #################################################################################################################### diff --git a/docs/figures/ram_layout.png b/docs/figures/ram_layout.png new file mode 100644 index 000000000..bac9c63d4 Binary files /dev/null and b/docs/figures/ram_layout.png differ diff --git a/rtl/core/neorv32_package.vhd b/rtl/core/neorv32_package.vhd index b8ff1d4ef..e38b6e74d 100644 --- a/rtl/core/neorv32_package.vhd +++ b/rtl/core/neorv32_package.vhd @@ -65,7 +65,7 @@ package neorv32_package is -- Architecture Constants (do not modify!) ------------------------------------------------ -- ------------------------------------------------------------------------------------------- constant data_width_c : natural := 32; -- native data path width - do not change! - constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01060707"; -- no touchy! + constant hw_version_c : std_ulogic_vector(31 downto 0) := x"01060708"; -- no touchy! constant archid_c : natural := 19; -- official NEORV32 architecture ID - hands off! -- Check if we're inside the Matrix ------------------------------------------------------- diff --git a/sw/common/neorv32.ld b/sw/common/neorv32.ld index ed20dcdcc..b61f2c522 100644 --- a/sw/common/neorv32.ld +++ b/sw/common/neorv32.ld @@ -3,7 +3,7 @@ /* # ********************************************************************************************* # */ /* # BSD 3-Clause License # */ /* # # */ -/* # Copyright (c) 2021, Stephan Nolting. All rights reserved. # */ +/* # Copyright (c) 2022, Stephan Nolting. All rights reserved. # */ /* # # */ /* # Redistribution and use in source and binary forms, with or without modification, are # */ /* # permitted provided that the following conditions are met: # */ @@ -75,6 +75,10 @@ SECTIONS /* start section on WORD boundary */ . = ALIGN(4); + /* default heap size: we can use up to 1/4 of available data memory (RAM) by default */ + /* WARNING! the heap will collide with the stack if allocating too much memory! */ + __heap_size = DEFINED(__heap_size) ? __heap_size : LENGTH(ram) / 4; + /* Actual instructions */ .text : @@ -178,8 +182,8 @@ SECTIONS *(.srodata.cst16) *(.srodata.cst8) *(.srodata.cst4) *(.srodata.cst2) *(.srodata .srodata.*) *(.sdata .sdata.* .gnu.linkonce.s.*) - PROVIDE_HIDDEN (__tdata_start = .); - *(.tdata .tdata.* .gnu.linkonce.td.*) + PROVIDE_HIDDEN (__tdata_start = .); + *(.tdata .tdata.* .gnu.linkonce.td.*) /* finish section on WORD boundary */ @@ -187,6 +191,8 @@ SECTIONS _edata = .; PROVIDE (edata = .); . = .; + __DATA_END__ = .; + __global_pointer$ = __DATA_END__ + 0x800; } > ram AT > rom @@ -194,7 +200,8 @@ SECTIONS /* zero/non-initialized read/write data placed in RAM */ .bss (NOLOAD): { - __bss_start = .; + . = ALIGN(4); + __BSS_START__ = .; *(.dynsbss) *(.sbss .sbss.* .gnu.linkonce.sb.*) *(.sbss2 .sbss2.* .gnu.linkonce.sb2.*) @@ -215,13 +222,21 @@ SECTIONS pad the .data section. */ . = ALIGN(. != 0 ? 32 / 8 : 1); - . = ALIGN(32 / 8); + . = ALIGN(4); __BSS_END__ = .; - __global_pointer$ = MIN(__SDATA_BEGIN__ + 0x800, MAX(__DATA_BEGIN__ + 0x800, __BSS_END__ - 0x800)); _end = .; PROVIDE (end = .); } > ram + /* heap for dynamic memory allocation (use carefully!) */ + .heap : + { + PROVIDE(__heap_start = .); + . = __heap_size; + PROVIDE(__heap_end = .); + } > ram + + /* Yet unused */ .jcr : { KEEP (*(.jcr)) } .got : { *(.got.plt) *(.igot.plt) *(.got) *(.igot) } .interp : { *(.interp) } @@ -299,7 +314,7 @@ SECTIONS PROVIDE(__ctr0_imem_begin = ORIGIN(rom)); PROVIDE(__ctr0_dmem_begin = ORIGIN(ram)); PROVIDE(__crt0_stack_begin = (ORIGIN(ram) + LENGTH(ram)) - 4); - PROVIDE(__crt0_bss_start = __bss_start); + PROVIDE(__crt0_bss_start = __BSS_START__); PROVIDE(__crt0_bss_end = __BSS_END__); PROVIDE(__crt0_copy_data_src_begin = __etext + SIZEOF(.rodata)); PROVIDE(__crt0_copy_data_dst_begin = __DATA_BEGIN__); diff --git a/sw/lib/source/syscalls.c b/sw/lib/source/syscalls.c new file mode 100644 index 000000000..fb850e709 --- /dev/null +++ b/sw/lib/source/syscalls.c @@ -0,0 +1,310 @@ +/* An extremely minimalist syscalls.c for newlib + * Based on riscv newlib libgloss/riscv/sys_*.c + * + * Copyright 2019 Clifford Wolf + * Copyright 2019 ETH Zürich and University of Bologna + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH + * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY + * AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, + * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM + * LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR + * OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR + * PERFORMANCE OF THIS SOFTWARE. + */ + +/**********************************************************************//** + * @file syscalls.c + * @author Modified for the NEORV32 RISC-V Processor by Stephan Nolting + * @brief Newlib system calls + * + * @warning UART0 (if available) is used to read/write STDOUT data + * + * @note Original source file: https://github.com/openhwgroup/cv32e40p/blob/master/example_tb/core/custom/syscalls.c + * @note Original license: SOLDERPAD HARDWARE LICENSE version 0.51 + **************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include + +#undef errno +extern int errno; + +// /* write to this reg for outputting strings */ +// #define STDOUT_REG 0x10000000 - use NEORV32.UART0 for output +// /* write test result of program to this reg */ +// #define RESULT_REG 0x20000000 +// /* write exit value of program to this reg */ +// #define EXIT_REG 0x20000004 - use NEORV32.UART0 for output + +#define STDOUT_FILENO 1 + +/* It turns out that older newlib versions use different symbol names which goes + * against newlib recommendations. Anyway this is fixed in later version. + */ +#if __NEWLIB__ <= 2 && __NEWLIB_MINOR__ <= 5 +# define _sbrk sbrk +# define _write write +# define _close close +# define _lseek lseek +# define _read read +# define _fstat fstat +# define _isatty isatty +#endif + +void unimplemented_syscall() +{ + // const char *p = "Unimplemented system call called!\n"; + // while (*p) + // *(volatile int *)STDOUT_REG = *(p++); + if (neorv32_uart0_available()) { + neorv32_uart0_print(" Unimplemented system call called!\n"); + } +} + +int nanosleep(const struct timespec *rqtp, struct timespec *rmtp) +{ + errno = ENOSYS; + return -1; +} + +int _access(const char *file, int mode) +{ + errno = ENOSYS; + return -1; +} + +int _chdir(const char *path) +{ + errno = ENOSYS; + return -1; +} + +int _chmod(const char *path, mode_t mode) +{ + errno = ENOSYS; + return -1; +} + +int _chown(const char *path, uid_t owner, gid_t group) +{ + errno = ENOSYS; + return -1; +} + +int _close(int file) +{ + return -1; +} + +int _execve(const char *name, char *const argv[], char *const env[]) +{ + errno = ENOMEM; + return -1; +} + +void _exit(int exit_status) +{ + //*(volatile int *)EXIT_REG = exit_status; + if (neorv32_uart0_available()) { + neorv32_uart0_printf(" Exit status: %i\n", (int32_t)exit_status); + } + asm volatile("wfi"); + while(1); +} + +int _faccessat(int dirfd, const char *file, int mode, int flags) +{ + errno = ENOSYS; + return -1; +} + +int _fork(void) +{ + errno = EAGAIN; + return -1; +} + +int _fstat(int file, struct stat *st) +{ + st->st_mode = S_IFCHR; + return 0; + // errno = -ENOSYS; + // return -1; +} + +int _fstatat(int dirfd, const char *file, struct stat *st, int flags) +{ + errno = ENOSYS; + return -1; +} + +int _ftime(struct timeb *tp) +{ + errno = ENOSYS; + return -1; +} + +char *_getcwd(char *buf, size_t size) +{ + errno = -ENOSYS; + return NULL; +} + +int _getpid() +{ + return 1; +} + +int _gettimeofday(struct timeval *tp, void *tzp) +{ + errno = -ENOSYS; + return -1; +} + +int _isatty(int file) +{ + return (file == STDOUT_FILENO); +} + +int _kill(int pid, int sig) +{ + errno = EINVAL; + return -1; +} + +int _link(const char *old_name, const char *new_name) +{ + errno = EMLINK; + return -1; +} + +off_t _lseek(int file, off_t ptr, int dir) +{ + return 0; +} + +int _lstat(const char *file, struct stat *st) +{ + errno = ENOSYS; + return -1; +} + +int _open(const char *name, int flags, int mode) +{ + return -1; +} + +int _openat(int dirfd, const char *name, int flags, int mode) +{ + errno = ENOSYS; + return -1; +} + +ssize_t _read(int file, void *ptr, size_t len) +{ + int read_cnt = 0; + + if (neorv32_uart0_available()) { + char *char_ptr; + char_ptr = (char *)ptr; + while (len > 0) { + *char_ptr++ = (char)neorv32_uart0_getc(); + read_cnt++; + len--; + } + } + + return read_cnt; +} + +int _stat(const char *file, struct stat *st) +{ + st->st_mode = S_IFCHR; + return 0; + // errno = ENOSYS; + // return -1; +} + +long _sysconf(int name) +{ + + return -1; +} + +clock_t _times(struct tms *buf) +{ + return -1; +} + +int _unlink(const char *name) +{ + errno = ENOENT; + return -1; +} + +int _utime(const char *path, const struct utimbuf *times) +{ + errno = ENOSYS; + return -1; +} + +int _wait(int *status) +{ + errno = ECHILD; + return -1; +} + +ssize_t _write(int file, const void *ptr, size_t len) +{ + if (file != STDOUT_FILENO) { + errno = ENOSYS; + return -1; + } + + const void *eptr = ptr + len; + //while (ptr != eptr) + // *(volatile int *)STDOUT_REG = *(char *)(ptr++); + if (neorv32_uart0_available()) { + while (ptr != eptr) { + neorv32_uart0_putc(*(char *)(ptr++)); + } + } + return len; +} + +extern char __heap_start[]; +extern char __heap_end[]; +static char *brk = __heap_start; + +int _brk(void *addr) +{ + brk = addr; + return 0; +} + +void *_sbrk(ptrdiff_t incr) +{ + char *old_brk = brk; + + if (__heap_start == __heap_end) { + return NULL; + } + + if ((brk += incr) < __heap_end) { + brk += incr; + } else { + brk = __heap_end; + } + return old_brk; +}