7. Example of Memory Programming
In general, the application image, besides the SysCPU code, may also include the CMAC and/or the SNC code. In case of CMAC, the CMAC code is included as a library and is loaded in RAM10.
If CMAC code and data are larger than RAM10’s size, RAM9 will also be used, otherwise, both RAM9 and the rest of RAM10 can be used by the SysCPU. In case of SNC, the code is included as a header file and the SysCPU code copies it in the beginning of RAM1. All projects use linker scripts to place the pieces of code and data in the RAM. In this section, DA1470x SNC template project is used as example project to describe and analyze the use of these scripts. The SNC template project refer to two separate applications and corresponding projects, os_app_retarget and os_snc_retarget. In this example, the os_snc_retarget application, the SNC code sends every 1 second some data (a timestamp and a counter value) to the SysCPU, which the SysCPU then prints to console through the UART, as implemented in the os_app_retarget application.
These two applications exchange data through RAM8, that is the shared memory RAM cell. The os_snc_retarget application defines a sector in this cell, for its own data, and the symbols that are to be shared are published to the os_app_retarget application at runtime. The os_app_retarget application will take into account the starting address and the size of SNC defined section in the shared memory in order to avoid using the same space in the shared RAM cell. When building the os_snc_retarget application, a header file is produced, namely snc_fw_embed.h, which contains a hex table with the SNC image, a void table for SNC shared space and all necessary definitions for os_app_retarget. Those definitions include the starting address of SNC shared space and the size of it, the size of SNC firmware and the address of a structure used by SNC and SysCPU to publish shared symbols to each other. This file must be copied to the os_app_retarget application. On the other hand, the os_app_retarget application will use this header file in order to load the SNC firmware to the correct place in RAM and get access to SNC shared space.
All projects use two different kind of linker scripts, the memory (mem.ld) and the sections (sections.ld), which are produced in the output folder after compilation. These scripts are produced from the memory (mem_*.ld.h) and sections (sections_*.ld.h) header files, respectively, located in the ldscripts/ble_projects folder of the SDK.
7.1. Programming with Linker Scripts
7.1.1. os_snc_retarget Application Linker Scripts
The mem.ld linker script of the os_snc_retarget application is presented below:
__SNC_BASE = 0x00000000;
__SNC_SIZE = 64K;
__SHARED_BASE = 0x00030000;
__SHARED_SIZE = 128K;
MEMORY
{
SNC (rwx) : ORIGIN = __SNC_BASE, LENGTH = __SNC_SIZE
SHARED (rw) : ORIGIN = __SHARED_BASE, LENGTH = __SHARED_SIZE
}
The whole memory is divided into areas. Each area has a name (i.e. SNC and SHARED), base address, size and attributes (rwx). These areas are used by the sections.ld linker script.
In the sections.ld
linker script, the user declares the memory sections and defines the name, the contents and the memory area to be placed in for each section, as the example presented below:
.retention_ram_zi (NOLOAD) :
{
__retention_ram_zi_start__ = .;
*(privileged_data_zi)
*(retention_mem_zi)
__retention_ram_zi_end__ = .;
} > SNC
More specifically, section retention_ram_zi
is defined, which includes all the variables of privileged_data_zi
and retention_mem_zi
type. This section will be loaded in the SNC memory area, which is defined in the mem.ld linker script. The output of this in the map file is presented below:
.retention_ram_zi
0x000047f8 0x293c
0x000047f8 __retention_ram_zi_start__ = .
*(privileged_data_zi)
privileged_data_zi
0x000047f8 0x2728 ./sdk/dgCoRoutines/portable/MemMang/heap_4.o
privileged_data_zi
0x00006f20 0x110 ./sdk/dgCoRoutines/dialog_croutine.o
0x00006f70 pxCurrentTCB
privileged_data_zi
0x00007030 0x20 ./sdk/dgCoRoutines/dialog_queue.o
0x00007030 xQueueRegistry
privileged_data_zi
0x00007050 0x3c ./sdk/dgCoRoutines/dialog_timers.o
*(retention_mem_zi)
retention_mem_zi
0x0000708c 0x2 ./startup/config.o
*fill* 0x0000708e 0x2
retention_mem_zi
0x00007090 0x4e ./sdk/sys_man/sys_power_mgr_da1470x.o
*fill* 0x000070de 0x2
retention_mem_zi
0x000070e0 0xc ./sdk/sys_man/sys_tcs_da1470x.o
*fill* 0x000070ec 0x4
retention_mem_zi
0x000070f0 0x14 ./sdk/sys_man/sys_timer.o
0x00007100 lp_last_trigger
retention_mem_zi
0x00007104 0x4 ./sdk/snc/api/src/snc.o
retention_mem_zi
0x00007108 0x18 ./sdk/peripherals/src/hw_timer.o
retention_mem_zi
0x00007120 0x4 ./sdk/peripherals/src/hw_watchdog.o
retention_mem_zi
0x00007124 0x10 ./sdk/peripherals/src/hw_wkup_v2.o
0x00007134 __retention_ram_zi_end__ = .
There are macros that can be used in the SNC code to declare a variable as privileged_data_zi
or retention_mem_zi
type and, therefore, determine which specific memory area will be placed to. The macros for the cases of this example are the following:
#define PRIVILEGED_DATA __attribute__((section("privileged_data_zi")))
and
#define __RETAINED __attribute__((section("retention_mem_zi")))
This way, all the variables of the project are placed in a memory area.
In case no macros are used, all global zero initialized or uninitialized variables and all static zero initialized or uninitialized variables are placed in the bss section. The global and static initialized variables are placed in the data section which is included in the retention_ram_init
section.
In the os_snc_retarget application, the linker scripts are quite simple, since all code and data are linked to the SNC memory area
The last 0x300 bytes of RAM2, as defined by __STACK_SIZE
macro, are used for stack, as presented below:
.stack (ORIGIN(SNC) + LENGTH(SNC) - 0x300) (COPY) :
{
. = ALIGN(8);
__StackLimit = .;
KEEP(*(.stack*))
. = ALIGN(8);
__StackTop = .;
} > SNC
The size of stack is configurable by the __STACK_SIZE
definition in the custom_config_snc.h
header file of os_snc_retarget project.
7.1.2. os_app_retarget Application Linker Scripts
Build configurations in os_app_retarget application provide the option to execute SysCPU code either from OQSPI XIP Flash or RAM.
7.1.2.1. Flash Build Configuration
The mem.ld
linker script of the os_app_retarget
application for Flash build configuration is presented below:
MEMORY
{
IVT : ORIGIN = 0x0F000000 + 0x200, LENGTH = 8K - 0x200
SNC : ORIGIN = 0x20000000, LENGTH = 64K
SHARED : ORIGIN = 0x20110000, LENGTH = 128K
}
MEMORY
{
CMAC : ORIGIN = (0x20150000 + ((0x30000) - ((0x30000) - (0)))), LENGTH = (((0x30000) - (0)))
}
MEMORY
{
RAMC (x) : ORIGIN = 0x00000000, LENGTH = 256K
RAMS (rwx) : ORIGIN = 0x20050000, LENGTH = 768K
}
There are totally seven different memory areas defined. Two of them have the same name as in os_snc_retarget
application, namely SNC and SHARED. They also have the same size but different base addresses, adjusted to each CPU’s address range, so that both can refer to the same place in RAM (i.e. RAM1, RAM2 and RAM8). There are also areas defined for the Interrupt Vector Table (IVT) in RAM0, an area for the extra CMAC code (CMAC) in RAM10, the retention code (RAMC) and data (RAMS) in RAM3, and code and data which reside in Flash memory (FLASH). Since we are using the Instruction Cache controller to run the retention code, the base address for RAMC is 0x10010000 which physically is the same as 0x20010000, used as base address in RAMS memory. There is a mechanism used to avoid overlapping code and data in this memory area, presented later in this section. If the project does not use SNC, then RAMC can also include RAM1 and RAM2, thus starting from address 0x10000000. Furthermore, the OQSPI XIP Flash is remapped to 0, therefore the base address of FLASH memory is 0x00000000.
In the flash build configuration, the flash is powered down when SysCPU goes to sleep. Therefore, the code executed after the flash is powered down until SysCPU finally goes to sleep and after SysCPU wakes up until the flash is finally powered up again, is retained in RAM, and is called retained code. Retained data are the data that need to be maintained while SysCPU is in sleep mode.
In the sections.ld
linker script, all non- retained code and constant data sections are placed to the FLASH memory area, retained code is placed in RAMC, retained data, bss and stack in RAMS, CMAC code and data in CMAC and the shared retained data in SHARED memory area.
In order to force pieces of code and variables to be placed to retained sections there are appropriate macros, such as __RETAINED_CODE
for code and __RETAINED
, __RETAINED_RW
, __RETAINED_UNINIT
etc, to be used in the declaration of variables and functions.
The RAMS memory area, in the os_app_retarget application, has a size of 1152K, which means that it includes all RAM cells from RAM3 till RAM8, as presented below:
# ifdef CONFIG USE_SNC
/* reserve first 64K for SNC */
# define RAMC_ORG 6x1e016800
# define RAMS ORG 6x2ee18e880
# define SNC_MEM_LEN 6
# else
# define RAMC_ORG 6x1e900000
# define RAMS ORG 6x28000806
# define SNC_MEM_LEN 64K
# endif
# if (dg_configUSE_CMAC_MEMORIES FOR _DATA == @)
# define CMAC_MEM_LEN 6
# else
# define CMAC_MEM_LEN 320K
# endif
# define RAMS LEN (896K + SNC_MEM_LEN + CMAC_MEM_LEN)
Since SNC is used in os_app_retarget application, RAM1 and RAM2 are not available for SysCPU execution context. Therefore, SNC_MEM_LEN is 0. CMAC_MEM_LEN is also 0, since CMAC memory cells are not available for SysCPU data as part of RAMS region. If all of them are available, as implied by an application project which uses neither CMAC nor SNC, RAMS_LEN will increase to 1152 + 320 + 64 = 1536K, including RAM1, RAM2, RAM9 and RAM10, which is the maximum RAM space of the system.
All retained data are placed in the RAMS memory area which occupies a memory area from RAM3 cell up to RAM8 cell, RAM8 included. The retained code is stored in the RAMC memory area, which resides in RAM3 cell. The linker script uses a symbol to mark the end of code and data sections that are stored in the FLASH memory area.
This symbol will be used during creation of the loading image, as the load address of the RAMC memory area code.
The data of the RAMS memory area, will use the same symbol (__etext)
and the START/END linker script symbols of the RAMC memory area (i.e. __retention_text_start__
/ __retention_text_end__
) to be loaded in the image right after the RAMC code and data.
Since both RAMC and RAMS, though they have different base addresses, are placed in the same physical RAM cell (RAM3), it is necessary not to link different pieces of code and data to the same place. This is achieved by using RAM_UNUSED_THROUGH_CPU_S
section, as presented below:
.retention_text : AT (0x0 + __etext)
{
. = ALIGN(4);
__retention_text_start__ = .;
*(text_retained)
*libnosys.a:sbrk.o (.text*)
*libgcc.a:_aeabi_uldivmod.o (.text*)
*libgcc.a:_muldi3.o (.text*)
*libgcc.a:_dvmd_tls.o (.text*)
*libgcc.a:bpabi.o (.text*)
*libgcc.a:_udivdi3.o (.text*)
*libgcc.a:_clzdi2.o (.text*)
*libgcc.a:_clzsi2.o (.text*)
. = ALIGN(4);
__retention_text_end__ = .;
} > RAMC
.RAM_UNUSED_THROUGH_CPU_S (NOLOAD) :
{
. += (__retention_text_end__ - __retention_text_start__);
} > RAMS
.retention_ram_init : AT (0x0 + __etext + (__retention_text_end__ - __retention_text_start__))
{
. = ALIGN(4);
__retention_ram_init_start__ = .;
*(privileged_data_init)
*(.retention)
*(vtable)
*(retention_mem_init)
*(retention_mem_const)
*libg_nano.a:* (.data*)
*libnosys.a:* (.data*)
*libgcc.a:* (.data*)
*libble_stack_da1470x.a:* (.data*)
*crtbegin.o (.data*)
KEEP(*(.jcr*))
. = ALIGN(4);
__retention_ram_init_end__ = .;
} > RAMS
The RAM10 cell is used by SysCPU for storing data of Deterministic Random Bit Generator (DRBG), True Random Number Generation (TRNG) and CHACHA20 Random Generator. If the project doesn’t use CMAC, just like the current example, then these data are placed in the beginning of the RAM10 cell. Otherwise, it is placed just after the reserved space for CMAC code, which is controlled by the following definitions:
#define CMAC_AREA_SIZE (139)
and
#define CMAC_AREA_BYTES \
(CMAC_AREA_SIZE > 0 ? (CMAC_AREA_SIZE * 1024) : 0)
Reserving memory space for SysCPU in order to store DRBG and application data in RAM10 cell is enabled with the use of the following definitions. Similar definitions need to be defined for reserving space in RAM9 cell (i.e. size needs to be defined as a >0 value), provided that CMAC code and data size does not exceed the size of RAM10 cell.
#define dg_configUSE_CMAC_RAM9_BASE (0)
#define dg_configUSE_CMAC_RAM9_SIZE (0)
and
#define dg_configUSE_CMAC_RAM10_BASE (0)
#define dg_configUSE_CMAC_RAM10_SIZE (0)
In case specific application symbols, i.e. variables or functions, need to be placed in RAM9 or RAM10 cells, the following definitions need to be used for each RAM cell, respectively.
#if (__RAM9_SIZE_FOR_MAIN_PROC > 0)
/**
* \brief Attribute for data to be placed in RAM9
* (which has slower access times for the MAIN PROCESSOR)
*/
# define __IN_CMAC_MEM2 __attribute__((section("m33_data_in_ram9"))
/**
* \brief Attribute for uninitialized data to be placed in RAM9
* (which has slower access times for the MAIN PROCESSOR)
*/
# define __IN_CMAC_MEM2_UNINIT __attribute__((section("m33_uninit_data_in_ram9")))
#endif
#if (__RAM10_SIZE_FOR_MAIN_PROC > 0)
/**
* \brief Attribute for data to be placed in RAM10
* (which has slower access times for the MAIN PROCESSOR)
*/
# define __IN_CMAC_MEM1 __attribute__((section("m33_data_in_ram10")))
/**
* \brief Attribute for uninitialized data to be placed in RAM10
* (which has slower access times for the MAIN PROCESSOR)
*/
# define __IN_CMAC_MEM1_UNINIT __attribute__((section("m33_uninit_data_in_ram10")))
#endif
Finally, the last sections linked in the RAMS memory area are the stack section for application stack, and the .bss
section, which contains the static variables of the application. The default size of stack is 0x200 bytes, as defined by the macro __STACK_SIZE
in the bsp_memory_defaults.h
header file.
.stack_section (NOLOAD) :
{
. = . + 1;
. = ALIGN(8);
__StackLimit = .;
KEEP(*(.stack*))
. = ALIGN(8);
__StackTop = .;
PROVIDE(__stack = __StackTop);
} > RAMS
.bss :
{
. = ALIGN(4);
__bss_start__ = .;
*(EXCLUDE_FILE(*libg_nano.a:* *libnosys.a:* *libgcc.a:* *libble_stack_da1470x.a:* *crtbegin.o) .bss*)
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > RAMS
7.1.2.2. RAM Build Configuration
The mem.ld
linker script of the os_app_retarget application for RAM build configuration is presented below:
MEMORY
{
IVT : ORIGIN = 0x0F000000 + 0x200, LENGTH = 8K - 0x200
SNC : ORIGIN = 0x20000000, LENGTH = 64K
SHARED : ORIGIN = 0x20110000, LENGTH = 128K
}
MEMORY
{
CMAC : ORIGIN = (0x20150000 + ((0x30000) - ((0x30000) - (0)))), LENGTH = (((0x30000) - (0)))
}
MEMORY
{
RAMC (x) : ORIGIN = 0x00000000, LENGTH = 256K
RAMS (rwx) : ORIGIN = 0x20050000, LENGTH = 768K
}
As expected, IVT, SNC, SHARED and CMAC memory areas are the same as in the case of the Flash build, but in this case, there is no FLASH memory area. Moreover, since OQSPI XIP Flash is not used and RAM1 and RAM2 cells host the SNC code, the RAM3 cell must be remapped to 0. Therefore, the base address of RAMC now becomes 0x00000000, while RAMS area now starts from the RAM4 cell (i.e. 0x20050000) and also includes RAM5, RAM6 and RAM7 cells. Compared to the section.ld linker script created for the FLASH build, the RAM build defines a new memory area, called LMA_RAM (Load Memory Address RAM) which replaces the FLASH memory area maintaining the same pieces of code and data. This is necessary since the code is linked as if running from 0x0, but will be loaded at physical address of RAM3 start address (i.e. 0x20010000), until remapping of RAM3 to 0x0 is executed. The rest of the sections are similar to the FLASH build output, which has been discussed in the previous section.
7.2. Changing Linker Scripts
The user can modify the memory map of the os_app_retarget and os_snc_retarget applications, by applying corresponding changes to the linker scripts. In the following sections some basic changes that can be made are presented.
7.2.1. Add New Memory Area and Section in SysCPU Code
In this section, using the previous example, it is presented how to add some code and data in the os_app_retarget application, in a specific RAM cell, e.g. RAM7. This means that, first, the user must create a new memory area located in the RAM7 cell and then some of the existing areas must be re-defined too. The build configuration used for this example is the OQSPI XIP Flash build, and the header file mem_da1470x.ld.h needs to be changed as follows. Note that all new lines that are added have the comment “NEW” at the end. The lines that are removed or altered have the comment “OLD”.
/*
* Region for data (connected to AHB CPUS)
*/
RAMS (w) : ORIGIN = RAMS_ORG, LENGTH = RAMS_LEN
RAM7 (rwx) : ORIGIN = 0x200F0000, LENGTH = 128K //NEW
A new memory area has been added, named “RAM7”, which includes the whole RAM7 cell. Since the RAM7 cell is included in the RAMS memory area, the user also needs to limit RAMS up to cell RAM6.
//# define RAMS_LEN (1152K + SNC_MEM_LEN + CMAC_MEM_LEN) //OLD
# define RAMS_LEN (896K + SNC_MEM_LEN + CMAC_MEM_LEN) //NEW
Then, the user has to add a new section in the sections_da1470x.ld.h header file in order to steer the new code and data to the RAM7 memory area. For minimum changes, the new section is added at the end of the image, as shown below:
.bss :
{
. = ALGIN(4);
__bss_start__ = .;
#if DEVICE_FPGA
*(EXCLUDE_FILE(*libg_nano.a:* *libnosys.a:* *libble_stack_d2798_00.a:*\
*libad9361_radio.a:* *crtbegin.o) .bss*)
#else
*(EXCLUDE_FILE(*libg_nana.a:* *libnosys.a:* *libble_stack_da1470x.a:*\
*crtbegin.o) .bss*)
#endif
*(COMMON)
. = ALIGN(4);
__bss_end__ = .;
} > DATA
.ram7_init : __AT__ (__etext + RETENTION_TEXT_SIZE + RETENTION_RAM_INIT_SIZE \
+ NON_RETENTION_RAM_INIT_SIZE + RAM9_DATA_FOR_M33_SIZE + RAM10_DATA_FOR_M33_SIZE) //NEW
{ //NEW
. = ALIGN(4);
__ram7_init_start__ = .;
*(text_ram7)
. = ALIGN(4);
*(data_ram7)
. = ALIGN(4);
__ram7_init_end__ = .;
} > RAM7
The new section, called .ram7_init
, includes all code and data of type text_ram7
and data_ram7
, respectively. All contents are linked in the address of RAM7 cell and are loaded at the end of the image, right after the RAM10 data for SysCPU. To do that, some more changes in the file are required:
○ Define the new section’s size.
# define RETENTION TEXT_SIZE (__retention_text_end_ - __retention_text_start_)
# define RETENTION_RAM INIT_SIZE (_retention_ram_init_end__ - __retention_ram_init_start__)
# define NON_RETENTION RAM_INIT_SIZE (_non_retention_ram_init_end_ - __non_retention_ram_init_start__)
# define RAM7_INIT_SIZE (__ram7_init_end__ - _ram7_init_start_) //NEW
○ Update the copy.table, which contains the code and initialized data of RAM:
.copy.table :
{
. = ALIGN(4);
__copy_table_start__ = .;
#if CODE_IS_IN FLASH
LONG (__etext)
LONG (__retention_text_start__)
LONG (RETENTION _TEXT_STZE)
LONG (__etext + RETENTION _TEXT_SIZE)
LONG (__retention_ram_init_start__)
LONG (RETENTION_RAM_INIT_SIZE)
LONG (__etext + RETENTION TEXT SIZE + RETENTION_RAM_INIT_SIZE)
LONG (__non_retention_ram_init_start__)
LONG (NON_RETENTION RAM_INIT_SIZE)
#endif /* CODE_IS_IN FLASH */
#if (__RAM9_SIZE_FOR MAIN PROC > 0)
LONG (__etext + RETENTION _TEXT_SIZE + RETENTION RAM INIT_SIZE + NON_RETENTION RAM_INIT_SIZE)
LONG (__ram9_area_for_m33_start__)
LONG (RAM9_DATA_FOR_M33_SIZE)
#endif
#if (__RAM10_SIZE_FOR_MAIN PROC > 0)
LONG (__etext + RETENTION TEXT SIZE + RETENTION _RAM_INIT_SIZE + NON_RETENTION RAM INIT_SIZE \
+ RAM9_DATA_FOR_M33_SIZE)
LONG (__ram10_area_for_m33_start__)
LONG (RAM10_DATA_FOR_M33_SIZE)
#endif
LONG (__etext + RETENTION _TEXT_SIZE + RETENTION_RAM_INIT_SIZE + NON_RETENTION RAM_INIT_SIZE \
+ RAM9_DATA FOR_M33_SIZE + RAM1@_DATA_FOR_M83_SIZE) //NEW
LONG (__ram7_init_start__) //NEW
LONG (RAM7_INIT_SIZE) //NEW
__copy_table_end__ = .;
} > TEXT __TEXT_LMA_
○ Update the Image size.
//#define IMAGE_SIZE \
// (__etext + RETENTION_TEXT_SIZE + RETENTION_RAM_INIT_SIZE + NON_RETENTION RAM_INIT_SIZE \
// + RAM9_DATA_FOR_M33_SIZE + RAMI@_DATA_FOR_M33_SIZE) //0LD
#tdefine IMAGE_SIZE \
(__etext + RETENTION_TEXT_SIZE + RETENTION_RAM_INIT_SIZE + NON_RETENTION_RAM_INIT_SIZE \
+ RAM9_DATA_FOR_M33_SIZE + RAM10_DATA_FOR_M33_SIZE + RAM7_INIT_SIZE) //NEW
Now, the user must add the data and code to the new memory area, using the corresponding macros.
First, for the needs of the example, in the main.c file a new table is added (i.e. extra_data_table[]
) of 4000 bytes and a new function (i.e. calc_extra_data())
, which calculates and fills the content of the new table:
#define __RAM7_CODE __attribute__((section("text_ram7")))
#define __RAM7_DATA __attribute__((section("data_ram7")))
__RAM7_DATA uint32_t extra_data_table[1000];
__RAM7_CODE uint32_t calc_extra_data(app_shared_data_t *p)
{
uint32_t index = p->buffer[1] % 1000;
extra_data_table[index] = p->buffer[0] * 1000;
return extra_data_table[index] / p->buffer[1];
}
After building the project, both the data and the code are placed to the RAM7 cell, as shown in the map file below:
.ram7_init 0x200f0000 0xfc8 load address 0x@001371c
0x200f0000 . = ALIGN(4)
0x200f0000 __ram7_init_start__ = .
*(text_ram7)
text_ram7 0x200f0000 0x28 ./main.o
0x200f0000 calc_extra_data
0x200f0028 . ALIGN (0x4)
*(data_ram7)
data_ram7 0x200f0028 0xfa0 ./main.o
0x200f0028 extra_data_table
0x200f0fc8 . = ALIGN (0x4)
0x200f0fc8 __ram7_init_end__ = .
0x200f0fc8 __FLASH_BUILD__ = .
0x200f0fc9 __unused_ram_start__ = (. + 0x1)