AcheronVM - Instruction Set

The file was automatically generated by Acheron VM build tools

Contents


Naming Conventions

r0-r127The registers currently visible in the register window. r0 is always the head of the register stack.
rPThe 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.
rDA numbered register operand for data or destination, which will become the new rP after the instruction completes.
rAA numbered register operand for auxiliary/address use by the instruction.
imm88-bit immediate value, representing 0-255.
imm8p8-bit immediate value, representing 1-256.
imm1616-bit immediate value, 0-65535.
rel8Used 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

Number of Instruction Opcodes Used: 115


Register and Stack Operations

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
clrp
 
rP := 0
copyr
 rA
rP := rA. Copy another register into rP.
dropp
 
Drop a 16-bit value from the CPU stack.
getsp
 
rP := CPU stack pointer
grow
 imm8
Grow the register stack by imm8 slots. rP is unchanged.
ldrptr
 rA
rP := current address of rA. Note that task switching or otherwise swapping out zp can leave this pointer dangling.
mgrow
 imm8
Push a mark of the register stack, and grow it by imm8 slots. rP is unchanged.
movep
 rD
rD := rP. Effectively move rP and its value to another register.
popp
 
Pop rP from the CPU stack.
pushp
 
Push rP to the CPU stack.
setp16
 imm16
rP := imm16
setp8
 imm8
rP := imm8
setsp
 
CPU stack pointer := rP. Only the low byte is used.
shrink
 imm8
Shrink the register stack by imm8 slots. The resulting r0 becomes the new rP.
shrinkm
 
Shrink the register stack to last mark, and pop it. The resulting r0 becomes the new rP.

Pseudo Instructions
setpimmBecomes clrp, setp8, or setp16.

Native Routines
jsr clear_rstack
Initializes 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 0
Highest zeropage memory location in use, for copying out process context.
rptr:
 .res 1
Pointer to the head of the register stack, which is also r0, and the lowest used byte in zp.
pptr:
 .res 1
Pointer to the prior register, rP.
rstackTop:
 .res 0
The memory location right after the register stack. This is the value of rptr when the register stack is empty.


Flow Control

To call an Acheron subroutine from native code:

 jsr acheron
 call label
 native
This supports reentrant usage from within native portions of a running Acheron call chain.

Instructions
ba
 rel8
Always branch.
bc
 rel8
Pop a carry bit, branch if it is set.
bnc
 rel8
Pop a carry bit, branch if it is clear.
bneg
 rel8
Branch if rP is negative.
bnz
 rel8
Branch if rP is non-zero.
bpos
 rel8
Branch if rP is non-negative.
bz
 rel8
Branch if rP is zero.
call
 imm16
Call subroutine at imm16.
calln
 imm16
Call native 6502 subroutine at imm16. .X points to rP. .X and .Y are saved and restored.
callp
 
Call subroutine at the address held in rP.
case16
 imm16, rel8
Branch if rP = imm16.
case8
 imm8, rel8
Branch if rP = imm8.
caser
 rA, rel8
Branch if rP = rA
decloop
 rel8neg
Decrements rP by r[P+1], looping back 0-255 bytes if it hasn't underflowed yet.
decloopi
 imm8, rel8neg
Decrements rP by imm8, looping back imm8 bytes if it hasn't underflowed yet.
jump
 imm16
Jump to imm16.
jumpp
 
Jump to address held in rP.
native
 
Enter 6502 mode, starting at the byte after this instruction.
noop
 
No operation.
ret
 
Return from subroutine.
retm
 
Pop & restore the register stack mark from mgrow, return from subroutine. Resets rP to the returned r0.

Pseudo Instructions
caseimm, rel8Becomes case8 or case16

Native Routines
jsr acheronNest
Enter 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 acheron
Enter Acheron mode, interpreting bytecodes immediately after the JSR instruction.

Zeropage Locations
iptr:
 .res 2
Instruction 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 1
Temp storage for the .Y offset to iptr when using the register for other purposes.


Bitwise Operations

Instructions
addea2
 rA
rP := rP + (rA << 1). Does not affect carry.
andi
 imm16
rP := rP & imm16
andr
 rA
rP := rP & rA
bswap
 
Swap high and low bytes of rP.
dropc
 
Discard the most recent carry bit.
dupc
 
Duplicate the top carry bit.
flipc
 
Flip the state of the most recent carry bit, leaving it on the carry stack.
fromhex
 
Convert rP from a 2-byte ASCII hex representation into an 8-bit value. This operation is case-insensitive.
hibyte
 
rP := upper byte of rP.
lobyte
 
rP := lower byte of rP.
not
 
rP := rP ^ $ffff
nswap
 
Swap nybbles of the low byte of rP.
ori
 imm16
rP := rP | imm16
orr
 rA
rP := rP | rA
pushcc
 
Push a clear carry bit.
pushcs
 
Push a set carry bit.
roll
 imm8
rP := (rP << imm8) | (rP >> (16 - imm8)), imm8 is nonzero.
shl
 imm8
rP := rP << imm8 (nonzero), bits shift into the carry stack.
shr
 imm8
rP := rP >> imm8 (nonzero), bits shift into the carry stack.
signx
 
Sign extend an 8-bit value in rP into 16 bits.
sshr
 imm8
