ARM Cortex M processors provide several ways to access constant and immediate values directly within instructions. This allows efficient access to data without having to load values from memory. The main methods are: 1) Immediate values encoded directly in instruction opcodes, 2) PC-relative addressing to access constants in literal pools, 3) Load multiple registers using stacked PC-relative literals.
1. Immediate Values Encoded in Instructions
Many ARM instructions allow small immediate values to be directly encoded within the instruction opcode. For example: ADD R1, R2, #10 ; Add immediate value 10 to R2 and store in R1 SUB R3, R4, #100 ; Subtract 100 from R4 and store in R3
Here the immediate values 10 and 100 are directly stored within the encoded instruction. The allowed range of immediate values depends on the particular instruction. Typical ranges are 8-bit (0-255) or 12-bit (0-4095) unsigned integers. Signed integers are also supported in various ranges such as 8-bit (-128 to 127) or 5-bit (-16 to 15). Bitwise logic instructions support wider immediate values with bit masks.
Advantages
- Faster and smaller code vs loading from memory
- Allow simple constants for math, addresses, bit masks, etc
Disadvantages
- Limited range – can’t store 32-bit constants
- Constant takes space in instruction stream
2. PC-Relative Addressing Into Literal Pools
To access larger constants and addresses, ARM Cortex M processors provide PC-relative addressing into literal pools located in the instruction stream. This allows 32-bit values to be accessed using a pc-relative load instruction like LDR. LDR R1, =0x12345678 ; Load R1 from literal pool with 32-bit value LDR R2, =AddressVal ; Load R2 with address from literal pool
The literal pools are placed by the assembler/linker in the instruction stream alongside the code. The processor calculates the address as the PC value plus a byte offset encoded in the LDR instruction. This allows position-independent code access to constants.
Advantages
- 32-bit values supported
- Position independent code
- Constants don’t take space in instructions
Disadvantages
- Requires literal pool lookup
- Range limited to 1MB from PC value
3. Load Multiple Registers Using Stacked PC-Relative Literals
The LDM instruction can load multiple registers using stacked PC-relative literals. For example: LDM R0, {R1-R5} ; Load R1-R5 from stacked literals
R0 is used as a dummy base register. The values for R1-R5 are loaded from a table of PC-relative literals assembled into the instruction stream. This provides an efficient way to initialize multiple registers to constants.
Advantages
- Initialize multiple registers
- Position independent code
- Save instruction space vs individual LDRs
Disadvantages
- Fixed at load time, can’t index
- Requires literal pool lookup
Accessing String Constants
String constants can be accessed using PC-relative loads one character at a time. For example: LDR R0, =MyString ; R0 loaded with address of string LDRB R1, [R0,#0] ; Load byte 0 of string into R1 LDRB R2, [R0,#1] ; Load byte 1 of string into R2
R0 is first loaded with the address of the string constant. Then LDRB instructions are used to load individual bytes into registers. This allows efficient access to string constants without requiring the string data to be copied into RAM.
Load Addresses Into Inline Constants
The LDR instruction can be used to load an address into a register and add an immediate offset to compute an inline constant. For example: LDR R1, =0x20000000 LDR R2, [R1, #16] ; Loads 0x200000010 into R2
Here R1 is loaded with base address 0x20000000. Then LDR uses that base and adds an offset of 16 to compute address 0x200000010. This is useful for accessing constants spread out at fixed offsets from a base.
Position Independence and ADR
When loading addresses using PC-relative literals, positioning independence must be considered. If code is relocated, literal pool addresses may change. The ADR assembler directive can be used to ensure position independence: ADR R0, MyData ; R0 loaded with PC-relative offset LDR R1, [R0,#0] ; Load using offset in R0
ADR computes the PC-relative offset to MyData and stores it in R0. This offset will remain constant if code moves. LDR uses offset indirection to access the data.
Load 32-bit Values Efficiently
For 32-bit values, use LDR over LDRB to avoid stalling the pipeline. LDR loads the full 32-bits efficiently in one instruction vs four LDRB instructions.
Use MOVW/MOVT for Large Inline Constants
For very large inline constants, the MOVW and MOVT instructions can embed a 32-bit value directly in the instruction stream spread across two instructions. For example: MOVW R1, #0x1234 MOVT R1, #0x5678 ; R1 loaded with 0x12345678
This avoids the need for a PC-relative literal pool lookup.
Summary
- Use immediate values for small 8-12 bit constants
- Use PC-relative literals for 32-bit values and addresses
- LDM initializes multiple registers efficiently
- Access string constants directly from literal pool
- Use ADR for position independence
- Prefer LDR over LDRB for 32-bit values
- MOVW/MOVT embed large 32-bit constants
By utilizing these techniques, ARM Cortex M code can access constants and immediate values directly and efficiently without requiring runtime lookups. This improves performance and reduces memory usage.
Example Code Snippets
Here are some example code snippets demonstrating constant access methods: // Small immediate value ADD R1, R2, #100 // 32-bit value loaded via PC-relative LDR LDR R1, =0x10000000 // String constant LDR R0, =MyString LDRB R1, [R0,#0] // ADR for position independence ADR R0, MyData LDR R1, [R0,#0] // MOV/MOVT large embedded constant MOVW R1, #0x1234 MOVT R1, #0x5678 // Load multiple registers via literal stack LDM R0, {R1-R5} // Compute inline constant via load offset LDR R1, =0x20000000 LDR R2, [R1,#16]
Conclusion
ARM Cortex M provides flexible options for embedding and accessing constants and immediate values directly within code. By understanding the range, performance and position independence of each method, developers can optimize code for size, speed and simplicity. Proper utilization of these techniques is key to enabling efficient firmware on ARM Cortex M cores.