Skip to content

Procedures and the Stack ​

Laboratory Manual

The lab manual contains the full theory, circuit diagrams, and requirements for this experiment - keep it open while you work. Download Experiment 4 PDF

Examples ​

Example 1: Nested Uppercase Counter ​

This example demonstrates a nested call: CountUpperNested(ptr) scans a null-terminated string and calls IsUpper(ch) for each character. It shows saving/restoring LR and using callee-saved registers (R4 for the running count, R5 for the scan pointer). Both values must survive the BL IsUpper call, so under the AAPCS they cannot be kept in caller-saved registers like R0–R3. Compare it with Example 2 of Experiment 3, which counts the same letters inline with no procedures.

asm
        AREA    RESET, CODE, READONLY
        EXPORT  __Vectors
__Vectors
        DCD     0x20001000          ; Initial SP
        DCD     Reset_Handler       ; Reset vector
        ALIGN
        AREA    MYCODE, CODE, READONLY
        ENTRY
        EXPORT  Reset_Handler
IsUpper                              ; IsUpper(R0 = ch) -> R0 = 1 if 'A'..'Z', else 0
        CMP     R0, #'A'
        BLT     not_upper
        CMP     R0, #'Z'
        BGT     not_upper
        MOV     R0, #1
        BX      LR
not_upper
        MOV     R0, #0
        BX      LR
CountUpperNested                    ; CountUpperNested(R0 = ptr) -> R0 = Upper Count
        PUSH    {R4, R5, LR}        ; save callee-saved regs + return address
        MOV     R5, R0              ; R5 = ptr (callee-saved: must survive the BL below)
        MOV     R4, #0              ; R4 = count
cu_next
        LDRB    R0, [R5], #1        ; R0 = *ptr++; post-increment pointer in R5
        CBZ     R0, cu_done         ; if null terminator, finish
        BL      IsUpper             ; R0 = 0/1 based on 'A'..'Z' (R0-R3 are caller-saved: may be clobbered)
        ADD     R4, R4, R0          ; count += result
        B       cu_next
cu_done
        MOV     R0, R4              ; return count in R0
        POP     {R4, R5, PC}
Reset_Handler
        LDR     R0, =mystring
        BL      CountUpperNested
        LDR     R2, =UPPERCOUNT
        STR     R0, [R2]
STOP    B       STOP
        AREA    CONSTANTS, DATA, READONLY
mystring DCB    "Hello ARM World!", 0  ; Uppercase: H, A, R, M, W -> 5
        AREA    MYDATA, DATA, READWRITE
UPPERCOUNT  SPACE 4                    ; should be 5
        END

Check: Verify that UPPERCOUNT contains 5 for the test string.

Example 2: Integer to Decimal String with the Stack ​

UIntToStr(value, buf) converts an unsigned integer into a null-terminated decimal ASCII string and returns the number of digits in R0. It finds each digit by dividing by 10 (UDIV, then MLS for the remainder), and this produces the digits in reverse order, least-significant first. Pushing each digit and then popping them all back reverses that order again, because the stack is last-in, first-out. Each PUSH {R12} takes a full 4-byte word even though it holds only one character. Because every push is matched by a pop, SP returns to its starting value before the final POP {R4, PC}.

asm
        AREA    RESET, CODE, READONLY
        EXPORT  __Vectors
__Vectors
        DCD     0x20001000          ; Initial SP
        DCD     Reset_Handler       ; Reset vector
        ALIGN
        AREA    MYCODE, CODE, READONLY
        ENTRY
        EXPORT  Reset_Handler
UIntToStr                           ; UIntToStr(R0 = value, R1 = buf) -> R0 = digit count
        PUSH    {R4, LR}            ; save callee-saved R4 and the return address
        MOV     R2, #0              ; R2 = digit count
        MOV     R3, #10             ; R3 = divisor
its_div
        UDIV    R4, R0, R3          ; R4 = value / 10
        MLS     R12, R4, R3, R0     ; R12 = value - (value / 10) * 10 = value % 10
        ADD     R12, R12, #'0'      ; digit -> ASCII character
        PUSH    {R12}               ; digits are produced least-significant first...
        ADD     R2, R2, #1          ; count++
        MOV     R0, R4              ; value = value / 10
        CMP     R0, #0
        BNE     its_div             ; repeat until value is 0 (value 0 still gives "0")
        MOV     R0, R2              ; return value = digit count
its_pop
        POP     {R12}               ; ...so popping returns them most-significant first
        STRB    R12, [R1], #1       ; *buf++ = character
        SUBS    R2, R2, #1          ; count--
        BNE     its_pop
        MOV     R12, #0
        STRB    R12, [R1]           ; null terminator
        POP     {R4, PC}            ; restore R4 and return (SP is back where it started)
Reset_Handler
        LDR     R0, =40213          ; value to convert
        LDR     R1, =NUMSTR         ; destination buffer in RAM
        BL      UIntToStr           ; R0 = 5, NUMSTR = "40213"
STOP    B       STOP
        AREA    MYDATA, DATA, READWRITE
NUMSTR  SPACE   12                  ; up to 10 digits + null (written before it is read)
        END

Check: Verify that R0 = 5 after the call, and open a Memory window at NUMSTR to confirm the bytes 34 30 32 31 33 00 ("40213"). Single-step the first loop with the Registers window open and watch SP drop by 4 for each digit. Then confirm that it climbs back during the second loop. Try the value 0 as well; the result should be "0".

Tasks ​

Task 1: Count Vowels with Procedures ​

Rewrite Task 1 of Experiment 3 as procedures:

  • Create a procedure IsVowel that takes a character in R0 and returns 1 in R0 if it is a vowel (a, e, i, o, u, upper- or lowercase), and 0 otherwise.
  • Create a procedure CountVowels that takes a string pointer in R0 and returns the number of vowels in R0, calling IsVowel for each character.
  • Follow AAPCS conventions for parameter passing and register usage.
  • Test it with the Experiment 3 string; the result must match your earlier answer.

Task 2: Factorial Calculation (Recursive) ​

Note on recursion

Example 1's nested call has one procedure calling a different one. In recursion, FactorialRec calls itself. Each call needs its own copy of LR (and of any register it modifies) preserved across the inner call. So every invocation must PUSH its own LR, and usually the current n or partial result, before recursing, then POP them back on the way out. Because each active call keeps its own saved values on the stack, the recursion unwinds in reverse order as each POP/BX LR returns to its caller.

Implement a recursive procedure to calculate the factorial of a non-negative integer:

  • Create a procedure FactorialRec that takes a non-negative integer in R0 and returns its factorial in R0.
  • Use recursion to compute the factorial, with a base case that handles 0! = 1.
  • Manage the stack appropriately to save and restore registers as needed.
  • Follow AAPCS conventions for parameter passing and register usage.
  • Test the procedure with several inputs (including 0 and 1) to verify correctness.