Commands in normal ARM programming are 32 bit, this means despite being a RISC processor, the ARM has a wide variety of commands and clever functions such as the barrel shifter, and a wide range of addressing modes.
However, there may be times when using 32 bit commands is impractical due to their size, such as when writing embedded firmware and bytes are limited
The Thumb mode was designed for times when 32 bit commands are excessive. In "ARM Thumb" mode all commands compile to 16 bits / two bytes. Although there are more limited commands, overall we will be able to do the same tasks with much less space. Despite using 16 bit command bytes, all the registers are still 32 bits, and all the commands function in 32 bits � it's just the resulting byte code that has haved.
ARM Thumb still has a varied instruction set, however it's much more limited compared to ARM, and in some ways resembles 68000 style code.
Arm Thumb has many limitations, some of the most significant are:
1. The 'Barrel Shifter' is not available as part of the addressing modes, we have dedicated ROR, ASR LSR and LSL commands in Thumb.
2. Auto Increment and Decrement are not possible, except with the LDMIA/STMIA commands. We do now have dedicated PUSH and POP commands to work with the stack.
3. Condition codes are set automatically, we can't use the 'S' suffix to define which commands set flags.
4. Individual commands can not be conditionally, we can only use conditional branches, such as BCC and BEQ to jump over code we don't want to run.
5. We now have very limited options for using Immediate values with commands, many times we'll need to load a value defined at a label in our code with "LDR Rn,Label", the 'Label" must be after the line of code, it cannot be before, as the offset can only be positive.
6. The ARM processor starts in the normal mode, we must switch to Thumb mode using the BX command. The way the BX command works is a little odd "BX Rn" will branch to the address in register Rn, however the Bit 0 of the register does not function as part of the address, it defines the 'T flag', a value of 1 will turn Thumb mode on after the jump, a value of 0 will turn Thumb mode off, enabling regular ARM mode. We can switch between modes whenever we want in this way.
If you want to learn ARM get the Cheatsheet! it has all the ARM7 commands, it covers the commands, and options like Bitshifts and conditions as well as the bytecode structure of the commands! |
We'll be using
the excellent VASM for our assembly in these tutorials... VASM
is an assembler which supports Z80, 6502, 68000, ARM and many
more, and also supports multiple syntax schemes... You can get the source and documentation for VASM from the official website HERE |
Thumb mode shares the same registers as regular ARM mode, however due to the reduction in the size of the commands, many of the commands only function with the so called "Low Registers" (R0-R7). There are a few commands which also work with the "High Registers".
The Stack Pointer, Link Register and Program Counter still have the same purpose as before.
Register |
Purpose |
---|---|
R0 |
General |
R1 |
General |
R2 |
General |
R3 |
General |
R4 |
General |
R5 |
General |
R6 |
General |
R7 |
General |
R8 |
General |
R9 |
General |
R10 |
General |
R11 / FP |
Frame Pointer (Optional) |
R12 / IP |
Intra Procedural Call (Optional) |
R13 / SP |
Stack Pointer |
R14 / LR / LK |
Link Register |
R15 / PC |
Program Counter |
A "Frame pointer" points to data areas in the Stack, effectively
allocating a 'small stack allocation' for a subroutine. This frame pointer
register would be used with relative offsets. It's 'Suggested' R11 is used
for this purpose if you need it, it's entirely optional if you use R11 for
this or not.
We'll be
covering everything pretty completely in the ARM Thumb series, but
it will be assumed you know some ARM, Not particularly because the tutorials have anything 'missing' but because ARM can do more than THUMB, and ARM is the default CPU mode, so normal ARM is the more sensible place to start... check out the tutorial here. |
We're going to be using VASM
as an assembler, it's a free which works on windows, OSX and
Linux My Devtools provide a batch file which will build the programs for you, but if you don't want to use them, the format of the build script is shown below: -Fbin ... Specifies to create a Binary file -Dxxx=Y ... Specifies to define a symbol xxx=y (we'll learn about symbols later. -L ... Specifies a Listing file - this shows source code and resulting bytes... it's used for debugging if we have problems -o ... Specifies the output file. %BuildFile%... this would be the sourcefile you want to compile... Eg: Lesson1.asm -m7tdmi... (or equivalent) specifies the ARM architecture we're building for. -noialign... This will disable 32 bit alignment - we need this as each thumb command is 16 bit. -chklabels -nocase ... Disable case sensitivity, and check for lines where we've forgotten a tab on a command (it will be mistaken for a label) |
|
Once we've successfully compiled our program, we can run it with
VisualBoyAdvance |
To allow us to get started programming quickly and see the
results, we'll be using a 'template program'...
(Thumb_Minimal.asm) This consists of 3 parts: A Generic Header - this will set up the screen and a few parameters we'll need to start. The Thumb switch... we need to use the BX (Branch and Xchange) command to turn on thumb mode... strangely, setting bit zero to 1 will turn on thumb mode after the branch! We then need a compiler directive .thumb to tell the assembler we're now using THUMB mode... We use .arm to re-enable normal ARM mode. The Program - this is the body of our program where we do our work. A Generic Footer - this gives us some support tools, and includes a common bitmap font. This template program will compile on any of the systems in these tutorials (RiscOS and the GameboyAdvance!) |
There's a lot of
complex scary stuff in the include files - don't panic about
it for now, you'll be able to understand it more later once
you've covered all the lessons. |
BX exists in ARM and Thumb. It all comes down to Bit 0 - which can enable or disable Thumb mode. We can switch at any time to have a subroutine which is ARM, then another that is Thumb and so on! |
Lets look at another subroutine. This one stars with a label 'BranchLinkTest'... we know it's a label because it's not indented and ends in a colon... this is the name of the subroutine - we'll see the name with BL (Branch and Link) statements (calls on the arm). Then there is an ADD Command... it adds 1 to r0 (R0=R0+1)... it is indented, so they are clearly commands... Finally there is a MOV PC,LR command - this ends a subroutine... BL transfers PC (the program counter... the current running byte) to LR - transferring LR back to PC returns to the command after the BL command if our code has a RET at the end - it's a subroutine and should probably be started with a CALL... if we start it with a JMP something bad will probably happen! |
Subroutines work the same
in ARM Thumb as they do in classic ARM, in the sense they use
the Link Register to contain the return address, and we have
to |
The example here shows data is stored by the
ARM in 'Little Endian' format... meaning the lowest value byte
in a 32 bit register is stored first... and the highest is
stored last. This is basically always the case with the ARM - however the ARM CPU can actually also work in Big Endian mode. |
The previous LDR and STR worked with 32 bit registers... but
we'll often want to work with bytes, The ARM allows this with a LDRB and STRB command - they work the same as the other commands, but just load a single byte |
|
We loaded in a byte from TestVal with
LDRB... Note that the 24 unused bits of the register changed to 0 We then added 255 - causing the R1 to expand out of a single byte... We then save back with STRB - because we used a byte command, only the low byte was saved |
LDR
and STR work with 32 bit values... LDRB and STRB work at 8
bit...But what about 16 bit? well LDRH and STRH (H=Half) will
load and save 16 bit... but these commands only exist on later processors, the Gameboy Advance uses them fine - but RiscOS can't use them! |
Because
the ARM is 32 bit, a WORD is 32 bits on arm, rather than 16 bit
like on the Z80 or 68000 VASM uses the statement '.long' to define a 32 bit value - but a LONG on the ARM would typically be 64 bit. To avoid confusion the terms WORD and LONG won't be used in these tutorials - the length will be referred to in bits instead |
ARM Thumb doesn't really have predecrement or
postincrement... you'll have to use an extra command to change the
register. There are, however PUSH and POP commands, and the LDMIA/STMIA command. |
There are far fewer
addressing options than the classic arm, but that's the price
you pay for halving the instruction length. Compared to 'Normal' ARM some commands will now take two or more, but overall your program will still end up smaller! |
Flags in THUMB work very much the way they do on a Z80 or 68000,
Some commands set flags, other's don't. Unlike ARM we cannot select
which commands change flags. Here we've used 3 commands, ADD and MOV change the flags. LDR does not. The only way you can know which change the flags is to check the documentation - or the ChibiAkumas Cheatsheet!. |
|
Here are the results |
We're
going to look at some examples of these flags and condition
codes - but really you should try them yourselves! You'll notice commented out code (starting ;) - these are alternative tests you can do to see the conditions in action - Ideally you should try them yourselves, but they'll all be shown on the video! |
Lesson
4
- The Stack... and SWI The Stack in Thumb works basically the same as ARM, but now we have a 'proper' PUSH and POP command... lets recap stack usage, and learn about them. |
Thumb_Lesson4.asm |
'Stacks' in assembly are like an
'In tray' for temporary storage... Imagine we have an In-Tray... we can put items in it, but only ever take the top item off... we can store lots of paper - but have to take it off in the same order we put it on!... this is what a stack does! If we want to temporarily store a register - we can put it's value on the top of the stack... but we have to take them off in the same order... The stack will appear in memory, and the stack pointer goes DOWN with each push on the stack... so if it starts at $2000 and we push 2 bytes, it will point to $1FFE As the ARM is 32 bit, we'll push onto the stack 32 bits at a time. |
If
you're programming the Gameboy Advance or NDS then you'll
probably never need SWI... these tutorials use the firmware as
little as possible, so you won't see it much in those
either... If you're using the firmware though, you'll have to check the manual for Risc-OS, and beware! there are different versions for later Risc OS versions! |
Lesson
5
- More Maths! We're nearly done... but we need to look at operations that work at the bit level, and a few other important commands... lets take a look! |
Thumb_Lesson5.asm |
Test Operations TST
Testing sets the flags like an AND, but does not alter the
registers TST = effectively ANDs the two perimeters setting the flags accordingly |
|
Here is the result! |
Multiplication
Arm Thumb offers a single multiplication command MUL - MUltiplies two parameters together... in this case R0=R0*R1 (R1 is unchanged) |
|
The result of the two operation is shown here 3*2=6 |
We've covered pretty much all
the ARM thumb commands... so if you need something you've not seen
you've got two choices... simulate it with the Thumb commands, or
use BX to switch back to full ARM mode, and use the command there! For example the MRS command to get the flags doesn't exist in ARM Thumb, but this tutorial needed it, a BX to full arm mode was the solution! |