This is the documentation for the SPU Mark II instruction set architecture. It is a stack based 16 bit processor that features a highly configurable instruction set.
SPU Mark II - ArchitectureOverviewTable Of ContentsDocumentation StyleNumbersBit RangesWordingUndefined (value)Undefined behaviorCPU VariantsSPU Mark II ArchitectureBasic PropertiesWord Encoding and SignednessMemory AccessCPU RegistersStack Pointer (SP)Base Pointer (BP)Instruction Pointer (IP)Flag Register (FR)Interrupt Register (IR)Instruction EncodingConditional ExecutionArgument Input 0 and 1Flag ModificationResult OutputCommandsMemory AccessFetch-Execute-CycleInterrupt handlingMaskingInterrupt TableResetNMIBUSARITHIRQPrioritiesChip I/OsChangelogv1.8v1.7v1.6v1.5v1.4v1.3v1.2v1.1v1.0TODO
Numbers are documented in three ways:
Decimal numbers are written the usual style. Hexadecimal numbers are prefixed by a 0x
. Binary numbers are postfixed with a subscript ₂. If both decimal and binary notation are given, the decimal notation is postfixed with a subscript ₁₀ to make the difference clear.
Examples:
10
, 23
, 44
₁₀0x10
, 0xFF
, 0xCC3D
10
₂, 1100101
₂10
₂ (2₁₀), 1100
₂ (12₁₀)This document uses some special notations to define bit ranges or indices.
[n]
is a placeholder for the nth bit in a word.
[n:m]
is a placeholder for the bit range from the nth (highest significant bit) to the mth (lowest significant bit).
So [3]
means the the bit with the significance of 2³
and [7:4]
is the upper nibble of a 8 bit word.
If a value is defined undefined, its initial value may be any possible value. The value may change between power cycles or even after a reset. Each bit must be considered random.
Undefined behavior is used similar to the way the C standard uses this word. It means that if a situation happens where undefined behavior would occur, the results of the operation may be anything. This can be a no-op, any state or memory change or even a CPU hang or hard reset (may even requires a power cycle).
This document specifies two different variants for the SPU Mark II architecture:
The SPU Mark II-L is a reduced version of the default instruction set and features no interrupt handling, thus making an implementation of the ISA much easier.
The SPU Mark II (Stack Processing Unit Mark II) is a 16 bit processor that, in contrary to the most popular CPUs, works primarily with a stack instead of registers. It features a RISC instruction set with highly configurable instructions. Although being a stack processor it still requires some basic registers to work. These registers are either accessed indirectly (like the IP
register) or by special instructions (like BP
or SP
register).
Each instruction is composed of a configuration part and a command part. Commands are the actual function of the instruction like STORE8
or MUL
. Each command has two input variables input0
and input1
which contain the two command parameters. A command also has an output value.
In which way the command parameters are filled and the result is processed is defined by the configuration bits of the instruction. These bits allow conditional execution, input parameter modification, affect flags and define how the result of the command is processed.
As each instruction may be conditional, there are no special conditional jump commands. In fact, there isn't even a jump command at all. A jump is done by taking the output of a command to be the next instruction pointer.
Thus, the most simple COPY
command can be used already for a whole set of different operations: jmp $imm
, push $imm
, pop
and many more.
The SPU Mark II uses the little endian encoding, so the less significant byte is at the lower address, the more significant byte at the higher address.
Integer arithmetic uses two-complement signed integers. This allows most arithmetic instructions to be used with signed and unsigned values.
The cpu only allows aligned memory access for word access. Unaligned word access must be programmed manually.
16 bit register storing the address of the topmost value of the stack. The stack grows downwards, so a push operation decrements the SP
by two and then stores a value to the decremented address. A pop or peek operation reads the value from SP
, and for pop, SP
will be incremented by 2.
Bit Fields | Description |
---|---|
[0] | reserved, must be zero |
[15:1] | aligned stack top address |
Initial Value: Undefined
16 bit register that may be used for indirect addressing via GET
and SET
commands and may be used as a frame pointer or index register.
Bit Fields | Description |
---|---|
[0] | reserved, must be zero |
[15:1] | aligned address |
Initial Value: Undefined
16 bit register pointing to the instruction to be executed next.
Bit Fields | Description |
---|---|
[0] | reserved, must be zero |
[15:1] | aligned instruction address |
Initial Value: Undefined
16 bit register saving CPU state and interrupt system
Bit Range | Name | Description |
---|---|---|
[0] | Z | Zero Flag |
[1] | N | Negative Flag |
[3:2] | - | Reserved, must be 0. |
[7:4] | I[3:0] | Interrupt Enabled bitfield for interrupts 4 to 7 |
[15:8] | - | Reserved, must be 0. |
Initial Value: 0x0000
Note: The flags
I[3:0]
are not available in SPU Mark II-L
16 bit register storing internal interrupt information.
Bit Range | Name | Description |
---|---|---|
[0] | RST | Reset was triggered |
[1] | NMI | Non-maskable interrupt triggered |
[2] | BUS | Bus error triggered |
[3] | Reserved, must be 0 | |
[7:4] | … | Interrupt I[3:0] triggered bitfield |
[15:8] | - | Reserved (must be 0) |
Initial Value: 0x0001
(Reset interrupt triggered)
Note: Not available in SPU Mark II-L
Instructions use 16 bit opcodes organized in different bit fields defining the behaviour of the instruction.
Bit Range | Description |
---|---|
[2:0] | Execution Conditional |
[4:3] | Input 0 Behaviour |
[6:5] | Input 1 Behaviour |
[7] | Flag Modification Behaviour |
[9:8] | Output Behaviour |
[14:10] | Command |
[15:15] | Reserved for future use (must be 0 ) |
This field determines when the command is executed or ignored. The execution is dependent on the current state of the flags.
This allows conditional execution of all possible opcodes.
Value | Enumeration | Description |
---|---|---|
000 ₂ (0₁₀) | Always | The command is always executed |
001 ₂ (1₁₀) | =0 | The command is executed when result is zero (Z=1 ) |
010 ₂ (2₁₀) | ≠0 | The command is executed when result is not zero (Z=0 ) |
011 ₂ (3₁₀) | >0 | The command is executed when result is positive (Z=0 and N=0 ) |
100 ₂ (4₁₀) | <0 | The command is executed when result is less than zero (N=1 ) |
101 ₂ (5₁₀) | ≥0 | The command is executed when result is zero or positive (Z=1 or N=0 ) |
110 ₂ (6₁₀) | ≤0 | The command is executed when result is zero or negative (Z=1 or N=1 ) |
111 ₂ (7₁₀) | Reserved | Reserved for future use (invokes undefined behavior). |
These two fields define what arguments are provided to the executed command.
Value | Enumeration | Description |
---|---|---|
00 ₂ (0₁₀) | Zero | The input register will be zero. |
01 ₂ (1₁₀) | Immediate | The input registers value is located after this command. |
10 ₂ (2₁₀) | Peek | The input register will be the stack top. |
11 ₂ (3₁₀) | Pop | The input register will be popped from the stack. |
Zero means that the argument will be zero, Immediate means that it will be fetched from the instruction pointer (it is located behind the opcode in memory). Peek will take the argument from the stack top, but won't change the stack and Pop will take the argument from the stack top and decreases the stack pointer.
input0
is fetched before input1
so when both arguments pop a value, input0
receives the stack top and input1
receives the value one below the stack top. Likewise, when both arguments use the Immediate option, the value for input0
must located directly after the opcode, input1
directly after input0
.
When the flag modification is enabled, the current flags will be overwritten by this command. Otherwise the flags stay as they were before the instruction.
Value | Enumeration | Description |
---|---|---|
0 ₂ (0₁₀) | No | The flags won't be modified. |
1 ₂ (1₁₀) | Yes | The flags will be set according to the command output. |
The flags are modified according to this table:
Flag | Condition |
---|---|
Z | output[15:0] = 0 |
N | output[15] = 1 |
I | unchanged |
Each command may output a value which can be processed in various ways. The output could be pushed to the stack, the command could be made into a jump or the output could be ignored.
Value | Enumeration | Description |
---|---|---|
00 ₂ (0₁₀) | Discard | The command output will be ignored. |
01 ₂ (1₁₀) | Push | The command output will be pushed to the stack. |
10 ₂ (2₁₀) | Jump | The instruction pointer will be set to the command output. |
11 ₂ (3₁₀) | Jump Relative | The command output will be added to the instruction pointer. |
For Jump Relative, the instruction pointer will point to the next instruction plus output
words. output
is considered a two-complements signed number. This differs from the Jump behavior which takes an address, not a word offset.
Commands are what define the core behaviour of the opcode. They allow arithmetics, modification of memory, changing system registers and so on.
Some hints on notation:
MEM16[x]
is the 16 bit word at address x
MEM8[x]
is the 8 bit word at address x
Value | Name | Pseudo-Code |
---|---|---|
00000 ₂ (0₁₀) | COPY | output = input0 |
00001 ₂ (1₁₀) | IPGET | output = IP + 2 * input0 |
00010 ₂ (2₁₀) | GET | output = MEM16[BP + 2 * input0] |
00011 ₂ (3₁₀) | SET | output = input1; MEM16[BP + 2 * input0] = input1 |
00100 ₂ (4₁₀) | STORE8 | output = input1 & 0xFF; MEM8[input0] = input1 |
00101 ₂ (5₁₀) | STORE16 | output = input1; MEM16[input0] = input1 |
00110 ₂ (6₁₀) | LOAD8 | output = MEM8[input0] |
00111 ₂ (7₁₀) | LOAD16 | output = MEM16[input0] |
01000 ₂ (8₁₀) | ??? | Invokes undefined behavior |
01001 ₂ (9₁₀) | ??? | Invokes undefined behavior |
01010 ₂ (10₁₀) | FRGET | output = (FR & ~input1) |
01011 ₂ (11₁₀) | FRSET | output = FR₀; FR₁ = (input0 & ~input1) | (FR & input1) |
01100 ₂ (12₁₀) | BPGET | output = BP |
01101 ₂ (13₁₀) | BPSET | output = BP₀; BP₁ = input0 |
01110 ₂ (14₁₀) | SPGET | output = SP |
01111 ₂ (15₁₀) | SPSET | output = SP₀; SP₁ = input0 |
10000 ₂ (16₁₀) | ADD | output = input0 + input1 |
10001 ₂ (17₁₀) | SUB | output = input0 - input1 |
10010 ₂ (18₁₀) | MUL | output = input0 * input1 |
10011 ₂ (19₁₀) | DIV | output = input0 / input1 |
10100 ₂ (20₁₀) | MOD | output = input0 % input1 |
10101 ₂ (21₁₀) | AND | output = input0 & input1 |
10110 ₂ (22₁₀) | OR | output = input0 | input1 |
10111 ₂ (23₁₀) | XOR | output = input0 ^ input1 |
11000 ₂ (24₁₀) | NOT | output = ~input0 |
11001 ₂ (25₁₀) | SIGNEXT | output = if(input[7] = 1) (0xFF00 | input0) else (input0 & 0xFF) |
11010 ₂ (26₁₀) | ROL | output = concat(input0[14:0], input0[15]) |
11011 ₂ (27₁₀) | ROR | output = concat(input0[0], input0[15:1]) |
11100 ₂ (28₁₀) | BSWAP | output = concat(input0[7:0], input0[15:8]) |
11101 ₂ (29₁₀) | ASR | output = concat(input0[15], input0[15:1]) |
11110 ₂ (30₁₀) | LSL | output = concat(input0[14:0], '0') |
11111 ₂ (31₁₀) | LSR | output = concat('0', input0[15:1]) |
MUL
, DIV
and MOD
use signed values as input and output. It is not possible to get the upper 16 bit of the multiplication result.
Only 2-aligned access to memory is possible with code or data. Only exception are the STORE8
and LOAD8
commands which allow 1-aligned memory access.
When accessing memory with alignment, the least significant address bit is reserved and must be 0
. If the bit is 1
, the behavior is undefined.
This pseudocode documents what the CPU does in detail when execution a single instruction.
x1if (IR & 1) != 0:
2 IP := fetch(0x0000)
3 FR := 0x0000
4 IR := 0x0000
5
6while IR != 0:
7 intr_bit := indexOfHighestSetBit(IR)
8 IR &= ~(1<<intr_bit) # clear "interrupt triggered"
9 if intr_bit < 4 or (FR & (1<<intr_bit)) != 0: # if interrupt is not masked
10 push(IP)
11 IP := fetch(2 * intr_bit) # fetch ISR handler
12 if intr_bit >= 4: # if interrupt is maskable
13 FR &= ~(1<<intr_bit) # unmask interrupt
14
15instruction := fetch(IP)
16IP += 2
17
18if isExecuted(instruction, FR):
19 input0 := fetchInput(instruction.input0)
20 input1 := fetchInput(instruction.input1)
21
22 output := instruction.command(input0, input1)
23
24 select instruction.output:
25 when 'push':
26 push(output)
27 when 'discard':
28 # ignore
29 when 'jmp':
30 IP := output
31 when 'rjmp':
32 IP += 2 * output
33
34 if instruction.modifyFlags:
35 FR.Z = (output == 0x0000)
36 FR.N = (output >= 0x8000)
37else
38 if instruction.input0 == IMM:
39 IP += 2
40 if instruction.input1 == IMM:
41 IP += 2
TODO: Add handling of
BUS
fault!
For the non-interrupt version, the state machine is simpler:
xxxxxxxxxx
311if RST is high:
2 IP := 0x0000
3 FR := 0x0000
4
5instruction := fetch(IP)
6IP += 2
7
8if isExecuted(instruction, FR):
9 input0 := fetchInput(instruction.input0)
10 input1 := fetchInput(instruction.input1)
11
12 output := instruction.command(input0, input1)
13
14 select instruction.output:
15 when 'push':
16 push(output)
17 when 'discard':
18 # ignore
19 when 'jmp':
20 IP := output
21 when 'rjmp':
22 IP += 2 * output
23
24 if instruction.modifyFlags:
25 FR.Z = (output == 0x0000)
26 FR.N = (output >= 0x8000)
27else
28 if instruction.input0 == IMM:
29 IP += 2
30 if instruction.input1 == IMM:
31 IP += 2
Note: Interrupt handling is not available in SPU Mark II-L
The SPU Mark II provides 8 possible interrupts, four unmasked and four masked interrupts.
When an interrupt is triggered the CPU pushes the current instruction pointer to the stack and fetches the new instruction Pointer from the interrupt table. Then the flag in the Interrupt Register is cleared as well as the mask bit in the Flag Register (if applicable).
The reset interrupt is a special interrupt that does not push the return address to the stack. It also resets the Interrupt Register and the Flag Register.
If an interrupt is masked via the Flag Register (corresponding bit is 0) it won't be triggered (the Interrupt Register bit can't be set).
It is possible to assign each interrupt another handler address. The entry points for those handlers are stored in the Interrupt Table at memory location 0x0000
:
# | Interrupt | Routine Pointer |
---|---|---|
0 | Reset | 0x00 |
1 | NMI | 0x02 |
2 | BUS | 0x04 |
3 | RESERVED | 0x06 |
4 | ARITH | 0x08 |
5 | RESERVED | 0x0A |
6 | RESERVED | 0x0C |
7 | IRQ | 0x0E |
This interrupt resets the CPU and defines the program entry point.
This interrupt is the non-maskable external interrupt. If the NMI
pin is signalled, the interrupt will be triggered.
This interrupt is a non-maskable external interrupt.
The BUS
interrupt is meant for an MMU interface and will prevent the currently executed instruction from having any side effects.
Remarks: It is required that the BUS interrupt happens while doing a memory operation. If after a memory read or write cycle the BUS
pin is signalled, the CPU will assume as bus error and will trigger this interrupt.
This interrupt is triggered when an error happens in the ALU. The reasons may be:
This interrupt is the maskable external interrupt. If the IRQ
pin is signalled, the interrupt will be triggered.
Before execution of each instruction the cpu checks if any interrupt is triggered. The handler for the lowest interrupt triggered will then be activated.
NEG
commandSIGNEXT
BP
SP
frset
, spset
, bpset
return the previous value instead of the newly set.
This allows improved or shorting handling of safed parameters.BUS
nonmaskableBUS
interrupt an external interrupt for a MMUBUS
prevent all effects from the current instructionFRGET
, FRSET
instructionHOLD
line