Index


RISC World

ARM code for Beginners
Part 5

Stacks and Subroutines - Brain Pickard explains how anyone can program in ARM code.

Last time I introduced the method of saving data to and loading data from memory. In this part I will extend these ideas by introducing a data structure called a STACK and show how this can be used to save and load values in registers. But first the solutions for the problems set in part 4.

Write an ARM routine using a lookup table to produce the answer to the (cube-square) of integer from 1 to 256. This can be done in a few lines.

     DIM code% 2048
     FOR pass%=0 TO 3 STEP3
     P%=code%
     [ OPT pass%
     ADR R2,table%        ;R2 points to start of the table
     SUB R2,R2,#4         ;subtract 4 since we are multiplying
                          ;the index R1by 4 (starting value
     LDR R0,[R2,R1,LSL#2] ;of R1 is 1 B% in BASIC) using LSL#2
                          ;to move to the correct word
     MOV PC,R14           ;in the table
     .table%
     ]
     FOR n%=1 TO 256
     [ 
     OPT FNformula(n%,pass%) ;this function sets up the lookup table
     ]
     NEXT
     NEXT
     PRINT"Formula macro Cubes-Squares"
     REPEAT
     INPUT"Enter an integer (1 to 256) "B%
                           REM this means R1 will contain index value
     IF B%>0 THEN PRINT"Its Cube-Square is ";USR(code%)
     UNTIL B%=0
     END
     DEF FNformula(n%,p%)
     [
     EQUD INT((n%^3)-(n%^2)) ;works out the
                             ;cube-square for number held in n%
     ]
     =p%

Write a routine to input a set of 10 positive integers storing them in memory, and then printing them out. You can use BASIC for inputting, printing and ARM code for storing in memory. This is slightly more involved.

     MODE28
     DIM code% 2048
     FOR pass%=0 TO 3 STEP3
     P%=code%
     [OPT pass%
     LDR R1,index%    ;we need to remember the position
                      ;in memory for each store
     ADR R2,memstore% ;R2 points to the start of the memory block
     STR R0,[R2,R1]   ;store the value at R2+R1
     ADD R1,R1,#4     ;update index to next word ready
                      ;for next number
     STR R1,index%    ;store this value
     MOV PC,R14
     .index%
     EQUD 0
     .memstore%
     ]
     FOR n%=1 TO 10
     [OPT pass%
     EQUD 0
     ]
     NEXT
     NEXT
     PRINT�Storing 10 numbers in memory�
     FOR n%=1 TO 10
     INPUT�Enter an integer �A%
     CALL code%
     NEXT
     FOR n%=0 TO 9
     PRINT;memstore%!(n%*4);�,�;
     NEXT

Write a routine which will allow a user to enter a password. Each key press should produce a * on the screen. The password should then be checked against one already in memory and if correct a beep should be heard. The password should be case insensitive and at least 10 characters long. (Hint there are a set of SWI's which output single characters: SWI256+42 will output a "*" and a beep is SWI256+7). This is the most difficult one!

MODE28 DIM code% 2048 FOR pass%=0 TO 3 STEP3 P%=code% [OPT pass% ADR R0,message% ;R0 points to message 'Enter password' SWI�XOS_Write0� ;this SWI writes it to the screen ADR R1,memstore%;R1 points to memory store for user ;entered password MOV R2,#0 ;R2 to be used as counter/index for ;storing chrs in memory .inputlp% ;start of the input loop SWI�XOS_ReadC� ;this SWI waits till key pressed ASC code ;of key then loaded in R0 CMP R0,#13 ;has the user pressed Return i.e. finished ;entering password BEQ chkpass% ;if branch to checking routine ORR R0,R0,#32 ;if not then make all letters ;lowercase (case insensitive) CMP R0,#ASC(�a�);check range of key pressed if the key ;is below a or above z BLT inputlp% ;then reject this character CMP R0,#ASC(�z�);do not save it to memory and do not BGT inputlp% ;print a * SWI256+42 ;SWI256+ swi's print to screen the ;character whose ASCII code added STRB R0,[R1,R2] ;store the letter in memory note the ;byte suffix only require 1 byte/chr CMP R2,#10 ;is this the tenth letter ADDLT R2,R2,#1 ;if not then add one to index for next ;letter else stay at tenth position B inputlp% ;branch back for next input .chkpass% ;this is checking routine ADR R3,password%;R3 points to the memory block ;containing the correct password MOV R2,#0 ;R2 is the index .comparelp% ;here starts the comparison loop LDRB R0,[R1,R2] ;load user entered letter again note ;the use of byte suffix LDRB R4,[R3,R2] ;load correct password letter ADD R2,R2,#1 ;add one to index CMP R0,R4 ;compare these letters if they are not equal BNE getout% ;then branch to getout condition NOT EQUAL CMP R0,#0 ;see if we have reached the end of the ;user entered password (the BGT comparelp% ;memory block ends with a zero byte). If ;not go and compare next CMP R4,#0 ;see if we have reached the end of ;the correct password again this BGT comparelp% ;ends with a zero byte. If not go back ;for next comparison .getout% ;this part is reached under two conditions. ;If equal then entered SWIEQ256+7 ;password correct so beep. If incorrect ;then condition was not equal. MOV PC,R14 ;return to BASIC. .message% ;start of memory block for message EQUS�Enter password � EQUB 0 EQUB 0 .memstore% ;start of memory store for user entered ;password. EQUD 0 ;this is twelve bytes long (3 words) so ;that there will always be EQUD 0 ;a zero byte after the last allowed letter EQUD 0 .password% EQUS�mypassword�;this is the allowed password can be altered ;but must be 10 chrs long EQUB 0 EQUB 0 ] NEXT CALL code%

As I have mentioned before if you have found a different way which works your solution is correct. In fact if you have managed a short cut please let me know!

Stacks

A stack is a block of memory which can be used to store and retrieve data. This data structure is used in many systems such as programming languages, assemblers etc. In fact the PostScript printer language invented by Adobe is based round stacks to store data and procedures.

If you imagine building a pile of coins (pence or pounds depending how rich you feel!). Placing coins on the pile is like saving data items onto a stack and removing coins off the pile is like retrieving data from a stack. Note the order in which you place and then remove the coins. You can only get to the top coin at any one time, so the last coin on will be the first one removed. This is called Last In First Out or LIFO. The action of saving data to a stack is called pushing and retrieving data is called pulling.

A computer must remember where the last data was placed in the block of memory. A stack pointer (SP) is used (see below). Referring to the diagram below.

This shows a small stack of 5 items of data. If we push an item then the length of the stack will grow as shown in the middle diagram. If we then pull 3 items of data off the stack it will shrink to 3 items in length as shown on the right.

Types of Stack

There are two types of stack called ascending and descending. The coin analogy was an example of an ascending stack. As data is pushed onto the stack the stack grows upwards (i.e. up the memory block).

A descending stack starts at the top of the memory block. Pushing data onto this type makes the stack 'grow' downwards (i.e. down the memory block).

The Stack Pointer (SP)

This pointer can be updated in different ways. Consider the following.

Here SP always points to the last item of data pushed onto or the next item of data to be pulled off the stack. Under these conditions, for an ascending stack the SP is updated as follows.

Pushing data

SP incremented upwards (or downwards for a descending stack).Data then pushed into the location SP is pointing.

Pulling data

Data pulled from the location SP is pointing, SP decremented downwards (upwards for an descending stack).

So for pushing SP is updated BEFORE the data is pushed and for pulling SP is updated AFTER the data is pushed. There is no reason why SP cannot be updated to point to the next available location (i.e. empty location) as shown below.

Under these conditions SP is updated as follows (for an ascending stack).

Pushing data:
Data pushed into the location SP is pointing.
SP incremented upwards (or downwards for a descending stack).

Pulling data:
SP decremented downwards (upwards for an descending stack).
Data pulled from the location SP is pointing so the location becomes the next free location.

As you can see this is the reverse of the previous case.

Full and Empty Stacks

A stack setup with SP pointing to the location containing data is called a FULL STACK. A stack setup with SP pointing to the next free location is called an EMPTY STACK.

So there are four ways of implementing a stack

  • Full ascending
  • Full descending
  • Empty ascending
  • Empty descending

Implementing Stacks in ARM code

There are two commands LDM and STM for loading or storing data to or from one or more registers. (LoaD Multiple registers and STore Multiple registers) These are used with suffices IA, IB, DA and DB to implement the four types of stack.

STMIA SP!,{register list}
This stores the register list Incrementing SP After each store. Note the use of the curly brackets { } This is pushing data onto an empty ascending stack.

LDMDB SP!,{register list}
This loads the register list Decrementing SP Before each load. This is pulling data from an empty ascending stack.

Note the use of the ! Write back is essential for stack implementation. Without it SP would not be updated! SP is a register whose contents point to the required position in memory being used as a stack. Inside {} a list of registers containing the data to be pushed or pulled. This list can be written different ways.

     LDMDB SP!,{R0,R3,R5}
     LDMDB SP!,{R0-R4}
     LDMDB SP!,{R0-R3,R6-R9,R11}

The use of the - means an inclusive list e.g. R0-R3 means R0 to R3. Register 13 is often used for SP (see later).

I will leave you to work out the suffices needed for implementing the above for the other types of stack.

Suffices FA,FD,EA and ED

As you might have noticed this is all a bit confusing! So the designers of the ARM assembler include the above suffices which make stack operations simpler.

FA and FD stand for Full Ascending and Descending stacks.
EA and ED stand for Empty Ascending and Descending stacks.

This simplifies the LDM and STM commands.

  • To implement a full descending stack use STMFD and LDMFD
  • For a full ascending stack STMFA and LDMFA
  • Empty descending STMED and LDMED
  • Empty ascending STMEA and LDMEA

It is up to the user to decide which type of stack to use. (I normally use full descending as this is the type of stack set up by Risc OS when using ARM code)

ARM code examples STMFD R13!,{R0-R8,R14} stores contents of R0 to R8 inclusive then R14 in a full descending stack using the contents of R13 as the stack pointer. LDMFD R13!,{R0-R8,R14} would then load the contents back into the registers. STMEA R0!,{R0,R5-R8} stores contents of R0 (which is the SP) then R5 to R8 in an empty ascending stack.

Using Stacks

This has been quite a long description but it is important to understand stacks. Now we come to their use. When you enter an ARM code routine from BASIC Risc OS treats it as a subroutine. It places the return address in R14, hence we have used MOV PC,R14 to return to BASIC. What if we wish to use R14 or modify its contents during our routine? We could use the STR R14,returnadd% then to return to BASIC we would have to use LDR PC,returnadd% This is fine for R14 but what if we wish to return to BASIC with the contents of ALL the registers preserved? This is where a stack is useful.

RISC OS sets up a full descending stack with the stack pointer in R13 when we enter an ARM routine.

     STMFD R13!,{R0-R12,R14}
will push the values of all useable registers onto the stack.
     LDMFD R13!,{R0-R12,PC}
will pull the values back into the registers and return us back to BASIC.

Subroutines

Duplicating code in any language leads to large and less easily serviced routines. Consider the problem of potting the following design.

There are two routines required to plot (red square, white circle). This could be done in a loop but what if we wish to plot the design smaller or larger, or even change the colours?

It would be better if we could produce code which acts like procedures in BASIC. These would take the three parameters (position, size and colour) and produce the design. Such routines are called subroutines.

ARM code Subroutines

To call a subroutine inside ARM code we use the branch command with an added link suffix thus:

     BL subroutine%

     .
     .
     BL plot%
     .
     .

The subroutine is defined else where in the code thus.

     .plot%
     MOV R0,R3  ;start of the subroutine coding
     .
     .
     .
     MOV PC,R14     ;returning back to the main routine.

Subroutines within subroutines

In our design example the three plotting subroutines should be placed in a subroutine thus

     .plotdesign%
     .
     .
     BL plotsquare%
     .
     .
     BL plotcircle%
     .
     .
     MOV PC,R14

There is a problem with the above structure. On entering the plotdesign subroutine R14 will contain the return address to the main routine. Branching to plotsquare will cause R14 to be over written with the return address to plotdesign. Similarly when branching to the other plot routines. I will leave it to you to see why the MOV PC,R14 make the routine go into endless loop!

To get over this problem we must store the contents of R14 before we branch to a sub routine, then restore the value when we return from a subroutine using it to return thus:

     .plotdesign%
     STMFD R13!,{,R14} 
     BL plotsquare%
     BL plotcircle%
     LDM R13!,{, PC}

I have included the design program called subrtine for you to try out. It starts with a black screen. Clicking the left mouse button produces the design with the top left hand corner of the square at the pointer position.

Clicking at another position will draw the design at that position. Clicking with the right mouse button will change the size of the design and change the colours. Clicking with the middle mouse button will exit the routine.

The SWI"OS_Mouse" is used to determine the mouse state. This SWI returns the following data: R0 screen x coordinate, R1 screen y coordinate, R2 button state and R3 the time when button state changed.

Using this method of storing contents of registers is similar to the use of local variables in BASIC.

Try these problems:

  • Write an arm routine containing a sub routine to plot two concentric circles of differing radii and colours. Use the left mouse button to increase the radius and the right mouse button to change the colour.
  • Using a full descending stack write a routine in which the user enters integers which are saved on the stack. The routine should then print the numbers as it pulls them off the stack, hence showing the stack is LIFO.

That's all for this time. Next time I will return to the BASIC CALL command and show how we can use BASIC variables in our ARM code.

Brian Pickard

 Index