ARM Cortex-M55 Freeze on Entry to main() at -O0 Optimization Level

The issue at hand involves the ARM Corstone SSE-300 MPS3 simulator freezing upon entry to the main() function when the code is compiled with the -O0 optimization level. This behavior is particularly perplexing because the same code executes without issues when compiled with -O3 optimization. The problem is further compounded by memory configuration challenges, scatter file errors, and potential conflicts with semihosting and FPU/MVE settings.

The freeze at the entry to main() suggests that the system is encountering a critical failure during the initialization phase, which is not immediately apparent when higher optimization levels are applied. This could be due to subtle differences in how the compiler handles code generation, memory allocation, or initialization routines at different optimization levels. The issue is further complicated by the use of scatter files for memory mapping, semihosting configurations, and the enabling of FPU and MVE (Helium) features.

Memory Configuration Mismatch and Scatter File Assertion Failures

One of the primary causes of this issue is the mismatch between the memory configuration defined in the scatter files and the actual memory layout of the Corstone SSE-300 MPS3 simulator. The scatter file defines multiple memory regions, including ITCM, DTCM, SRAM, and QSPI SRAM, each with specific base addresses and sizes. The scatter file also includes assertions to ensure that the code and data sections do not exceed the allocated memory regions.

The error message Error: L6388E: ScatterAssert expression (ImageLimit(SRAM_WATERMARK) <= ((0x30000000)) + ((0x40000))) failed on line 63 : (0x30200a60 <= 0x30040000) indicates that the linker has detected a violation of the memory constraints defined in the scatter file. Specifically, the SRAM_WATERMARK region exceeds the allocated memory space for the SRAM, leading to a linker error.

The scatter file defines the SRAM_WATERMARK region as follows:

ScatterAssert(ImageLimit(SRAM_WATERMARK) <= S_DATA_START + S_DATA_SIZE)

Here, S_DATA_START and S_DATA_SIZE are defined in the region_defs.h file, which specifies the base address and size of the SRAM region. The default value for S_DATA_SIZE is 0x00040000 (256 KB), which is insufficient for the code being compiled. This mismatch between the allocated memory size and the actual memory requirements of the code is a likely cause of the linker error.

Additionally, the scatter file includes multiple memory regions for different security states (Secure and Non-Secure), which further complicates the memory mapping process. The scatter file must ensure that the code and data sections are correctly placed in the appropriate memory regions, taking into account the security state and the memory attributes (e.g., executable, writable, etc.).

Semihosting and FPU/MVE Configuration Conflicts

Another potential cause of the freeze at the entry to main() is the conflict between the semihosting settings and the FPU/MVE configurations. Semihosting is a mechanism that allows the target device to communicate with the host system for debugging purposes, such as printing messages to the console or reading files from the host filesystem. However, semihosting can interfere with the normal operation of the system, especially when combined with other features like the FPU and MVE.

The Corstone SSE-300 MPS3 simulator is configured with the following parameters:

-C cpu0.semihosting-enable=1 -C cpu0.FPU=1 -C cpu0.MVE=2

These parameters enable semihosting, the FPU, and the MVE (Helium) features. However, enabling semihosting in both the simulator and the debugger can lead to conflicts, as both the simulator and the debugger may attempt to handle semihosting requests simultaneously. This can result in undefined behavior, including system freezes or crashes.

The FPU and MVE configurations are also critical, as they determine how the processor handles floating-point operations and vector instructions. If the FPU or MVE is not correctly initialized or enabled, the system may encounter hard faults or undefined behavior when executing code that relies on these features. This is particularly relevant when using -O0 optimization, as the compiler may generate additional initialization code or use different instruction sequences that depend on the FPU or MVE.

Debugging and Troubleshooting Steps

Step 1: Resolving Memory Configuration Issues

The first step in resolving the issue is to address the memory configuration mismatch in the scatter file. The S_DATA_SIZE value should be increased to match the actual memory requirements of the code. The default value of 0x00040000 (256 KB) can be increased to 0x00080000 (512 KB), which is the size of the ITCM in the Corstone SSE-300 MPS3 simulator.

The region_defs.h file should be modified as follows:

#define TOTAL_S_ROM_SIZE (0x00080000) /* 512 KB */

This change ensures that the scatter file assertions are satisfied and that the code and data sections do not exceed the allocated memory regions. After making this change, the code should be recompiled and linked to verify that the linker error is resolved.

Step 2: Disabling Semihosting in the Debugger

