The following conventions are used in asm commands and encodings.
n
- none type (no dst or src)r
- register (dst register generally encoded in last nibble of instruction)f
- the alu flags register (not one fo the general purpose registers)
i
- immediate constants (encoded in word after instruction)m
- a fixed location in memory (pc relative)mb
- an unsigned byte in memorymbs
- a signed byte in memorymw
- a word in memory (signed or unsigned, no sign extension needed)
p
- a memory location pointed to by a register (not pc relative)pb
- an unsigned byte in memorypbs
- a signed byte in memorypw
- a word in memory
off
- an addressing offset (stored in word after instruction)j
- a pc-relative jump addressra
- a pc relative address to be converted to a non pc-relative address (for loading pointer initial values)c
- a condition code (see below)sp
- a register used as a stack pointerp
- an io port numberi
- an interrupt vector entry (one of 8 ints)ipc
- the interrupt pc reg (pc copied to ipc reg on interrupt)
Registers are the program counter (pc), flags register (f), and registers r0-rf. By convention only, r0 is used as the stack pointer and r1 as the frame pointer. The hardware can use any register as a stack pointer, or have multiple stacks.
Extended register are formed by two registers next to each other starting on an even reg number. Extended registers are (e0, e2, e4, ... ec, ee)
Register Usage Conventions:
Reg | Extended | VBCC Usage |
---|---|---|
r0 | e0 low | GPR |
r1 | e0 high | GPR |
r2 | e2 low | GPR |
r3 | e2 high | GPR |
r4 | e4 low | GPR |
r5 | e4 high | GPR |
r6 | e6 low | GPR |
r7 | e6 high | GPR |
r8 | e8 low | GPR |
r9 | e8 high | GPR |
ra | ea low | GPR |
rb | ea high | GPR |
rc | ec low | Backend Temp |
rd | ec high | Backend Temp |
re | ee low | FP |
rf | ee high | SP |
There are 16 possible alu operations, stored as 4-bit alu opcodes. They are:
0 - | (bitwise or)
1 - ^ (bitwise xor)
2 - & (bitwise and)
3 - << (left shift)
4 - >> (unsigned right shift)
5 - >> (signed right shift)
6 - + (addition)
7 - - (subtraction)
8 - * (multiplication)
9 - ~ (bitwise complement) (of first operand)
a - - (unary minus) (of first operand)
b -
c -
d -
e -
f -
Condition codes in the flag register are set by the cmp
instruction. They are five bit values, encoded as follows:
4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|
signed greater than | signed less than | unsigned greater than | unsigned less than | equal to |
A conditional is a five bit value. It is or'd with the flags register, so multiple conditions can be combined. Thus, the following are all the posibilities for unsigned numbers:
000 - false
001 - ==
010 - <
011 - <=
100 - >
101 - >=
110 - !=
111 - true
NOTE: the flags register starts with the value 1 loaded so that an uncoditional jump always works.
s/u
- signedness of the value. 0=unsigned, 1=signedb/w
- width of the value. 0=word, 1=byte
All instructions that deal with immediate addresses are pc-relative. Pointer loads are not pc relative - real adresses for initilizing pointers should be loaded with ld.r.ra
.
PC relative addresses are found by taking the address of (inst_addr + mem_addr + 2), where instr_addr is the address where the first word of the instruction is written, and mem_addr is the pc-relative addr. The +2 is because the pc will already be incremented past the instruction by the time it executes.
SCP has 8 interrupt priority levels, ranging from int0 (highest priority) to int7 (lowest priority).
SCP has two privilege levels, 0 (system privilege), and 1 (user privilege). Interrupts can only occur in user privilege. An interrupts raises the privilege level to system privilege, and jumps to the interrupt vector. Because the privilege was raised to system, the page table base register is not used for page table calculations. This means that the interrupt vector that is jumped to will be in the system memory (ptb 0).
If an interrupt occurs and scp is in system privilege, the interrupts will wait till the privilege level goes back to user to take effect.
NOTE: because interrupts can only occur in user privilage and interrupts switch privilage to system priority, interrupts can't be interrupted by higher priority interrupts. Thus, interrupt priority only comes into play if there are two interrupts waiting to happen at the same time.
Interrupts can be triggered by an external event, or by the int.i.n
instruction. An interrupt is the only way to raise the privilage level from user privilage, and are used for system calls, etc. The interrupt instruction can trigger any interrupt, even if it is also wired to an external device.
Interrupt vectors start at 0x0010 in the system memory, and the address of each increases by 0x0004 (enough space for one unconditional jump instruction). They are fixed address and can't be configured. Thus, the vectors are:
int | vector |
---|---|
int0 | 0x0010 |
int1 | 0x0014 |
int2 | 0x0018 |
int3 | 0x001b |
int4 | 0x001d |
int5 | 0x0020 |
int6 | 0x0024 |
int7 | 0x0028 |
int8 | 0x002b |
Instr | opcode |
---|---|
nop.n.n |
000000 |
mov.r.r |
000001 |
cmp.r.f |
000010 |
jmp.c.r |
000011 |
alu.r.r |
0001__ |
ld.r.p.(b/bs/w) |
010000 |
st.r.p.(b/bs/w) |
010001 |
push.r.sp |
010011 |
pop.r.sp |
010100 |
call.r.sp |
010101 |
ret.n.sp |
010110 |
int.i.n |
010111 |
reti.ipc.n |
100000 |
mov.r.ipc |
100001 |
mov.ipc.r |
100010 |
ptb.r.n |
110000 |
mmu.r.r |
110001 |
ld.r.ra |
001000 |
alu.r.i |
0011__ |
ld.r.i |
011000 |
ld.r.m.(b/bs/w) |
011001 |
ld.r.p.off.(b/bs/w) |
011010 |
st.r.m.(b/bs/w) |
011011 |
st.r.p.off.(b/bs/w) |
011100 |
jmp.c.j |
011101 |
call.j.sp |
011110 |
out.r.p |
111000 |
in.r.p |
111001 |
Do nothing
nop
; nothing
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | ---- |
Copy one register to another.
mov.r.r dst src
; dst = src
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | ---- | srcreg | dst reg |
Perform an alu operation on two registers, and stores the result in the first register.
alu.r.r op dst src
; dst = dst op src
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | alu op | src reg | dst reg |
Perform an alu operation on a register and an immediate, and stores the result in the register.
alu.r.r op dst imd
; dst = dst op imd
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | alu op | ---- | dst reg | immediate value |
Compare two registers, and set alu flags based on comparison.
cmp.r.f reg1 reg2
; flags = reg1 cmp reg2
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | ---- | reg2 | reg1 |
Load an immediate into a register.
ld.r.i dst imd
; dst = imd
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | ---- | dst reg | immediate value |
Load a value from memory into a register, sign extend if needed.
ld.r.m.b/bs/w dst mem
; dst = (pc + mem)
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | b/w | s/u | ---- | dst reg | memory address |
Load a value pointed to by a register into a register, sign extend if needed.
ld.r.p.b/bs/w dst src
; dst = (src)
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | b/w | s/u | src reg | dst reg |
Load a value pointed to by a register plus offset into a register, sign extend if needed.
ld.r.p.off.b/bs/w dst src off
; dst = (src + off)
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | b/w | s/u | src reg | dst reg | address offset |
Load a not pc-relative adress from a pc-relative adress immediate into a register.
ld.r.ra dst addr
; dst = pc + addr
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | ----- | dst reg | address |
NOTE: Store instructions with b and bs modes do the same thing, as we can't un sign extend
Store a value from a register into memory.
st.r.m.b/bs/w src mem
; (pc + mem) = src
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | b/w | - | ---- | src reg | memory address |
Store a value from a register into the memory addr pointed to by a register.
st.r.p.b/bs/w src dst
; (dst) = src
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | b/w | - | dst reg | src reg |
Store a value from a register into the memory addr pointed to by a register plus an offset.
st.r.p.p.off.b/bs/w src dst off
; (dst + off) = src
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | b/w | - | dst reg | src reg | address offset |
Perform a conditional jump to a fixed addr.
jmp.c.j cond addr
; if flags | cond then pc = pc + addr
Note - an unconditional jump can be performed by using 0b11111
as the condition code.
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | - | cond | --- | address |
Perform a conditional jump to a non pc-relative addr in a register.
jmp.c.r cond reg
; if flags | cond then pc = reg
Note - an unconditional jump can be performed by using 0b11111
as the condition code.
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | - | cond | reg |
Push a register onto a stack.
push.r.sp reg sp
; sp = sp - 2
; (sp) = reg
Note - The register used as a stack pointer must be aligned on word boundries.
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | sp | reg |
Pop a value off a stack into a register.
pop.r.sp reg sp
; sp = sp + 2
; reg = (sp - 2)
Note - The register used as a stack pointer must be aligned on word boundries.
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | sp | reg |
Perform a function call to a pc relative adress.
call.i.sp sp addr
; sp = sp - 2
; (sp) = pc + 2
; pc = pc + addr
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | sp | --- | function address |
Perform a function call to a non pc-relative address in a register.
call.r.sp reg sp
; sp = sp - 2
; (sp) = pc
; pc = reg
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | sp | reg |
Return from a function call, given a return adress on the top of the stack.
ret.n.sp sp
; sp = sp + 2
; pc = (sp - 2)
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | sp | --- |
Output a register to the specified io port number.
out.r.p reg port_num
; io[port_num] = reg
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | reg | port_num |
Read a value from the specified io port into a register.
in.r.p reg port_num
; reg = io[port_num]
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | val16 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | reg | port_num |
Perform an interrupt to the specified interrupt vector.
int.i.n vector
; pc_cpy = pc
; pc = int_vectors[vector]
; priv_lv = 0
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | vector |
Return from an interrupt. Copies ipc to pc, and sets priv_lv to 1 (usr).
reti.ipc.n
; pc = ipc
; priv_lv = 1
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- |
Copy the value in a register into the ipc reg.
mov.r.ipc reg
; ipc = reg
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | reg |
Copy the value in the ipc reg to a reg.
mov.ipc.r reg
; reg = ipc
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | reg |
Set the mmu entry pointed to by (reg2>>11) + ptb to the low byte of reg1. priv_lv must be sys(0) going into the instruction. It will be sys(0) coming out of it.
mmu.r.r reg2 reg1
; priv_lv = 0
; page_table[ptb + (reg2 >> 11)] = reg1;
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | reg2 | reg1 |
Set the ptb to the value in reg. priv_lv should be sys(0) going into instruction. Calling in sys(1) will cause a strange jump and likely crash.
ptb.r.n reg
; ptb = reg
f | e | d | c | b | a | 9 | 8 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
opcode | --- | reg |
Three stages: EX (Execute), MEM (Memory Access), RW (Register Writeback)
- | EX | MEM | RW |
---|---|---|---|
Memory Access | Instr Register Word 1 | Memory Instruction | Instr Register Word 2 |
Alu | Offset Add | Alu instructions | - |
Register Write | - | sp increment/deincrement | Main Write Back |
CLK0 - Earlier Clock - MMU clk, alu op, reg write back
CLK1 - Delayed Clock - RAM clk, microcode clk