AQA assembly language and bitwise operations
AQA's instruction set, compare and branch, masks and shifts, and the bit fields that drive BugBot's motors.
Do this lesson in the simulatorAQA does not use the Little Man Computer. Its exam papers use their own assembly language, modelled on the ARM processors in phones, with thirteen general-purpose registers and a proper compare instruction. This lesson teaches that language, and the bitwise operations that low-level programs use to pack several values into one byte. That is exactly how BugBot's processor talks to its motor drivers.
The instruction set
AQA gives this table in the exam, so you do not need to memorise it, but you do need to use it fluently. Rd and Rn are register numbers from 0 to 12. <memory ref> is a memory address, used with direct addressing. <operand2> is either #n, an immediate value, or Rm, the value in a register.
| Instruction | What it does |
|---|---|
LDR Rd, <memory ref> |
load the value stored at the memory address into register d |
STR Rd, <memory ref> |
store the value in register d at the memory address |
ADD Rd, Rn, <operand2> |
add operand2 to the value in register n, and store the result in register d |
SUB Rd, Rn, <operand2> |
subtract operand2 from the value in register n, and store the result in register d |
MOV Rd, <operand2> |
copy operand2 into register d |
CMP Rn, <operand2> |
compare the value in register n with operand2 |
B <label> |
always branch to the instruction at the label |
B<condition> <label> |
branch to the label if the last comparison met the condition: EQ equal, NE not equal, GT greater than, LT less than |
AND Rd, Rn, <operand2> |
bitwise AND of register n and operand2, into register d |
ORR Rd, Rn, <operand2> |
bitwise OR of register n and operand2, into register d |
EOR Rd, Rn, <operand2> |
bitwise exclusive OR (XOR) of register n and operand2, into register d |
MVN Rd, <operand2> |
bitwise NOT of operand2, into register d |
LSL Rd, Rn, <operand2> |
shift the value in register n left by operand2 places, into register d |
LSR Rd, Rn, <operand2> |
shift the value in register n right by operand2 places, into register d |
HALT |
stop the program |
A label is written as a name followed by a colon, and names the instruction after it.
Remember the difference between MOV R0, #100, which puts the number 100 in R0 (immediate), and LDR R0, 100, which loads the value stored at address 100 (direct).
Selection
Store the larger of the values at addresses 100 and 101 in address 102:
LDR R0, 100
LDR R1, 101
CMP R0, R1
BGT first // R0 > R1
STR R1, 102 // R1 was bigger, or they were equal
B done
first:
STR R0, 102
done:
HALT
The unconditional B done matters: without it, the processor would fall straight through into the first: code as well.
Iteration
Add up 1 to 5 and store the total at address 100:
MOV R0, #0 // total
MOV R1, #1 // counter
loop:
ADD R0, R0, R1
ADD R1, R1, #1
CMP R1, #6
BNE loop
STR R0, 100
HALT
| R0 | R1 at CMP | Branch? |
|---|---|---|
| 1 | 2 | yes |
| 3 | 3 | yes |
| 6 | 4 | yes |
| 10 | 5 | yes |
| 15 | 6 | no |
Address 100 ends up holding 15.
Running AQA assembly in Python
This interpreter handles every instruction in the table, with 8-bit registers, so you can check your own programs:
def run_aqa(source, memory):
program, labels = [], {}
for text in source.split("\n"):
text = text.split("//")[0].strip()
if ":" in text:
label, text = text.split(":", 1)
labels[label.strip()] = len(program)
text = text.strip()
if text:
program.append(text)
r = [0] * 13
pc, compare = 0, 0
def reg(name):
return int(name.strip()[1:])
def op2(text):
text = text.strip()
return int(text[1:]) if text.startswith("#") else r[reg(text)]
while True:
mnemonic, _, rest = program[pc].partition(" ")
a = [part.strip() for part in rest.split(",")]
pc = pc + 1
if mnemonic == "HALT": return r
elif mnemonic == "LDR": r[reg(a[0])] = memory[int(a[1])]
elif mnemonic == "STR": memory[int(a[1])] = r[reg(a[0])]
elif mnemonic == "MOV": r[reg(a[0])] = op2(a[1]) & 255
elif mnemonic == "ADD": r[reg(a[0])] = (r[reg(a[1])] + op2(a[2])) & 255
elif mnemonic == "SUB": r[reg(a[0])] = (r[reg(a[1])] - op2(a[2])) & 255
elif mnemonic == "AND": r[reg(a[0])] = r[reg(a[1])] & op2(a[2])
elif mnemonic == "ORR": r[reg(a[0])] = r[reg(a[1])] | op2(a[2])
elif mnemonic == "EOR": r[reg(a[0])] = r[reg(a[1])] ^ op2(a[2])
elif mnemonic == "MVN": r[reg(a[0])] = ~op2(a[1]) & 255
elif mnemonic == "LSL": r[reg(a[0])] = (r[reg(a[1])] << op2(a[2])) & 255
elif mnemonic == "LSR": r[reg(a[0])] = r[reg(a[1])] >> op2(a[2])
elif mnemonic == "CMP": compare = r[reg(a[0])] - op2(a[1])
elif mnemonic == "B": pc = labels[a[0]]
elif mnemonic == "BEQ" and compare == 0: pc = labels[a[0]]
elif mnemonic == "BNE" and compare != 0: pc = labels[a[0]]
elif mnemonic == "BGT" and compare > 0: pc = labels[a[0]]
elif mnemonic == "BLT" and compare < 0: pc = labels[a[0]]
memory = [0] * 256
memory[50] = 0b10110110
r = run_aqa("""
LDR R0, 50
AND R1, R0, #15
ORR R2, R0, #1
EOR R3, R0, #255
MVN R4, R0
LSL R5, R0, #1
LSR R6, R0, #2
HALT
""", memory)
for i in range(7):
print("R" + str(i), format(r[i], "08b"), r[i])
What the bitwise operations are for
Each works on every bit at once, bit by bit. With 10110110 (182) in R0:
| Instruction | Result | Use |
|---|---|---|
AND R1, R0, #15 |
00000110 (6) |
mask: keep only the bits that are 1 in the mask (here the bottom four), clearing the rest; or test whether a bit is set |
ORR R2, R0, #1 |
10110111 (183) |
set chosen bits to 1, leaving the rest alone |
EOR R3, R0, #255 |
01001001 (73) |
toggle chosen bits; XOR with all ones flips every bit |
MVN R4, R0 |
01001001 (73) |
NOT: flip every bit |
LSL R5, R0, #1 |
01101100 (108) |
shift left: each place multiplies by 2, as long as no 1 falls off the top (here one did, so 364 became 108) |
LSR R6, R0, #2 |
00101101 (45) |
shift right: each place divides by 2, throwing away the remainder (182 / 4 = 45 remainder 2) |
A common exam question: test whether a number is odd. AND R1, R0, #1 leaves 1 if the lowest bit was set, so follow it with CMP R1, #1 and BEQ odd.
Bits that drive motors
A register inside a hardware chip is often split into fields, several values packed into one byte. BugBot's motor drivers work like this: the processor writes one byte to a driver's control register, over a two-wire bus called I2C, and:
| Bits 7 to 2 | Bits 1 and 0 |
|---|---|
| speed setting, 0 to 63 | mode: 00 coast, 01 forward, 10 reverse, 11 brake |
To read the fields back out: shift right by 2 to get the speed (the mode bits fall off the end), and AND with 00000011 to keep only the mode. To build a byte: shift the speed left by 2 and OR in the mode.
speed = 40
mode = 0b01 # forward
byte = (speed << 2) | mode # LSL, then ORR
print("control byte", format(byte, "08b"), "=", byte)
print("speed back out:", byte >> 2, " mode back out:", byte & 0b11)
Task: decode the control bytes
commands holds four motor control bytes, each a whole number from 0 to 255 laid out as in the table above. For each byte, use bitwise operators (>> and &) to split it into its speed (0 to 63) and its mode, and:
- print a line in the form
161: speed 40 forward: the byte in denary, the speed, then the mode ascoast,forward,reverseorbrake; - then act on it:
forwarddrives forward 10 cm at that speed,reversedrives backward 10 cm at that speed,brakecallsstop(), andcoastdoes nothing.
Do not convert the byte to a string of bits.
# the two lines every program starts with: the commands, then the robot
from bugbot import *
connect()
commands = [161, 98, 3, 0]
modes = ["coast", "forward", "reverse", "brake"]
Challenges
- Write AQA assembly that sets bit 0 of the value at address 60 to 1 and stores it back, leaving the other bits alone.
- Write AQA assembly that multiplies the value in R0 by 10 using only shifts and one ADD. (Hint: 10 = 8 + 2.)
- Write AQA assembly that counts how many of the bits in R0 are 1.