(ARM).Using the ARM assembler
.pdfDescribing Data Structures Using ^ and # Directives
An array is also included within this structure to illustrate the use of ADR on a registerbased symbol. If you want to load misc.data[misc.i] into R1, for instance, use the following code:
ADR |
MiscBase,misc |
|
LDR |
r0,Misc_I |
;Get misc.i into r0 |
ADR |
r1,Misc_data |
;Address misc.data |
LDR |
r1,[r1,r0,LSL #2] |
;Load array element |
At this point, it is worth mentioning the other main use of :INDEX: outside macros. Suppose that you want to operate on two structures of the same type at the same time¾for example, you want the equivalent of the pseudo-C code:
newloc.x = oldloc.x + (value in R0); newloc.y = oldloc.y + (value in R1); newloc.z = oldloc.z + (value in R2);
You alternately want PointBase to point to the oldloc structure and the newloc one. Continually changing PointBase in this way is inefficient: you just want to set up the two pointers in two different registers. This can be done using code similar to:
PointBase2 RN R9
ADR |
PointBase,oldloc |
ADR |
PointBase2,newloc |
LDR |
r3,Point_x |
ADD |
r3,r3,r0 |
STR |
r3,[PointBase2,#:INDEX:Point_x] |
LDR |
r3,Point_y |
ADD |
r3,r3,r1 |
STR |
r3,[PointBase2,#:INDEX:Point_y] |
LDR |
r3,Point_z |
ADD |
r3,r3,r1 |
STR |
r3,[PointBase2,#:INDEX:Point_z] |
Note Smaller and faster code exists for this example, for example using LDM, STM and a couple more registers can reduce its length by four instructions. However, this does not work if, for example, you are trying to access non-contiguous fields in the structure, or there are not enough registers available.
Application Note 50
ARM DAI 0050A |
9 |
Open Access
Describing Data Structures Using ^ and # Directives
Making faster access possible
Here, you describe your memory area as a structure, then use a register to address that structure while you are processing it.
For example, consider the definitions from above:
StartOfMyData |
EQU |
0x1000 |
EndOfMyData |
EQU |
0x2000 |
|
^ |
StartOfMyData |
Integer |
# |
4 |
Integer2 |
# |
4 |
String |
# |
MaxStrLen |
Array |
# |
ArrayLen*8 |
BitMask |
# |
4 |
EndOfUsedData |
# |
0 |
ASSERT EndOfUsedData <= EndOfMyData
Suppose that you want the equivalent of the C code:
Integer = 0;
Integer2 = 1;
String = "";
BitMask = 0xA000000A;
With the definitions as above, the assembler code is similar to:
MOV |
r0,#0 |
LDR |
r1,=Integer |
STR |
r0,[r1] |
MOV |
r0,#1 |
LDR |
r1,=Integer2 |
STR |
r0,[r1] |
MOV |
r0,#0 |
LDR |
r1,=String |
STRB |
r0,[r1] |
MOV |
r0,#0xA000000A |
LDR |
r1,=BitMask |
STRB |
r0,[r1] |
This contains separate LDRs of literals to address each of the data items; this is unnecessary, since you only need to load a single address to be able to access the entire area.
|
Application Note 50 |
10 |
ARM DAI 0050A |
Open Access
Describing Data Structures Using ^ and # Directives
The way to do this is effectively to create a my data area layout structure, with definitions as follows:
StartOfMyData |
EQU |
0x1000 |
EndOfMyData |
EQU |
0x2000 |
DataAreaBase |
RN |
r11 |
|
^ |
0,DataAreaBase |
StartOfUsedData |
# |
0 |
Integer |
# |
4 |
Integer2 |
# |
4 |
String |
# |
MaxStrLen |
Array |
# |
ArrayLen*8 |
BitMask |
# |
4 |
EndOfUsedData |
# |
0 |
UsedDataLen |
EQU |
EndOfUsedData - StartOfUsedData |
ASSERT UsedDataLen <= (EndOfMyData - StartOfMyData)
The assembler code for the above is then:
LDR DataAreaBase,=StartOfMyData
MOV r0,#0
STR r0,Integer
MOV r0,#1
STR r0,Integer2
MOV r0,#0
STRB r0,String
MOV r0,#0xA000000A
STRB r0,BitMask
This is significantly shorter and faster than the original.
Note Note that the ^ directive is ^ 0,DataAreaBase, not ^ StartOfMyData,DataAreaBase. The reason for this is that you are using the ^ and # directives to describe the layout of the data within the data area, relative to the DataAreaBase register, and not its absolute position. The LDR DataAreaBase,=StartOfMyData statement then provides the absolute position of the entire data area.
Finally, if you want to use the same technique for an area of memory containing memory mapped I/O (or whose absolute addresses must not change for other reasons), you must take care to keep the code maintainable. Unlike the corresponding situation for non- register-based ^ and # directives, you cannot easily revert to using EQU directives. One method is to add comments to the code warning maintainers to take care when modifying the definitions; a better one is to use definitions of the absolute addresses to control the register-based definitions. This is rather tricky and is one of the rare uses of a registerbased ^ directive with a non-zero expression: the basic idea is that using ^ offset,reg followed by label # 0 makes label into a register-based symbol with register part reg and numeric part offset.
Application Note 50
ARM DAI 0050A |
11 |
Open Access
Describing Data Structures Using ^ and # Directives
For example, the way to do this with the I/O location example above is to write:
StartOfIOArea EQU 0x1000000
SendFlag_Abs EQU 0x1000000
SendData_Abs EQU 0x1000004
RcvFlag_Abs EQU 0x1000008
RcvData_Abs EQU 0x100000C
IOAreaBase |
RN |
r11 |
|
^ |
(SendFlag_Abs-StartOfIOArea), IOAreaBase |
SendFlag |
# |
0 |
|
^ |
(SendData_Abs-StartOfIOArea),IOAreaBase |
SendData |
# |
0 |
|
^ |
(RcvFlag_Abs-StartOfIOArea),IOAreaBase |
RcvFlag |
# |
0 |
|
^ |
(RcvData_Abs-StartOfIOArea),IOAreaBase |
RcvData |
# |
0 |
After loading up the base address with LDR IOAreaBase,=StartOfIOArea, this allows the individual locations to be accessed with statements like LDR R0,RcvFlag and STR R4,SendData.
2.4 Warnings in the use of ^ and # directives
The use of ^ and # directives is encouraged as they allow the use of more maintainable data structures. This assumes that the order in which the elements are physically placed in memory is not important to the programmer or program. Unfortunately this can cause problems if multiple elements in a structure are loaded or stored in one instruction. This can occur when several single byte elements are loaded into one register, or when a Store or Load multiple registers instruction (STM and LDM) is used to store or load multiple words from or to multiple registers. The problem arises because both these operations require the different data elements to be contiguous in memory, and also to be in a specific order. These operations will work to start with, but if in future maintenance the order of these elements is changed (or a new element is added), this breaks the program in such a way that it will not be picked up at assembly time. This would therefore counteract the maintainability of these data structures. There are various methods of avoiding these problems.
A sample structure follows:
MiscBase |
RN |
r10 |
|
^ |
0,MiscBase |
MiscStart |
# |
0 |
Misc_a |
# |
1 |
Misc_b |
# |
1 |
Misc_c |
# |
1 |
Misc_d |
# |
1 |
MiscEndOfChars |
# |
0 |
MiscPadding |
# |
(-:INDEX:MiscEndOfChars) :AND: 3 |
Misc_I |
# |
4 |
Misc_J |
# |
4 |
Misc_K |
# |
4 |
Misc_data |
# |
4*20 |
MiscEnd |
# |
0 |
MiscLen |
EQU |
MiscEnd-MiscStart |
|
|
|
|
Application Note 50 |
12 |
ARM DAI 0050A |
Open Access
Describing Data Structures Using ^ and # Directives
There is, of course, no problem in using LDM/STM instructions for accessing single data elements which are larger than a word (that is, arrays). An example of this is the 20-word element Misc_data in the example below, which could be accessed as follows:
ArrayBase |
RN |
R9 |
ADR |
ArrayBase, MiscBase |
|
LDMIA |
ArrayBase, {R0-R5} |
The above example loads the first six elements in the array Misc_data. Since the array is a single element, it covers contiguous memory locations. It is unlikely that in the future anyone will split it into two separate arrays.
However, for the case of loading Misc_I, Misc_J, and Misc_K into registers R0, R1, and R2 the following would work, but could cause problems in the future:
ArrayBase |
RN |
R9 |
ADR |
ArrayBase, Misc_I |
|
LDMIA |
ArrayBase, {R0-R2} |
The problem lies in the fact that in future, maintenance of the code the order of Misc_I, Misc_J, and Misc_K could be changed, or a new element Misc_New could be added in the middle. Either of these small changes would break the code. If these elements need to be accessed separately elsewhere, so you do not want to amalgamate them into a single array element, you must amend the code. The first remedy is to comment the structure to prevent changes affecting this area:
Misc_I |
# |
4 |
; |
==} |
Do not split/reorder |
|
Misc_J |
# |
4 |
; |
} |
these 3 |
elements, STM |
Misc_K |
# |
4 |
; |
==} |
and LDM |
instructions used. |
If the code is strongly commented, no deliberate changes should be made which would affect the workings of the program. Unfortunately, mistakes can still occur. A second method of catching these problems would be to add ASSERT directives just before the STM/LDM instructions to check that the labels are in the correct order and are consecutive:
ArrayBase RN |
R9 |
; Check that the structure elements are correctly ordered for LDM
ASSERT |
(((Misc_J-Misc_I) = 4) :LAND: ((Misc_K-Misc_J) = 4)) |
ADR |
ArrayBase, Misc_I |
LDMIA |
ArrayBase, {R0-R2} |
This ASSERT directive stops assembly at this point if the structure is not in the correct order to be loaded with an LDM. Remember that the element with the lowest address always is loaded or stored to or from the lowest numbered register.
Application Note 50
ARM DAI 0050A |
13 |
Open Access
Using Macros
3 Using Macros
Using macros in your assembler source does not ease porting to another assembler for the ARM, but it does provide some improvement to code readability.
Macros give you a means of placing a single instruction in your source which is expanded at assembly time to several assembler instructions and directives, just as if you had written those instructions and directives within the source at that point.
As an example, consider a TestAndBranch instruction. This would normally take two ARM instructions. You must tell the ARM Assembler, by means of a macro definition, that whenever it meets the TestAndBranch instruction, it is to insert the code you have given it in the macro definition. This is a convenience; you could just as easily write the relevant instructions out each time, but instead you let the Assembler do it for you.
The Assembler determines the destination of the branch from a macro parameter. This is a piece of information specified each time the macro is invoked; the macro definition specifies how it is used. In the TestAndBranch example, you could also make the register to be tested a parameter, and even the condition to be tested for. Your macro definition might be:
|
MACRO |
|
$label |
TestAndBranch $dest, $reg, $cc |
|
$label |
CMP |
$reg, #0 |
|
B$cc |
$dest |
|
MEND |
|
The line after the MACRO directive is called the macro prototype statement. The lines containing the CMP and B instructions are the ones which will be substituted in the source. The macro ends when the Assembler encounters the MEND directive. The $label allows the line on which the macro is invoked to have a label which is substituted for each occurrence of $label in the macro.
This is an example of how this macro may be invoked:
test |
TestAndBranch |
NonZero, r0, NE |
|
; |
|
|
; |
|
NonZero |
|
|
After substitution this becomes:
test |
CMP |
r0, #0 |
|
BNE |
NonZero |
|
; |
|
|
; |
|
NonZero |
|
|
|
Application Note 50 |
14 |
ARM DAI 0050A |
Open Access
Using Macros
As a more complex example, the following macro performs an unsigned integer division. The macro expands into shift and subtract operations to calculate the quotient and remainder. The macro takes four parameters:
∙$Bot is the register which holds the divisor.
∙$Top is the register which holds the dividend. It also holds the remainder of the
division, that is, $Top = $Top MOD $Bot.
∙ $Div is the register where the quotient of the division will be placed, that is, $Div = $Top DIV $Bot, if required.
∙$Temp is a temporary register used during the calculation.
The macro checks that there is no conflict of register names (that is, that no two parameters use the same register), and is optimized in the case where the quotient is not required as an output, by checking whether the $Div parameter is NULL.
Note that local labels are used to avoid multiply defined labels if DivMod is used more than once in the assembler source. The letter b is used when referencing the labels to indicate that the Assembler should search backwards for the label. Use f to search forwards.
|
|
MACRO |
|
$Lab |
DivMod $Div,$Top,$Bot,$Temp |
||
|
ASSERT $Top <> $Bot |
;Produce an error if the |
|
|
ASSERT $Top <> $Temp |
;registers supplied are |
|
|
ASSERT $Bot <> $Temp |
;not all different. |
|
|
[ |
“$Div” <> “” |
|
|
ASSERT $Div <> $Top |
|
|
|
ASSERT $Div <> $Bot |
|
|
|
ASSERT $Div <> $Temp |
|
|
|
] |
|
|
$Lab |
|
|
|
|
MOV |
$Temp, $Bot |
;Put divisor in $Temp |
|
CMP |
$Temp, $Top, LSR #1 |
;double it until |
90 |
MOVLS |
$Temp, $Temp, LSL #1 |
;2 * $Temp > $Top. |
|
CMP |
$Temp, $Top, LSR #1 |
|
|
BLS |
%b90 |
|
|
[ |
“$Div” <> “” |
|
|
MOV |
$Div, #0 |
;initialise quotient |
|
] |
|
|
91 |
CMP |
$Top, $Temp |
;can we subtract $Temp? |
|
SUBCS |
$Top, $Top,$Temp |
;if we can, do so. |
|
[ |
“$Div” <> “” |
|
|
ADC |
$Div, $Div, $Div |
;Double $Div |
|
] |
|
|
|
MOV |
$Temp, $Temp, LSR #1 |
;Halve $Temp, |
|
CMP |
$Temp, $Bot |
;and loop until gone |
|
BHS |
%b91 |
;past original divisor |
|
MEND |
|
|
Application Note 50
ARM DAI 0050A |
15 |
Open Access
Using Macros
If this macro were to be invoked as follows:
DivMod |
r0, r5, r4, r2 |
the expanded assembler code would be:
ASSERT r5 <> r4
ASSERT r5 <> r2
ASSERT r4 <> r2
ASSERT r0 <> r5
ASSERT r0 <> r4
ASSERT r0 <> r2
|
MOV |
r2, r4 |
|
CMP |
r2, r5, LSR #1 |
90 |
MOVLS |
r2, r2, LSL #1 |
|
CMP |
r2, r5, LSR #1 |
|
BLS |
%b90 |
|
MOV |
r0, #0 |
91 |
CMP |
r5, r2 |
|
SUBCS |
r5, r5, r2 |
|
ADC |
r0, r0, r0 |
|
MOV |
r2, r2, LSR #1 |
|
CMP |
r2, r4 |
|
BHS |
%b91 |
;Produce an error if the ;registers supplied are ;not all different.
;Put divisor in $Temp ;double it until
;2 * r2 > r5.
;initialise quotient ;can we subtract r2? ;if we can, do so.
;Double r0 ;Halve r2,
;and loop until gone ;past original divisor
|
Application Note 50 |
16 |
ARM DAI 0050A |
Open Access
Using the Barrel Shifter
4 Using the Barrel Shifter
The ARM’s arithmetic logic unit has a 32-bit barrel shifter capable of various shift and rotate operations. Data involved in the data processing group of instructions may pass through the barrel shifter, either as the result of an explicit instruction from the programmer, or as a result of the Assembler’s internal computations.
The barrel shifter has a carry in, which takes its input from the C flag of the CPSR, and a carry out, which may be latched back into the C bit of the CPSR for logical data operations.
4.1 Assembler mnemonics
There are five assembler mnemonics for shift types:
LSR Logical Shift Right
LSL Logical Shift Left
ASR Arithmetic Shift Right
ROR Rotate Right
RRX Rotate Right with Extend.
Logical shift right
A logical shift right (LSR) takes the contents of the register operand and moves each bit by the specified amount to a less significant position, filling the most significant bits with zeros. The least significant bits of the register operand which do not map into the result are discarded¾except that the most significant discarded bit becomes the barrel shifter’s carry out.
For example, if r0 contains:
11110000111100001111000011110000
r0 LSR #5 gives:
00000111100001111000011110000111
Logical shift left
A logical shift left (LSL) takes the contents of the register operand and moves each bit by the specified amount to a more significant position, filling the least significant bits with zeros. The most significant bits of the register operand which do not map into the result are discarded¾except that the least significant discarded bit becomes the barrel shifter’s carry out.
For example, if r0 contains:
11110000111100001111000011110000
r0 LSL #5 gives:
00011110000111100001111000000000
Application Note 50
ARM DAI 0050A |
17 |
Open Access
Using the Barrel Shifter
Arithmetic shift right
An arithmetic shift right (ASR) is similar to a logical shift right, except that the most significant bits are filled with bit 31 of the register operand instead of zeros. This preserves the sign in 2’s complement notation.
For example, if r0 contains:
11110000111100001111000011110000
r0 ASR #5 gives:
11111111100001111000011110000111
Rotate Right
A rotate right (ROR) performs a logical shift right operation, but re-introduces at the most significant end of the register operand any bits which are discarded from the least significant end.
For example, if r0 contains:
11110000111100001111000011110000
then r0 ROR #5 will yield:
10000111100001111000011110000111
4.2 Register operands
Register operands can be of the following types:
Unshifted register
Syntax: register-name
Example: r0
Example use:
MOV r1, r0 |
; moves contents of r0 unshifted into r1 |
Register shifted by a constant amount
The register operand is shifted by a constant amount, in the range 0−31, 1−31 or 1−32 (depending on shift type).
Syntax: register-name, shift-type #amount
Example: r0, LSR #1
Example use:
MOV r1, r0, LSR #1 ; move contents of r0 divided by 2 into r1
|
Application Note 50 |
18 |
ARM DAI 0050A |
Open Access