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 to R7 (3 bits). The first argument is given the name a in microcode and the second argument is given the name b in microcode.
    • 32-bit register: The argument is any of the 4 general purpose 32-bit registers D0 to D3 (2 bits). The first argument is given the names a0 and a1 in microcode for the first and second words of the 32-bit register. The second argument is given the names b0 and b1 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 register C1 before the microcode executes (see Microcode Registers below)

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 or ADR is in the "fetch" phase. The number of LD opcodes within the fetch phase determines how many large the OZ-3 instruction is (beyond the code word). Store opcodes (ST and STP), lock opcodes (LK, LKR, PLK, and CLK), and control flow opcodes (JP, JC, JD, WAIT, HALT, END, and IRT) is not allowed during the fetch phase.
  • Execution phase: All microcode after the first UL or ADR 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 or b for word args; a0, a1, b0, or b1 for low/high word parts of dword args A and B). 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 internal MSR register at the end of the instruction (which starts as equal to ST). To change the persistent value of ST, use the MSTC/MSTS/MSTX/MSTM operations to set MST and return with the MSTR 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 of MB (and the associated bank mapping), use the CBK microcode.
  • rb: Byte of a word register. This is the same as r with the suffix :H for the high byte and :L for the low byte. For example, R0:H references the high byte of register R0 (equivalent to the value R0 >> 8).
  • rn: Nibble of a word register. This is the same as r with an suffix being : followed by the index of the nibble (low to high). For example, R0:2 references the third nibble in register R0 (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 instance MOVI or ADDI).
  • b: Memory bank. In microcode assembly this must be one of CODE, STACK, DATA, or EXTRA.
  • s: Status ZSCOI flag mask. In microcode assembly, this is any combination of Z, S, C, O, I, and _ characters (e.g. ZC or Z_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, or NO.
  • 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 of T, S, A, and _ characters (e.g. TA or T_A or just _ to indicate no mode flags).

Microcode Registers

Four additional addressable in microcode only registers are defined:

  • ST: The full value of the ST 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, and C2. C2 is always set to zero at the beginning of a microcode program. C0 and C1 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: The MIP register specifies what microcode within the OZ-3 instruction will be executed next. It is automatically advanced, or can be set via the JP, JC, and JD microcode.
  • MST: The MST register is the microcode version of the ST register, and is initialized to the ST register when microcode begins execution. Many microcode operations will update the MST register. However, these changes do not automatically get reflected in the ST register for subsequent instructions to use. To return some or all of the MST register to the ST register, the MSR(s,s); operation must be called.
  • MSR: The MSR register holds the final value that will be assigned to the ST register when execution completes. Like the RST register, it is initialized with the ST register when microcode begins execution. It is updated by the corresponding MSR(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 but r).
  • 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 if WAIT executed normally. Set if WAIT failed (it was called within another WAIT).

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 zero
  • S: reg1 high bit is set
  • C: Cleared
  • O: Negation overflowed (reg2 and now reg1 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 zero
  • S: reg1 high bit is set
  • C: Unsigned overflow occurred
  • O: 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 zero
  • S: reg1 high bit is set
  • C: Unsigned overflow occurred
  • O: 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 zero
  • S: reg1 high bit is set
  • C: Unsigned overflow occurred
  • O: 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 zero
  • S: reg1 high bit is set
  • C: Unsigned overflow occurred
  • O: 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 zero
  • S: reg1 high bit is set
  • C: Unsigned overflow occurred
  • O: 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 zero
  • S: reg1 high bit is set
  • C: cleared
  • O: 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 zero
  • S: reg1 high bit is set
  • C: Unsigned overflow occurred
  • O: 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 zero
  • S: reg1 high bit is set
  • C: cleared
  • O: 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 zero
  • S: reg1 high bit is set
  • C: cleared
  • O: 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 zero
  • S: reg1 high bit is set
  • C: cleared
  • O: 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 zero
  • S: reg1 high bit is set
  • C: cleared
  • O: 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 zero
  • S: reg1 high bit is set
  • C: cleared
  • O: 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 zero
  • S: reg1 high bit is set
  • C: cleared
  • O: 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 zero
  • S: reg1 high bit is set
  • C: bit pushed out of reg1
  • 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 zero
  • S: reg1 high bit is set
  • C: bit pushed out of reg1
  • 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 zero
  • S: reg1 high bit is set
  • C: bit pushed out of reg1
  • 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 zero
  • S: reg1 high bit is set
  • C: bit rotated out of reg1
  • 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 zero
  • S: reg1 high bit is set
  • C: bit rotated out of reg1
  • 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 zero
  • S: reg1 high bit is set
  • C: bit rotated out of reg1
  • 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 zero
  • S: reg1 high bit is set
  • C: bit rotated out of reg1
  • 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.