Memory Hierarchy for RTL87x3EP

This document introduces the memory system of RTL87x3EP generally. RTL87x3EP's memory system consists of ROM, RAM, SPIC Flash, and eFuse, all of which has a flexible configuration mechanism. The flexible configurability and extensibility make RTL87x3EP support a wide range of applications with different memory usage.

Memory Configuration

Memory Type

Start Addr

End Addr

Size (KB)

Block Size and Count

ROM

0x00000000

0x0009FFFF

640

ITCM1 RAM

0x00200000

0x0021FFFF

128

4 x 32KB

DTCM0 RAM

0x00280000

0x0028FFFF

64

2 x 32KB

DTCM1 RAM

0x002C0000

0x002F7FFF

224

7 x 32KB

Buffer RAM

0x00400000

0x0040FFFF

64

4 x 16KB

DSP RAM (Shared as General RAM)

DSP Share Start Address

DSP Share End Address

80 or 320

(10 x 8KB) or (40 x 8KB)

SPIC0 QPI Flash

0x02000000

0x03FFFFFF

SPIC1 OPI/QPI PSRAM; QPI Flash

0x04000000

0x05FFFFFF

SPIC2 QPI PSRAM; QPI Flash

0x10000000

0x1FFFFFFF

eFuse

PSRAM LPC CTRL OPI Psram

0x0A000000

0xA1FFFFFF

Note

  1. The address and size of shared DSP RAM depend on the configuration, which should match with DSP images.

  2. The SPIC0 flash size of RTL87x3EP supports up to 32M external Flash, which defaults to 4M or 2M.

  3. DSP RAM is not shared as general RAM by default, shared memory can only be 80KB or 320KB.

  4. eFuse doesn't support access directly by address.

  5. The size and block size of SPIC0/SPCI1/SPIC2/SPIC3/PSRAM depend on the type of flash/PSRAM connected.

ROM

Many fundamental functions are built in ROM, such as Bootloader, RTOS, Bluetooth Stack, and IO Drivers. The ROM code is located at 0x00000000 - 0x000A0000. RTK offers abundant API for users to access most ROM functions, which can improve users' development efficiency and reduce code size.

RAM

RTL87x3EP has five types of RAM: ITCM1, DTCM0, DTCM1, Buffer, and DSP RAM. And ITCM1/DTCM0/DTCM1 SRAM are internal SRAM of the ARM core, which are close to the CPU to get good performance. On the contrary, Buffer SRAM and DSP share RAM are relatively slow for CPU access. All types of memory could be used to store data and execute code. ITCM1 is recommended for RAM code and DTCM0/1 is recommended for storage data.

The following picture shows the default RAM configuration. Some parts are already used by RTK internal functions marked as RTK Used. The remaining parts should be allocated for global variables, RAM code, and heap area. Users can configure these parts with the header file mem_config.h:

  1. The remaining 89KB RAM of ITCM1 is recommended to be configured as APP RAM TEXT, and if any SRAM remains, could be configured as APP HEAP.

  2. DTCM0 is recommended to be configured as APP GLOBAL and APP HEAP.

../../../../_images/mem_ram_total_ep.png

RAM Configuration

There are several physical heap areas, but all of them are connected logically. Users can use malloc() to request RAM from the heap area dynamically. Total heap RAM size is 323KB by default, and some of it has already been used by RTK, which will be slightly different with different configurations generated by the MCUConfig Tool. As for the heap for the application, the RTK framework will consume about 32KB, though this size may vary depending on the features supported.

../../../../_images/mem_heap_cfg_ep.png

Heap Size

The size of APP RAM TEXT is 8KB (depending on the application) by default, which is used to run RAM code. The size of the APP GLOBAL area is 10KB (depending on the application) by default, which is used to store global variables. The RTK framework will cost about 1.9KB (depending on the application) global size. Users can configure them by modifying two macros in mem_config.h, and other macros cannot be modified in mem_config.h.

#define APP_RAM_TEXT_SIZE           (8*1024)
#define APP_GLOBAL_SIZE             (10*1024)
../../../../_images/mem_app_global_ep.png

APP Global Size

Users can use API malloc() to allocate RAM from the heap. If the heap is insufficient, a hardfault is generated. In this case, you can see from the following log. It is necessary to confirm whether too many features are enabled, or there is a problem in the code writing, and there is a memory leak, resulting in the heap consumption.

0007138  03-02 09:29:53.651  154  0319630.477  [PATCH] !!!ALLOC FAIL!RAM type:5,wanted sz:2056,remain sz:4176

