Trimble 4000SX Virtual Machine
Opcode 01: NOP
Opcode 02: Execute Inline Native
Executes 68k instructions starting at the next 16 bit aligned address. Execution is terminated with an RTS instruction.
Opcode 03: Not Sure What To Call This
Operand: Z (8 bits)
This instruction seems to be an alternate path to opcodes 20 (ADD.F) through to… hmm…
Opcode 04 through 07: PUSH.X
Operands: X (10 bits, overlaps opcode)
X - N1
Push 10 bit value onto stack
Opcode 08 through 0F: JR
Not investigated yet.
Opcode 10 through 17: JR
Operands: Y (11 bits, overlaps opcode)
Location in ROM: $00202c
Signed relative jump to Y
Opcode 18 through 1F: JRZ
Operands: Y (11 bits, overlaps opcode)
Location in ROM: $00202c
N1 -
Signed relative jump to Y if N1 is zero
Opcode 20: ADD.F
Location in ROM: $00279a
IEEE 754 64 bit float add (adds 2 64 bit floats on stack) pushes result onto stack
+ N1 N2 - N3
Opcode 21: SUB.F
Location in ROM: $002792
IEEE 754 64 bit float subtract (subtracts second number on stack from first) pushes result onto stack
- N1 N2 - N3
Opcode 22: MUL.F
Location in ROM: $0028d6
IEEE 754 64 bit float multiply (multiplies second number by first) pushes result onto stack
* N1 N2 - N3
Opcode 23: DIV.F
Location in ROM: $002a1c
IEEE 754 64 bit float divide (divides first number on stack by second) pushes result onto stack
÷ N1 N2 - N3
Opcode 24: GT.F
Location in ROM: $0024e4
> N1 N2 - FLAG
Pushes a true FLAG if N1 is greater than N2. Can be used to compare any 64 bit values on the stack.
Opcode 25: GTE.F
Location in ROM: $0024f0
>= N1 N2 - FLAG
Pushes a true FLAG if N1 is greater than or equal to N2. Can be used to compare any 64 bit values on the stack.
Opcode 26: EQ.F
Location in ROM: $0024d8
= N1 N2 - FLAG
Pushes a true FLAG if N1 is equal to N2. Can be used to compare any 64 bit values on the stack.
Opcode 27: ADD.L
Location in ROM: $002566
+ N1 N2 - N3
Add N1 and N2, giving sum N3.
Opcode 28: SUB.L
Location in ROM: $00256c
- N1 N2 - N3
Subtract N2 from N1, giving difference N3.
Opcode 29: MUL.L
Location in ROM: $002572
* N1 N2 - N3
Perform unsigned integer multiplication on N1 and N2, giving result N3
Opcode 2A: EQ.L
Location in ROM: $00249e
= N1 N2 - FLAG
Pushes a true FLAG if N1 is equal to N2.
Opcode 2B: NEQ.L
Location in ROM: $0024a6
≠ N1 N2 - FLAG
Pushes a true FLAG if N1 is not equal to N2.
Opcode 2C: AND.L
Location in ROM: $002554
AND N1 N2 - N3
Perform a bitwise AND on N1 and N2, giving result N3.
Opcode 2D: OR.L
Location in ROM: $00255a
OR N1 N2 - N3
Perform a bitwise OR on N1 and N2, giving result N3.
Opcode 2E: NOT.L
Location in ROM: $002550
NOT N1 - N2
Perform a bitwise NOT on N1, giving result N2.
Opcode 2F: MOD.L
Location in ROM: $0025b4
might be divide, need to test
Opcode 30: JSR.68K
Operands: W
Location in ROM: $00212c
Subtracts operand from VPC, performs 68k JSR to that address. Typically used to call native functions defined in the jump table at the beginning of the function. Often these native functions are simply jumps to the entry point of a VM function.
Opcode 31: SUBSP.B
Operands: B
Location in ROM: $002444
Subtracts operand from stack pointer.
Opcode 32: ADDSP.B
Operands: B
Location in ROM: $002458
Adds operand to stack pointer.
Opcode 33: ???
Location in ROM: $002300
Uses top of stack to compute something then copies bytes from stack to A4 stuff.
Opcode 34: RETURN
Location in ROM: $00213c
Always Called at the end of a function. Fiddles with A0. Still figuring out what it’s doing. Sometimes this opcode is followed by 0xFF, which is just a padding byte to ensure the function ends on a word boundary.
Opcode 35: SWITCH
Location in ROM: $002b48
The switch opcode represents a higher level structural concept. followed by a number of repeating blocks, one for each case statement. Each block is preceded with a variable length header:
Block Header
┌─ Number of keys; 0 = default
│
│ ┌─ Record Length in bytes
│ │
│ │ ┌─ Keys for this record
│ │ │
│ │ │
0001 000000001001 [00000000]
Each block can multiple keys. The first 4 bits specifies how many keys the record specifies. The record length calculation includes the keys.
The VCPU searches for matching keys in a linear fashion. It searches the each key in the first record, followed by each key in the second record. It operates on a first match basis, so if multiple keys exist, only the earliest will ever match.
The end of each block must have a JR to the far end of the switch statement, otherwise the VCPU will attempt to execute the header of the next block as VCPU code, which most likely won’t end well.
Default Header
The final record in the statement is marked by a ‘default’ header, which is 8 bits of zeros. The Default record must be specified last, because it signals to the VCPU to stop searching for additional blocks.
┌─ All zeros
│
│
00000000
The default record contains no code; it simply signals to the CPU to begin executing VCPU code after the 0.
Example
Addr Opcode Instruction Operands
-------------------------------------------------------
0A02DF 86 03 READ.L $03
0A02E1 35 SWITCH
0A02E2 10 09 00 CASE (0A02E1) $00
0A02E5 03 30 03 53... TRAP1 "SUN"
0A02EB 10 3E JR $03E => $0A032B
0A02ED 10 09 01 CASE (0A02E1) $01
0A02F0 03 30 03 4D... TRAP1 "MON"
0A02F6 10 33 JR $033 => $0A032B
0A02F8 10 09 02 CASE (0A02E1) $02
0A02FB 03 30 03 54... TRAP1 "TUE"
0A0301 10 28 JR $028 => $0A032B
0A0303 10 09 03 CASE (0A02E1) $03
0A0306 03 30 03 57... TRAP1 "WED"
0A030C 10 1D JR $01D => $0A032B
0A030E 10 09 04 CASE (0A02E1) $04
0A0311 03 30 03 54... TRAP1 "THU"
0A0317 10 12 JR $012 => $0A032B
0A0319 10 09 05 CASE (0A02E1) $05
0A031C 03 30 03 46... TRAP1 "FRI"
0A0322 10 07 JR $007 => $0A032B
0A0324 00 DEFAULT (0A02E1)
0A0325 03 30 03 53... TRAP1 "SAT"
0A032B 32 04 ADDSP.B $04
0A032D 34 RETURN