The Trillium Architecture — obsolete

Part of http://homepage.cs.uiowa.edu/~dwjones/ternary/
by Douglas W. Jones
THE UNIVERSITY OF IOWA Department of Computer Science

Abstract

The Trillium instruction set is designed for a small ternary computer with a 9-trit word and a 19,682 word address space. This architecture is comparable to some of the smaller 16-bit minicomputers of the 1960s and 1970s, and it might be suitable for a microcontroller or programmable interface processor in modern terms.

Warning: The material presented here is very preliminary. First posting on the web, Nov. 21, 2017.


Data Representation

The basic 9-trit word has the following format:

080706 050403 020100
value

This value can be interpreted as an unsigned ternary value:

0000000003=00027 =010
2222222223=ZZZ27 =+19,68310

Alternatively, the value can be interpreted as a 3's-complement signed ternary value:

1111111123=DDE27 =–9,84110
2222222223=ZZZ27 =–110
0000000003=00027 =010
1111111113=DDD27 =+9,84110

In the event that balanced ternary is needed, to convert a value to from 3's complement form to balanced form, just add the bias, 1111111113 (DDD27 or +9,841) and reinterpret the trit values 0 as –, 1 as 0, and 2 as +. To convert balanced ternary to 3's complement, reverse the process.

One word may also be used to hold a 9-bit ternary coded binary (TCB) number. Each trit has a value of either 0 or 1, corresponding to the value of one binary bit. There are fast algorithms for conversion from TCB to ternary, so generally, conversion should be done early.

Instruction Format

One word can hold one machine instruction (or at least the first word of a multi-word instruction), formatted as follows:

080706 050403 020100
op a src dst

The instruction word contains the following fields:

op
The opcode field, 2 trits specifying one of 9 instructions. Some of these are further subdivided by use of otherwise unused fields.

a
The autoincrement field, used to modify the addressing mode subfields of the src and dst fields. For details, see the discussion of addressing modes.

src
The source field specifies the first source of data to be manipulated by the instruction.

dst
The destination field specifies the destination where the result of the instruction is to be placed. In the case of two-operand instructions such as comparison and addition, it may also be used to specify a second source operand.

This instruction format is inspired by the formats of the DEC PDP-11. The Motorola 68000 was also inspired by the PDP-11, but followed it more slavishly. Here, the change of number base and the somewhat reduced information capacity of a word lead to a much looser adaptation.

Assembly Format

In the Trillium assembly language, instructions are written as

label:  opcode  src,dst ; comment
        opcode+ src,dst
        opcode- src,dst

The suffix + and - on the op field indicates the setting of the a field. The default if neither is used is simple memory addressing (neither autoincrement nor autodecrement). Labels are always followed by a colon, and comments, if present, are always preceeded by a semicolon, as with the classic MACRO-11 assembly language for the PDP-11.

Addressing Modes

The a field combined with the first trit of the src and dst fields determines the addressing modes used by the instruction. The final two trits of the src and dst fields usually specify the registers used by the instruction.

020100
m r

The three addressing modes specified by the m field are:

Constant mode m = 0:
For source operands specified by either the src or dst fields, the value of the r field is used as an unsigned constant, any value from 0 to 8.
For destination operands specified by the dst field, the value of the r field is ignored and the result of the computation is discarded, except for any side effects on the condition codes.

Register mode m = 1:
The operand is contained in the general purpose register specified by the r field.

Memory, autoincrement and autodecrement modes m = 2:
In all of these modes, the general purpose register specified by the r field holds the memory address of the operand, as modified by the associated register tag, on machines able to address over 19,682 words. This mode is modified by the a field of the instruction as follows:

Memory mode a = 0:
The general purpose register specified by the r field is unchanged.

Autoincrement mode a = 1:
After the register specified by the r field is used, it is incremented.

Autodecrement mode a = 2:
The register specified by the r field is decremented before it is used.

Note that, if src.m = 2 and dst.m = 2, they will both use the same addressing mode. It is not possible to have a source operand addressed with autoincrement mode while the destination operand is addressed using autodecrement mode.

Note that, if src.m = 2 and dst.m = 2, and src.r = dst.r, that is, if the same register is used as an address register in both the source and destination fields, autoincrement addressing will increment the register twice, once after using the register for the source address and a second time after using the register for the destination address. Similarly, autodecrement addressing will decrement the register twice, once before taking the source address, and once after taking the destination address.

Addressing relative to the program counter

Because the program counter is part of the register set, autoincrement addressing using the program counter allows an immediate operands to be stored in the instruction stream immediately after the instruction word. This should only be used in the src field.