FAQs

  1. How to get the current remaining heap size?

    The mem_peek() API in os_mem.h can return the current remaining heap size.

  2. How to get heap water level and task stack water level?

    The monitor_memory_and_timer() API in os_ext.h will set up a timer to periodically print the heap water level, the current heap remaining size, the number of remaining timers, and the water levels of each task stack.

  3. How to get the heap size used by a piece of code?

    Refer to the following code:

    size_t free_heap_start =  mem_peek();
    test_api();
    size_t free_heap_end =  mem_peek();
    size_t used_heap = free_heap_start - free_heap_end;
    
  4. How to get the size currently used by APP RAM TEXT and APP GLOBAL?

    The app.map file shows the size currently used by APP_RAM_TEXT and APP_RAM_GLOBAL.

    Execution Region RAM_TEXT (Exec base: 0x00208800, Load base: 0x021453cc, Size: 0x000011fc, Max: 0x00001400, ABSOLUTE)
    Execution Region RAM_GLOBAL (Exec base: 0x002c0000, Load base: 0x02144d0c, Size: 0x00002918, Max: 0x00003800, ABSOLUTE)
    

Flash

The RTL87x3EP supports external SPI flash memory. Onboard SPI flash is controlled by an SPIC that maps memory accordingly. The flash size mapping varies with different SPICs: up to 32MB for SPIC0 and SPIC1, 8MB for SPIC2, and 256MB each for SPIC3 and SPIC4. The RTL87x3EP features a flash cache for SPIC0, which enhances data read and code execution efficiency from SPI flash. Additionally, it provides a mapping address range from 0x4000000 to 0x5FFFFFF, enabling the CPU to access the SPIC0 flash directly. For further details, please refer to Flash.

eFuse

eFuse is a block of one-time programming memory that is used to store important and fixed information, such as UUID, security key, and many other configurations. Only certain sections can be modified by the user. The single bit of eFuse cannot be changed from 0 to 1, and there is no erase operation for eFuse, so be careful when updating eFuse.

PSRAM

The RTL87x3EP chipset supports external memory via SPI PSRAM, which is managed by an SPIC. The onboard SPI PSRAM is controlled by the SPIC, which maps memory to it. The maximum mapping size varies based on the different SPIC mapping memory ranges: up to 32MB for SPIC0 and SPIC1, 8MB for SPIC2, and 256MB for both SPIC3 and SPIC4. OPI PSRAMs and QPI PSRAM are supported. Specifically, the RTL87x3EP supports the APS1604M PSRAM model through SPIC1, SPIC2, and SPIC3. RTL87x3EP supports two OPI PSRAMs, APS6408L, and W955D8MBYA, through SPIC1; one QPI PSRAM, APS6404L, through all SPICs except for SPIC0. Besides, with cache, both data reading and code execution efficiencies on PSRAM could be largely increased. Further, Realtek offers APIs to initialize PSRAM where PSRAM can be read and written like SRAM after being initialized.

PSRAM Types and Support

PSRAM Type

Chip Models

Support SPIC

Selection Basis

QPI PSRAM

APS6408L, W955D8MBYA

SPIC1

Low power consumption and simple design.

OPI PSRAM

APS6404L

SPIC2, SPIC3, SPIC4

High transmission rate requirements.

How to Init PSRAM?

Before using PSRAM, you must initialize it. For example, to initialize a Winbond OPI-interface PSRAM, use the following code:

if (fmc_psram_winbond_opi_init(FMC_SPIC_ID_1))
{
   DBG_DIRECT("WB OPI psram init success!");
}
else
{
   DBG_DIRECT("WB OPI psram init fail!");
}

Note

  1. Different hardware platforms may support different types or brands of PSRAM. Please refer to the relevant hardware documentation to ensure PSRAM compatibility and correct initialization procedures.

  2. fmc_psram_winbond_opi_init is used to initialize the Winbond OPI PSRAM on the SPIC1 controller.

How to Use PSRAM?

This guide provides examples of how to place code and global data in PSRAM using Keil and GCC toolchains.