rP := rP >>> imm8 (nonzero), bits shift into the carry stack.
tohex
 
Convert rP into a 2-byte ASCII hex representation of its low byte, e.g. $00f1 => $3146 (little endian, so 'f1').
xori
 imm16
rP := rP ^ imm16
xorr
 rA
rP := rP ^ rA


Arithmetic

Instructions
add
 rA
rP := rP + rA
addc
 rA
rP := rP + rA + carry
addi16
 imm16
rP := rP + imm16
addi16c
 imm16
rP := rP + imm16 + carry
addi8
 imm8p
rP := rP + imm8 (1-256)
addi8c
 imm8p
rP := rP + imm8 (1-256) + carry
cmpi16
 imm16
Compare rP - imm16 and push carry result on stack, without affecting registers
cmpi8
 imm8
Compare rP - imm8 and push carry result on stack, without affecting registers
cmpr
 rA
Compare rP - rA and push carry result on stack, without affecting registers (0: rP<rA, 1: rP>=rA)
decp
 
rP := rP - 1
decp2
 
rP := rP - 2
div
 rA
rP := quotient, r[P+1] := remainder, of rP/rA.
incp
 
rP := rP + 1
incp2
 
rP := rP + 2
ldiv
 rA
rP := quotient, r[P+1] := remainder, of rP:r[P+1]/rA.
mac
 rA
rP:r[P+1] := rP * rA + r[P+1]
mul
 rA
rP:r[P+1] := rP * rA
negate
 
rP := -rP
sub
 rA
rP := rP - rA
subc
 rA
rP := rP - rA - borrow
subi8
 imm8p
rP := rP - imm8 (1-256)
subi8c
 imm8p
rP := rP - imm8 (1-256) - borrow

Pseudo Instructions
addiimmBecomes addi8, addi16, or subi8.
addicimmBecomes addi8c, addi16c, or subi8c.
subiimmBecomes subi8, addi16, or addi8.
subicimmBecomes subi8c, addi16c, or addi8c.

Zeropage Locations
cstack:
 .res 1
Carry stack. MSB is the current carry bit.


Memory Operations

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
clrm
 
memory(rP) := 0
clrmb
 
memory(rP) byte := 0
clrmn
 imm8p
memory(rP .. rP+imm8-1) := 0. Clears 1-256 bytes of memory, starting at rP.
deref
 
rP := memory(rP)
derefb
 
rP := memory(rP) byte
derefbi
 imm8
rP := memory(rP + imm8) byte
derefi
 imm8
rP := memory(rP + imm8)
ldm
 rD
rD := memory(rP)
ldma
 imm16
rP := memory(imm16)
ldmb
 rD
rD := memory(rP) byte
ldmba
 imm16
rP := memory(imm16) byte
ldmbi
 rD, imm8
rD := memory(rP + imm8) byte
ldmbr
 rD, rA
rD := memory(rP + rA) byte. Load Memory Byte, Register indexed.
ldmi
 rD, imm8
rD := memory(rP + imm8)
ldmr
 rD, rA
rD := memory(rP + rA). Load Memory, Register indexed.
stm
 rA
memory(rA) := rP.
stma
 imm16
memory(imm16) := rP
stmb
 rA
memory(rA) byte := rP.
stmba
 imm16
memory(imm16) byte := rP
stmbi
 rA, imm8
memory(rA + imm8) byte := rP.
stmbr
 rD, rA
memory(rD + rA) byte := rP. Store Memory Byte, Register indexed.
stmi
 rA, imm8
memory(rA + imm8) := rP.
stmr
 rD, rA
memory(rD + rA) := rP. Store Memory, Register indexed.


Global Variables (FEATURE__GLOBALS)

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
 imm8
rP := pointer to global(rP)
ldg
 imm8
rP := global(imm8)
stg
 imm8
global(imm8) := rP

Zeropage Locations
gptr:
 .res 2
Pointer to the globals area. Must be directly initialized before use.


Exception Handling (FEATURE__EXCEPTIONS)

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
 imm16
Register an exception handler routine at imm16, with rP:r[P+1] receiving the exception info.
popcatch
 
Discard the most recent exception handler.
throw
 
If 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 1
CPU stack position describing the currently registered exception handler.


Monitor Traps (FEATURE__TRAPS)

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 enableInstructionTrap
Enable the instruction trap to call <.A >.X.
jsr disableInstructionTrap
Disable the break functionality.
jmp continueFromInstructionTrap
Continue running the VM after a break was triggered. If the break is still enabled, this effectively single-steps.
jsr enableExceptionTrap
Enable the exception trap to call <.A >.X
jsr disableExceptionTrap
Disable exception trap.
jmp continueFromExceptionTrap
Continue processing the exception thrown.


Data Frames (FEATURE__DATAFRAMES)

Instructions
dsalloc
 
Allocate rP bytes as a new data stack frame, pushing the old location on the CPU stack.
dsi
 imm8
rP := pointer to dataframe(imm8)
dspop
 
Discard the most recently allocated data stack frame, restoring state from the CPU stack.
getdsptr
 
rP = pointer to dataframe(rP)

Zeropage Locations
dsptr:
 .res 2
Pointer to the dataframe head. Must be directly initialized before use.