Apollo AGC is a Funny Computer
I saw this writeup on the web but with no contact.... I think
a bunch of it is wrong.
The program of the CM is very weird too; I strongly doubt it piloted anything; it could not even be compiled, that is transformed into machine code to be executed. Then how come other people have been able to compile it and run it on a simulator?
If I summarize some of the main problems if the Apollo computer, before I discuss them more in detail, I can cite the following points:
- The Apollo computer uses a technique of switchable memory which is absurd since it doesn't use the full capability of the addressing system, and leads to wasting time and memory which are very limited in the Apollo computer; and switching executable program memory makes no sense, because it means that the instructions which follow the switching instruction will never be executed. Because banked memory was added after the initial design. I thought that when working with the 8088 after the 68000. The initial processor design did not expect that memory requirements would more than double (more like x6) and so the easiest thing was to add banked memory. The MC9S12 processors which are currently produced by Freescale and used in many embedded applications use bank switching.
- The Apollo computer doesn't have the mininal basic set of instructions that any processor usually has, and has instead instructions which are weird and impractical to use. Weird, yes. Impractical? Hardly. I thought that when I was forced to work with the 8088.
- The Apollo computer does useless things which waste processor time (like saving the contents of the instruction following the call to a subroutine moreover saving its address which is the only thing which should be saved). Weird things were done to save gates.
- The Apollo computer provides instructions which compute something so weird in the accumulator (main register of a processor) that it's equivalent to destroying its contents, and thus makes these instructions unusable. No, that is an instruction side effect.
- The Apollo computer has instructions which don't require a parameter which should yet be necessary for these instructions to work properly, or conversely which require a parameter which is useless for the way they work. Useless parameters don't make the instructions useless, things like that were done to economize on gates. Similarly, instructions that require additional parameters may use register values as their parameters.
- The Apollo computer has instructions which are unclear; they don't really specify what they do. That means you haven't studied them enough.
- The Apollo computer is said to be able to do real time (real time allows several tasks to run simultaneously) and yet it doesn't even have the minimum environment which would be necessary for the real time to work (no stack, and no instruction to manage real time). You can simulate a stack using a minimal of software. There is a real-time periodic interrupt available at 10ms intervals. You can fake an RTOS in a Microchip PIC with a hidden stack. No problem. People do real-time programming on stackless micros all of the time.
- The Apollo computer has instructions which uselessly waste processor time (like the "unprogrammed instructions" which count hardware pulses; such instructions have never existed on any processor for the good reason that they make no sense). The do make sense. Rather than having a separate counter/timer system, this steals the ALU and one memory cycle to count something. It reduced the amount of space and power consumed by the computer. These days when the hundred or so transistors are essentially free, it doesn't make sense but back then it did.
- Anything which runs on the processor comes from programmed instructions, so the fact that something would steal time from the computer, like they say, is hilarious...unless a programmer would have programmed something and wouldn't have told the others, LOL! Any interrupt in a modern computer 'steals time'. If you are out of hardware counters but you have a free interrupt line and need to count something, just imagine that program sequence. Two lines of code on something like a 9S12 processor... ' inc _my_addr ; rti'. Basically the AGC hardwired several sets of this instruction, each with a hardwired pointer. It's not rocket science!
- The Apollo computer makes a distinction between +0 and -0...but it's the same thing; no other computer makes a distinction between +0 and -0, 0 is the only number which has no negative value; in fact, the way they represent -0 is actually the representation of -1 in any other computer. One's complement does exist. It makes it easier (in hardware) to do a negative as you simply invert the data. Not common because 2's complement makes more sense intuitively.
I) Criticism of the operator's manual.
1) Even if in what follows you have some difficulty understanding my technical explanations, there is something everybody should be able to understand:
In the documentation there are lines which have been crossed out; does it make sense? On a typed documentation, you never cross text out, you just remove it. If you had to manually re-type the documentation using a typewriter (the entire page) to remove a line, you might change your mind.
2) PAge 3: AGC memory "words" are 15 bits in size plus an odd-parity bit.
Words are never 15 bits in size; the parity check is always hardware made; it makes no sense to have a parity bit in the word, how would it be checked?
The parity bit is an extra bit which is not made available to the user, and the parity check is made electronically.
Making it by software would be way too much time consuming, it would severely bring the performance of the processor down. Hardware did check it. The 16 bit length (in memory) with parity made it easier to weave the core memory array, but you're right that gives a 15 bit word. Not that unusual unless you're fixated on a multiple-of-8 word length. The Microchip PIC uses a 14 bit word length. Actually, the Microchip PIC has a variety of available instruction word lengths but the 14 bit version is most popular.
3) Page 3: zero has two different representations as "+0" and "-0".
This is absolutely ridiculous, 0 has a single representation as all bits set to 0.
In any CPU on a 16 bits integer "1111111111111111" represents -1 and "1000000000000000" represent -32768.
The idea of making a disctinction between +0 and -0 is totally ridiculous because it represents the same thing.
the adoption of 1s compliment must be regarded as a joke, not something serious. Reduction of hardware to make a negative. In the 1960's for a computer that was in an environment where ounces of weight reduction counted, this was very logical.
4) Page 5: they say the following things:
a) counter/timer registers are incremented by hardware pulses.
b) Incrementing a counter/timer takes CPU time.
This is contradictory: If incrementing a counter takes CPU time, that means it is incremented by software, and thence it is not incremented by hardware pulses.
If it is incremented by hardware pulses, the incrementation has no reason to take CPU time.
There's no such thing as an "unprogrammed sequence" even if this unprogrammed sequence bears a name like "PINC" or "MINC".
The idea that a repetitive hardware pulse could generate CPU time is heretical.
Only executed code can generate CPU time; an external signal can't generate CPU time at the exception of interrupts which provoke the execution of programmed routines.
if a hardware signal was generating CPU time to increment a counter, it would be perfectly stupid (and a great waste of CPU time) because an electronic counter can do it just as well and the CPU has more intelligent things to do than to count a hardware signal.
When a hardware signal must be counted, an electronic counter is used; not only it is currently the case, but it has always been the case (electronic counters are basic circuits and have existed even before processors did).
The processor can read the counter through an I/O channel; of course when he does, it takes CPU cycles, but he doesn't have to do it at each hardware pulse, only when needed.
A looping of the counter can also generate an interrupt on the processor, allowing the processor to do a treatment when a programmed count has elapsed. A hardware pulse triggered a flipflop in the instruction decoder that would do an increment or decrement on a memory location. Essentially it was a hardwired interrupt-and-return with one instruction in the interrupt routine. Just because a modern microprocessor does not do it this way does not mean that one of the first embedded processors has to do it that way.
5) Page 6: They say that the 16th bit of the accumulator (special register of the CPU) is used in association with the 15th bit to indicate overflow.
There are other status flags which exist (like the carry for instance) and these status flags are gathered in a special register called status register.
The accumulator is never used to indicate overflow; an overflow of the accumulator causes the overflow bit of the status register to be set like for any other register of memory data besides.
It has always been the case from early processors. No, not always. Only the ones you are familiar with. Effectively this does shorten the memory word, but it's a different design before 'better' was defined.
There's no reason to waste a bit of the accumulator to store a status flag in it. And why not?
6) Page 8: LRUPT is a register provided for storing the value of the L register during an interrupt service routine.
But what's comical is that they say that vectoring to the interrupt routine does not automatically load the register LRUPT with the contents of L; and the restoration the L register from LRUPT is not automatic either.
This is absolutely ridiculous for L could be more conveniently pushed onto a stack to save it, and popped from the stack upon return.
If the save to LRUPT and the restoration from LRUPT are not automatic, then this register is useless. No, it just takes a different technique to use, that's all.
7) Page 9: TIME1 is a counter on 14 bits (why not 16 bits!) which overflows every 163.84 seconds; upon overflow of TIME1, the 14 bits counter TIME2 is automatically incremented.
This is ridiculous; why isn't TIME1 16 bits long, and TIME2 too?
They could count a value 16 times greater.
With a clock of one millisecond, they could count even more than 31 days, and be more precise in the same time. They are 14 bits because they should be unsigned and need to feed the ALU. Plus the AGC never needed to count longer than a round-trip to and from the moon - about two or three weeks.
8) Page 9/10: TIME3 is a counter incremented every 10ms which generates an interrupt upon overflow.
TIME4 is also a counter incremented every 10ms which generates an interrupt upon overflow.
They say that the incrementation of TIME3 is dephased of 5 ms relatively to the one of TIME4 so that their interrupt routines cannot occur in the same time, provided that their treatment does not exceed 5 ms.
Then they say that TIME5 is also a counter which is incremented every 10ms which generates an interrupt upon overflow; but nothing is provided to synchronize TIME5 either with TIME3 or TIME4.
That means that the interrupt routine associated to TIME5 can interrupt either the interrupt routine associated to TIME3, or the one associated to TIME4. Yes but that is ok since TIME5 would be preset to run 'N' times away. So long as the programmer knows how it will work, it's no big deal.
Furthermore the addresses of the interrupt routines associated to these counters are separated only by 10 octal, and 10 octal, that leaves only 8 memory bytes to program the interrupt routine; and this interrupt routine must mandatorily end with an instruction allowing the return to the interrupted program (which makes the interrupt routine itself still shorter).
What do you want to program in less than 8 bytes (that makes only four instruction, since each instruction uses two bytes)?
This is ridiculous!
Of course there can be a jump at the interrupt location to another part of the memory where there is more room to program a service routine; but, to program the jump, only two bytes are needed, and there are 8 unused bytes; that means that the intervals between the locations of the interrupt routines could have been reduced to two bytes.
So, to summarize, the interval between the interrupt routines is either two short or too long. There are other processors like this. The Z80 and ARM architectures both have this spacing. You can program a short interrupt routine without a jump, or a long one with a jump. You can compile in the first few lines of the interrupt then jump. The Microchip PIC does this as well.
9) Page 10: TIME6 is a counter incremented every 1/1600 of second by an unprogrammed sequence.
This means absolutely nothing; a sequence is always programmed, otherwise it is not a sequence.
After having loaded TIME6 for the count corresponding to the desired delay, the user enables the counter by setting a bit in an I/O register.
Upon overflow of TIME6, an interrupt routine is called; they say that the enable bit of the I/O register is automatically reset, but in fact it couldn't be automatically reset; it would be up to the user to reset the enabling bit in the I/O register! That register is incremented by a hardware trigger just like pulses from the CDU or radar - it does practically an increment followed by a return from interrupt in one memory cycle. Bit 10 of register 14 (gyrotorquing) is set up to automatically clear when the counter crosses zero. Subtle but it's there on page A19-2 of the AGC schematic.
10) Page 11: PIPAX is a register which stands for "Pulsed integrating pendulous accelerator".
This is absolutely ridiculous: Registers of a CPU just represent data memory, they don't bear such dedicated names. Unless it's hardwired somehow. In the case of the PIPAX, it is a dedicated register specifying the acceleration counts that have been received. In a 9S12 microcontroller, that would be the equivalent of something like the TC0 register. In that case it's a hardware register but it times what happens on PORTT bit0.
11) Page 13: The way the memory is mapped makes no sense.
The banks of memory can be addressed without the need of making bank switching.
Bank switching consumes both space and time in a completely unnecessary way.
Read-write and Read-only memory may be anywhere.
The CPU registers which allow bank switching are a pure heresy; never have such registers existed in any CPU architecture. Yes bank switching exists. It exists on a number of platforms. The Apple //e computer, for example, used bank switching to give 128k of RAM where 48k used to go. The Freescale MC9S12X series of parts feature bank switching for both code and data. Also, read/write and read memory cannot necessarily be anywhere. On a 9S12XEP processor, ROM (read only) is from 0x4000 to 0xffff, with 0x8000 to 0xbfff being bankswitched. RAM (read-write) is from 0x1000 to 0x3fff with 0x1000 to 0x1fff being bank-switched. You can't say this doesn't exist when clearly an awful lot of automotive code is running on exactly that architecture. Same actually applies to the Microchip PIC.
12) Page 15: They say that the processing of an interrupt routine can be deferred if an interrupt routine is already in progress and not yet terminated (by a RESUME instruction).
In that case, why having dephased the incrementation of the timer TIMER3 relatively to the incrementation of the timer TIME4 in order to avoid their interrupt to occur in the same time, since the one which would occur second would wait for the first one to end before processing? That one is easy. If the interrupt routine triggered by one or the other is particularly long, then the sampling interval of the other will be very imprecise and it actually makes no sense to add it. For control loops, jitter-free is good and that is how they do it.
13) Page 15: They say that the step 2 of the processing of an interrupt routine is to save the instruction appearing at the memory location pointed to by the program counter is saved into the BRUPT register.
This makes absolutely no sense!
It's the address of the instruction which is saved, but the instruction itself would never be saved; no CPU has ever done that!
They don't finish the description of the processing of the interrupt, that is explain that the RESUME instruction reloads the program counter from the ZRUPT register. You could save one or both and have it work, it depends on the processor's pipelining and instruction decoding.
14) Page 16: They say that an instruction is represented the following way:
CCC AAA AAA AAA AAA
That is an instruction code on 3 bits only, and a memory address on 12 bits.
Normally the instruction code is not mixed with the address, but separated from it.
The instruction code would typically be provided on a byte, which would allow to provide a set of up 256 instructions.
The address would not systematically be provided after the instruction; some instructions only act on internal registers of the CPU and don't require a memory address to be specified; the memory address would only be provided when needed by the instruction.
The address would be provided on 16 bits in the following word, its length would have no reason to be provided on only 12 bits; that would extend the capacity of memory addressing by a factor 16, and would eliminate the need of making bank switching which consumes both CPU time and space, and is not advisable in a system which is already slow and limited in memory space.
Now in the Apollo AGC, the instruction code is in fact mixed with the address, because not all addresses are allowed for the memory address.
The addresses starting from zero cannot be used (they are used as registers of the CPU) and are used to complete the instruction code.
For example, if the instruction is "01000" octal, the adress is "1000" and is a valid address, in that case the instruction code "0" indicates that it is a TC instruction calling the subroutine located at address 1000.
But, if the instruction is "00001", the instruction code is also "0", but the address "0001" indicates that the instruction is in fact "XLQ" instead of TC.
That means that the knowledge of the instruction code is not enough to know what instruction to execute, the processor still has to analyze the address before he knows what instruction he has to execute; this is less efficient that if the the processor could directly determine what instruction to execute from the instruction code. This exact instruction decoding method is used on the Microchip PIC. Instruction codes are different lengths.
15) In All CPUs, including the very old ones, there is a set of instructions specially dedicated to make conditional jumps.
These instructions test status bits set by previous operations: It can be addition, subtraction, but it also can be simple compare.
These instructions include conditional jumps such as: Jump if equal, jump if greater, jump if greater of equal, jump if lower, jump if lower or equal, jump if carry, jump if not carry...
On this CPU there are only two conditional instructions: BZF and CCS.
BZF only tests if the acculutator is zero, and it's totally insufficient, there also should be an instruction to test the sign.
Oh there is the CCS instruction which can test the sign of a memory data.. the problem is that this instruction destroys the contents of the accumulator by computing something from the memory data in a determined way that the user can't choose; and it only performs skips according to the result of the test, which means that the user has to add jumps behind CCS to execute the desired sequence according to the result of the test.
This is made to be as unpractical as possible, in a totally irrational way; no serious conceptor of CPU would make instructions so unpractical to use.
It's not that this CPU works differently from other CPUs, it's that it works in an irrational way. Just different.
16) Page 24: The "DTCB" (Double transfer control switching both banks) instruction is said to perform a jump and switch both fixed and erasable banks.
This is hilarious: This instruction is so inconvenient to use that it's difficult to imagine in what context it could be used.
Switching just one memory bank is already extremely inconvenient to use (not to say impossible), but switching both banks in the same time still makes less sense! Oh, I don't know about that. Sounds like potentially a task switch. You switch both banks and jump to some memory location.
17) The "DV" instruction divides the pair of CPU registers A and L by a data of which the memory address is given on 12 bits.
They say that this instruction can work according to two different modes (divide the pair A&L by a single precision value or by a "double length 1s complement integer" pointed to by the memory location).
The problem is that there is absolutely nothing which tells the CPU what mode to use, since there is just the instruction and the memory location and no additional information.
The CPU must be extralucid to determine what mode to use! Easy. Call the instruction twice in a row to do a double length.
18) Page 28: The way the "INDEX" instruction works is hilarious.
It is said to change the behavior of the instruction which follows; they give the following example:
JMPTAB TCF LOC0
The TC instruction normally calls a subroutine, but the fact that it's preceded by the INDEX instruction makes that it becomes a conditional JUMP according to the contents of the accumulator.
They say that if the accumulator contains 0, it jumps to the label JMPTAB which performs a jump to LOC0, if the accumulator contains 1, it jumps to the next instruction after JMPTAB which performs a jump to LOC1, if the accumulator contains 2, it jumps to the second instruction after JMPTAB which performs a jump to LOC2...
But where it becomes hilarious is that if the accumulator contains -1, it jumps to the instruction before JMPTAB, and if it contains -2, it jumps to the instruction still before.
The instruction before JMPTAB performs a jump to the instruction one word before the label LOC, and the instruction before the latter performs a jump to the instruction two words before the label LOC.
But if there is a jump to the instruction two words before the label LOC, the instruction one word before the label LOC will also be executed...unless they is a jump to another label at the instruction two words before the label LOC, but in that case why not directly use this label in the instruction "TCF LOC-2". Sounds like an off-by-one calculation, no big deal.
19) Page 32: the NOOP instruction is hilarious too; not because it makes no sense to have an instruction which makes nothing, for this instruction effectively exists in normals CPUs, and is used to provided short delays.
What's hilarious is that this instruction is said to take two cycles if executed in erasable memory and one cycle in fixed memory.
In normal CPUs, this instruction always takes one cycle, wherever it is executed in memory. Erasable memory needs to be rewritten if it is in core memory. The normal cycle timing of the erasable core memory includes a read-followed-by-write, but the instruction fetch from erasable core memory takes longer because the memory fetch for instruction happens late in the memory cycle and the instruction needs to be rewritten into core which takes the next cycle. One instruction on one of the new Pentium-type machines may take many cycles if you cross a memory page boundary and you need to do a SDRAM read cycle. It depends where it is in memory.
20) Page 33: the "RAND" instruction is said to logically bitwise ANDs the contents of an I/O channel into the accumulator.
Oh really: None of the CPUs which exist and existed in the world ever provided this possibility.
There is only an instruction to read an I/O channel (when it is readable) or to write it (when it is writable).
This is a purely imaginary instruction. Hardly imaginary. Maybe different but not imaginary. The 9S12 instruction set has BRSET and BRCLR for an AND followed by a conditional branch, and also BSET and BCLR for set/clear bits of an I/O channel. The Microchip PIC provides similar constructs. The 8088 may only have IN and OUT but other processors are not the same.
21) Page 34: the "RESUME" instruction allows to terminate an interrupt routine and to go back to the instruction which was about to be processed when the interrupt occurred.
They say that, when the interrupt occurs, the instruction pointed to by the program counter is automatically saved into the BRUPT register of the CPU.
Upon return, the instruction saved in BRUPT is automatically executed; but why save it into BRUPT, since it will be executed anyway upon return of the interrupt routine if the BRUPT register is not modified by the interrupt routine!
And, if the interrupt routine modifies BRUPT to have another instruction executed upon return, why not directly execute it, which would be faster, since if the interrupt routine copies the instruction into BRUPT, the time of the copy of the instruction will be added to the execution time of the instruction, whereas there will just be the execution time if the instruction is directly executed.
This is totally illogical and makes no sense at all! Number of gates has a lot to do with it.
22) Page 35: the "RETURN" instruction allows to return from a subroutine by loading the program counter (Z register) with the Q register which normally contains the return address; the TC instruction which allows to call a subroutine automatically saves the return address into the Q register.
Since there is an unique register to save the return address, a subroutine cannot call another one.
In a normal CPU, the return addresses are saved onto a stack (a part of memory which specially dedicated to save/recall memory data, return addresses..) which allows to call a subroutine from another subroutine.
In this CPU, calling a subroutine (by the TC instruction) from another subroutine is not possible since the return address is saved into an unique register; calling a subroutine within a subroutine would result in the return address of the first subroutine to be overwritten by the return address of the second subroutine; it would become impossible to return from the first subroutine. There may be no hardware stack but you could implement one in about two lines of code.
23) Page 39: the "TS" instruction (transfer to storage) is hilarious.
First it transfers the contents of the accumulator to the memory location indicated as operand.
Till then, nothing abnormal.
But what's really weird is what is made with the accumulator:
If the accumulator contains an overflow, and only in this case, it is loaded with +1 or -1 (what does that means?), and the next instruction is skipped!
This instruction has a very unpractical use.
There should be an instruction just to perform the storage, and another one to perform that very special function on the accumulator, but having an instruction which does both in the same time makes no sense; it's almost impossible to use. Unless you figure out how to use it.
24) Page 43: From page 43, they describe what they call "Pseudo-operations".
If an instruction is not an instruction existing in the set of instructions of the CPU, then it can only be a "macro-instruction", that is a set of programmed CPU instructions which is associated to this macro-instruction.
They describe the pseudo-operation 1DNADR as transmitting the two words pointed to by the provided memory location...but transmitting to what?
There is always a destination in a transmission, saying it's just transmitted means nothing, if the destination of the transmission and the way it is transmitted are not specified! 1DNADR = one downlink address... downlink through the telemetry system (which is a TRANSMITTER) and to be displayed on a console down in Houston.
25) Page 45: the "BANK" pseudo-operation is said to reposition the yaYUL's internal location counter to the first unused location of fixed memory bank 5!
But what does mean "unused location"?
How can the CPU know where this "unused location" physically is?
And if it is a part of this bank in which nothing is programmed, what's the use of positioning the program counter on a part of memory which contains nothing to execute?
This makes absolutely no sense at all!
26) They describe the "pseudo-operation" STCALL this way:
and say X is in unswitched erasable bank.
But they don't describe what this operation does!
27) Page 48: They say that the "SETLOC" operation places the next instruction or pseudo-op at the specified address.
But what does that mean?
If only one instruction is placed at this address, executing from this address will only execute this instruction. Think of the ORG operation in an assembler with an absolute segment.
28) Page 50.
the instruction "STORE" probably stores something into the specified address; I say "probably" because what it does is not explicitly specified.
They say that 'X' is either an unswitched erasable address or in the current erasable bank.
if it is in the unswitched erasable bank, it is provided as such in the instruction word.
If it is in the current erasable bank, it is computed as: 0400 * Erasable BankNumber + (X-1400)
This is where it becomes comical: Why provide the number of the erasable bank in the formula, since it can only be the one of the current erasable bank and no other one?
And what happens if in the formula another number of erasable bank is used instead (which is normally forbidden)? How will the instruction behave?
You see the contradiction! No, this is exactly what happens with many bank-switched processors like the 9S12X family.
29) Why memory bank switching makes no sense.
The AGC has reduced memory addressing to 12 bits, whereas 16 bits should normally used for memory addressing, it has no reason to be limited to 12 bits.
It would allow to directly address 16 times more memory, and would eliminate the need of making memory bank switching.
Memory bank switching on data already makes no sense.
Imagine that you have a data in bank 1 that you want to add to a data in bank 2 and want to put the result into bank 3.
You have to switch to bank 1, take the first data, then switch to bank 2 add the second data, and finally switch to bank 3 in order to write the result into the destination of the operation.
That means that you have had to program the bank switching instructions, that is they take memory, when they are executed they take execution time; it's a waste of both memory space and execution time.
This waste would be avoided if there was no memory bank switching.
Memory bank switching still makes less sense on programming code.
When you make a fixed memory bank switching, no value is initially provided for the program counter.
That means that the execution normally starts at the beginning of the new bank.
Of course, it would be possible to put a value in a data memory that the new bank could test to know where to jump to, but it's rather complicated to use.
When you call a subroutine or branch to another sequence, it must be in the same memory bank, it can't be in another bank; this is not practical at all; there would be no such problem if there was no bank switching, a single program file could be used for the whole program.
And if you put a program memory bank switching instruction in your code, that means that the code which follows this instruction will not be executed since the processor has jumped to another bank; if the instruction which follows has not label, it will be unreachable. Bank switching is a common way of extending a processor's memory. It works reasonably well but yes it is a bit of a pain to program. Obviously linear memory is better but an awful lot of processors have bank switching. The Microchip PIC is one example, MC9S12, and the Infineon C167 family has it, too. Yea, it's a pain in the arse but it works.
II) Criticism of the CM program.
General considerations on the CM program.
a) Even if you have some difficulties understanding my technical explanations, do you really think that the comments (the text after the character '#') fit with the instructions?
b) The labels are the strings of characters which begin at the first character of a line; they are used to identify a location in the program, and allow a direct branch to that location.
The labels must only contain letters and numeric characters, and some other characters (such as underscore, for example).
They cannot contains blanks, punctuation characters (.,;), arithmetical characters (+-*/).
The labels must also be unique; there cannot be two labels with the same name in the program (otherwise when a branch is made to that label, the CPU could not know which of the duplicate labels to go to).
A program containing a duplicate label cannot be compiled, that is transformed into machine code, and therefore cannot be executed.
c) In the instructions requiring a memory address, this memory address can only specified as a symbol (eventually with a valued added or subtracted) or eventually an octal address (generally it's a hexadecimal address, but in this CPU octal addressing seems to be privileged).
In no case this memory address can contain a multiplication or a division (such as "A/B") or be a numeric floating value (such as "0.1234").
d) Several instructions are not referenced in the programmer's manual, neither as CPU instructions, nor as pseudo-operations; therefore it's difficult to know what they perform.
e) There are several examples of useless instructions, such as saving several times a same data which is never modified (not even initialized) and never used; or a memory dara which is written with a value, and rewritten with another value without the previous value having been used.
f) the TC instruction allows to call a subroutine; in the documentation it is said that a suboutine called by TC cannot call another subroutine inside its treatment for the good reason that the return address is saved into an unique register and not onto a stack.
I give thereafter some examples of incongruities in the LCM program.
1) the instruction "BANK 35" switches to fixed memory bank 35; this bank contains another program which means that the instructions which follow are not going to be executed since they are not in the same bank!
And the instruction which is behind this BANK instruction has no label, which means that it can't be branched at; thence it has no chance to be ever executed!
2) The instruction "SETLOC BODYATT" is used to place the next instruction at the address "BODYATT".
But "BODYATT" is defined nowhere, only used in this instruction.
3) The instruction "BANK" allows to load the program counter with the first "unused" location of the current fixed bank.
That means we are going to execute unprogrammed instructions...very insteresting!
4) CM/POSE is not a valid label.
5) the instruction "TC INTPRET" calls a subroutine INTPRET, but this subroutine is defined nowhere, at least in the code which is shown. It is present in Colossus249 on line 47,505.
6) In the program there is this sequence:
the instruction "SETPD" is nor defined in the programmer's manual.
It must be a virtual operation!
The same for "VXSC" and "VXV".
7) The instruction "STORE -VREL" allows to "store" something into the specified memory location.
This pseudo-operation is documented, but the documentation doesn't say what it does.
Furthermore the address should be a memory symbol and could in no case be preceded with the minus sign!
8) The label "REF COORDS" cannot contain a blank.
9) The instruction or pseudo-operation UNIT is not documented.
The operand "LXA,1" is strange.
10) The label "COORDS" appears twice; a label can only appear once; duplicating a label always results in a compilation error.
If a jump was performed to that label, the processor would not know which of the two labels to jump to.
11) The instruction "STORE UXA/2" stores something (not specified) into the specified memory location.
The second member can only be a symbolic name of a memory address, with eventually a value added or subtracted, but cannot contain a division.
12) The instruction "DEC .019405" decrements the data memory of which the memory location is given by the second member of the instruction.
The second member is a memory address and not a numerical value; ".019405" is a numerical floating value, and is invalid as a memory address.
13) At different points of the program we find the sequence "PUSH CDULOGIC".
Apparently this sequence pushes a variable CDULOGIC onto a stack; this is weird for the following reasons:
- CDULOGIC is not initialized.
- CDULOGIC is never modified.
- CDULOGIC is repetitively pushed without having be modified, and is never popped, which means that its contents is never used.
- And anyway the documentation says that the CPU uses no stack! There may well be an instruction sequence that does a push, and that would make sense. And you don't have to POP because you could simply purge that part of the pseudo-stack the same way that a frame pointer is used in a modern processor to remove temporary storage. In the case of the AGC you would basically relinquish your coreset or VAC at the end of the task and the stuff would be gone.
14) The instruction "BZF DOGAMDOT" jumps to the label DOGAMDOT" if the accumulator is zero; if not, it continues in sequence; the instruction "TC NOGAMDOT" calls the subroutine "NOGAMDOT"; note that this subroutine is imbricated in the main program; it should not, it should be separated from it.
this "subroutine" NOGAMDOT itself calls a subroutine CORANGOV by the instruction "TC CORANGOV; normally it shouldn't as a subroutine cannot call another subroutine (unless it saves the return address); it is true that the subroutine NOGAMDOT doesn't make a return to the caller, but in that case why use TC and not TCF to jump to NOMGAMDOT?
the subroutine CORANGOV is strange:
CORANGOV TS L
It only does something if the accumulator contains an overflow (otherwise it returns with the second instruction "TC Q"); and in the case that the accumulator contains an overflow, it is loaded with all +1 or -1, according to the documentation of TS.
The accumulator is then added to a variable which is called "LIMITS".
But in fact there is an "instruction "INDEX A" which would modify the meaning of the instruction "CA LIMITS"; in the documentation they only give an example of how the instruction "INDEX A" can modify the behavior of the instruction "TC" (to make it become a conditional jump).
Here it means that the contents of the accumulator is added to the address of the variable "LIMITS" to form the address of the variable which is to be added to the accumulator .
But the contents of the accumulator in currently all "+1" or "-1" (and what does that mean, I only know all "1" or all "0" in computer language).
So the indexing is very limited.
That doesn't seem to make much sense!
15) The subroutine "NOGAMDOT" called by "TC NOGAMDOT" transfers the contents of the accumulator to the data memory "GAMDOT" by the instruction "TS GAMDOT".
Upon return of this subroutine (if there is a return), the execution continues in sequence and meets the instruction "TS GAMDOT" again.
Between these two instructions, GAMDOT has not been used (and is never used); so what is the use of copying the accumulator into GAMDOT, if it is to copy the accumulator into it again without having used the previous copy? That is because they used a skip with a fixed number of instructions - the BZMF +3 immediately before it. Have you never hand-encoded a branch offset? If you are severely short of memory you can do some creative stuff that way.
16) To end with this program, the conclusion is that the program contains 302 lines generating 7027 bytes.
That makes an average of 23 bytes per line.
For an assembler program, it seems absolutely delirious. I think you haven't read the whole listing. I see about 60,000 lines of code generating around 36,000 words of code.