Addressing relative to stack pointers

Because stack pointers are held in registers, autoincrement and autodecrement addressing can be used to push and pop operands.

If a register is used as a stack pointer with the stack growing up toward increasing addresses, then immediate to auto-increment addressing can be used to push immediate operands onto the stack. This slightly biases the architecture toward upward growing stacks.

Indexing

There is no indexed addressing. For indexed addressing, first compute the address in a register and then use that register as a memory address. Because most objects are small, index displacements will frequently be between 1 and 8. As a result, a common instruction sequence for indexing will be to copy the base register (and its tag) and then add a constant-mode displacement to the copy in order to compute the indexed memory address.

Assembly Format

In the Trillium assembly language, the source and destination addressing modes are specified as follows:

label:  opcode  =0,=8   ; constants 0 and 8 (although the 8 may be ignored)
        opcode  r1,r2   ; registers 1 and 2 as source and destination
        opcode  *r3,*r4 ; registers 3 and 4 as operand pointers
        opcode+ *pc,=   ; auto-increment pc (immediate) to discard

In addition, assembers should take constants that are over 8 and generate immediate operands when possible. In the following, when the assembler encounters the first instruction, it should generate the same code shown by the second sequence of two instructions.

        opcode  =12,r1  ; out-of-range constant 12

        opcode+ *pc,r1  ; immediate 12 to register 1
	.word   12

Similarly:

        opcode+ =12,*r3 ; out-of-range constant 12 to auto-increment r3

        opcode+ *pc,*r3 ; immediate 12 to auto-incrment r3
	.word   12

Registers

There are 9 registers, of which 7 are the general-purpose operand registers r1 to r7.

   080706 050403 020100
t8 sp (r8)
t7 r7
t6 r6
t5 r5
t4 r4
t3 r3
t2 r2
t1 r1
t0 pc (r0)

The program counter, pc or r0 always points to the next word in the instruction stream. The stack pointer, sp or r8 always points to the next word in the instruction stream. Effectively, fetching an instruction is done using autoincrement addressing.

The stack pointer, sp or r8 always points to the first free word beyond the stack top, assuming that the stack grows upward, or to the topmost word on the stack, assuming that the stack grows downward. None of the user-mode instructions on this machine make any assumptions about the direction of stack growth.

The register tags t0 to t8 are only present on machines able to address more than 19,682 words of memory. These may be as simple as extra bits attached to the register when it is used for memory addressing, or as complex as segment identifiers when used with a memory management unit supporting segmenting.

Whatever their form, tags allow (at minimum) a separation of program and data space. Typically, the program counter will be tagged with the program segment while the stack pointer is tagged with the data segment. Other registers used for addressing will have values derived from either the program counter or stack pointer, and hence, will carry the appropriate tag.

Processor Status Word

080706 050403 020100
  cc

The processor status word is divided into several fields:

cc
The condition codes, giving information about the result of the most recent data manipulated by the processor. Arithmetic instructions all set the condition codes some of them also use them as an operand. Conditional instructions test the condition codes.

Condition Codes

020100
s v c

s — The sign trit.
The sign of the result of the most recent operation:
s = 0 — zero
s = 1 — positive
s = 2 — negative

v — The overflow bit.
Indicates whether the most recent operation produced a signed overflow.
v = 0 — no overflow (s is correct)
v = 1 — overflow (s is inverted)
v = 2 — ?

c — The carry trit.
The carry out of the adder for the most recent operation.
c = 0 — no carry
c = 1 — carry (also indicates unsigned overflow)
c = 2 — carry (relevant only after shift operations)

The condition codes correspond closely to the 4-bit condition codes used on the PDP-11 and copied by most more recent binary p rocessors. The s condition code encodes all useful values of the binary N and Z condition codes, while the v and c condition codes correspond to the V and C condition codes of the PDP-11.

Instructions

Move

080706 050403 020100
0 0 a src dst
move src,dst
dstsrc; set cc

Copies the source operand to the destination, overwriting anything previously stored there. Register-to-register moves always copy the tag field along with the register. The condition codes are set to report on the result of the move:

s reports the sign
v reset to 0
c reset to 0

Examples

        move    =0,r1   ; clear r1
        move    r2,=    ; set the condition codes according to r2
        move    r3,*r4  ; store r3 where r4 points in memory

Because the program counter is a register, the move instruction also serves as a branch instruction:

        move    r5,pc   ; branch to the location pointed to by R5
        move    *r5,pc  ; branch indirect through the location
	move    =dst,pc ; absolute branch to dst

	move+   *pc,pc  ; long form of absolute branch
	.word   dst

