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
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.
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.
|R2-R11||General Purpose Registers
|R12||(software)||(L)||Subroutine Link Register
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).
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.
|0000 aaaa bbbb cccc||MOVE Ra,(Rb:c)
|0001 aaaa bbbb cccc||MOVE Ra,-(Rb:c)
|0010 aaaa bbbb cccc||MOVE (Rb:c),Ra
|0011 aaaa bbbb cccc||MOVE (Rb:c)+,Ra
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
ADD stores the result of Rb+Rc in Ra. SUB stores the result of Rb-Rc in
|0100 aaaa bbbb cccc||ADD Rb,Rc,Ra
|0100 aaaa bbbb cccc||SUB Rb,Rc,Ra
|0100 aaaa bbbb cccc||SHZ Rb,Rc,Ra
|0100 aaaa bbbb cccc||SHF Rb,Rc,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.
AND stores the result of Rb AND Rc in Ra. The rest
should be obvious.
|1000 aaaa bbbb cccc||AND Rb,Rc,Ra
|1001 aaaa bbbb cccc||OR Rb,Rc,Ra
|1010 aaaa bbbb cccc||BIC Rb,Rc,Ra
|1011 aaaa bbbb cccc||XOR Rb,Rc,Ra
Flow Control Instructions
This is where it gets messy
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.
|1100 aaaa bbbb cccc||SKP Rb CONDa Rc
|1101 aaaa xxxx xxxx||LDI Ra,xxxxxxxx
|111x xxxx xxxx xxxx||BRA xxxxxxxxxxxxx
LDI simply loads Ra with an 8 bit constant (sign extending it to 16 bits
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
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.
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:
| 000||Rb<Rc unsigned||100||Rb>=Rc unsigned
| 001||Rb<Rc signed||101||Rb>=Rc signed
| 011||Rb+Rc gives carry||111||Rb+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.