Search code examples
cassemblyarmembeddedds-5

Is this an ARM Compiler code generation error?


I am working on an embedded system which includes an ARM Cortex-M4 CPU and a few peripherals. One of the peripherals contains blocks of SRAM which are accessible from the CPU side (via the AHB bus), but access must be a word size transaction (using LDR). If a byte transaction is performed (LDRB), an exception is generated.

In my code, I am reading a value from an array in that memory and assigning it to a local variable. The declarations are like this:

typedef enum
{
     eType0 = 0,
     eType1 = 1,
} type_t;

type_t    arr_type;
uint32_t *array  = BUF_ADDR; // array on periph. memory
uint32_t  offset = 0;

arr_type = (type_t) array[offset]; // exception!

When running this code, I get an exception when reading the memory. It happens that this assignment generates the assembly code:

LDRB R1, [R2, R3, LSL #2]; // R2=array, R3=offset

This is also true even when I add parentheses and explicitly cast the expression:

type = (uint32_t) (array[offset]);

The way to resolve this was to declare arr_type as uint32_t instead of type_t. Now, the code is:

LDR R1, [R2, R3, LSL #2];

Is this an expected behaviour? I'd assume that the parentheses and the cast (if not the natural type of the array pointer) would make the compiler generate and LDR instruction. Hence this looks like a bug.


Solution

  • The compiler can normally use whatever load instructions it wants so long as it works, from perspective of the program itself, as if the 32-bit load implied by array[offset] was performed. Since the value loaded ends up being truncated to 8 bits when stored in arr_type, it doesn't change the value stored in arr_type if the compiler uses an 8-bit load instead.

    To tell the compiler that size of the memory access is important and has visible effects outside of the program itself, you should use the volatile qualifier.

    type_t    arr_type;
    uint32_t volatile *array = BUF_ADDR;
    uint32_t  offset = 0;
    
    arr_type = (type_t) array[offset]; 
    

    More generally you should use the volatile qualifier when performing any sort of memory mapped I/O. It not only ensures that accesses are always performed using the intended size (where possible), it also guarantees that that accesses aren't removed or reordered.