Because the stack pointer (if used) is a register, assuming that the stack grows upward through memory and the stack pointer always points to the free location just above the stack top, we can use move to push and pop the stack:

        move+   r5,*sp   ; push r5 onto the upward-growing stack
        move-   *sp,r6   ; pop the stack into r6
        move+   =0,*sp   ; push zero onto the stack
        move+   =150,*sp ; push the constant 150 onto the stack

        move+   *pc,*sp  ; long form of push 150 on an upward-growing stack
	.word   150

We can also push and pop on a downward going stack where the stack pointer always refers to the top element of the stack.

        move-   r5,*sp   ; push r5 onto the downward-growing stack
        move+   *sp,r6   ; pop the stack into r6
        move-   =0,*sp   ; push zero onto the stack
        ; constants above 8 must be moved to a register before pushing

Add

080706 050403 020100
0 1 a src dst
add src,dst
dstdst + src ; set cc

Adds the source operand to the destination operand, replacing the destination. The condition codes are set to report on the sum:

s reports the sign
v indicates overflow
c indicates the carry out of the adder

Examples

        add     =1,r1   ; increment r1
        add     r2,=5   ; set the condition codes according to r2 + 5
        add     r3,*r4  ; add r3 to the memory location pointed to by r4
        add-    *sp,r6  ; pop from an upward-growing stack and add to r6
        add+    *sp,r6  ; pop from a downward-growing stack and add to r6

Because the program counter is a register, the add instruction also serves as a pc-relative branch instruction. The assembler uses the dot (.) as the symbol for the current location, so subtracting this from dst, the destination address, we get the relative address suitable for use in position independent code:

        add     =dst-(.+1),pc

	add+    *pc,pc
	.word	dst-.

Subtract

              080706 050403 020100              
0 2 a src dst
sub src,dst
dstdst + (ZZZ27src) + 1 ; set cc

Subtracts the source operand from the destination operand, replacing the destination. The condition codes are set to report on the difference:

s reports the sign
v indicates overflow
c indicates the carry out of the adder
c = 0 — borrow
c = 1 — no borrow
c = 2 — never

Note the inversion of the c conditon code. This is because the Trillium uses 3's-complement arithmetic, where a – b is computed as a + (2222222223 – b) + 1. A carry out of this sum indicates that there was no borrow, that is, it indicates that a ≥ b as unsigned ternary numbers.

Examples

        sub     =1,r1   ; decrement r1
        sub     r2,=5   ; compare r2 with the constant 5
        sub     r3,*r4  ; subtract r3 from the memory location pointed to by r4
        sub-    *sp,r6  ; pop from an upward-growing stack and subtract from r6

Add With Carry

080706 050403 020100
? ? ? 1 src dst
addc src,dst
dstdst + src + c ; set cc

Adds the source operand to the destination operand, including the carry bit, replacing the destination. Unlike the regular add instruction, the only addressing mode supported for the source operand is register mode. The condition codes are set to report on the sum:

s reports the sign
v indicates overflow
c indicates the carry out of the adder

Examples

Add with carry allows high precision addition. Consider computing the 18-trit addition of r1-r2 to r3-r4, where the first register of each pair holds the least significant half:

        add     r1,r3   ; add less significant trits
        addc    r2,r4   ; add most significant trits

Consider adding the 27-trit value in r1-r3 to a 27-trit value in the memory location pointed to by r4, where the least significant word comes first in memory:

        add+    r1,*r4  ; add less significant trits
        addc+   r2,*r4  ; add middle trits
        addc    r3,*r4  ; add most significant trits
        sub     r4,=2   ; restore pointer

In the above, autoincrement addressing is used to access consecutive words of the operand in memory, and then the register is restored to its original value. After-the-fact restoration using an immediate subtract avoids the need to make a temporary copy of r4.

Subtract With Borrow

              080706 050403 020100              
? ? ? 2 src dst
subb src,dst
dstdst + (ZZZ27src) + c ; set cc

Subtracts the source operand from the destination operand, including the borrow information conveyed by the carry bit, replacing the destination. Unlike the regular subtract instruction, the only addressing mode supported for the source operand is register mode. The condition codes are set to report on the difference:

s reports the sign
v indicates overflow
c indicates the carry out of the adder
c = 0 — borrow
c = 1 — no borrow
c = 2 — never

Examples

Subtract with borrow allows high precision subtraction. Consider computing the 18-trit subtraction of r1-r2 from r3-r4, where the first register of each pair holds the least significant half:

        sub     r1,r3   ; subtract less significant trits
        subb    r2,r4   ; subtract most significant trits

