Skip to content

Mbed Memory Bank Information

Jamie Smith edited this page Jul 13, 2024 · 31 revisions

This page documents a proposal to add a "memory bank information" feature to the build system of Mbed OS Community Edition.

1. Goals

The Memory Bank Information feature will provide information about the available flash and RAM banks on an MCU to the application and build system. This will allow various pieces of the Mbed system to have a better picture of where & how much memory is available and do useful things with it.

1.1 Intended Users

  • C/C++ source code: The Mbed library and application code may be interested in knowing how much memory is available and what addresses it is located at. Current examples include:
    • mbed_stats.c, which collects stats about the total memory available on the device
    • FlashIAPBlockDevice, which would highly benefit from a way to know where the flash starts and ends
    • HAL layer code using DMA, such as the Ethernet MAC drivers, which needs to detect if buffers are inside a given RAM bank or not and use that to control cache handling or decide whether to copy them somewhere else
  • memap: This script currently just prints the total RAM and flash usage, but this does not include a percentage of how much of the RAM is actually used. Additionally, it sums all the memory banks together, ignoring the fact that Mbed can only use one contiguous memory bank for static data (dang GNU LD limitations). In both cases, this requires the user to remember annoying details about their specific MCU, like which memory bank Mbed primarily targets and how much space it has.
  • Linker scripts: Mbed OS is chock full of copy-pasted linker scripts. In many cases, the only real differences between them are the sizes of memory banks (if even that -- some are identical!). If the linker scripts had well-defined access to information about a target's memory banks, they could use this info to adjust themselves, to some extent, to the current target being compiled for. This, in turn, would let us significantly cut down on the number of duplicate/near duplicate linker scripts in Mbed.
  • Custom target users: In many cases, Mbed officially supports one specific variant of an MCU with X amount of memory, but someone wants to develop for another variant with Y amount of memory. Currently, this requires editing the linker script, which is a fairly high barrier to entry (I didn't really learn linker scripts until I'd been employed full time as an embedded SW engineer for over a year). Or, you could not do that, and just hope that the memory layout of your new chip is similar enough to work. With a smarter system to declare memory bank information, and a linker script that is set up to take in that information, making these kind of custom targets could be done without modifying any linker scripts!

1.2 Goals of the proposal

  1. Provide memory bank information to Mbed and its tools that conveys:
    1. Where each bank starts and ends
    2. The type of each mem bank (flash vs RAM)
    3. The user identifiable name of each bank (e.g. builtin flash, DTCM, ITCM, etc).
  2. Be compatible, within reason, with C/C++ code and JSON files designed for the Mbed CLI 1 memory bank system.
    1. Naming of #defines should not change, though new ones can be added
    2. Where it makes sense, mbed_app.json properties should work in the same way as Mbed CLI 1.

2. Prior Work

As people with experience with Mbed CLI 1 may remember, Mbed OS actually used to have a feature somewhat similar to this. Mbed CLI 1 had behavior where it would read the CMSIS MCU description file, list out the memory banks as #defines, and then add those to the build. However, this feature was... flawed, for several reasons:

  • It was not very well documented, making it a bit of a mystery where the memory bank defines came from. Like, by default, they came out of the CMSIS MCU descriptions file, but they could also be overridden via undocumented mbed_app.json/targets.json options.
  • It didn't preserve the names of memory banks, losing out on potentially useful information (e.g. which of the memory banks is DTCM and which is general RAM)
  • It was unclear to users and developers if it was supposed to declare the memory that exists on a target, or the memory that Mbed should be using. (as in, for splitting up RAM and flash to share with a bootloader or another core). In practice, this led to different people using it in different ways.

2.1 Prior Behavior

As far as I can tell, the only real place that the Mbed CLI 1 behavior was documented was in the pull request [4] and the issue [3], and by the code itself (here and here).

As best I understand it, this code would:

  • look for the following properties in mbed_app.json: target.mbed_app_start, target.mbed_app_size, target.mbed_rom_start, target.mbed_rom_size, target.mbed_ram_start, target.mbed_ram_size.
  • Also read the memory banks info from the CMSIS MCU descriptions JSON
  • Given that the target can have multiple ROM and RAM banks, use some rather shaky name-based logic to determine the "primary" bank for each
  • Override the primary banks' address and size based on the target.[rom/ram]_[start/size] properties
  • Add definitions in the from of MBED_[RAM/ROM][START/SIZE], MBED[RAM/ROM]1_[START/SIZE], MBED_[RAM/ROM]2_[START/SIZE]... for each memory bank, indicating the bank's start address and size (and throwing away the actual name of the memory bank as declared in CMSIS JSON)
  • Also add MBED_APP_[START/SIZE] if target.mbed_app_start and target.mbed_app_size were declared in JSON.

