Spoc currently supports 8 instructions:
|DO||Executes specified operation (+,-,or,and,xor,not)||2|
|SEL||Selects a register||1|
|NOT||Logical not (inverse all the bits)||1|
|JMP||Jumps to a new memory location||1|
|JSR||Jumps to a subroutine||1|
|RET||Returns from subroutine||0|
inc RA2 // increments register RA2 dec A // decrements accumulator sel WA10 // selects WA10 do #0xBA00 + WA22 -> A // adds value 0xBA00 to register WA22, and writes the result to the accumulator do A and #0x5555 -> @ // logical AND between accumulator and value goes to memory do A xnor #0x5555 -> RA0 // logical XNOR between accumulator and value goes to register RA0, selects RA0 not RA1 // inverts all the bits in register RA1, selects RA1 jsr A+RA10 // calculates A+RA10, and jumps to this address (subroutine, returns with RET) jmp #loop // jmp to label "loop"
Spoc supports 3 addressing modes:
|Immediate||Reads a constant from code memory||#|
|Direct||Accesses a register||WAxx or RAxx|
|Indirect||Accesses a memory location||@|
do #1234 -> A // Immediate addressing: moves value 1234 to accumulator do A -> RA4 // Direct addressing: writes accumulator value to register RA4 do @ -> A // Indirect addressing: reads memory to accumulator do A -> @ // Indirect addressing: writes accumulator to memory
It is possible to use indirect addressing for both source and destination operands.
do @ -> @ do @ + #0x22 -> @ do A or @ -> @Indirect addressing is related to "selected registers" (see below "Memory and register files" paragraph).
do #22 -> A, RA0 do A + #22 -> A, WA1 do A - @ -> A, WA1 do A or RA3 -> WA4, A do @ and #22 -> A, @ do WA6 xor #22 -> A, @When writing to 2 destinations, one is always the accumulator, the other is either a register (RAxx/WAxx), or the memory (@).
do.bit #1 -> A // writes 1 to the 1-bit accumulator inc.byte A // increments the 8-bits accumulator do.word A + #0x1000-> A // adds 0x1000 to the 16-bits accumulator do.dw #0x12DECF80 -> A // writes 0x12DECF80 to the 32-bits accumulator
jmp #gothere // jumps unconditionally jsr #gotosubroutine // jumps unconditionally to subroutine jmp.Z=0 #gothere // jumps conditionally (if flag Z is 0)The branching instructions can use calculated addresses to branch. For example, it is possible to have tables of subroutines.
do #0x0C00 -> SP // stack from 0x0C00 to 0x0FFF, enough for a depth of 64 subroutine calls jsr #mysubroutineThe stack is used to store the subroutines return addresses. The stack uses memory and grows upwards (in Spoc0, the SP pointer increments by 16 for each JSR instruction, and decrements by 16 for each RET).
do #3 -> A dec A // A becomes 0x0002, C is 0, Z is 1 dec A // A becomes 0x0001, C is 0, Z is 1 dec A // A becomes 0x0000, C is 0, Z is 0 dec A // A becomes 0xFFFF, C is 1, Z is 1 dec A // A becomes 0xFFFE, C is 0, Z is 1 dec A // A becomes 0xFFFD, C is 0, Z is 1You can execute a DO operation that has no destination. In that case, the operation is executed, the result is lost, but the flags are still updated.
do #3 -> WA0 // writes 3 to register WA0 // now we are going to do 3 subtractions, without saving the results. But the flags are still updated. do WA0-#2 // WA0>2, so we get C=0, Z=1 do WA0-#3 // WA0=3, so we get C=0, Z=0 do WA0-#4 // WA0<4, so we get C=1, Z=1 // now run some conditional instructions jmp.c=0 #mylabel1 // conditional jump, not executed since C=1 add.z=0 WA0 + A -> RA2 // conditional addition, not executed since Z=1 jmp.z=1 #mylabel2 // conditional jump, executed since Z=1Finally, if a conditional instruction is not executed, the flags are not updated either.
do #1 -> A // A is 0x0001, C is 0, Z is 1 dec.z=0 A // not executed, and flags not changed
do CY -> A do A + #22 + C -> A do A xor CARRY -> RA0
do #0 -> A dec A // A=0xFFFF, C=1 do CY + #22 -> A // arithmetic operation, so A=23 do #0 -> A dec A // A=0xFFFF, C=1 do CY xor #0x1111 -> A // logical operation, so A=0xEEEE
Example of memory read (read from address 0x200):
do #0x0200 -> RA0 do @ -> A // reads memory 0x200, and puts the value in accumulator
Example of memory write (write to address 0x200):
do #0x0200 -> WA17 do RA3 -> @ // writes content of RA3 to memory 0x200The address of a read memory access is given by a "RAxx" register.
do #0x0200 -> RA5 // writes 0x200 to RA5, and selects it do #0x0300 -> WA7 // writes 0x300 to WA7, and selects it // RA5 and WA7 are both selected do @ -> @ // copies the value from memory location 0x200 to memory location 0x300 // now RA5=0x210 and WA7=0x310 do WA7 + #0x20 -> RA6 // RA6 is now selected with the value 0x330 do @ -> A // memory location 0x330 is read and copied to the accumulator sel RA5 // re-select RA5. Since it was non-persistent, its value is back to 0x0200 (see later for explanation on persistent registers)The registers RAxx/WAxx can also be used for other purpose than memory accesses. They have almost the same capabilities than the accumulators, so can be incremented, added, xor-ed...
do #GreetingString -> CS // CS points to a string, and selects CS do.byte @ -> A // read the "H" into the accumulator ... GreetingString: data.byte "Hello world!", 13Another reserved value is "$". It represents the actual PC (program code location, the location of the currently executed instruction). That's a read-only value.
jmp $ // forever loop (if you want spoc to "die")The code space is bit-addressable when you use a blockram (like in Spoc0), or byte-addressable if you use a serial Flash. That gives 64Kbits space for Spoc0, and 64Kbytes space when serial flash is used.
do #0x0200 -> RA5 // RA5 is selected (not persistent) do @ -> A // copies the value from memory location 0x200 to accumulator // RA5 value is now 0x210 do #0x0300 -> RA6.p // RA6 is selected (persistent) do @ -> A // copies the value from memory location 0x300 to accumulator // RA6 value is now 0x310 sel RA5.p // re-selects RA5. Since it was non-persistent, its value is back to 0x0200 // note that it is now persistent! do @ -> A // copies the value from memory location 0x200 to accumulator // RA5 value is now 0x210 sel RA6 // re-selects RA6. Since it was persistent, its value is 0x0310 do @ -> A // copies the value from memory location 0x310 to accumulator // RA6 value is now 0x320 sel RA5 // re-selects RA5 (not persistent). But since it was persistent, its value is 0x0210 do @ -> A // copies the value from memory location 0x210 to accumulator // RA5 value is now 0x220