Shift Left

080706 050403 020100
? ? ? 0 n dst
sl n,dst
dstdst <<3 n ; set cc

Shifts the destination operand left. A shift count of 00 is interpreted as meaning 9. Autoincrement and autodecrement modes are not applicable. The condition codes are set as follows:

s reports the sign of the result
v indicates overflow
c holds the last trit shifted out

In the absence of overflow, dst <<3 n = dst × 3n. In this context, overflow does not merely report that the sign of the result differs from the sign of the operand, but it also reports any loss of precision. So, if dst × 3n (computed with unlimited precision) has a sign different from dst << 3n (computed in a 9-trit word), the overflow bit will be set.

Examples

        sl      1,r1    ; multiply r1 by 3
        sl      2,*r2   ; multiply the memory location pointed to by r2 by 9
        sl      9,=0    ; set the condition codes to 000
        sl      9,=1    ; set the condition codes to 011
        sl      9,=2    ; set the condition codes to 012
        sl      8,=1    ; set the condition codes to 100
        sl      8,=2    ; set the condition codes to 210
        sl      8,=4    ; set the condition codes to 111
        sl      8,=5    ; set the condition codes to 211

Shift Right Unsigned

080706 050403 020100
? ? ? 1 n dst
sru n,dst
dstdst >>3 n ; set cc

Shifts the unsigned destination operand right. A shift count of 00 is interpreted as meaning 9. Autoincrement and autodecrement modes are not applicable. The condition codes are set as follows:

s reports the sign of the result
s = 0 — zero
s = 1 — positive
s = 2 — never
v were any nonzero trits shifted out
c holds the last trit shifted out

For unsigned right shifts, zeros are shifted in from the left, so the result is always positive or zero. In general, dst >>3 n = dst / 3n, with the unsigned result truncated toward zero. When the overflow bit is set, it indicates that the unsigned operand was not evenly divisible by the indicated power of three.

Shift Right Signed

080706 050403 020100
? ? ? 2 n dst
sru n,dst
dstdst >>3 n ; set cc

Shifts the unsigned destination operand right. A shift count of 00 is interpreted as meaning 9. Autoincrement and autodecrement modes are not applicable. The condition codes are set as follows:

s reports the sign of the result
v were any nonzero trits shifted out
c holds the last trit shifted out

For signed right shifts, the value shifted in from the left depends on the sign of the operand. Zeros are shifted in for positive operands, and twos are shifted in for negative operands. For example:

111111111 >>3 1 = 011111111 (+9841 >>3 1 = +3280)
111111112 >>3 1 = 211111111 (–9841 >>3 1 = –3281)

In general, dst >>3 n = dst / 3n, with the signed result rounded down so that the remainder is either zero or positive. When the overflow bit is set, it indicates that the unsigned operand was not evenly divisible by the indicated power of three.

Conditional Branches

The following branch instructions are named so that, following sub src,dst, which computes dst ← dst – src, the operators report on dst ? src, using the original value of src prior to the subtraction and using ? as a stand-in for a comparison operator.
beq – branch if equal
dst = src  —  s = 0

bne – branch if not equal
dst ≠ src  —  s ≠ 0

blts – branch if less than signed
dst < src  —  ((s = 2) & (v = 0)) | ((s = 1) & (v = 1))

bles – branch if less than or equal signed
dst ≤ src  —  ((s = 2) & (v = 0)) | ((s = 1) & (v = 1)) | (s = 0)

bges – branch if greater than or equal signed
dst ≥ src  —  ((s = 1) & (v = 0)) | ((s = 2) & (v = 1)) | (s = 0)

bgts – branch if greater than signed
dst > src  —  ((s = 1) & (v = 0)) | ((s = 2) & (v = 1))

bltu – branch if less than unsigned
dst < src  —  c = 0

bleu – branch if less than or equal unsigned
dst ≤ src  —  (c = 0) | (s = 0)

bgeu – branch if greater than or equal unsigned
dst ≥ src  —  (c ≠ 0)

bgtu – branch if greater than unsigned
dst > src  —  (c ≠ 0) & (s ≠ 0)

The following additional condition code tests are not typically used after comparisons:

bpos – branch if result positive
s = 1

bnpos – branch if result non-positive
s ≠ 1

bneg – branch if result negative
s = 2

bnpos – branch if result non-negative
s ≠ 2

bvr – branch if no overflow
v = 0

bvs – branch if overflow
s ≠ 0