Programming the LGP-30
A program for the LGP-30 is a list of instructions and numbers, each placed at an address on the drum that you choose. The IDE uses a small assembly notation for this. Writing a correct program is the easy part. Choosing the addresses well is the skill.
Addresses
An address names a track and a sector, each from 00 to 63. The IDE writes them as
four decimal digits, track first: 1005 is track 10, sector 5. The real
machine used its own hexadecimal, with the letters f, g, j, k, q and w standing for
10 to 15 (see LGP-30 Hex). The IDE uses plain
decimal instead.
Syntax
One instruction or directive per line. A semicolon starts a comment.
start 0000 ; where execution begins 0000 b 0008 ; <address> <instruction letter> <operand address> 0008 dw 7 ; <address> dw <signed decimal number>
A first program
This is Example 1 from the IDE. It adds three numbers and stores the total.
start 0000 0000 b 0008 ; acc = 7 0001 a 0009 ; acc = acc + 12 0002 a 0010 ; acc = acc + 23 0003 h 0011 ; store the total in 0011 0004 z 0000 ; stop 0008 dw 7 0009 dw 12 0010 dw 23 0011 dw 0 ; result: 42
b brings a word from memory into the accumulator. a adds a
word to it. h stores the accumulator and keeps it. z stops
the machine. The numbers live in sectors 8 to 11 of track 0.
Instructions
| Letter | Name | What it does |
|---|---|---|
z |
Stop | Halts computation. |
b |
Bring | acc = mem[addr] |
y |
Store address | Replaces mem[addr]'s address-field bits with the accumulator's. |
r |
Return address | Stores (this instruction's address)+2 into mem[addr]'s address field — call/return idiom. |
i |
Input | Shifts one 4-bit code per step into the accumulator, until 8 characters or a stop code. |
d |
Divide | acc = acc / mem[addr], rounded. Overflows outside a magnitude of 1. |
n |
N Multiply | acc = low 31 bits of acc * mem[addr] (magnitudes). Left-shifting idiom. |
m |
M Multiply | acc = top 30 bits of acc * mem[addr] (magnitudes) + XOR sign. Fractional idiom. |
p |
addr's track field (6 bits) is the literal output code — not a memory address. | |
e |
Extract | acc = acc & mem[addr] (bitwise AND mask). |
u |
Unconditional transfer | counter = addr. |
t |
Test | Branches to addr if the accumulator is negative. |
h |
Hold and Store | mem[addr] = acc; acc unaffected. |
c |
Clear and Store | mem[addr] = acc, then acc = 0. |
a |
Add | acc = acc + mem[addr] (signed). Overflows outside [-2^30, 2^30-1]. |
s |
Subtract | acc = acc - mem[addr] (signed). Overflows outside [-2^30, 2^30-1]. |
Timing
Every time the machine fetches an instruction or reads or writes an operand, it waits for that sector to come round under the head. Look at Example 1 again. The instruction at sector 0 reads its operand from sector 8. By the time that is done, sector 1, where the next instruction lives, has already gone by. The machine waits most of a revolution to fetch it, and the same happens at every step.
The program is correct, but it is slow. Arranging instructions and data so that each one arrives just as it is needed can make the same program several times faster. The debugger shows this as it happens: the drum tape marks how many sectors each step swept past, and the memory view highlights what was read and written.
The Magnetic Drum page has the background. The quickest way to learn is to open an example in the IDE, run it in the debugger, and then try to make it faster.