Due to some arm twisting from Paul @Urbite, I am exploring implementing a Space Voyage clone for the TMS9900.
Since I have been doing comparative CPU architectures, these are my initial impressions about this processor.
For good and bad, the designers of the 9900 must have felt that through the competition was 8-bit, they were 16-bit and we had better not forget that.
First the good.
There are sixteen 16-bit registers. With one exception, they are equal in capability.
There are a few simple but very usable addressing modes.
-
Register.
-
Register indirect. The register contains the address of the operand in memory.
-
Register indirect with automatic post-increment. One is added to the register following a byte-sized operation; two after a word-sized operation.
-
Symbolic. The address of a location in memory is specified.
-
Symbolic with register indexing. Because of how instructions are encoded, R0 cannot be used for indexing; a zero for the register means no indexing. With a 16-bit offset value, indexing may be used in two ways:
-
A constant offset into an array or other data structure whose address is in a register.
-
A register contains an offset into an array or other data structure in memory.
Many instructions are two-operand to the extreme. Two memory locations may participate without having to load one of them into a register first.
The general purpose registers are not built into the processor, but reside in memory. A special register called the Workspace Pointer contains the address of this array of sixteen words. This register can be quickly reloaded to make a different “set of registers” available. A register may be loaded with the address of the previous workspace to provide easy access to the prior context.
And now the bad and the ugly.
-
There is no stack. A subroutine call is made with the BL (Branch and Link) instruction; the return address is placed into R11. Returning from the subroutine is by branching to the address in R11 (B *R11). Not a problem unless subroutines are nested. The saving grace is that a stack can be easily implemented with a few simple instructions…
-
The designers appear to have only begrudgingly added some single-byte operations. Not many operations have single-byte forms. Words are stored in memory in a big-endian manner, meaning that their most significant byte comes first. A byte operation on memory involves the first or “upper” byte at an address as expected, but surprisingly, a byte operation on a register also uses the upper byte of a register. But wait; there’s more… There is no byte oriented compare immediate instruction. A program to search a string for a character must either preload that character into another register for use with the compare byte instruction or load characters (with move byte) from the string into the upper half of a register, ensuring that the lower half remains zero, and comparing that with a word whose value is the character to be found times 256.
-
There are no add with carry or subtract with borrow operations. This seems to be indicative of a belief that entities larger than sixteen bits are not needed.
-
There is no direct way to shift the carry flag into a value, making multiple-precision shifts cumbersome.
-
OR or AND operations must involve a constant in the form of OR immediate or AND immediate. Paul @urbite has since informed me that a general purpose OR instruction is masquerading as Set Ones Corresponding (SOC). Why in the world would they call it that? Perhaps for consistancy with Set Zeros Corresponding which is essentially an AND with the inverse of another value. I was afraid I was going to have to resort to self-modifying code to get around this limitation.
-
On the other hand, there is an XOR instruction with a register interacting with a general operand, but not an immediate constant.
Space Voyage was originally written for an 8-bit machine. Transcribing to the 9900 will be awkward in some ways. The first step is converting byte variables, where possible, to word variables so that they may be manipulated natively.
Code to perform multiple-precision addition on the 9900:
00001 *
00002 * R1 = address of one addend
00003 * R2 = address of second addend
00004 * R3 = address of sum
00005 * R4 = number of words in numbers
00006 *
0000 00007 BigAdd
0000 04C5 00008 clr R5 ; Clear "carry"
0002 C031 00009 mov *R1+,R0 ; Get a word of addend 1
0004 1007 (0014) 00010 jmp AddStage
0006 00011 AddLoop
0006 C185 00012 mov R5,R6 ; Save "carry"
0008 04C5 00013 clr R5 ; Clear next "carry"
000A C031 00014 mov *R1+,R0 ; Get a word of addend 1
000C A006 00015 a R6,R0 ; Add "carry" from previous word
000E 1702 (0014) 00016 jnc AddStage ; Skip if no carry
0010 0265 0001 00017 ori R5,1 ; Set "carry"
0014 00018 AddStage
0014 A032 00019 a *R2+,R0 ; Get a word of addend 2
0016 CCC0 00020 mov R0,*R3+ ; Store a word of sum
0018 1702 (001E) 00021 jnc NoCarry2 ; Skip if no carry
001A 0265 0001 00022 ori R5,1 ; Set "carry"
001E 00023 NoCarry2
001E 0604 00024 dec R4
0020 16F2 (0006) 00025 jne AddLoop
and on the 6502:
00005 ;
00006 ; One = address of one addend
00007 ; Two = address of second addend
00008 ; Sum = address of sum
00009 ; X = number of bytes in numbers
00010 ;
0003 00011 BigAdd
0003 18 [2] 00012 clc
0004 A0 00 [2] 00013 ldy #0
0006 00014 AddLoop
0006 B1 00 [5/6] 00015 lda (One),Y
0008 71 01 [5/6] 00016 adc (Two),Y
000A 91 02 [6] 00017 sta (Sum),Y
000C C8 [2] 00018 iny
000D CA [2] 00019 dex
000E D0 F6 (0006) [2/3] 00020 bne AddLoop
Code to perform a multiple-precision left shift on the 9900:
00029 *
00030 * R1 = address of number to shift
00031 * R2 = number of words in number
00032 *
0022 00033 BigSHL
0022 04C3 00034 clr R3 ; Clear "carry"
0024 00035 SHLLoop
0024 C103 00036 mov R3,R4 ; Save "carry"
0026 04C3 00037 clr R3 ; Clear next "carry"
0028 C011 00038 mov *R1,R0 ; Get word of number
002A 0A10 00039 sla R0,1 ; Shift left one bit
002C 1701 (0030) 00040 jnc NoCarry ; Skip if no carry
002E 0583 00041 inc R3 ; Set next "carry"
0030 00042 NoCarry
0030 A004 00043 a R4,R0 ; Combine with previous "carry"
0032 CC40 00044 mov R0,*R1+ ; Put into result
0034 0602 00045 dec R2
0036 16F6 (0024) 00046 jne SHLLoop
and on the 6502:
00024 ;
00025 ; One = address of number to shift
00026 ; X = number of bytes in number
00027 ;
0010 00028 BigSHL
0010 18 [2] 00029 clc
0011 A0 00 [2] 00030 ldy #0
0013 00031 SHLLoop
0013 B1 00 [5/6] 00032 lda (One),Y
0015 2A [2] 00033 rol A
0016 91 00 [6] 00034 sta (One),Y
0018 C8 [2] 00035 iny
0019 CA [2] 00036 dex
001A D0 F7 (0013) [2/3] 00037 bne SHLLoop