2.2 Mbed CLI 2 Behavior

Mbed CLI 2, which is the basis for Mbed CE's build system, unfortunately didn't seem to understand the nuances of this behavior. They only set it up to handle the case where the attributes are defined directly in the target JSON, not the case where the memory bank info is loaded from the CMSIS MCU description file [1] [5]. They also didn't attempt to handle mbed_app_start and mbed_app_size, which causes issues with certain target definitions that use those attributes (e.g. Arduino boards with a bootloader).

We've only gotten away with this for so long because few places in the code actually make use of the memory defines, and nothing immediately explodes if they aren't defined (e.g. linker scripts will fall back to hardcoded values). However, these defines should be restored in order to bring back functionality that's been lost with Mbed CLI 2!

3. Proposed Mbed CE Behavior.

3.1 Rationale

The Mbed CLI 1 behavior mostly made sense. However, I'd like to make three changes to it.

First of all, Mbed CLI 1 threw away the names of the memory banks, instead putting them in a relatively arbitrary order. These bank names are valuable information, as, within a target family, it's very useful to have a standardized way to know which bank is DTCM, which is ITCM, which is backup SRAM, etc. In the new system, I would like to still provide the old-style numbered definitions, but also provide named definitions, based on the name of each bank in the CMSIS MCU descriptions. This will be much more useful for target code and linker scripts that wants to identify specific banks of memory.

The second change is, the old system made it rather unclear whether variables like mbed_rom_start/size and mbed_ram_start/size represented the entire physical memory on the device, or a specific area of RAM that the application was supposed to use. I'd like to resolve that ambiguity. In Mbed CE, we will define that mbed_rom_start/size and mbed_ram_start/size always represent the memory banks available on the device, regardless of what Mbed is configured to use. These attributes can only be populated through cmsis_mcu_descriptions.json; they cannot be overridden. In exchange, we will add new targets.json/mbed_app.json attributes like mbed_override_rom_<name>_start/size and mbed_override_ram_<name>_start/size which can be used to override the start address and/or size of a named memory bank.

The configured bank addresses and sizes shall be exposed to the application through a new set of defines, similar to the original names but with CONFIGURED in their names. This allows application code to decide for itself whether it wants the actual physical sizes of the memories (e.g. FlashIAPBlockDevice) or the configured sizes (e.g. Mbed Stats).

Lastly, I'd like to have the configuration script dump this information to a JSON file in the build dir. This adds an easy place for other tooling, such as memap, to get information about memory banks.

3.2 Specific Behavior

  1. The configuration script will read the memory bank info from the CMSIS MCU description for the target based on the device_name property in JSON. A warning will be issued if the JSON is missing the device_name property.
  2. For each bank, compile definitions will be added, in the form of MBED_[ROM/RAM][[number]/_BANK_name]_[START/SIZE].
    1. For legacy compatibility, "number" in the above definition starts as empty string for the zeroth bank, then increments to 1, 2, etc.
    2. Per the CMSIS spec, if there's a bank with default = true, it will become the "bank 0" of that type, e.g. MBED_RAM_START/MBED_ROM_START. Otherwise, the first listed bank in the MCU description will become bank 0.
  3. Overrides, given through a memory_override section (see below for format), will be processed.
    1. Note that banks not defined in JSON are also allowed to be overridden. This is a good way to implement configuration of external memories that are addressable, but are not built into the MCU (and therefore aren't described by the CMSIS mcu descriptions file)
  4. For each bank, a second set of compile definitions will be added including the override information, in the form of MBED_CONFIGURED_[ROM/RAM][number/name]_[START/SIZE].
    1. Note that numbering will be consistent for overridden banks, e.g. MBED_CONFIGURED_RAM1_START is guaranteed to refer to the same memory bank as MBED_RAM1_START.
  5. A target_memory_banks.json file will be written out in the build directory, containing information about each memory bank any overrides (see below for format).

4. Worked Example

Let's demonstrate how this system could be used to implement multi-core support on the RP2040 (this doesn't currently exist in Mbed, but having a working memory bank info system would make it a lot easier)!

cmsis_mcu_descriptions.json5

The RP2040 is currrently described as:

