Cambridge AS & A Level · Part 4 · Chapter 28
Low-level Programming
Turning everyday pseudocode into accumulator instructions, one tiny step at a time. Every table, task and exam question from the chapter is here, with a simulator to run each program, an addressing-mode lab and a queue you can watch move through memory.
Guru Jazzy🎯 Learning objective
By the end of this chapter you should be able to:
- write low-level code that uses the addressing modes immediate, direct, indirect, indexed and relative
📋 28.01 The processor instruction set Table 28.01
Every program in this chapter uses the instruction set below. It belongs to a simple imaginary processor with one general-purpose register, the accumulator (ACC), and an index register (IX).
| Label | Op code | Operand | Explanation |
|---|---|---|---|
| Data movement | |||
| LDM | #n | Immediate addressing. Load the denary number n into ACC. | |
| LDD | <address> | Direct addressing. Load the contents of the given address into ACC. | |
| LDI | <address> | Indirect addressing. The given address holds the address to use; load the contents of that second address into ACC. | |
| LDX | <address> | Indexed addressing. Form the address from <address> + IX and copy its contents into ACC. | |
| LDR | #n | Immediate addressing. Load the denary number n into IX. | |
| MOV | <register> | Move the contents of ACC into the given register (IX). | |
| STO | <address> | Store the contents of ACC at the given address. | |
| STX | <address> | Indexed addressing. Form the address from <address> + IX and copy ACC to it.* | |
| STI | <address> | Indirect addressing. The given address holds the address to use; store ACC at that second address.* | |
| Arithmetic | |||
| ADD | <address> | Add the contents of the given address to ACC. | |
| ADD | #n | Add the denary number n to ACC. | |
| SUB | <address> | Subtract the contents of the given address from ACC. | |
| SUB | #n | Subtract the denary number n from ACC. | |
| INC | <register> | Add 1 to the register (ACC or IX). | |
| DEC | <register> | Subtract 1 from the register (ACC or IX). | |
| Comparison and jumps | |||
| JMP | <address> | Jump to the given address. | |
| CMP | <address> | Compare ACC with the contents of <address>. | |
| CMP | #n | Compare ACC with the denary number n. | |
| CMI | <address> | Indirect addressing. The given address holds the address to use; compare ACC with the contents of that second address. | |
| JPE | <address> | After a compare, jump to <address> if the compare was True. | |
| JPN | <address> | After a compare, jump to <address> if the compare was False. | |
| Input and output | |||
| IN | Key in a character and store its ASCII value in ACC. | ||
| OUT | Output to the screen the character whose ASCII value is in ACC. | ||
| Bit manipulation | |||
| AND | #n | Bitwise AND of ACC with the operand. | |
| AND | <address> | Bitwise AND of ACC with the contents of <address>. | |
| XOR | #n | Bitwise XOR of ACC with the operand. | |
| XOR | <address> | Bitwise XOR of ACC with the contents of <address>. | |
| OR | #n | Bitwise OR of ACC with the operand. | |
| OR | <address> | Bitwise OR of ACC with the contents of <address>. | |
| LSL | #n | Shift the bits in ACC logically n places left. Zeros come in at the right-hand end. | |
| LSR | #n | Shift the bits in ACC logically n places right. Zeros come in at the left-hand end. | |
| Other | |||
| END | Return control to the operating system. | ||
| <label>: | <opcode> | <operand> | Labels an instruction. |
| <label>: | <data> | Gives the symbolic address <label> to the memory location holding <data>. | |
* STX and STI are not in the 9618 syllabus instruction set. The book uses them to show what becomes possible; in the exam you only get the others.
Notation used in the code
| Symbol | Means | Example |
|---|---|---|
| ACC | the accumulator | INC ACC |
| IX | the index register | MOV IX |
| # | immediate addressing: the operand is the value | LDM #34 |
| B | a binary number | B01001010 |
| & | a hexadecimal number | &4A |
| <address> | an absolute address (a number) or a symbolic address (a label) | LDD 200 · LDD TOTAL |
🎮 Name the instruction
Which op code does the job described? Watch for the difference between a value and an address.
🏷️ 28.02 Symbolic addresses
You could write every program with absolute addresses such as LDD 210, but that is very restrictive: add one instruction near the top and every address below it moves, so every operand that refers to them must be rewritten by hand.
Writing LDD TOTAL instead lets you think at a higher level. The assembler works out the absolute address of each label during assembly (the two-pass assembler in Chapter 6, section 6.03).
Absolute
200 LDD 205 201 ADD 206 202 STO 205 203 END 204 205 0 206 7
Symbolic
LDD TOTAL
ADD STEP
STO TOTAL
END
TOTAL: 0
STEP: 7Insert an instruction at the top of the absolute version and 205/206 are now wrong. The symbolic version still works: the assembler simply gives TOTAL and STEP new addresses.
🧩 28.03 Problem-solving and assembly-language programs
To solve a problem in low-level code, break it down into steps small enough for the instruction set. A good way in is to think in the same basic constructs you use in a high-level language: assignment, selection, repetition and input/output. The tables below are design patterns: learn the shape and reuse it.
➡️ Assignment Table 28.02
A ← 34
LDM #34 STO A
To store a value in memory it must first be generated in the accumulator.
B ← B + 1
LDD B INC ACC STO B
Load the value into ACC, increment it, then store ACC back to the same location.
B ← B + A
LDD B ADD A STO B
Load the first value, add the value at the second location, store the result where it is needed.
A ← -A
LDD A XOR #&FF INC ACC STO A ; or LDM #0 SUB A STO A
Assuming 8 bits: XOR with 1111 1111 flips every bit (the one's complement); adding 1 gives the two's complement, which is −A. The alternative sets ACC to 0 and subtracts A.
A ← 2, B ← 10, C ← A + B, D ← A − B. LDM #2
STO A ; A ← 2
LDM #10
STO B ; B ← 10
LDD A
ADD B
STO C ; C ← 12
LDD A
SUB B
STO D ; D ← -8
END
A: 0
B: 0
C: 0
D: 0 Run it in the simulator (Task 28.01). D ends as −8, which in 8-bit two's complement is 1111 1000.
🔀 Selection Table 28.03
IF A = 0
THEN
B ← B + 1
ENDIF LDD A
CMP #0
JPN ENDIF
THEN: LDD B
INC ACC
STO B
ENDIF: ...Load the value to test and compare it with the value you need. If the comparison is False (A ≠ 0), jump over the THEN part. If it is True, fall through into the THEN instructions. The labels THEN and ENDIF are only there to make it readable.
IF A = B
THEN
OUTPUT "Y"
ELSE
OUTPUT "N"
ENDIF LDD A
SUB B
CMP #0
JPN ELSE
THEN: LDM #89
OUT
JMP ENDIF
ELSE: LDM #78
OUT
ENDIF: ...A − B is zero exactly when A = B. If the compare is False, jump to ELSE; if True, the THEN part runs. 89 and 78 are the ASCII codes of Y and N. Note the JMP ENDIF: without it the THEN part would run straight on into the ELSE part.
(LDD A then CMP B would do the same job in one instruction fewer.)
IF A <> 0 THEN B ← A ELSE B ← B − 1 ENDIF LDD A
CMP #0
JPE ELSE ; A = 0, so the condition A <> 0 is False
THEN: STO B ; ACC still holds A, so B ← A
JMP ENDIF
ELSE: LDD B
DEC ACC
STO B ; B ← B - 1
ENDIF: END
A: 5
B: 9 The test is not equal, so this time the jump to ELSE uses JPE. CMP does not change ACC, so A is still there for STO B. Try A = 5 and A = 0 in the simulator.
🔁 Repetition Table 28.04
A ← 0 REPEAT OUTPUT "*" A ← A + 1 UNTIL A = 5
LDM #0
STO A
LOOP: LDM #42
OUT
LDD A
INC ACC
STO A
CMP #5
JPN LOOPStore the counter's start value. Inside the loop, generate the ASCII code of * (42) and output it; load the counter, increment it, save it, and test for the final value. If it has not been reached, jump back to the start of the loop. A REPEAT loop tests at the bottom, so the body always runs at least once.
Number ← 1 Total ← 0 Max ← 5 REPEAT Total ← Total + Number Number ← Number + 1 UNTIL Number = Max
LDM #1
STO NUMBER
LDM #0
STO TOTAL
LDM #5
STO MAX
LOOP: LDD TOTAL
ADD NUMBER
STO TOTAL ; Total ← Total + Number
LDD NUMBER
INC ACC
STO NUMBER ; Number ← Number + 1
CMP MAX
JPN LOOP ; UNTIL Number = Max
END
NUMBER: 0
TOTAL: 0
MAX: 0 Total finishes as 1 + 2 + 3 + 4 = 10: when Number reaches 5 the loop stops before adding it. CMP MAX compares with the contents of MAX (direct), not the number 5 itself.
⌨️ Input and output Table 28.05
INPUT A
IN STO A
Store a character typed at the keyboard at memory location A. ACC holds its ASCII code.
OUTPUT B ; B is a string
LDM #-1
MOV IX
LOOP: INC IX
LDX B
OUT
CMP #13
JPN LOOPTo output a string of characters stored in consecutive locations starting at B, use a loop and indexed addressing. IX starts at −1, so the first time round INC IX makes it 0 and the character at B itself is loaded. After each OUT, check for the end of the string: the carriage-return character, ASCII 13. If it is not the end, jump back.
To output a number, it must first be converted into its string of digit characters, stored in consecutive locations, and then output this way.
INPUT A ; A is a string
LDM #-1
MOV IX
LOOP: INC IX
IN
STX A
CMP #13
JPN LOOPStore a string typed at the keyboard in consecutive locations starting at A. Note: STX is not in the 9618 instruction set, so with the exam set a string cannot be stored like this.
Count ← 0 REPEAT Count ← Count + 1 INPUT Character UNTIL Character = "N"
LDM #0
STO COUNT
LOOP: LDD COUNT
INC ACC
STO COUNT ; Count ← Count + 1
IN
STO CHARACTER ; INPUT Character
CMP #78 ; 78 is the ASCII code of "N"
JPN LOOP
END
COUNT: 0
CHARACTER: 0 OUTPUT Count after the loop.Replace the final END with:
LDD COUNT
ADD #48 ; digit value → ASCII code ("0" is 48)
OUT
END OUT sends a character, so the count must become the ASCII code of its digit. This works for counts 0–9. For a count of 10 or more you would have to split it into tens and units digits and output each one, exactly as the book says about outputting numbers.
🎮 Which code matches?
Fresh pseudocode every time. Pick the assembly code that does exactly what it says. The wrong ones contain the mistakes students really make.
📍 28.04 Absolute and relative addressing
When the program is loaded, its base address is stored in the base register (BR). Instructions then use the base register's value plus an offset. For example STO [BR] + 10 stores ACC at the address (contents of BR) + 10.
The same program three ways Table 28.06
| Symbolic | Offset from START | Relative (BR holds base) | Absolute |
|---|
Change the base address and only the absolute column changes. The relative code is identical wherever it is loaded, which is the whole point.
100; with the base at 200 it should be 200 (every other row is 201–210).END marks the end of the program's instructions.Note: the 9618 instruction set does not include relative (base register) addressing.
🧮 Relative → absolute
👉 28.05 Indirect addressing
Indirect addressing is useful when the address an instruction should use changes while the program runs. One example is a subroutine whose parameters are passed by reference (beyond this book). Another is a pointer variable: a memory location whose contents is an address.
🔬 Lab: the four ways to load ACC
Fresh memory every time. Pick an instruction and see which locations it touches. The amber cell is the operand's address; the green cell is where the value finally comes from.
Your turn
🚶 Worked Example 28.01 · A program for a simple queue
At the top level the problem can be written in structured English:
Add a character to the queue: 1 Store the contents of the accumulator in the memory location pointed to by the tail pointer. 2 Increment the tail pointer.
Remove a character from the queue: 1 Load the contents of the memory location at the head of the queue. 2 Increment the head pointer.
Queue processing Table 28.07
| Label | Op code | Operand | Explanation |
|---|---|---|---|
| JOINQ: | STI | TAILPTR | Store ACC in the location pointed to by the tail pointer |
| LDD | TAILPTR | Increment the tail pointer | |
| INC | ACC | ||
| STO | TAILPTR | ||
| JMP | ENDQ | ||
| LEAVEQ: | LDI | HEADPTR | Load the contents of the location at the head of the queue |
| OUT | Output the character | ||
| LDD | HEADPTR | Increment the head pointer | |
| INC | ACC | ||
| STO | HEADPTR | ||
| JMP | ENDQ | ||
| ENDQ: | |||
| HEADPTR: | QSTART | Pointer to the start of the queue | |
| TAILPTR: | QSTART | Pointer to the next free location in the queue | |
| QSTART: | " " | Start of the memory reserved for the queue, currently empty |
HEADPTR and TAILPTR hold the address QSTART, not a character: they are pointers. As characters join, TAILPTR increases to point just past the end of the queue data; as characters leave, HEADPTR increases to point at the new head. STI TAILPTR and LDI HEADPTR are indirect, because the address they use is whatever the pointer holds at that moment.
Note: the 9618 instruction set does not include STI, so with the exam set a character cannot be added to the queue.
🚶♀️ Watch the queue move
QSTART is at address 50. Join puts the character in ACC into the queue with STI TAILPTR; Leave runs LDI HEADPTR and OUT.
LDD HEADPTR, CMP TAILPTR, JPE …) and would wrap round as a circular queue (Chapter 13).🔄 Task 28.06 · Reverse a word with a stack
Push each character onto a stack until Enter (ASCII 13) is pressed, then pop them off, so the last character typed comes out first. IX is the stack pointer; COUNT remembers how many characters are on the stack, because nothing can compare IX directly.
LDM #0
STO COUNT
LDR #0 ; IX = 0: stack empty
PUSH: IN
CMP #13 ; Enter ends the word
JPE POP
STX STACK ; push: store at STACK + IX
INC IX
LDD COUNT
INC ACC
STO COUNT
JMP PUSH
POP: LDD COUNT
CMP #0 ; stack empty?
JPE DONE
DEC ACC
STO COUNT
DEC IX ; IX back to the top item
LDX STACK ; pop: load from STACK + IX
OUT
JMP POP
DONE: END
COUNT: 0
STACK: 0 ; the stack grows from here Load Task 28.06 in the simulator and type a word: STRESSED comes out as DESSERTS.
🖥️ The accumulator machine
This runs the whole of Table 28.01, including STX, STI and relative addressing. Choose one of the chapter's programs or a model answer, or write your own, then Step through it. Memory, ACC, IX, BR and the program counter update as you go, and every step is added to the trace table.
- One instruction or data value per line. A line can start with an address (
200 LDM #0, always followed by something) or a label (LOOP: OUT), or both. Lines without an address follow on from the line before. - A line holding just a number is a data value, so a string can be listed one code per line. A data line can also be a label name, which stores that label's address (
HEADPTR: QSTART): a pointer. - Values can be denary
48, binaryB0011or hex&30; immediate operands start with#. - Relative operands are written
[BR] + 10. Set the base register with.BR 200. ;starts a comment..ACC n,.IX nand.BR nset registers before the run;.START asets where it begins.INtakes from the Input box: each key's ASCII code, the same followed by Enter (13), or whole numbers.
| Addr | Label | Contents |
|---|
| Step | Addr | Instruction | ACC | IX | Memory | Output |
|---|
🛠️ Challenge: write it yourself
Write each one in the simulator, then compare with the model.
IN
STO FIRST
IN
STO SECOND
SUB FIRST ; SECOND - FIRST
AND #B10000000 ; sign bit set if the result is negative
CMP #0
JPN FIRSTBIG
LDD SECOND
JMP SHOW
FIRSTBIG: LDD FIRST
SHOW: OUT
END
FIRST: 0
SECOND: 0 There is no "jump if greater" instruction, so this looks at the sign bit of SECOND − FIRST (8-bit two's complement). Digits are ASCII codes 48–57, so the difference is small and the sign bit is reliable.
LDM #57 ; "9"
LOOP: OUT
DEC ACC
CMP #47 ; one below "0"
JPN LOOP
END LDM #0
STO COUNT
LOOP: IN
CMP #13
JPE DONE
LDD COUNT
INC ACC
STO COUNT
JMP LOOP
DONE: LDD COUNT
ADD #48
OUT
END
COUNT: 0 This is a WHILE loop: it tests before counting, so pressing Enter straight away gives 0. Choose "… then Enter (13) at the end" for the input.
🪞 Reflection point
These are the addressing modes you should be able to use when writing low-level code. Tick the ones you can use easily; cross the ones you find difficult, and revisit them.
| Addressing mode | Example | Me |
|---|
📌 Summary
- A problem must be broken down into simple steps that can be programmed using the processor's instruction set.
- A value must be copied into the accumulator before it can be processed.
- Processing includes: arithmetic (adding, incrementing, decrementing); comparison (equal or not equal); bitwise operations (AND, OR, XOR, shifting); and output to the screen.
- A value can be put in the accumulator by input from the keyboard, by immediate addressing, or by loading it from memory using direct, relative, indirect or indexed addressing.
- An address can be absolute (a number) or symbolic (a label).
🃏 Key-term flashcards
Tap a card to flip it.
✅ Quick quiz
📝 Exam-style questions
Write your answer first, then open the model answer. These are my mark-scheme-style points, not official mark schemes.
Question 1 uses this instruction set for a processor with one general-purpose register, the accumulator:
| Label | Op code | Operand | Explanation |
|---|---|---|---|
| LDD | <address> | Direct addressing. Load the contents of the given address into ACC | |
| STO | <address> | Store the contents of ACC at the given address | |
| ADD | <address> | Add the contents of the given address to ACC | |
| IN | Key in a character and store its ASCII value in ACC | ||
| AND | <address> | Bitwise AND of ACC with the contents of <address> | |
| LSL | #n | Shift the bits in ACC logically n places left; zeros come in at the right | |
| END | Return control to the operating system | ||
| <label>: | <data> | Gives the symbolic address <label> to the location holding <data> | |
Each bit of ACC is ANDed with the bit in the same position of the contents of the given address; a result bit is 1 only if both bits are 1, and the result is left in ACC.
AND MASK, where MASK: B00001111 (&0F). The mask keeps the low nibble (the digit's value) and clears the 0011 in the high nibble. For example '7' = 0011 0111 AND 0000 1111 = 0000 0111 = 7.
IN ; input first digit
AND MASK ; convert from ASCII to its digit value
LSL #4 ; move to upper nibble
STO RESULT ; store in location Result
IN ; input second digit
AND MASK ; convert from ASCII to its digit value
ADD RESULT ; combine the two values
STO RESULT ; store result
END ; end of program
MASK: B00001111 ; mask to convert from ASCII to digit
RESULT: &00 ; memory location for result Only ADD can combine the nibbles with this instruction set (there is no OR here). That is safe because the low nibble of RESULT is 0000 after the shift. Keying in 4 then 7 gives 0100 0111 = &47. Load Exam question 1c in the simulator to check.
Question 2 is for a processor with one general-purpose register, ACC, and one index register, IX. Part of its instruction set: LDM #n, LDD, LDX (indexed), LDR #n, STO, STX, ADD, INC, JMP, CMP <address>, CMP #n, JPE, JPN, IN, END, plus labels.
LDR #0 ; set index register to zero
LOOP: LDX STRING ; load ACC with the character at STRING + IX
INC IX ; increment index register
OUT ; output character
CMP #33 ; is this character the ! key?
JPN LOOP ; no: jump to beginning of loop
END ; end of program
STRING: 72 ; H
69 ; E
76 ; L
80 ; P
33 ; ! The output is HELP!. The '!' is output before the test, so it appears too.
OUT, but the program cannot be written without it ("Output character"). Assume OUT is available, as in Table 28.01.Load Exam question 2 in the simulator to run it.
Guru Jazzy · Cambridge AS & A Level Computer Science · Chapter 28 Low-level Programming · Opener image from the Cambridge coursebook (Langfield & Duddell)