Placing Code in PSRAM

  1. Add a PSRAM text section.

    • For Keil (app.sct):

      PSRAM_TEXT PSRAM_CODE_START_ADDR PSRAM_CODE_SIZE
      {
          * (.psram.text)
      }
      
    • For GCC (app.ld):

      MEMORY
      {
          // other sections
          PSRAM_TEXT(rw) : ORIGIN = 0x4000000, LENGTH = 0x400000 // user defined
          // other sections
      }
      
      PSRAM_TEXT_SECTION :
      {
          __psram_text_start = .;
          *(.psram.text)
          __psram_text_end = .;
      } > PSRAM_TEXT AT > FLASH
      
      __psram_text_length__    = __psram_text_end - __psram_text_start;
      __psram_text_load_addr__ = LOADADDR(PSRAM_TEXT_SECTION);
      __psram_text_dst_addr__  = ADDR(PSRAM_TEXT_SECTION);
      
  2. Define the section macro in mem_config.h:

    #define PSRAM_TEXT_SECTION __attribute__((section(".psram.text")))
    
  3. Copy code from flash to PSRAM:

    void prepare_psram_text_to_ram(void)
    {
    #ifdef __CC_ARM
        extern unsigned int Image$$PSRAM_TEXT$$Base;
        extern unsigned int Load$$PSRAM_TEXT$$Base;
        extern unsigned int Image$$PSRAM_TEXT$$Length;
    
        void *image_base  = (void *)&Image$$PSRAM_TEXT$$Base;
        void *load_base   = (void *)&Load$$PSRAM_TEXT$$Base;
        unsigned int size = (unsigned int)&Image$$PSRAM_TEXT$$Length;
    
        memcpy(image_base, load_base, size);
    #elif defined(__GNUC__)
        extern char __psram_text_start[];
        extern char __psram_text_end[];
        extern char __psram_text_load_addr__[];
    
        uint32_t len = __psram_text_end - __psram_text_start;
        memcpy(__psram_text_start, __psram_text_load_addr__, len);
    #endif
    }
    
  4. Mark functions for placement in PSRAM using the macro:

    PSRAM_TEXT_SECTION void psram_test_code(void)
    {
        // function implementation
    }
    

Placing Global Data in PSRAM

  1. Add a PSRAM data section.

    • For Keil (app.sct):

      PSRAM_DATA PSRAM_DATA_START_ADDR PSRAM_DATA_SIZE
      {
          * (.psram.data)
      }
      
    • For GCC (app.ld):

      MEMORY
      {
          // other sections
          PSRAM_DATA(rw) : ORIGIN = 0x4000000, LENGTH = 0x400000 // user defined
          // other sections
      }
      
      PSRAM_DATA_SECTION :
      {
          __psram_data_start = .;
          *(.psram.data)
          __psram_data_end = .;
      } > PSRAM_DATA AT > FLASH
      
      __psram_data_length__    = __psram_data_end - __psram_data_start;
      __psram_data_load_addr__ = LOADADDR(PSRAM_DATA_SECTION);
      __psram_data_dst_addr__  = ADDR(PSRAM_DATA_SECTION);
      
  2. Define the section macro in mem_config.h:

    #define PSRAM_DATA_SECTION __attribute__((section(".psram.data")))
    
  3. Copy data from flash to PSRAM before use:

    void prepare_psram_data_to_ram(void)
    {
    #ifdef __CC_ARM
        extern unsigned int Load$$PSRAM_DATA$$RW$$Base;
        extern unsigned int Image$$PSRAM_DATA$$RW$$Base;
        extern unsigned int Image$$PSRAM_DATA$$RW$$Length;
        extern unsigned int Load$$PSRAM_DATA$$ZI$$Base;
        extern unsigned int Image$$PSRAM_DATA$$ZI$$Base;
        extern unsigned int Image$$PSRAM_DATA$$ZI$$Length;
    
        uint32_t load_addr = (uint32_t)&Load$$PSRAM_DATA$$RW$$Base;
        uint32_t dest_addr = (uint32_t)&Image$$PSRAM_DATA$$RW$$Base;
        uint32_t len = (uint32_t)&Image$$PSRAM_DATA$$RW$$Length;
        memcpy((uint8_t *)dest_addr, (uint8_t *)load_addr, len);
    
        dest_addr = (uint32_t)&Image$$PSRAM_DATA$$ZI$$Base;
        len = (uint32_t)&Image$$PSRAM_DATA$$ZI$$Length;
        memset((uint8_t *)dest_addr, 0, len);
    #elif defined(__GNUC__)
        extern char __psram_data_start[];
        extern char __psram_data_end[];
        extern char __psram_data_load_addr__[];
    
        uint32_t len = __psram_data_end - __psram_data_start;
        memcpy(__psram_data_start, __psram_data_load_addr__, len);
    #endif
    }
    
  4. Mark global variables for placement in PSRAM using the macro:

    PSRAM_DATA_SECTION uint32_t psram_global = 0
    

See Also

Please refer to the relevant API Reference: