The problem: The Robot With One Pocket
The school shop has a new robot that stacks tins of beans into a triangle: one tin on the top row, two tins on the row below, three below that, and so on, down to
Ntins on the bottom row.The robot is not clever. It has exactly one pocket, which holds one number at a time, and a row of boxes with names written on them. It follows a list of instruction cards, one card at a time, from the top:
N DC 4 SUM DC 0 LOOP LOAD SUM ADD N STORE SUM LOAD N SUB =1 STORE N BG LOOP PRINT SUM ENDThe first two cards write
4in the boxNand0in the boxSUM.LOAD SUMputs a copy of what is inSUMinto the pocket;ADD Nadds what is inNto the pocket;STORE SUMcopies the pocket back intoSUM;SUB =1takes 1 away from the pocket;BG LOOPjumps back to the card labelledLOOPif the pocket holds a number greater than 0;PRINT SUMprints the boxSUM; andENDstops the robot.Two questions:
-
What number does the robot print?
-
If the first card said
N DC 100instead, what would it print, and what is the robot working out for anyN?
-
This little language is ACSL's assembly language, and the pocket is called the accumulator. It is on the ACSL Intermediate and Senior papers. Lesson 1 teaches the accumulator, memory and arithmetic, Lesson 2 teaches jumps, loops, input and output, and Lesson 3 teaches how to see what a whole program computes. Along the way you will turn assembly programs into Python, and at the end of Lesson 3 you will write a Python program that runs assembly programs, and use it to check the robot.
-
Read a line of ACSL assembly as
LABEL OPCODE LOC. -
Trace the accumulator and memory through
DC,LOAD,STORE,ADD,SUB,MULT,DIVandEND, including immediate data such as=5. -
Follow jumps (
BE,BG,BL,BU), loops,READandPRINT. -
Work out what a program computes in general, and run assembly programs in Python.
-
Lesson 1 — The accumulator and memory
Division: Intermediate and above.
A computer's processor understands only very simple instructions: "fetch this number", "add that number", "store the result here". Assembly language writes those instructions as short words, one per line. Real processors each have their own assembly language, so ACSL made up a small one of its own, with the ideas they all share.
An ACSL assembly program works with two things:
- the accumulator,
ACC: one special place that holds one number, like the robot's pocket. Every calculation happens inACC, andACCstarts at 0.
- the accumulator,
- memory: boxes that each hold one number, called locations.
Each location has a name, its label, such as
SUM,AorTEMP.
The program runs from the first line downwards, one line at a time, until it reaches
END.- memory: boxes that each hold one number, called locations.
Each location has a name, its label, such as
The shape of a line
Every line has up to three parts, called fields:
LABEL OPCODE LOC-
LABELis optional. It names the line (and the location aDCline makes). A label starts with a letter and may go on with letters and digits. Labels are case-sensitive:SumandSUMare different labels. -
OPCODEsays what to do. Opcodes are always in capitals, and an opcode can never be used as a label. -
LOCsays which location the instruction uses.
-
Programs are printed in columns, with the labels at the left and a blank where a line has no label:
SUM DC 0 LOAD SUMSo you read a line like this: if its first word is an opcode, the line has no label; otherwise the first word is the label and the opcode comes next.
DONE ENDis anENDline with the labelDONE;LOAD Bis aLOADline with no label.The first opcodes
Opcode What it does DC"define constant": the label names a new location, holding the number in LOC.ACCdoes not change. ADCline must have a label.LOADcopy the number in LOCintoACC.LOCdoes not change.STOREcopy ACCintoLOC.ACCdoes not change.ADDACCbecomesACC+LOCSUBACCbecomesACC−LOCMULTACCbecomesACC×LOCDIVACCbecomesACC÷LOC, keeping only the whole-number part (see below)ENDstop the program. Its LOCis empty.The arithmetic opcodes change only
ACC; the location they read stays the same. OnlySTOREwrites into memory.In this book, every
DCline sets its location before the program starts, wherever the line sits in the program; when the robot reaches aDCline it simply moves on. The programs here put theirDClines at the top.Immediate data
Instead of a label,
LOCcan be a number written after=. This is called immediate data: the number itself is used, not a location.-
LOAD =123puts 123 inACC. -
SUB =1takes 1 away fromACC. -
MULT =-3multipliesACCby −3.
Only
LOAD,ADD,SUB,MULTandDIVmay use immediate data, because they only read theirLOC.STOREwrites into itsLOC, soSTORE =5makes no sense and is not allowed.-
DIVrounds toward zeroACSL says
DIVkeeps "the signed integer part of the quotient". That means: divide, then throw away everything after the decimal point, and keep the sign.-
7 DIV 2: 7 ÷ 2 = 3.5, so the answer is3. -
-7 DIV 2: −7 ÷ 2 = −3.5, so the answer is-3. -
-7 DIV -2: 3.5, so3; and7 DIV -2is-3.
-
For a negative answer this is not the same as the
intof ACSL's pseudocode or Python's//, which both round down (−3.5 becomes −4).DIVrounds toward zero: −3.5 becomes −3. This is the one place in ACSL where that happens, and it is the most common trap in this category.Also watch the order. ACSL writes that
LOCis "divided into"ACC, which meansACC÷LOC:ACCis the number being divided.How big can a number be?
ACSL says
ADD,SUBandMULTwork modulo 1,000,000, andREAD(Lesson 2) reads a number modulo 1,000,000. For a positive number that simply means keeping the last six digits:999999 + 1gives0, and1000 MULT =1000gives0.ACSL gives no example for negative numbers. Book convention: this book keeps the sign and the last six digits, so
-999999 - 2gives-1. This is the book's own reading of the rule, not ACSL's. It never matters in this unit: in every exercise, every number stays between −999,999 and 999,999, and no program ever divides by zero.Tracing a program
To trace a program, go down it line by line and write down
ACCevery time it changes. Here is the first sample program from ACSL's own page:TEMP DC 0 A DC 8 B DC -2 C DC 3 LOAD B MULT C ADD A DIV B SUB A STORE TEMP ENDThe
DClines setTEMP= 0,A= 8,B= −2 andC= 3.Line ACCWhy LOAD B−2 copy BMULT C−6 −2 × 3 ADD A2 −6 + 8 DIV B−1 2 ÷ −2 SUB A−9 −1 − 8 STORE TEMP−9 ACCis copied intoTEMP;ACCdoes not changeSo after the program runs,
TEMPholds −9.Example 2. What is in
XandYafter this program?X DC 5 Y DC 0 LOAD =20 SUB X STORE Y MULT =-2 DIV =4 STORE X ENDLine ACCMemory LOAD =2020 SUB X15 STORE Y15 Y= 15MULT =-2−30 DIV =4−7 (−30 ÷ 4 = −7.5, toward zero) STORE X−7 X= −7So
Xholds −7 andYholds 15. Notice thatSUB Xread the oldX(5): a location only changes when aSTOREwrites into it.Assembly in Python
Turning a short assembly program into Python is easy: each location becomes a variable, and
ACCbecomes a variable too. The only hard part isDIV. Python's//rounds down, so we need our own function that rounds toward zero:-
divide the sizes of the two numbers with
//(for positive numbers, rounding down is rounding toward zero) -
if exactly one of the two numbers is negative, the answer is negative
-
print(-7 // 2) def div(x, y): q = abs(x) // abs(y) if x * y < 0: q = -q return q print(div(-7, 2)) print(div(7, -2)) print(div(-7, -2)) print(div(7, 2)) print(div(2, -2))Scroll the code for more ↓
Output
-4 -3 -3 3 3 -1x * y < 0is true exactly when one ofxandyis negative and the other positive. Whenxis 0 the answer is 0 anyway, so the sign does not matter.Here is ACSL's sample program in Python, printing
ACCafter every change, so you can compare it with the trace table.def div(x, y): q = abs(x) // abs(y) if x * y < 0: q = -q return q temp = 0 a = 8 b = -2 c = 3 acc = b print(acc) acc = acc * c print(acc) acc = acc + a print(acc) acc = div(acc, b) print(acc) acc = acc - a print(acc) temp = acc print("TEMP =", temp)Scroll the code for more ↓
Output
-2 -6 2 -1 -9 TEMP = -9Put it together: this program reads the three starting values of
A,BandC, one per line, runs ACSL's sample program with them, and prints what ends up inTEMP. It is saved asassets/l1.py.Try it def div(x, y): q = abs(x) // abs(y) if x * y < 0: q = -q return q a = int(input()) b = int(input()) c = int(input()) acc = b acc = acc * c acc = acc + a acc = div(acc, b) acc = acc - a temp = acc print(temp)Scroll the code for more ↓
Run it from this unit's folder:
python assets/l1.py < assets/l1/1.inWith ACSL's values (8, −2 and 3) it prints
-9. Tryassets/l1/2.in,assets/l1/3.inandassets/l1/4.in, and check each answer by tracing the program by hand.assets/l1/4.inhas a negativeDIV: work out whether//would have given a different answer.Lesson 2 — Jumps, loops, input and output
Division: Intermediate and above.
So far every program ran straight from top to bottom. Four branch opcodes let a program jump to another line. Their
LOCis the label of the line to jump to, and three of them look atACCto decide:Opcode What it does BEjump to the line labelled LOCifACC= 0 ("branch if equal")BGjump if ACC> 0 ("branch if greater")BLjump if ACC< 0 ("branch if less")BUjump always ("branch unconditionally") If a branch does not jump, the program simply goes on to the next line. A branch never changes
ACC.There is no "compare" opcode. To compare two numbers
XandY, work outX − YinACCand branch on its sign: it is greater than 0 whenX > Y, 0 when they are equal, and less than 0 whenX < Y.Two more opcodes:
READandPRINTOpcode What it does READread the next input number into the location LOC.ACCdoes not change.PRINTprint the number in the location LOC.ACCdoes not change.A
STOREor aREADinto a label that has noDCline simply makes that location. A question tells you the input numbers in the order the program reads them.When a question asks what a program prints, this book writes the printed numbers in order, on one line, separated by single spaces:
13 8 3.Example 3. A jump forward. What does this program print?
X DC 15 Y DC 22 LOAD X SUB Y BL YBIG PRINT X BU DONE YBIG PRINT Y DONE ENDACCbecomes 15 − 22 = −7, which is less than 0, soBL YBIGjumps to the line labelledYBIG, which printsY. Then the program reachesDONE ENDand stops. It prints 22: the program prints the larger ofXandY.If
Xwere bigger,BLwould not jump: the program would printX, and thenBU DONEwould jump over thePRINT Yline. A jump forward over some lines works like anif…elsein Python.Example 4. A jump backward makes a loop. What does this program print?
N DC 13 LOOP PRINT N LOAD N SUB =5 STORE N BG LOOP ENDPRINT NprintsACCafterSUB =5NBG LOOP13 8 8 jumps 8 3 3 jumps 3 −2 −2 does not jump So it prints
13 8 3. Notice that the test comes at the bottom of the loop, so the body always runs at least once, and thatNstill gets the value −2 before the loop stops.Example 5. Here is ACSL's second sample program. If the input is a whole number
N, what is the final value ofX?READ X LOAD X TOP SUB =1 BE DONE STORE A MULT X STORE X LOAD A BU TOP DONE ENDTrace it for
N= 4.READ XmakesX= 4, andLOAD Xputs 4 inACC.At TOP,ACCafter SUB =1AX=ACC×X4 3 3 12 3 2 2 24 2 1 1 24 1 0 — BE DONEjumpsSo
Xends as 24 = 4 × 3 × 2 × 1. In generalX=N× (N− 1) × … × 1, which is writtenN!("N factorial"). This is how ACSL answers the sample:X = N!. (The program needsN≥ 1. ForN= 0,ACCgoes to −1, −2, … and never reaches 0, so the program never stops.)Loops in Python
A jump forward is an
if…else. A jump backward is a loop, and because the test is at the bottom, the loop body runs before the first test. One way to write that in Python is awhileloop controlled by a variablegoing, which starts asTrueand is set by the test at the bottom. Here is Example 4:n = 13 going = True while going: print(n) acc = n acc = acc - 5 n = acc going = acc > 0 print("N ends as", n)Scroll the code for more ↓
Output
13 8 3 N ends as -2Put it together: this program reads
Nand prints the final value ofXin ACSL's sample program. In that program the test (BE DONE) comes straight afterSUB =1, at the top of the loop, so here an ordinarywhileloop fits. It is saved asassets/l2.py.Try it x = int(input()) acc = x acc = acc - 1 while acc != 0: a = acc acc = acc * x x = acc acc = a acc = acc - 1 print(x)Scroll the code for more ↓
Run it from this unit's folder:
python assets/l2.py < assets/l2/1.inFor
N= 1 it prints1:ACCbecomes 0 straight away, so the loop never runs. Tryassets/l2/2.in,assets/l2/3.inandassets/l2/4.intoo. Line by line, match each Python line to the assembly line it comes from.Lesson 3 — What does the program compute?
Division: Intermediate and above.
Some questions ask not "what is printed?" but "what does this program do?", as in ACSL's
N!sample. The method is always the same:-
Trace the program for a few small inputs, and put the results in a table.
-
Look for the pattern.
-
Check the pattern on one more input, and check the edge cases (0, 1, negative numbers) too: a program often behaves differently there.
Example 6. This program reads
Aand thenB. What does it print?-
READ A READ B LOAD =0 STORE P LOOP LOAD P ADD A STORE P LOAD B SUB =1 STORE B BG LOOP PRINT P ENDABprinted 3 4 12 5 2 10 7 1 7 The loop adds
AtoPonce for each pass, andBcounts the passes down, so it printsA × B. But tryB= 0: the body runs once before the test,Bbecomes −1, and the program printsA, not 0. So the honest answer is: "it printsA × BwheneverB≥ 1". Contest options are often built on exactly this kind of edge case.An assembly interpreter in Python
Division: Senior and above.
An interpreter is a program that reads another program and runs it. Here is a plan for an interpreter for ACSL assembly.
First pass: read the program. Split each line into words. If the first word is not an opcode, it is a label: remember in a dict
wherewhich line number it is on, and move on to the next word. Keep the opcodes and theLOCs in two lists,opsandlocs, so linekisops[k]andlocs[k]. For aDCline, also set its location in a second dict,memory, which maps each label to its number.Second pass: run it. Keep
accandline, the number of the line to run next. Run the line, and movelineon by one, unless a branch jumps, whenlinebecomeswhere[loc]. Stop atEND.Here is the first pass on some sample lines:
OPCODES = ["LOAD", "STORE", "ADD", "SUB", "BG", "BU", "PRINT", "DC", "END"] for text in ["LOOP LOAD SUM", " ADD =1", "DONE END", " END"]: parts = text.split() if parts[0] not in OPCODES: print("label", parts[0], "then opcode", parts[1]) else: print("no label, opcode", parts[0])Scroll the code for more ↓
Output
label LOOP then opcode LOAD no label, opcode ADD label DONE then opcode END no label, opcode ENDA
LOCthat starts with=is immediate data. The functionvaluebelow turnsLOCinto a number: the number after the=, or the number in that memory location.Put it together: this interpreter runs programs that use
DC,LOAD,STORE,ADD,SUB,BG,BU,PRINTandEND, which is enough for the robot. Its input is a line with the number of program linesn, and then thenlines of the program. It is saved asassets/l3.py.Try it OPCODES = ["LOAD", "STORE", "ADD", "SUB", "BG", "BU", "PRINT", "DC", "END"] def value(loc, memory): if loc[0] == "=": return int(loc[1:len(loc)]) return memory[loc] n = int(input()) ops = [] locs = [] where = {} memory = {} for k in range(n): parts = input().split() first = 0 if parts[0] not in OPCODES: where[parts[0]] = k first = 1 op = parts[first] loc = "" if first + 1 < len(parts): loc = parts[first + 1] if op == "DC": memory[parts[0]] = int(loc) ops.append(op) locs.append(loc) acc = 0 line = 0 while ops[line] != "END": op = ops[line] loc = locs[line] line = line + 1 if op == "LOAD": acc = value(loc, memory) elif op == "STORE": memory[loc] = acc elif op == "ADD": acc = acc + value(loc, memory) elif op == "SUB": acc = acc - value(loc, memory) elif op == "BG": if acc > 0: line = where[loc] elif op == "BU": line = where[loc] elif op == "PRINT": print(memory[loc])Scroll the code for more ↓
Run it from this unit's folder:
python assets/l3.py < assets/l3/1.inassets/l3/1.inis the robot's program. Tryassets/l3/2.in(the robot withN DC 100),assets/l3/3.inandassets/l3/4.intoo, and trace each program by hand to check. The Challenge exercise at the end of this unit asks you to add the missing opcodes.Back to the robot
Trace the robot's program with
N= 4:At LOOP,SUMSUMafterADD NNafterSUB =1BG LOOP0 4 3 jumps 4 7 2 jumps 7 9 1 jumps 9 10 0 does not jump - The robot prints 10: 4 + 3 + 2 + 1 tins, the triangle with 4 tins on the bottom row.
- For any
N≥ 1, it prints 1 + 2 + … +N, the number of tins in the whole triangle. ForN= 100 that is 5050, which is whatassets/l3.pyprints forassets/l3/2.in. (There is a shortcut: 1 + 2 + … +N=N× (N+ 1) ÷ 2, and 100 × 101 ÷ 2 = 5050.)
- For any
-
ACCstarts at 0;LOADcopies a value into it, andSTOREcopies it back into memory. -
DIVrounds toward zero, so-7÷2is-3, unlike ACSL'sint, which gives-4. -
BE,BGandBLjump whenACCis 0, greater than 0 or less than 0;BUalways jumps. -
To say what a program computes, trace small inputs in a table, find the pattern, and check the edge cases.
-
You can now trace an assembly program and describe what it computes, like the robot's 1 + 2 + … + N.