The 3op Processor

3op is a general purpose 16 bit processor with 16 registers and a 3 operand instruction set. 3 operands of 4 bits each leaves only 4 bits to specify an operation, making it quite challenging to define a useful set of instructions.

Memory is 64K of 16 bits words, and is word addressed. The only data type supported is the 16 bit word.

The 3op Instruction Set


3op has 16 registers. 3 of them have fixed (hardware-defined) uses, and another 3 have conventional (software-defined) uses.

R0(hardware)(0)Constant 0
R1(software)(-1)Constant -1
R2-R11General Purpose Registers
R12(software)(L)Subroutine Link Register
R13(software)(SP)Stack Pointer
R14(hardware)(IV)Interrupt Vector
R15(hardware)(PC)Program Counter
The software-defined registers may be used for other purposes if a particular program doesn't need them. Operating Systems may impose their own register conventions.

PC is incremented before the instruction is executed, so it will always be pointing to the next program word.


The instruction set is divided into four groups: load/store, arithmetic, logical, and flow control. The instruction encoding depends on the instruction in question, but in all cases the top two bits determine the group, and the next two the operation (with one exception).

Load/Store Instructions

0000 aaaa bbbb ccccMOVE Ra,(Rb:c)
0001 aaaa bbbb ccccMOVE Ra,-(Rb:c)
0010 aaaa bbbb ccccMOVE (Rb:c),Ra
0011 aaaa bbbb ccccMOVE (Rb:c)+,Ra
The first two instructions are stores, while the second two are loads. The address is the sum of a 4 bit constant and the contents of a register. -() will decrement the register before address calculation occurs; ()+ will increment it after.

Immediate loads can be implemented with MOVE (PC:0)+,Ra. Using the (Rb:c) mode on PC is useless, and the predecrement mode is fatal, so these combinations should be considered illegal.

The first 16 parameters passed on the stack may be accessed with MOVE Ra,(SP:offset) and MOVE (SP:offset),Ra. Local and global variables may be accessed in a similar way if registers are loaded with the appropriate pointers.

Arithmetic Instructions

0100 aaaa bbbb ccccADD Rb,Rc,Ra
0100 aaaa bbbb ccccSUB Rb,Rc,Ra
0100 aaaa bbbb ccccSHZ Rb,Rc,Ra
0100 aaaa bbbb ccccSHF Rb,Rc,Ra
ADD stores the result of Rb+Rc in Ra. SUB stores the result of Rb-Rc in Ra.

SHZ and SHF are the shift instructions. They both load Ra with the result of shifting Rb by Rc places, shifting to the left if Rc is positive, and to the left if Rc is negative. SHZ fills with 0. SHF fills with the contents of bit 15 of Rb at each step of the shift.

If SHF is shifting to the right, the result will be an arithmetic shift. If the shift is to the left, the result will be a 16 bit roll.

Logical Instructions

1000 aaaa bbbb ccccAND Rb,Rc,Ra
1001 aaaa bbbb ccccOR Rb,Rc,Ra
1010 aaaa bbbb ccccBIC Rb,Rc,Ra
1011 aaaa bbbb ccccXOR Rb,Rc,Ra
AND stores the result of Rb AND Rc in Ra. The rest should be obvious.

Flow Control Instructions

This is where it gets messy
1100 aaaa bbbb ccccSKP Rb CONDa Rc
1101 aaaa xxxx xxxxLDI Ra,xxxxxxxx
111x xxxx xxxx xxxxBRA xxxxxxxxxxxxx
SKP compares the registers Rb and Rc using the condition specified by aaaa, and skips the next instruction if the comparison succeeds. It is still not clear what the conditions should be.

LDI simply loads Ra with an 8 bit constant (sign extending it to 16 bits first).

BRA is an unconditional branch with a 13 bit offset. Subroutine calls may be implemented by loading a return address into the Subroutine Link Register immediately before the branch.


Interrupts are level sensitive, and interrupting devices should keep /IRQ low until the interrupt is acknowledged. This is the way interrupts are handled on the 6502. It requires polling to determine the interrupt source, but allows a very simple hardware implementation. Applications that require a faster response can speed things up with more sophisticated hardware.

On recieving an interrupt, the processor must first complete the current instruction. It then sets the Interrupt Disable Flag and swaps the contents of IV and PC. The interrupt handler must determine the source of the interrupt, acknowledge it, and carry out any processing required.

To return from an interrupt, the handler must ensure that interrupts get enabled, IV is loaded with the address of an interrupt handler, and PC is loaded with the return address. A little register juggling will be required, since the return address is in IV.

Unresolved Issues

The Interrupt Disable Flag

The location of the Interrupt Disable Flag has not been determined. One proposal is that it is write-only, and can be modified by writing to R0 (which is always read as 0). Another possibility is that it is stored in a memory-mapped I/O register. Or the BRA offset could be reduced to 12 bits, and another instruction added to access a control register.

The SKP Instruction

A possible modification of the SKP instruction is to split it into two instructions, determined by the top bit of the condition field. One of these would be a normal skip on one of 8 conditions. The other would be a skip on the same 8 conditions, but it would decrement Rb first. This makes loops somewhat easier.

In this case, the 8 conditions would be:
000Rb<Rc unsigned100Rb>=Rc unsigned
001Rb<Rc signed101Rb>=Rc signed
011Rb+Rc gives carry111Rb+Rc does not give carry

Indirect Memory Referencing

3op has only 64K of memory, and can't afford to waste any. But consider a machine like 3op with 32 registers, and a 19 bit word. With this machine, it would be possible to use the top bit of an address as an indirect bit. If the indirect bit is clear, then the address points directly to the data required. If it is set, then the location that it points to is not the data, but another pointer. If this pointer's indirect bit is clear, then it points to the data. Otherwise it points to yet another pointer...

The top bit of PC is ignored, and always read as 1. The top bit of IV is the interrupt disable flag. When PC is copied to IV, the interrupt disable flag will be set.