"RP2040": {
        "memories": {
            "IRAM1": {
                "access": {
                    "execute": false,
                    "peripheral": false,
                    "read": true,
                    "secure": false,
                    "write": true
                },
                "default": true,
                "size": 0x40000, // 256kiB
                "start": 0x20000000,
                "startup": false
            },


            // Scratch banks are commonly used for critical data and functions accessed only by
            // one core (when only one core is accessing the RAM bank, there is no opportunity for stalls).
            "SCRATCH_X": {
                "access": {
                    "execute": false,
                    "peripheral": false,
                    "read": true,
                    "secure": false,
                    "write": true
                },
                "default": false,
                "size": 0x1000, // 4kiB
                "start": 0x20040000,
                "startup": false
            },
            "SCRATCH_Y": {
                "access": {
                    "execute": false,
                    "peripheral": false,
                    "read": true,
                    "secure": false,
                    "write": true
                },
                "default": false,
                "size": 0x1000, // 4kiB
                "start": 0x20041000,
                "startup": false
            }
        },
        "name": "RP2040",
        "processor": {
            "Symmetric": {
                "core": "CortexM0Plus",
                "fpu": "None",
                "mpu": "Present",
                "units": 2
            }
        },
        "vendor": "Raspberry Pi:x"
    }

This doesn't need any changes and can stay as is. Note that in older versions of CMSIS pack descriptions, the memory bank names apparently had to be IRAMx/IROMx, but now they can be anything -- the access field is used to determine if it is ROM or RAM.

Adding Flash Information

We would first have to add information about the board flash to the RP2040 target in targets.json:

"RP2040": {
    ...
    "memory_overrides:" {
        "QSPI_FLASH": {
                // Mark this memory as flash.
                // Note: Meaning of these values (albeit in XML rather than JSON) is documented here:
                // https://www.keil.com/pack/doc/CMSIS_Dev/Pack/html/pdsc_family_pg.html#element_memory
                // See also here for how they get converted from XML to JSON:
                // https://github.com/pyocd/cmsis-pack-manager/blob/032a73a93e108e1b0e268ea47d92dbe573002846/rust/cmsis-pack/src/pdsc/device.rs#L466
                "access": {
                    "execute": true,
                    "peripheral": false,
                    "read": true,
                    "secure": false,
                    "write": false
                },
                "default": true,
                "startup": true,

                // Configure size and start address in memory
                "size": 0x200000, // 2MiB
                "start": 0x10000000 
            }
        }
    }
}

This bank, plus the ones defined in cmsis_mcu_descriptions.json5, would create the following definitions:

  • MBED_RAM_START=0x20000000, MBED_RAM_SIZE=0x40000, MBED_RAM_BANK_IRAM1_START=0x20000000, MBED_RAM_BANK_IRAM1_SIZE=0x40000 (from the main internal RAM bank)
  • MBED_RAM1_START=0x20040000, MBED_RAM1_SIZE=0x1000, MBED_RAM_BANK_SCRATCH_X_START=0x20040000, MBED_RAM_BANK_SCRATCH_Y_SIZE=0x1000 (from scratch X)
  • MBED_RAM2_START=0x20041000, MBED_RAM2_SIZE=0x1000, MBED_RAM_BANK_SCRATCH_X_START=0x20041000, MBED_RAM_BANK_SCRATCH_Y_SIZE=0x1000 (from scratch Y)

Then, after applying the override, we would get:

  • MBED_CONFIGURED_RAM_START=0x20000000, MBED_CONFIGURED_RAM_SIZE=0x40000, MBED_CONFIGURED_RAM_BANK_IRAM1_START=0x20000000, MBED_CONFIGURED_RAM_BANK_IRAM1_SIZE=0x40000 (from the main internal RAM bank)
  • MBED_CONFIGURED_RAM1_START=0x20040000, MBED_CONFIGURED_RAM1_SIZE=0x1000, MBED_CONFIGURED_RAM_BANK_SCRATCH_X_START=0x20040000, MBED_CONFIGURED_RAM_BANK_SCRATCH_Y_SIZE=0x1000 (from scratch X)
  • MBED_CONFIGURED_RAM2_START=0x20041000, MBED_CONFIGURED_RAM2_SIZE=0x1000, MBED_CONFIGURED_RAM_BANK_SCRATCH_X_START=0x20041000, MBED_CONFIGURED_RAM_BANK_SCRATCH_Y_SIZE=0x1000 (from scratch Y)
  • MBED_CONFIGURED_ROM_START=0x10000000, MBED_CONFIGURED_ROM_SIZE=0x200000, MBED_CONFIGURED_ROM_BANK_QSPI_FLASH_START=0x10000000, MBED_CONFIGURED_ROM_BANK_QSPI_FLASH=0x200000 (from the external QSPI flash)

We would add two new target definitions to Mbed:

"RP2040_CORE_0": {
    
}

Linker Script

We coul

References