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:
INDEX A
TC JMPTAB
...
TCF LOC-2
TCF LOC-1
JMPTAB TCF LOC0
TCF LOC1
TCF LOC2
TCF LOC3
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:
STCALL X
Y
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:
SETPD VLOAD
0
VN
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
TC Q
INDEX A
CA LIMITS
ADS L
TC Q
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.