2IC30 · Topic 06
Assembly & Programming
Writing and translating programs: stack, subroutines, interrupts, ARM, and compiling high-level code.
The stack: expressions, push & pull
A stack is a last-in-first-out region of memory, addressed by a stack pointer (SP) . It grows downward: pushing decrements SP, pulling increments it.
Push R2: SUB SP 1 ; STOR R2 [SP] = STOR R2 [--SP] (PUSH R2). Pull R2: LOAD R2 [SP] ; ADD SP 1 = LOAD R2 [SP++] (PULL R2). Pull is the exact inverse of push. The stack systematically evaluates expressions: push operands, then a routine pops two, combines, pushes the result. Convention: the result of evaluating an expression is left on top of the stack.
Auto-decrement/increment addressing makes push/pull single instructions. Expression evaluation mirrors the parse tree: post-order traversal pushes leaves and applies operators. The same stack discipline underlies subroutine calls, local variables and recursion. Crucially, every subroutine must leave the stack unchanged overall (allocate at entry, deallocate before return).
Stack-based evaluation is how calculators (RPN), the JVM and many bytecode VMs work.
The stack: expressions, push & pull worked examples
1 questions
Common mistakes
- Getting push/pull SP direction backwards (grows downward).
- Leaving the stack unbalanced on return.
- Overwriting [SP] before decrementing it.
Exam tips
- Use [--SP] / [SP++] shorthand if allowed; otherwise show SUB/ADD SP explicitly.
- Track SP-relative positions carefully when locals are on the stack.
Memory aids
- Push = SUB SP then STOR; Pull = LOAD then ADD SP.
- Stack grows DOWN; balance it before RTS.
The stack: expressions, push & pull practice
2 questions
Subroutines, recursion & interrupts
A subroutine (procedure/method) is reusable code called from many places. The CPU must remember the return address to resume after the call.
Return addresses differ per call, so storing them on the stack enables nesting and recursion . Instructions: BRS reg disp pushes the return address (reg, RAM[reg−1], IP ← reg−1, IP, IP+disp) and jumps; RTS reg pops it (reg, IP ← reg+1, RAM[reg]). A fixed register/location instead of a stack would forbid recursion. Interrupts : an external signal makes the CPU push IP (and flags) onto the stack and jump to a handler; the handler saves registers it uses, does its work, restores them, and executes RTI (return-from-interrupt, also restoring flags).
An interrupt routine must be transparent : save and restore every register/flag it touches so the interrupted program is unaffected. Hardware-interrupt sequence: device raises IRQ → CPU finishes current instruction, acknowledges → device supplies vector/IRQ number → CPU pushes IP (often PSW) → jumps to the selected handler. Interrupts must complete faster than their period or the main program stalls; they are essential for multitasking and I/O.
Recursion (factorial, tree traversal) relies on the stack; interrupts drive every keyboard press, timer tick and network packet.
Subroutines, recursion & interrupts worked examples
1 questions
Common mistakes
- Using a fixed location for the return address (breaks recursion).
- Interrupt handler that forgets to save/restore a register it clobbers.
- Unbalanced stack after recursion.
Exam tips
- Q5 often involves a subroutine on the stack — state the calling convention explicitly.
- Interrupts/transparency and RTI vs RTS are Q6 favourites.
Memory aids
- Return address on the STACK → recursion works.
- Handlers must be transparent: save → work → restore → RTI.
Subroutines, recursion & interrupts practice
2 questions
The ARM processor
ARM is a real RISC processor (in the Raspberry Pi). All registers are 32-bit; instructions are 32-bit (16-bit in Thumb mode).
Registers R0–R12 general, R13=SP, R14=LR (link register), R15=PC, plus APSR (status flags N,Z,C,V, interrupt-disable I/F, Thumb T, mode bits). Conditional execution : any instruction can carry a condition (ADDCS adds only if C set; ADDEQ if Z set) — avoids branches, helps pipelining. S suffix sets flags (ADD doesn't; ADDS does). Barrel shifter before the ALU: MOV R0,R1,LSL #2 = R0:=R1·4; LSR (logical), ASR (arithmetic/ signed), ROR, RRX. Immediates : only 8 bits + an even rotate, so e.g. MOV R0,#4096 = MOV R0,#0x40 ROR 26; LDR R0,=big lets the assembler synthesise large constants. Memory: LDR/STR with pre-index [R1,#12], auto-index [R1,#12]!, post-index [R1],#12; LDM/STM load/store multiple (great for stacks). Arithmetic: ADD/ADC/SUB/RSB/SBC; logic AND/BIC/EOR/ORR; MUL/MLA; tests CMP/CMN/TST/TEQ (set flags only). Branch: B (relative), BL (saves PC in LR). Return = MOV PC,LR (push LR for recursion).
Signed vs unsigned condition mnemonics after CMP: unsigned HI/LS/HS/LO, signed GT/LT/GE/LE — they work correctly even when the subtraction overflows. SWI/SVC (software interrupt) calls a supervisor routine: LR:=PC−4, PC:=8, CPSR saved — the basis of system calls. ARM has security modes (user vs supervisor vs interrupt): user mode sees restricted memory; a software interrupt is the controlled gateway to privileged operations. Multithreading primitives LDREX/STREX implement atomic operations.
ARM powers virtually all phones and the Raspberry Pi; conditional execution and the barrel shifter are signature ARM features.
The ARM processor worked examples
1 questions
Common mistakes
- Forgetting the S suffix when you need flags set.
- Trying to MOV an arbitrary 32-bit immediate directly (use LDR =value).
- Forgetting to preserve LR before a nested/recursive BL.
Exam tips
- You may answer Q5 in ARM or PP2 — pick the one you know best and state the calling convention.
- Know LSL #n = ×2ⁿ and ASR #n = ÷2ⁿ (signed).
- SWI mechanics (LR, PC:=8, save CPSR) is examinable.
Memory aids
- LR holds the return address; BL sets it, MOV PC,LR returns.
- LSL/LSR/ASR = shift = multiply/divide by powers of two.
The ARM processor practice
2 questions
Translating high-level code to assembly
Compilers translate high-level programs to assembly systematically. Local variables and parameters live on the stack ; you bind each to an SP-relative offset with .equ name offset .
Calling convention (stack-based): caller reserves a word for the result, pushes arguments, then BRS; on entry the stack holds [SP]=return addr, [SP+1]=N, [SP+2]=X, [SP+3]=result slot. The subroutine allocates locals (SUB SP k), runs its body using offsets, deallocates (ADD SP k), and RTS — leaving the result in the reserved slot. Pseudo-instructions: .equ/.set/x= bind names to addresses or constants; .data .int .byte .asciz place data; labels replace hand-counted branch distances. Use LDR R0,=label for full addresses, MOV R0,#x only for small immediates.
Global variables sit on the heap , reached via a global-base register (GB): LDR R1,[GB,#x] . Local-variable offsets must be adjusted when extra words are pushed (a parameter at SP+1 becomes SP+1+locals after SUB SP locals). Control structures translate uniformly (see Module 7): if/while/assignment each have a fixed code template. Generations of languages: 1st machine code, 2nd assembly, 3rd high-level (C/Java), 4th domain-specific (SQL).
This is exactly what gcc/clang do; understanding it demystifies stack overflows, calling conventions and debugging assembly.
Translating high-level code to assembly worked examples
1 questions
Common mistakes
- Forgetting to adjust parameter offsets after allocating locals.
- Not deallocating the stack before RTS.
- Using MOV for an address that doesn't fit in the immediate field.
Exam tips
- State explicitly: how the parameter is passed, where each local lives, how the result returns.
- Use unsigned branches (BHI/BHS) for unsigned loop bounds — a graded detail.
- Comment each line with the high-level statement it implements.
Memory aids
- Caller: reserve result, push args, call, read result, clean up.
- After SUB SP k, add k to every earlier SP-relative offset.
Translating high-level code to assembly practice
2 questions