Implementing a loop position independent in ARM Cortex-M0+ requires utilizing the relative branch instructions available in the Thumb-2 instruction set. The key is to use PC-relative addressing rather than absolute jumps so that the target addresses do not need to change when the loop’s position in memory changes.
Overview of Cortex-M0+ Branch Instructions
The Cortex-M0+ CPU implements the Thumb-2 instruction set which includes both relative and absolute branch instructions. The relevant instructions are:
- B – Unconditional absolute branch
- BL – Branch with link (absolute)
- B.W – Unconditional absolute branch (wide)
- BL.W – Branch with link (absolute, wide)
- CBZ – Compare and Branch on Zero
- CBNZ – Compare and Branch on Non-Zero
- B.cond – Conditional branch (absolute)
- B.cond.W – Conditional branch (absolute, wide)
- IT – If-Then instruction for conditional execution
- BX – Branch and Exchange for function return
- BLX – Branch with link and Exchange for function calls
The absolute branches use a signed offset from the PC as the target address. This requires changing the offset if the position of the instruction changes. The relative branches use a signed offset from the instruction itself as the target, making them position independent.
Position Independent Loop Structure
To create a position independent loop with Cortex-M0+ assembly, the following basic structure can be used: loop_start: // loop body loop_end: CMP R0, R1 BNE.W loop_start next:
The unconditional relative branch BNE.W at the end of the loop branches back to the loop_start label using the PC-relative addressing. The target address is calculated automatically from the branch instruction’s position. This avoids having to recalculate an absolute address if the loop code moves in memory.
The other key part is labeling the end of the loop and target of the branch separate from the loop start. This provides the ability to branch to code following the loop using standard absolute addressing without issue.
Example Position Independent Loop
Here is an example Cortex-M0+ assembly function with a position independent loop: my_func: MOVS R0, #0 ; Initialize R0 to 0 loop: ADDS R0, #1 ; Increment R0 CMP R0, #100 ; Compare R0 to 100 BNE.W loop ; Loop back if not equal BX LR ; Return
This simple loop increments R0 from 0 to 100. The loop is positioned independently since the branch target at the end uses relative addressing to loop_start label. This loop can be moved or copied freely around in memory without modifying the branch offset.
The BX LR instruction at the end provides a standard ARM function return. This could be an absolute branch back to the caller, but using the standard return instruction avoids having to track return addresses.
Position Independent Loop with Inline C
Position independent loops can also be generated by inline ARM assembly in C code. This allows creating portable looping constructs that do not rely on absolute code addresses. For example: int my_func(int n) { int i = 0; __asm volatile( “loop%=:\n” ” ADDS %[i], #1\n” ” CMP %[i], %[n]\n” ” BNE.W loop%=\n” : [i]”+r”(i) : [n]”r”(n) ); return i; }
The loop label “loop%” and branch “BNE.W loop%” use relative addressing techniques. When compiled, this will generate a position independent loop structure like the earlier example.
Inline assembly allows combining C variables and register operands for easy interaction with the C program. The input operand [n] provides the loop count, and the [i] variable is updated by the loop and returned.
Guidelines for Position Independent Code
Here are some general guidelines for generating position independent loops and code with Cortex-M0+:
- Use relative branches like B.W and B.cond.W for local loops and control flow.
- Avoid absolute branches within loops or functions as much as possible.
- Use BL and BLX for function calls rather than absolute branches.
- If necessary, absolute branches can be used for non-local jumps like returning.
- Assign labels for branch targets at the destination not branch site.
- Utilize inline assembly with relative branches when possible.
- Place loops, functions, and other blocks of code in separate sections using assembler directives if needed.
Following these practices will generally result in code that can be relocated or copied without modification. Though with complex flows and interactions some dependencies may still occur requiring changes.
Working Around Absolute Branch Limitations
In some cases it may not be possible to make all branches position independent. The Cortex-M0+ absolute branches have a limited range of +-1MB from the PC. Some options for working around this include:
- Minimize use of absolute branches whenever possible.
- Use linker scripts to assign code/data to sections within the +-1MB range.
- For larger programs split code into multiple position independent sections that can be linked within the range.
- For time critical loops use relative branches. Use absolute jumps for less frequent non-performance critical segments.
- Some compilers support automatic workarounds like trampoline functions to extend branch ranges.
With careful planning, even large programs can be organized into multiple independently positioned code sections. Each section uses relative branches locally, with a few absolute branches between sections.
Conclusion
Implementing position independent loops and code blocks with the Cortex-M0+ primarily involves utilizing the relative branch instructions like B.W and B.cond.W. Avoiding absolute branches for local control flow is key. Following the guidelines outlined here should enable creating reusable and relocatable code modules and libraries.
There are some limitations due to the absolute branch range, but these can typically be managed through code organization and linker scripts. With good practices, the position independent capabilities of the Thumb-2 instruction set can be leveraged effectively on Cortex-M0+ platforms.