2.1 Microcode - jpursey/oz-3 GitHub Wiki
Microcode Assembly
Instructions for the OZ-3 CPU are implemented entirely in microcode assembly. Like regular assembly language, microcode assembly consists of very small individual instructions that can be put together to make a larger program, or in this case, a regular assembly instruction. However, microcode instructions are much simpler and more constrained in what they can do.
Throughout documentation, "OZ-3 instruction" or just "instruction" on its own refers to an instruction callable by OZ-3 programs that is implemented in terms of microcode. The term "microcode instruction" or just "microcode" is used specifically for microcode instructions that are used to implement an "instruction" / "OZ-3 instruction".
OZ-3 Instruction Definition
All instructions for the OZ-3 are defined by a 16-bit code word, and then zero or more 16-bit values that immediately follow. The instruction definition specifies how the 16-bit code word is parsed. The high order byte is the instruction type or category and corresponds to a name (for instance, MOV
) and the low order byte contains a bit code that may include up to two embedded argument codes which can be a 16-bit register index, 32-bit register index, or an unsigned integer value.
0 0 0 0 0 1 1 0|0 1 0|r r r|# #
\-------------/ \---/ \---/ \-/
type sub arg2 arg1
- type: The high order byte is the type, in the example this is 6
- sub: Additional bits not allocated to the type or arguments define a sub-type, which can define a totally different microcode program.
- arg1/arg2: Up to two arguments may be embedded in the low order byte and may be 1 to 8 bits (no more than 8 bits total, of course). Each argument can be one of three types:
- 16-bit register: The argument is any of the 8 general purpose 16-bit registers
R0
toR7
(3 bits). The first argument is given the namea
in microcode and the second argument is given the nameb
in microcode. - 32-bit register: The argument is any of the 4 general purpose 32-bit registers
D0
toD3
(2 bits). The first argument is given the namesa0
anda1
in microcode for the first and second words of the 32-bit register. The second argument is given the namesb0
andb1
in microcode. - immediate value: The argument is an unsigned integer (1 to 8 bits). The first argument is loaded into register
C0
and the second argument is loaded into registerC1
before the microcode executes (see Microcode Registers below)
- 16-bit register: The argument is any of the 8 general purpose 16-bit registers
In addition, a fixed set of 16-bit words may follow the code word for additional values. How many additional 16-bit words exist is defined by the microcode during the "fetch" phase (see below).
OZ-3 Instruction Code Format
Instructions are defined by a sequence of microcode instructions. These microcode instructions are logically divided into two phases: the "fetch" phase and the "execution" phase.
- Fetch phase: All microcode up to the first
UL
orADR
is in the "fetch" phase. The number ofLD
opcodes within the fetch phase determines how many large the OZ-3 instruction is (beyond the code word). Store opcodes (ST
andSTP
), lock opcodes (LK
,LKR
,PLK
, andCLK
), and control flow opcodes (JP
,JC
,JD
,WAIT
,HALT
,END
, andIRT
) is not allowed during the fetch phase. - Execution phase: All microcode after the first
UL
orADR
are in the "execution" phase. All microcode is allowed at this point (within normal opcode requirements).
Each microcode instruction may have an optional label, and zero, one, or two arguments, and a terminating semicolon. For example, here is a pointless instruction that has an additional word required in the "fetch" phase and demonstrates 0, 1, and 2 arguments and a label:
LD(C0);
UL;
@label:ADDI(a,1);
JC(NZ,@label);
Microcode assembly is also written entirely without whitespace. The above is written on separate lines for readability, but when passed to the microcode compiler as part of an instruction definition, all whitespace must be removed. In this case:
LD(C0);UL;@label:ADDI(a,1);JC(NZ,@label);
Argument Types
Microcode operations take arguments of specific types:
r
: Word register. In microcode assembly, this may be any explicitly named word register (e.g.R0
,IP
,ST
, etc.) or a register provided from the instruction code (a
orb
for word args;a0
,a1
,b0
, orb1
for low/high word parts of dword argsA
andB
). There are some registers that can't be permanently changed via standard microcode (this does mean they can be used as temporary storage within an instruction's microcode, as they will be restored automatically):- The
ST
register is set to the internalMSR
register at the end of the instruction (which starts as equal toST
). To change the persistent value ofST
, use theMSTC
/MSTS
/MSTX
/MSTM
operations to setMST
and return with theMSTR
operation. - The
MB
register specifies the memory bank mapping and is reset to the actual memory bank binding at the end of the instruction. To change the persistent value ofMB
(and the associated bank mapping), use theCBK
microcode.
- The
rb
: Byte of a word register. This is the same asr
with the suffix:H
for the high byte and:L
for the low byte. For example,R0:H
references the high byte of registerR0
(equivalent to the valueR0 >> 8
).rn
: Nibble of a word register. This is the same asr
with an suffix being:
followed by the index of the nibble (low to high). For example,R0:2
references the third nibble in registerR0
(equivalent to the value(R0 >> 8) & 0xF
).u
: Immediate value. This is an 8-bit unsigned value. In microcode assembly, this is an explicit integer in the range [0,255].v
: Immediate value. This is an 8-bit signed value. In microcode assembly, this is an explicit integer in the range [-128,127]. This is sign extended to a 16-bit value, if used as a 16-bit value in microcode (for instanceMOVI
orADDI
).b
: Memory bank. In microcode assembly this must be one ofCODE
,STACK
,DATA
, orEXTRA
.s
: StatusZSCOI
flag mask. In microcode assembly, this is any combination ofZ
,S
,C
,O
,I
, and_
characters (e.g.ZC
orZ_C_
or just_
to indicate no flags).c
:ZSCO
flag condition. In microcode assembly, this is one of the following:Z
,NZ
,S
,NS
,C
,NC
,O
, orNO
.a
: Relative microcode address. In microcode assembly, this is an integer value that is added to the microcode program counter (MIP
) to jump to a new location in the microcode program. It can also be an @ label to jump to the specified instruction with the same label.p
: Port mode. In microcode assembly, this is any combination ofT
,S
,A
, and_
characters (e.g.TA
orT_A
or just_
to indicate no mode flags).
Microcode Registers
Four additional addressable in microcode only registers are defined:
ST
: The full value of theST
register (see Specifications) is available to microcode. However, it is a cached value and does not affect the actual flags (see above)- Cache registers: Three additional 16-bit "cache" registers are available:
C0
,C1
, andC2
.C2
is always set to zero at the beginning of a microcode program.C0
andC1
are set to the first and second argument immediate values in the instruction, if there are any, otherwise they are also set to zero.
In addition to these registers, microcode defines three additional non-addressable registers:
MIP
: TheMIP
register specifies what microcode within the OZ-3 instruction will be executed next. It is automatically advanced, or can be set via theJP
,JC
, andJD
microcode.MST
: TheMST
register is the microcode version of theST
register, and is initialized to theST
register when microcode begins execution. Many microcode operations will update theMST
register. However, these changes do not automatically get reflected in theST
register for subsequent instructions to use. To return some or all of theMST
register to theST
register, theMSR(s,s);
operation must be called.MSR
: TheMSR
register holds the final value that will be assigned to theST
register when execution completes. Like theRST
register, it is initialized with theST
register when microcode begins execution. It is updated by the correspondingMSR(s,s);
operation.
Microcode Definitions
The following operations are documented first by their microcode assembly
opcode and required arguments and types. For instance OP(s,r);
means that
the operation requires two arguments, the first of which is a ZSCOI
flag mask
and the second is a word register.
In the description, the arguments are referred to as follows:
arg1
,arg2
: The literal argument value provided (anything butr
).reg1
,reg2
: The register value provided to the argument index (r
).rbyte1
,rbyte2
: The register byte provided to the argument index (rb
).rnib1
,rnib2
: The register nibble provided to the argument index (rn
).
MSC(s);
Cycles: 0
Clears any flags specified in arg1
from microcode ZSCOI
status flags (MST
):
MST = MST & ~arg1
MSS(s);
Cycles: 0
Sets any flags specified in arg1
inside microcode ZSCOI
status flags (MST
):
MST = MST | arg1
MSX(s);
Cycles: 0
Exclusively ors arg1
with microcode ZSCOI
status (MST
):
MST = MST ^ arg1
MSM(s,r);
Cycles: 1
Moves (copies) the flags specified by arg1
in reg2
to the microcode status
flags (MST
):
MST = (MST & ~arg1) | (reg2 & arg1)
MSR(s,s);
Cycles: 0
Returns microcode status flags (MST
) to the return status register (MSR
)
and the ST
register, by clearing any unset bits from MST
specified by arg1
and setting any set bits from MST
specified by arg2
. Explicitly:
MSR = (MSR & (MST | ~arg1)) | (MST & arg2)
ST = MSR
Examples:
MSR/ST Before MST arg1 (clr) arg2 (set) MSR/ST After
____I ZSCO_ ZS___ __CO_ __COI
ZS___ _____ ZS___ __CO_ _____
Z___I _S_O_ ZS___ ZSCO_ _S_OI
ZS_O_ Z_C__ ZS___ __CO_ Z_CO_
Z_C_I _S_O_ ____I ____I Z_C__
_S_O_ C_Z_I ____I ____I _S_OI
WAIT(r);
Cycles: 0 (see description)
Puts core into a waiting state for reg1
cycles from the beginning of the fetch phase of the containing instruction. Further execution of
microcode in the instruction is terminated. If the wait time is less than the amount of time taken so far executing the instruction (including the
time when microcode execution is paused), then the waiting state ends immediately. After WAIT
successfully completes, reg1
is set with the number of cycles over the requested number that actually passed.
If an interrupt occurs while the core is waiting, the interrupt will still be handled normally, and when the interrupt returns waiting will resume if the deadline has not already passed. If a WAIT
is executed from within the interrupt during an existing WAIT, then the inner WAIT will fail and exit immediately, and reg1
is not updated.
It is invalid to call during the "fetch" phase.
This sets or clears MST
flags as follows:
O
: Cleared ifWAIT
executed normally. Set ifWAIT
failed (it was called within anotherWAIT
).
HALT;
Cycles: 0 (see description)
Puts the core into kIdle
state. Further execution of microcode in the
instruction is terminated. Execution will not continue until the core is
reset.
It is invalid to call during the "fetch" phase.
LK(b);
Cycles: 0 (see description)
Lock memory bank arg1
and sets it as the active memory bank. This is used
to lock a memory bank for exclusive access. If the bank is already locked,
the microcode execution is paused for the instruction until the bank is
unlocked. Only one lock can be active at a time (memory bank, port, or
core).
LKR(r);
Cycles: 0 (see description)
Lock memory bank associated with reg1
(see Registers for mapping)
and sets it as the active memory bank. This is most useful when the register
is dynamic (a
, a0
, a1
, b
, b0
, or b1
). Otherwise it is identical
to LK
.
UL;
Cycles: 0
Unlock memory bank previously locked by LK
. All locked banks must be
unlocked before microcode execution completes in the instruction.
ADR(r);
Cycles: 1
Sets the memory bank address bus to reg1
. The memory bank must be locked
from LK
.
LAD(r);
Cycles: 0
Loads the current address from the memory into reg1
. The memory bank must
be locked from LK
.
LD(r);
Cycles: 1
Loads the value from memory into reg1
, and advances the address bus. The
memory bank must be locked from LK
, and the address set previously by ADR
.
ST(r);
Cycles: 1
Stores the value from reg1
into memory, and advances the address bus. The
memory bank must be locked from LK
, and the address set previously by ADR
.
STP(r);
Cycles: 1
Decrements the address bus, then stores the value from reg1
into memory.
The memory bank must be locked from LK
, and the address set previously by
ADR
.
MOVI(r,v);
Cycles: 1
Copies the signed value arg2
into reg1
:
reg1 = arg2
MOV(r,r);
Cycles: 1
Copies the value from reg2
into reg1
:
reg1 = reg2
MVBI(rb,u);
Cycles: 1
Copies the unsigned value arg2
to the register byte specified by rbyte1
.
rbyte1 = arg2;
MVB(rb,rb);
Cycles: 1
Copies register byte value rbyte2
to the register byte specified by rbyte1
.
rbyte1 = rbyte2;
MVNI(rn,u);
Cycles: 1
Copies the unsigned value arg2
to the register nibble specified by rnib1
.
rnib1 = arg2 & 0xF;
MVN(rn,rn);
Cycles: 1
Copies register nibble value rnib2
to the register nibble specified by rnib1
.
rnib1 = rnib2;
NEG(r,r);
Cyles: 1
Negates the value in reg2
and assigns to reg1
.
reg1 = -reg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: ClearedO
: Negation overflowed (reg2
and nowreg1
is 0x8000)
ADDI(r,v);
Cycles: 1
Adds the signed value arg2
to reg1
:
reg1 = reg1 + arg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: Unsigned overflow occurredO
: Signed overflow occurred
ADD(r,r);
Cycles: 1
Adds the value from reg2
to reg1
:
reg1 = reg1 + reg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: Unsigned overflow occurredO
: Signed overflow occurred
ADC(r,r);
Cycles: 1
Adds the value from reg2
to reg1
with carry:
reg1 = reg1 + reg2 + C
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: Unsigned overflow occurredO
: Signed overflow occurred
SUB(r,r);
Cycles: 1
Subtracts the value from reg2
from reg1
:
reg1 = reg1 - reg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: Unsigned overflow occurredO
: Signed overflow occurred
SBC(r,r);
Cycles: 1
Subtracts the value from reg2
from reg1
with borrow:
reg1 = reg1 - reg2 - C
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: Unsigned overflow occurredO
: Signed overflow occurred
TST(r);
Cycles: 1
Sets flags based on the the value in reg1
. Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: clearedO
: cleared
CMP(r,r);
Cycles: 1
Compares the value in reg1
with reg2
:
reg1 - reg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: Unsigned overflow occurredO
: Signed overflow occurred
MSKI(r,u);
Cycles: 1
Bitwise AND of the values in reg1
and arg2
, updating flags.
reg1 & arg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: clearedO
: cleared
MSK(r,r);
Cycles: 1
Bitwise AND of the values in reg1
and reg2
, updating flags.
reg1 & reg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: clearedO
: cleared
NOT(r,r);
Cycles: 1
Bitwise NOT of the value in reg2
and stores it in reg1
:
reg1 = ~reg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: clearedO
: cleared
AND(r,r);
Cycles: 1
Bitwise AND of the values in reg1
and reg2
and stores it in reg1
:
reg1 = reg1 & reg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: clearedO
: cleared
OR(r,r);
Cycles: 1
Bitwise OR of the values in reg1
and reg2
and stores it in reg1
:
reg1 = reg1 | reg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: clearedO
: cleared
XOR(r,r);
Cycles: 1
Bitwise XOR of the values in reg1
and reg2
and stores it in reg1
:
reg1 = reg1 ^ reg2
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: clearedO
: cleared
SL(r);
Cycles: 1
Shifts the value in reg1
left:
reg1 = reg1 << 1
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: bit pushed out ofreg1
O
: cleared
SR(r);
Cycles: 1
Shifts the value in reg1
right:
reg1 = reg1 >> 1
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: bit pushed out ofreg1
O
: cleared
SRA(r);
Cycles: 1
Shifts the value in reg1
right with sign extension:
reg1 = reg1 >> 1
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: bit pushed out ofreg1
O
: cleared
RL(r);
Cycles: 1
Rotates the value in reg1
left:
reg1 = (reg1 << 1) | (reg1 >> 15)
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: bit rotated out ofreg1
O
: cleared
RR(r);
Cycles: 1
Rotates the value in reg1
right:
reg1 = (reg1 >> 1) | (reg1 << 15)
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: bit rotated out ofreg1
O
: cleared
RLC(r);
Cycles: 1
Rotates the value in reg1
left with carry:
reg1 = (reg1 << 1) | C
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: bit rotated out ofreg1
O
: cleared
RRC(r);
Cycles: 1
Rotates the value in reg1
right with carry:
reg1 = (reg1 >> 1) | (C << 15)
Sets or clears all MST
flags as follows:
Z
:reg1
is zeroS
:reg1
high bit is setC
: bit rotated out ofreg1
O
: cleared
JP(a);
Cycles: 0
Sets the microcode program counter (MIP
) to the relative address specified
in arg1
:
MIP = MIP + arg1
It is invalid to jump to an address outside the microcode program, or
from/to an address to where the lock state differs (for instance,
between LK
and UL
of a different memory type). It is also invalid to jump
from, to, or within the "fetch" phase.
JC(c,a);
Cycles: 0 if condition is false, 1 if condition is true
Set the microcode program counter (MIP
) to the relative address specified
in arg2
if the condition specified by arg1
is true:
if (arg1) MIP = MIP + arg2
It is invalid to jump to an address outside the microcode program, or
from/to an address to where the lock state differs (for instance,
between LK
and UL
of a different memory type). It is also invalid to jump
from, to, or within the "fetch" phase.
JD(r,a);
Cycles: 1
Decrements register reg1
, and then sets the microcode program counter (MIP
)
to the relative address specified in arg2
if reg1
is not zero:
reg1 = reg1 - 1
if (reg1 != 0) MIP = MIP + arg2
It is invalid to jump to an address outside the microcode program, or
from/to an address where the lock state differs (for instance,
between LK
and UL
of a different memory type). It is also invalid to jump
from, to, or within the "fetch" phase.
INT(r);
Cycles: 0
Initiates an interrupt with the interrupt number specified in reg1
. This
does not end microcode execution, and the interrupt is not processed until
the next instruction is executed.
If a different core is locked (via CLK
), then the interrupt will be raised
on that core instead. If the CLK
failed, then this does nothing.
ILD(r,r);
Cycles: 1
Loads handler address of interrupt reg1
into reg2
.
If a different core is locked (via CLK
), then the interrupt handler address will be
from that core instead. If the CLK
failed, then this does nothing.
IST(r,r);
Cycles: 1
Stores handler address in reg2
to interrupt reg1
.
If a different core is locked (via CLK
), then the interrupt handler address is set will be
on that core instead. If the CLK
failed, then this does nothing.
IRT;
Cycles: 3
Returns from interrupt, restoring ST and IP from the stack.
PLK(r);
Cycles: 0
Locks the port specified by reg1
and sets it as the active port. This is
used to lock a port for exclusive access. If the port is already locked,
the microcode execution is paused for the instruction until the port is
unlocked. Only one lock can be active at a time (memory bank, port, or
core). If the port does not exist on the processor, all port operations are
no-ops.
PUL;
Cycles: 0
Unlocks the port previously locked by PLK
. All locked ports must be
unlocked before microcode execution completes in the instruction.
PLD(p,r);
Cycles: 1
Loads the value from the port into reg2
, respecting specified mode flags in
arg1
as follows:
T
: Load only if the port status is set. If the port status is cleared, then the value in reg2 is not updated.S
: The port status is cleared after the load operation.A
: The port address is updated after the load operation. Ports have two words of memory, and this toggles between the two words.
Sets or clears the MST
flags as follows:
S
: Port status was set (before the load operation).
The port must be locked from PLK
PST(p,r);
Cycles: 1
Stores the value from reg2
into the port, respecting specified mode flags
in arg1
as follows:
T
: Store only if the port status is cleared. If the port status is set, then value on the port is not updated.S
: The port status is set after the store operation.A
: The port address is updated after the store operation. Ports have two words of memory, and this toggles between the two words.
Sets or clears the MST
flags as follows:
S
: Port status was set (before the store operation).
The port must be locked from PLK
.
CLK(r);
Cycles: 0
Locks the CPU core specified by reg1
and sets it as controlled core for
core control operations. This is used to lock a core for exclusive access.
If the core is already locked, the microcode execution is paused for the
instruction until the core is unlocked. Only one lock can be active at a
time (memory bank, port, or core).
If the specified core does not exist on the processor, all core control
operations are no-ops until CUL
is called. Before CLK
, the controlled core
is this core (the one running the microcode).
CUL;
Cycles: 0
Unlocks the CPU core previously locked by CLK
. All locked cores must be
unlocked before microcode execution completes in the instruction. The
controlled core is reset to this core (the one running the microcode).
CBK(b,r);
Cycles: 1
Sets the memory bank of the controlled core (from CLK
) to the bank
specified by reg2
. If no core is locked, then this affects this core.
This is the only way to set the MB
register.
CLD(r,r);
Cycles: 1
Loads the value from the controlled core register reg1
into this core's
reg2
. If this is the controlled core, then this is equivalent to
MOV(reg2,reg1)
.
CST(r,r);
Cycles: 1
Stores the value from this core's reg2
to the controlled core register
reg1. The MB
and ST
registers cannot be modified by this op. Otherwise, if
this is the controlled core, then this is equivalent to MOV(reg1,reg2)
.
END;
Cycles: 0
Terminates the microcode execution for the instruction. It is invalid to call during the "fetch" phase.