To avoid conflicts between the simulator and the debugger, semihosting should be disabled in the debugger. This can be achieved by creating a simple script that disables semihosting and specifying it as a Run Target initialization script in the Debug Configurations pane.

The script should contain the following command:

set semihosting enabled off

This script should be saved as a .ds file and specified in the Debug Configurations pane under the "Run Target Initialization Script" section. This ensures that semihosting is disabled in the debugger, preventing any potential conflicts with the simulator.

Step 3: Verifying FPU and MVE Initialization

The FPU and MVE configurations should be verified to ensure that they are correctly initialized and enabled. This can be done by adding initialization code to the startup file or the main() function to explicitly enable the FPU and MVE.

The following code can be used to enable the FPU and MVE:

#define CM_DEMCR (*((volatile uint32_t*)0xE000EDFC))
#define CM_TRCENA_BIT (1UL<<24)
#define CM_DWT_LAR (*((volatile uint32_t*)0xE0001FB0))
#define CM_DWT_CONTROL (*((volatile uint32_t*)0xE0001000))
#define CM_DWT_CYCCNTENA_BIT (1UL<<0)
#define CM_DWT_CYCCNT (*((volatile uint32_t*)0xE0001004))

void enable_fpu_mve() {
    CM_DEMCR |= CM_TRCENA_BIT;
    CM_DWT_LAR = 0xC5ACCE55;  // Unlock the DWT registers
    CM_DWT_CONTROL |= CM_DWT_CYCCNTENA_BIT;  // Enable the cycle counter
    CM_DWT_CYCCNT = 0;  // Reset the cycle counter
}

This code should be called at the beginning of the main() function to ensure that the FPU and MVE are correctly initialized before any floating-point or vector operations are performed.

Step 4: Debugging the Freeze at main()

If the system still freezes at the entry to main(), the next step is to use the debugger to identify the exact location of the freeze. The debugger should be configured to halt execution at the entry to main() and step through the code to identify any potential issues.

The following steps can be used to debug the freeze:

  1. Set a breakpoint at the entry to main().
  2. Run the code and wait for the debugger to halt at the breakpoint.
  3. Step through the code line by line to identify any potential issues, such as uninitialized variables, incorrect memory accesses, or hard faults.
  4. Check the values of critical registers, such as the stack pointer (SP) and program counter (PC), to ensure that they are correctly initialized.
  5. Verify that the FPU and MVE are correctly enabled by checking the relevant control registers.

If a hard fault is encountered, the hard fault handler should be implemented to provide additional information about the cause of the fault. The hard fault handler can be used to print the values of the stacked registers, the fault status registers, and the memory address that caused the fault.

Step 5: Measuring Cycles and MIPS

To measure the number of cycles and MIPS (Millions of Instructions Per Second) in the simulator, the DWT (Data Watchpoint and Trace) cycle counter can be used. The cycle counter can be enabled and read using the following code:

void start_cyccnt() {
    CM_DEMCR |= CM_TRCENA_BIT;
    CM_DWT_LAR = 0xC5ACCE55;  // Unlock the DWT registers
    CM_DWT_CONTROL |= CM_DWT_CYCCNTENA_BIT;  // Enable the cycle counter
    CM_DWT_CYCCNT = 0;  // Reset the cycle counter
}

void stop_cyccnt() {
    CM_DWT_CONTROL &= ~CM_DWT_CYCCNTENA_BIT;  // Disable the cycle counter
}

uint32_t get_cyccnt() {
    return CM_DWT_CYCCNT;  // Read the cycle counter
}

This code can be used to measure the number of cycles taken by a specific function or code segment. The cycle counter can be started before the function is called and stopped after the function returns. The difference between the start and stop values gives the number of cycles taken by the function.

The MIPS value can be calculated by dividing the number of cycles by the clock frequency of the processor. For example, if the processor is running at 100 MHz, the MIPS value can be calculated as follows:

uint32_t cycles = get_cyccnt();
float mips = (float)cycles / 1000000.0f;  // Assuming a 100 MHz clock

This value can be printed to the console or logged for further analysis.

Conclusion

The freeze at the entry to main() when using -O0 optimization on the ARM Corstone SSE-300 MPS3 simulator is likely caused by a combination of memory configuration mismatches, semihosting conflicts, and FPU/MVE initialization issues. By addressing these issues through careful memory configuration, disabling semihosting in the debugger, and verifying the FPU and MVE initialization, the issue can be resolved. Additionally, the use of the DWT cycle counter provides a valuable tool for measuring performance and identifying potential bottlenecks in the code.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *