The file was automatically generated by Acheron VM build tools
r0-r127
The registers currently visible in the register window. r0 is always the head of the register stack. rP
The remembered Prior register, rD from the last instruction that used one (including 'with rD'). Acts like an implicit accumulator. rP:r[P+1]
32-bit effective register, with rP being the low word and the next register in memory being the high word. rD
A numbered register operand for data or destination, which will become the new rP after the instruction completes. rA
A numbered register operand for auxiliary/address use by the instruction. imm8
8-bit immediate value, representing 0-255. imm8p
8-bit immediate value, representing 1-256. imm16
16-bit immediate value, 0-65535. 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
The mark for the register stack set by mgrow is put on the CPU stack. It does not affect any registers and marks can nest.
Instructions
clrprP := 0 copyr rArP := rA. Copy another register into rP. droppDrop a 16-bit value from the CPU stack. getsprP := CPU stack pointer grow imm8Grow the register stack by imm8 slots. rP is unchanged. ldrptr rArP := current address of rA. Note that task switching or otherwise swapping out zp can leave this pointer dangling. mgrow imm8Push a mark of the register stack, and grow it by imm8 slots. rP is unchanged. movep rDrD := rP. Effectively move rP and its value to another register. poppPop rP from the CPU stack. pushpPush rP to the CPU stack. setp16 imm16rP := imm16 setp8 imm8rP := imm8 setspCPU stack pointer := rP. Only the low byte is used. shrink imm8Shrink the register stack by imm8 slots. The resulting r0 becomes the new rP. shrinkmShrink the register stack to last mark, and pop it. The resulting r0 becomes the new rP. Pseudo Instructions
setp
imm
Becomes clrp, setp8, or setp16. 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 (globals, dataframes, etc). 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, rP. rstackTop:
.res 0The memory location right after the register stack. This is the value of rptr when the register stack is empty.
To call an Acheron subroutine from native code:
jsr acheron call label nativeThis supports reentrant usage from within native portions of a running Acheron call chain.
Instructions
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. calln imm16Call native 6502 subroutine at imm16. .X points to rP. .X and .Y are saved and restored. callpCall subroutine at the address held in rP. case16 imm16, rel8Branch if rP = imm16. case8 imm8, rel8Branch if rP = imm8. caser rA, rel8Branch if rP = rA decloop rel8negDecrements rP by r[P+1], looping back 0-255 bytes if it hasn't underflowed yet. decloopi imm8, rel8negDecrements rP by imm8, looping back imm8 bytes if it hasn't underflowed yet. jump imm16Jump to imm16. jumppJump to address held in rP. nativeEnter 6502 mode, starting at the byte after this instruction. noopNo operation. retReturn from subroutine. retmPop & restore the register stack mark from mgrow, return from subroutine. Resets rP to the returned r0. Pseudo Instructions
case
imm, rel8
Becomes case8 or case16 Native Routines
jsr acheronNestEnter Acheron mode as if called as a subroutine, interpreting bytecodes immediately after the JSR instruction. When this code ret's, the prior running Acheron code will resume. jsr acheronEnter Acheron mode, interpreting bytecodes immediately after the JSR instruction. Zeropage Locations
iptr:
.res 2Instruction pointer base. This plus .Y addresses the current program byte. The dispatcher keeps .Y in the range of 0 to 127, so overflow after INY does not need to be checked. iptr_offset:
.res 1Temp storage for the .Y offset to iptr when using the register for other purposes.
Instructions
addea2 rArP := rP + (rA << 1). Does not affect carry. andi imm16rP := rP & imm16 andr rArP := rP & rA bswapSwap high and low bytes of rP. dropcDiscard the most recent carry bit. dupcDuplicate the top carry bit. flipcFlip the state of the most recent carry bit, leaving it on the carry stack. fromhexConvert rP from a 2-byte ASCII hex representation into an 8-bit value. This operation is case-insensitive. hibyterP := upper byte of rP. lobyterP := lower byte of rP. notrP := rP ^ $ffff nswapSwap nybbles of the low byte of rP. ori imm16rP := rP | imm16 orr rArP := rP | rA pushccPush a clear carry bit. pushcsPush a set carry bit. roll imm8rP := (rP << imm8) | (rP >> (16 - imm8)), imm8 is nonzero. shl imm8rP := rP << imm8 (nonzero), bits shift into the carry stack. shr imm8rP := rP >> imm8 (nonzero), bits shift into the carry stack. signxSign extend an 8-bit value in rP into 16 bits. sshr imm8rP := rP >>> imm8 (nonzero), bits shift into the carry stack. tohexConvert rP into a 2-byte ASCII hex representation of its low byte, e.g. $00f1 => $3146 (little endian, so 'f1'). xori imm16rP := rP ^ imm16 xorr rArP := rP ^ rA
Instructions
add rArP := rP + rA addc rArP := rP + rA + carry addi16 imm16rP := rP + imm16 addi16c imm16rP := rP + imm16 + carry addi8 imm8prP := rP + imm8 (1-256) addi8c imm8prP := rP + imm8 (1-256) + carry cmpi16 imm16Compare rP - imm16 and push carry result on stack, without affecting registers cmpi8 imm8Compare rP - imm8 and push carry result on stack, without affecting registers cmpr rACompare rP - rA and push carry result on stack, without affecting registers (0: rP<rA, 1: rP>=rA) decprP := rP - 1 decp2rP := rP - 2 div rArP := quotient, r[P+1] := remainder, of rP/rA. incprP := rP + 1 incp2rP := rP + 2 ldiv rArP := quotient, r[P+1] := remainder, of rP:r[P+1]/rA. mac rArP:r[P+1] := rP * rA + r[P+1] mul rArP:r[P+1] := rP * rA negaterP := -rP sub rArP := rP - rA subc rArP := rP - rA - borrow subi8 imm8prP := rP - imm8 (1-256) subi8c imm8prP := rP - imm8 (1-256) - borrow Pseudo Instructions
addi
imm
Becomes addi8, addi16, or subi8. addic
imm
Becomes addi8c, addi16c, or subi8c. subi
imm
Becomes subi8, addi16, or addi8. subic
imm
Becomes subi8c, addi16c, or addi8c. Zeropage Locations
cstack:
.res 1Carry stack. MSB is the current carry bit.
Indexed modes take immediate constants, and are intended to reference structure slots without modifying the base pointer. Traversing through memory in order would instead use incp/decp or add/sub instructions on the address register.
Indexing uses (zp),y addressing behind the scenes, so is limited to +255. Referencing 16-bit data at an index of 255 will wrap to an index of 0 for the high byte and probably break things.
Memory stores keep rP pointing at the data register, so the destination address should be computed before the value to store into it.
Note that ldm commands will not work to load into rP. Use deref instead.
Instructions
clrmmemory(rP) := 0 clrmbmemory(rP) byte := 0 clrmn imm8pmemory(rP .. rP+imm8-1) := 0. Clears 1-256 bytes of memory, starting at rP. derefrP := memory(rP) derefbrP := memory(rP) byte derefbi imm8rP := memory(rP + imm8) byte derefi imm8rP := memory(rP + imm8) ldm rDrD := memory(rP) ldma imm16rP := memory(imm16) ldmb rDrD := memory(rP) byte ldmba imm16rP := memory(imm16) byte ldmbi rD, imm8rD := memory(rP + imm8) byte ldmbr rD, rArD := memory(rP + rA) byte. Load Memory Byte, Register indexed. ldmi rD, imm8rD := memory(rP + imm8) ldmr rD, rArD := memory(rP + rA). Load Memory, Register indexed. stm rAmemory(rA) := rP. stma imm16memory(imm16) := rP stmb rAmemory(rA) byte := rP. stmba imm16memory(imm16) byte := rP stmbi rA, imm8memory(rA + imm8) byte := rP. stmbr rD, rAmemory(rD + rA) byte := rP. Store Memory Byte, Register indexed. stmi rA, imm8memory(rA + imm8) := rP. stmr rD, rAmemory(rD + rA) := rP. Store Memory, Register indexed.
Similar to the 6502's zeropage, 256 bytes of global storage (as opposed to register stack storage) are addressable in short form.
Dereferencing through gptr allows faster context switching and reusable code between processes with different global tables.
Instructions
getgptr imm8rP := pointer to global(rP) ldg imm8rP := global(imm8) stg imm8global(imm8) := rP Zeropage Locations
gptr:
.res 2Pointer to the globals area. Must be directly initialized before use.
These are non-local returns that can also be used for error handling.
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 ; normal fallthrough into the 'finally' clause grow 2 with r0 clrp 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.
Instructions
catch imm16Register an exception handler routine at imm16, with rP:r[P+1] receiving the exception info. popcatchDiscard the most recent exception handler. throwIf rP is nonzero, throw an exception with tag rP and parameter r[P+1]. Can be used to rethrow from inside a catch handler. Zeropage Locations
currentCatch:
.res 1CPU stack position describing the currently registered exception handler.
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.
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.
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.
Instructions
dsallocAllocate rP bytes as a new data stack frame, pushing the old location on the CPU stack. dsi imm8rP := pointer to dataframe(imm8) dspopDiscard the most recently allocated data stack frame, restoring state from the CPU stack. getdsptrrP = pointer to dataframe(rP) Zeropage Locations
dsptr:
.res 2Pointer to the dataframe head. Must be directly initialized before use.