Virtual Machine
Posted: Thu Jan 05, 2017 11:34 pm
I'm designing a virtual machine to implement my first Gamebuino project (viewtopic.php?f=13&t=3579)
My first draft is here, I'd love to get some feedback before starting its implementation. Despite my project involving a BASIC compiler, I'd like the VM to be general enough to run other compiled languages, like Pascal and C/C++.
Sorry for the bad alignment.
REGISTERS
uint16_t PC (program counter, wraps around after 65535)
uint16_t SP (stack pointer, each element in the stack has 16 bits)
uint16_t CS (code segment)
uint16_t DS (data segment)
uint16_t PS (pending code segment)
The current instruction is pointed to by CS:PC. CS doesn't change when PC wraps. All data accesses are relative to DS.
INSTRUCTION SET
+----------------------------+----------+-------------------------------------------------------------------------------------------------------+
| OPCODE | MNEMONIC | DESCRIPTION |
+----------------------------+----------+-------------------------------------------------------------------------------------------------------+
| 00xxxxx0 -------- -------- | getl x | gets the value of the local variable x and pushes onto the stack (0 <= x <= 31) |
| 00xxxxx1 -------- -------- | setl x | sets the value of the local variable x from the top of the stack (0 <= x <= 31) |
| 01xxxxxx -------- -------- | asp x | adds x to the stack pointer (-32 <= x <= 31) |
| 100xxxxx -------- -------- | push x | pushes a constant onto the stack (-15 <= x <= 16) |
| 1010xxxx -------- -------- | sys x | calls a native function with an array of x values (0 <= x <= 15) |
| 10110xxx -------- -------- | cmp x | pops b and then a from the stack and pushes the result of their comparison [1] |
| 10111000 yyyyyyyy yyyyyyyy | jmp y | continues execution at y [2] |
| 10111001 -------- -------- | ijmp | pops an address from the stack and continues execution at that location [2] |
| 10111010 yyyyyyyy yyyyyyyy | jf y | pops a value from the stack and continues execution at y if the value is zero |
| 10111011 yyyyyyyy yyyyyyyy | jt y | pops a value from the stack and continues execution at y if the value is not zero |
| 10111100 yyyyyyyy yyyyyyyy | call y | pushes the current selector and the address of the next instruction onto the stack and jumps to y [2] |
| 10111101 -------- -------- | dup | duplicates the value at the top of the stack |
| 10111110 -------- -------- | ret | pops an address and a code selector from the stack and jumps to that location |
| 10111111 -------- -------- | retv | pops an address and a code selector from the stack and jumps to that location [3] |
| 11000000 -------- -------- | add | pops two values x and y from the stack, and pushes x + y |
| 11000001 -------- -------- | sub | same as add but pushes x - y |
| 11000010 -------- -------- | mul | same as add but pushes x * y |
| 11000011 -------- -------- | div | same as add but pushes x / y |
| 11000100 -------- -------- | mod | same as add but pushes x % y |
| 11000101 -------- -------- | and | same as add but pushes x & y |
| 11000110 -------- -------- | or | same as add but pushes x | y |
| 11000111 -------- -------- | xor | same as add but pushes x ^ y |
| 11001000 -------- -------- | neg | pops x from the stack and pushes -x |
| 11001001 -------- -------- | not | same as neg but pushes !x |
| 11001010 -------- -------- | shl | same as add but pushes x << y |
| 11001011 -------- -------- | shr | same as add but pushes x >> y |
| 11001100 -------- -------- | ldb | pops an address from the stack and pushes the byte at that location |
| 11001101 -------- -------- | ldw | same as ldc, but reads a 16-bit signed value at address and address+1 [4] |
| 11001110 -------- -------- | stb | pops an address and a value from the stack, and sets memory |
| 11001111 -------- -------- | stw | same as stb, but writes a 16-bit value |
| 11010000 -------- -------- | setcs | sets the code segment selector [5] |
| 11010001 -------- -------- | setds | sets the data segment selector |
| 11010010 yyyyyyyy -------- | push y | pushes a constant onto the stack (-128 <= y <= 127) |
| 11010011 yyyyyyyy -------- | push y | pushes a constant onto the stack (128 <= y <= 383) |
| 11010100 yyyyyyyy yyyyyyyy | push y | pushes a constant onto the stack (-32768 <= x <= 32767) |
| 11010101 yyyyyyyy | asp y | adds y to the stack pointer (-128 <= y <= 127) |
| 11010110 yyyyyyyy | getl | gets the value of the local variable y and pushes onto the stack (32 <= y <= 287) |
| 11010111 yyyyyyyy | setl | sets the value of the local variable x from the top of the stack (32 <= x <= 287) |
+----------------------------+----------+-------------------------------------------------------------------------------------------------------+
[1] The comparison depends on the value of x (0=z, nz, eq, ne, lt le, gt, 7=ge). For x=0 and x=1, only b is popped from the stack, and is checked if is's zero or not zero, respectively
[2] These operations set the pending value of the code segment
[3] This return instruction pops a value from the stack, pops the address and the selector, pushes the value back onto the stack, and then resumes operation at the popped segment and address
[4] The most significant byte it in the memory pointed to by the data segment and the address, the next byte in memory is the least significant byte.
[5] The code segment is not set at the time the instruction is issued, but keeps pending until an actual jump or call instruction is issued
My first draft is here, I'd love to get some feedback before starting its implementation. Despite my project involving a BASIC compiler, I'd like the VM to be general enough to run other compiled languages, like Pascal and C/C++.
Sorry for the bad alignment.
REGISTERS
uint16_t PC (program counter, wraps around after 65535)
uint16_t SP (stack pointer, each element in the stack has 16 bits)
uint16_t CS (code segment)
uint16_t DS (data segment)
uint16_t PS (pending code segment)
The current instruction is pointed to by CS:PC. CS doesn't change when PC wraps. All data accesses are relative to DS.
INSTRUCTION SET
+----------------------------+----------+-------------------------------------------------------------------------------------------------------+
| OPCODE | MNEMONIC | DESCRIPTION |
+----------------------------+----------+-------------------------------------------------------------------------------------------------------+
| 00xxxxx0 -------- -------- | getl x | gets the value of the local variable x and pushes onto the stack (0 <= x <= 31) |
| 00xxxxx1 -------- -------- | setl x | sets the value of the local variable x from the top of the stack (0 <= x <= 31) |
| 01xxxxxx -------- -------- | asp x | adds x to the stack pointer (-32 <= x <= 31) |
| 100xxxxx -------- -------- | push x | pushes a constant onto the stack (-15 <= x <= 16) |
| 1010xxxx -------- -------- | sys x | calls a native function with an array of x values (0 <= x <= 15) |
| 10110xxx -------- -------- | cmp x | pops b and then a from the stack and pushes the result of their comparison [1] |
| 10111000 yyyyyyyy yyyyyyyy | jmp y | continues execution at y [2] |
| 10111001 -------- -------- | ijmp | pops an address from the stack and continues execution at that location [2] |
| 10111010 yyyyyyyy yyyyyyyy | jf y | pops a value from the stack and continues execution at y if the value is zero |
| 10111011 yyyyyyyy yyyyyyyy | jt y | pops a value from the stack and continues execution at y if the value is not zero |
| 10111100 yyyyyyyy yyyyyyyy | call y | pushes the current selector and the address of the next instruction onto the stack and jumps to y [2] |
| 10111101 -------- -------- | dup | duplicates the value at the top of the stack |
| 10111110 -------- -------- | ret | pops an address and a code selector from the stack and jumps to that location |
| 10111111 -------- -------- | retv | pops an address and a code selector from the stack and jumps to that location [3] |
| 11000000 -------- -------- | add | pops two values x and y from the stack, and pushes x + y |
| 11000001 -------- -------- | sub | same as add but pushes x - y |
| 11000010 -------- -------- | mul | same as add but pushes x * y |
| 11000011 -------- -------- | div | same as add but pushes x / y |
| 11000100 -------- -------- | mod | same as add but pushes x % y |
| 11000101 -------- -------- | and | same as add but pushes x & y |
| 11000110 -------- -------- | or | same as add but pushes x | y |
| 11000111 -------- -------- | xor | same as add but pushes x ^ y |
| 11001000 -------- -------- | neg | pops x from the stack and pushes -x |
| 11001001 -------- -------- | not | same as neg but pushes !x |
| 11001010 -------- -------- | shl | same as add but pushes x << y |
| 11001011 -------- -------- | shr | same as add but pushes x >> y |
| 11001100 -------- -------- | ldb | pops an address from the stack and pushes the byte at that location |
| 11001101 -------- -------- | ldw | same as ldc, but reads a 16-bit signed value at address and address+1 [4] |
| 11001110 -------- -------- | stb | pops an address and a value from the stack, and sets memory |
| 11001111 -------- -------- | stw | same as stb, but writes a 16-bit value |
| 11010000 -------- -------- | setcs | sets the code segment selector [5] |
| 11010001 -------- -------- | setds | sets the data segment selector |
| 11010010 yyyyyyyy -------- | push y | pushes a constant onto the stack (-128 <= y <= 127) |
| 11010011 yyyyyyyy -------- | push y | pushes a constant onto the stack (128 <= y <= 383) |
| 11010100 yyyyyyyy yyyyyyyy | push y | pushes a constant onto the stack (-32768 <= x <= 32767) |
| 11010101 yyyyyyyy | asp y | adds y to the stack pointer (-128 <= y <= 127) |
| 11010110 yyyyyyyy | getl | gets the value of the local variable y and pushes onto the stack (32 <= y <= 287) |
| 11010111 yyyyyyyy | setl | sets the value of the local variable x from the top of the stack (32 <= x <= 287) |
+----------------------------+----------+-------------------------------------------------------------------------------------------------------+
[1] The comparison depends on the value of x (0=z, nz, eq, ne, lt le, gt, 7=ge). For x=0 and x=1, only b is popped from the stack, and is checked if is's zero or not zero, respectively
[2] These operations set the pending value of the code segment
[3] This return instruction pops a value from the stack, pops the address and the selector, pushes the value back onto the stack, and then resumes operation at the popped segment and address
[4] The most significant byte it in the memory pointed to by the data segment and the address, the next byte in memory is the least significant byte.
[5] The code segment is not set at the time the instruction is issued, but keeps pending until an actual jump or call instruction is issued