r0-r15
The 16 registers currently visible in the register window. r0 is always the head of the register stack. rD
The primary data register that will be affected in the instruction. rA
Auxiliary/address register for 2-reg instructions. This register tends to be read-only. rP
The prior register, which is rD from the last instruction that used one. Determines branch tests and is the target of some implied-mode instructions. rH
The register immediately after rD, usually used as the high word of a 32-bit value starting in rD. For example, if an instruction is using r3 as rD, then rH is r4. There is no wraparound, so if r15 is used, rH will be the currently unaddressable effective r16. rD:rH
32-bit effective register, with rD being the low word and rH being the high word. imm4
4-bit immediate value, usually 0-15, though some instructions disallow 0. imm4p1
4 bit immediate value interpreted as 1-16. imm8
8-bit immediate value, usually 0-255, or -128 to +127 signed. imm16
16-bit immediate value, usually 0-65535, or -32768 to +32767 signed. rel8
Used in branches, a target address which is within an 8-bit signed range of bytes from the first byte of the instruction. A branch to a rel8-encoded $00 loops back to the branch instruction itself. :=
Assignment *
Embedded instruction, taking 16 opcodes.
The following was auto-generated by AcheronVM build tools
clr *rDrD := 0 clrmbpmemory(rP) := 0 byte clrmpmemory(rP) := 0 copy rD, rArD := rA grow4 *imm4p1Grow the register stack by imm4 (1-16) slots. The resulting r0 becomes the new rP. grow8 imm8Grow the register stack by imm8 (signed) slots. The resulting r0 becomes the new rP. ldm rD, rArD := memory(rA) ldmb rD, rArD := memory(rA), unsigned byte ldmi rD, rA, imm8rD := memory(rA + imm8) ldrptr rD, rArD := current address of rA. Note that task switching or otherwise swapping out zp can leave this pointer dangling. popregs rD, imm4Pop imm4 (1-15) registers starting at rD from the CPU stack. pushregs rD, imm4Push imm4 (1-15) registers starting at rD to the CPU stack. set16 *rD, imm16rD := imm16 set8 *rD, imm8rD := imm8 stm rD, rAmemory(rA) := rD stmb rD, rAmemory(rA) := low byte of rD stmi rD, rA, imm8memory(rA + imm8) := rD stpm rDmemory(rD) := rP stpmb rDmemory(rD) := low byte of rP with *rDExplicitly assign rP. Pseudo Instructions
grow
imm
Becomes grow4 or grow8 set
reg, imm
Becomes clr, set8, or set16. Native Routines
jsr clear_rstackInitializes an empty register stack. This is the minimum initialization needed for basic test code, but note that other features may not be initialized (gptr in particular). Zeropage Locations
zpTop:
.res 0Highest zeropage memory location in use, for copying out process context. rptr:
.res 1Pointer to the head of the register stack, which is also r0, and the lowest used byte in zp. pptr:
.res 1Pointer to the prior register. rstackTop:
.res 0The memory location right after the register stack. This is the value of rptr when the register stack is empty.
ba rel8Always branch. bc rel8Pop a carry bit, branch if it is set. bnc rel8Pop a carry bit, branch if it is clear. bneg rel8Branch if rP is negative. bnz rel8Branch if rP is non-zero. bpos rel8Branch if rP is non-negative. bz rel8Branch if rP is zero. call imm16Call subroutine at imm16. call2 rD, rA, imm16r0 := rD, r1 := rA, call subroutine at imm16. callpCall subroutine at the address held in rP. callp2 rD, rAr0 := rD, r1 = rA, call subroutine at the address held in rP. case16 imm16, rel8Branch if rP = imm16. case8 imm8, rel8Branch if rP = imm8. decloop rD, imm4p1, rel8rD := rD - imm4 (1-16), branch until it wraps past zero. Does not affect carry stack. jump imm16Jump to imm16. jumppJump to address held in rP. nativeShifts to 6502 mode starting at the byte after this instruction. .X is preloaded with rptr for convenience. noopNo operation. retReturn from subroutine. rets4 *imm4p1Return from subroutine, and shrink register stack by imm4 (1-16) slots. The resulting r0 becomes the new rP. rets8 imm8Return from subroutine, and shrink register stack by imm8 slots. The resulting r0 becomes the new rP. Pseudo Instructions
rets
imm
Becomes ret, rets4 or rets8. case
imm, rel8
Becomes case8 or case16 Native Routines
jsr acheronEnter Acheron mode, interpreting bytecodes immediately after the JSR instruction. Zeropage Locations
iptr:
.res 2Instruction pointer. Always points to the beginning of an instruction, with (zp),y addressing reading the parameters.
add rD, rArD := rD + rA addc rD, rArD := rD + rA + carry addi4 rD, imm4p1rD := rD + imm4 (1-16) addi4c rD, imm4rD := rD + imm4 + carry addi8 rD, imm8rD := rD + imm8 (1-256) addp16 imm16rP := rP + imm16 addp16c imm16rP := rP + imm16 + carry addp8 imm8rP := rP + imm8 addp8c imm8rP := rP + imm8 + carry decprP := rP - 1 decp2rP := rP - 2 div rD, rArD := quotient, rH := remainder, of rD/rA. incprP := rP + 1 incp2rP := rP + 2 ldiv rD, rArD := quotient, rH := remainder, of rD:rH/rA. mac rD, rArD:rH := rD * rA + rH mul rD, rArD:rH := rD * rA negateprP := -rP sub rD, rArD := rD - rA subc rD, rArD := rD - rA - borrow subi rD, imm4p1rD := rD - imm4 (1-16) subic rD, imm4rD := rD - imm4 - borrow subp8 imm8rP := rP - imm8 subp8c imm8rP := rP - imm8 - borrow test rD, rArH := rD - rA Pseudo Instructions
addp
imm
Becomes addp8, addp16, or subp8. addpc
imm
Becomes addp8c, addp16c, or subp8c. subp
imm
Becomes subp8, addp16, or addp8. subpc
imm
Becomes subp8c, addp16c, or addp8c. Zeropage Locations
cstack:
.res 1Carry stack. MSB is the current carry bit.
All add and sub variations push a carry bit, inc and dec do not.
andp imm16rP := rP & imm16 andr rD, rArD := rD & rA bswapSwap high and low bytes of rP. dropcDiscard the most recent carry bit. flipcFlip the state of the most recent carry bit, leaving it on the carry stack. hibyterP := upper byte of rP. lobyterP := lower byte of rP. notprP := rP ^ $ffff nswapSwap nybbles of the lowest byte of rP. orp imm16rP := rP | imm16 orr rD, rArD := rD | rA roll rD, imm4rD := (rD << imm4) | (rD >> (16 - imm4)), imm4 is nonzero. shl rD, imm4rD := rD << imm4 (nonzero), bits shift into the carry stack. shr rD, imm4rD := rD >> imm4 (nonzero), bits shift into the carry stack. sshr rD, imm4rD := rD >>> imm4 (nonzero), bits shift into the carry stack. xorp imm16rP := rP ^ imm16 xorr rD, rArD := rD ^ rA
Similar to the 6502's zeropage, 256 bytes of global storage (as opposed to register stack storage) are addressable in short form.
addgptrrP := pointer to global(rP) ldg *rD, imm8rD := global(imm8) stgp imm8global(imm8) := rP Zeropage Locations
gptr:
.res 2Pointer to the globals area. Must be directly initialized before use.
Dereferencing through gptr allows faster context switching and reusable code between processes with different global tables.
These are non-local returns that can also be used for error handling.
catch imm16Register an exception handler routine at imm16. popcatchDiscard the most recent exception handler. throw rD, rAIf rD is nonzero, throw an exception with tag rD and parameter rA. Can be used to rethrow from inside a catch handler. Zeropage Locations
currentCatch:
.res 1CPU stack position describing the currently registered exception handler.
When a throw is triggered, the CPU and register stacks are restored to the state at the time of the catch, the register stack grows by 2 slots, and r0 & r1 become the exception tag and parameter, respectively, with rP pointing to r0. Usually a handler will use 'case' instructions to branch on desired tags, rethrowing the exception otherwise.
For handling 'finally' situations, normal code flow and exception rethrowing need to be handled in the same code:
catch finally ... popcatch grow 2 set r0, 0 finally: ... throw r0,r1 ; ignored if no exception was thrown and r0 is still zero
Each catch context takes 5 bytes on the CPU stack.
The trap system allows breaking into native code before each instruction is executed, or when exceptions are thrown. Native code can then check the iptr for breakpoint matches, resume, single-step, swap tasks, or do whatever it wants.
Native Routines
jsr enableInstructionTrapEnable the instruction trap to call <.A >.X. jsr disableInstructionTrapDisable the break functionality. jmp continueFromInstructionTrapContinue running the VM after a break was triggered. If the break is still enabled, this effectively single-steps. jsr enableExceptionTrapEnable the exception trap to call <.A >.X jsr disableExceptionTrapDisable exception trap. jmp continueFromExceptionTrapContinue processing the exception thrown.
When the trap runs, the iptr points to the beginning of the instruction yet to be executed.
These handlers self-modify the main loop, so there is no runtime overhead when this feature is included and disabled, besides the memory footprint. Since the selfmod happens only on native instruction boundaries, it is safe to enable/disable traps from interrupt handlers. Preemptive task switchers should use this sort of approach.