vurce machine description
This page describes the vurce virtual machine and its standard I/O devices.
memory and registers
vurce is a stack-based system. The machine uses the following memory spaces, which are all completely separate from each other and use separate address spaces:
0x0000-0xffff
(64 KiB) main memory0x00-0xff
(256 B) main stack0x00-0xff
(256 B) call stack0x00-0xff
(256 B) device memory
The implementation must ensure that device memory is initialized to all zeroes. The behavior of uninitialized memory for all other memory spaces is undefined.
The machine uses the following three registers:
pc
(Program Counter): Holds the 16-bit address of the current instruction to be executed.sp
(Stack Pointer): Holds the 8-bit address of the next free slot in the main stack.csp
(Call Stack Pointer): Holds the 8-bit address of the next free slot in the call stack.
All registers have an initial value of zero.
vurce is a little-endian system. That is, 16-bit values are stored with their least significant byte in the lowest memory address, and their most significant byte in the highest memory address. For example, the hexadecimal integer 0x12ab
would actually be stored in memory as the bytes 0xab
followed by 0x12
.
All opcodes exclusively treat values as unsigned integers. However, an I/O device may occasionally wish to encode a signed integer value; in this case, two's compliment encoding is used.
Both stacks always grow upwards. When a value is pushed to a stack, that value is written to the address pointed to by the stack pointer, then the stack pointer is incremented. When a value is popped, the stack pointer is decremented, and the value pointed to by the stack pointer is read. Stack underflows and overflows do not trigger errors: the stack pointer simply wraps around.
The majority of opcodes operate on the main stack (which will often be referred to as just "the stack" in this document). The main stack exclusively holds 16-bit values. Single byte values can still be read and written between the stack and main memory; they will just be converted to/from 16-bit values on the stack. The main stack can hold a maximum of 128 values.
The call stack is used for subroutine calls, and exclusively stores 16-bit addresses to main memory. vurce supports a maximum of 128 nested subroutine calls.
Device memory provides a memory-mapped interface to I/O devices. Addresses to device memory are known as "ports". I/O devices can perform tasks in response to ports being read from or written to. Often, two consecutive ports are used to hold a single 16-bit value.
vectors
A vector is a location in a program that is executed when an event occurs. The reset vector is located at address 0x0000
, and is executed when the program starts. Other vectors may be executed by I/O devices. Vectors are the primary means by which I/O events are handled, as opposed to interrupts or polling.
instructions
All instructions consist of solely a single byte opcode.
Boolean values are represented as 0x0000
for false, 0xffff
for true. This is so that boolean NOT is equivalent to bitwise NOT.
When an instruction jumps to an address, the next instruction to be executed should be the byte at that address, not the byte after. An easy way to accomplish this is to increment pc
after the opcode is read, but before it is executed.
Name | Opcode | Synopsis | Description |
---|---|---|---|
ret |
0x00 |
-- |
Returns from the current subroutine (pops from the call stack, then sets pc to that value). If not currently in a subroutine (ie. csp is 0), stops execution of the current vector. |
push |
0x01 |
-- N |
Reads the 16-bit value stored in the 2 bytes immediately following the opcode, and pushes it to the stack. Increments pc by 2, so as to skip over the immediate value. This is the only opcode which takes a postfix argument. |
dup |
0x02 |
x -- x x |
Duplicates the top stack value. |
swap |
0x03 |
x y -- y x |
Swaps the top two stack values. |
over |
0x04 |
x y -- x y x |
Takes the second value from the top and duplicates it to the top. |
rot |
0x05 |
x y z -- y z x |
Takes the third value from the top and moves it to the top. |
drop |
0x06 |
x -- |
Pops a value from the stack and discards it. |
setb |
0x07 |
x addr -- |
Writes the lower byte of x into addr . |
getb |
0x08 |
addr -- x |
Reads a byte from addr . |
set |
0x09 |
x addr -- |
Writes the 16-bit value x into addr . |
get |
0x0a |
addr -- x |
Reads a 16-bit value from addr . |
add |
0x0b |
x y -- x+y |
Push x plus y . |
sub |
0x0c |
x y -- x-y |
Push x minus y . |
mul |
0x0d |
x y -- x*y |
Push x multiplied by y . |
div |
0x0e |
x y -- x/y |
Push x divided by y . If y is 0, pushes 0. |
mod |
0x0f |
x y -- x%y |
Push x modulo y . If y is 0, pushes 0. |
and |
0x10 |
x y -- x&y |
Performs a bitwise AND operation on x and y . |
or |
0x11 |
x y -- x|y |
Performs a bitwise OR operation on x and y . |
xor |
0x12 |
x y -- x^y |
Performs a bitwise XOR operation on x and y . |
not |
0x13 |
x -- ~x |
Performs a bitwise NOT operation on x .Note that this inverts all 16 bits of x , so there may be unexpected results if you store boolean variables as single bytes. |
eq |
0x14 |
x y -- x==y |
Pushes 0xffff if x is equal to y , 0x0000 otherwise. |
neq |
0x15 |
x y -- x!=y |
Pushes 0xffff if x is not equal to y , 0x0000 otherwise. |
gt |
0x16 |
x y -- x<y |
Pushes 0xffff if x is greater than y , 0x0000 otherwise. |
lt |
0x17 |
x y -- x>y |
Pushes 0xffff if x is less than y , 0x0000 otherwise. |
jmp |
0x18 |
addr -- |
Jumps to addr (sets pc to addr ). |
jc |
0x19 |
cond addr -- |
Jumps to addr if cond is a non-zero value. |
call |
0x1a |
addr -- |
Calls the subroutine at addr (pushes pc to the call stack, then sets pc to addr ). |
outb |
0x1b |
x port -- |
Writes the lower byte of x into port . |
inb |
0x1c |
port -- x |
Reads a byte from port . |
out |
0x1d |
x port -- |
Writes the 16-bit value x into port . |
in |
0x1e |
port -- x |
Reads a 16-bit value from port . |
standard devices
In theory, one can simply implement the "vurce CPU" and map the I/O ports to whatever functions suit the implementation's desires. However, vurce has a standard set of I/O devices, described below.
The higher nybble (4 bits) of a port specify which device the port belongs to, while the lower nybble specifies the port within that device. For example, port 0x42
refers to port 0x2
of device 0x4
. Thusly, a maximum of 16 devices are supported, each holding 16 ports.
Some I/O ports may hold an address to a vector to be executed when an event occurs. In this case, the vector must not be executed if its value is 0x0000
(ie. uninitialized).
NOTE: vurce's standard devices are a work in progress. Some devices have yet to be specified, and the existing ones are subject to change.
system device (0x00-0x0f
)
The system device provides access to some generic system functionality, including interfacing with standard terminal input/output.
Port | Name | Description |
---|---|---|
0 |
stdout |
When written, write the given character to stdout. |
1 |
stdin |
When read, read a character from stdin. Reads from stdin may be blocking. |
2-3 |
printd |
When written, prints a value to the console as an unsigned decimal integer. Mostly intended for debugging and convenience. |
screen device (0x10-0x1f
)
The screen device provides an interface to a graphics display with a resolution of 240x180 pixels and 216 possible colors. Graphics drawing is accomplished primarily by copying rectangles of pixel data from main memory onto the framebuffer.
The pallete of 216 colors is the same as the "web-safe" color pallete: each red, green, and blue value can be from 0 to 5. Each pixel is represented by a single byte, encoded by the formula: r*36 + g*6 + b
. Eventually I'll make a reference image for how colors are encoded, but for now, there's this very hard to read image from a different VM which uses the same scheme.
Coordinates follow the common convention in which x=0,y=0 is the top left of the screen, with positive X going rightwards and positive Y going downwards.
Port | Name | Description |
---|---|---|
0-1 |
vector |
Stores a vector which is executed before drawing each frame, at a rate of 60hz. |
2 |
x |
Stores the X position of the rectangle to copy to. |
3 |
y |
Stores the Y position of the rectangle to copy to. |
4 |
width |
Stores the width of the rectangle to copy to. |
5 |
height |
Stores the height of the rectangle to copy to. |
6 |
color |
Stores the color to be used by a drawing operation. |
7-8 |
source |
Stores the address of pixel data to be copied to the specified rectangle. |
9 |
cmd |
When written, executes a drawing operation (see below). |
The possible drawing operations are:
Command | Description |
---|---|
0x00 |
Fills the rectangle specified by x y width height with the color color . |
0x01 |
Copies pixels from source into the rectangle specified by x y width height .The implementation must ensure that pixels which go outside the edges of the screen are not drawn in any way, and that sprites do not appear distorted when going past the edges. If the pixel value 0xff is encountered, no pixel at all is drawn to the screen, allowing sprites to have masked backgrounds. |
0x02 |
Copies a bitmap from source with the color color into the rectangle specified by x y width height .This is the same as the previous command, except that values are read one bit at a time (from most to least significant) instead of one byte at a time. If a bit is 1, color is written to that pixel, otherwise nothing is written at all (ie. the background is "transparent"). |
audio device (0x20-0x2f
)
The audio device will eventually allow programs to play audio in some way. However, it does not yet exist.
keyboard device (0x30-0x3f
)
The keyboard device provides an interface to a keyboard. The keyboard device only recognizes a reduced set of often-used keys. The implementation must ignore any key presses/releases involving an unsupported key.
Port | Name | Description |
---|---|---|
0-1 |
vector |
Stores a vector which is executed when a key is pressed/released. |
2 |
key |
Stores the keycode that was just pressed or released. Bit 7 (the leftmost, most significant bit) will be 1 if the key was released, or 0 if the key was pressed. |
Each key is assigned a 7-bit code (0x00-0x7f
). Any key which types a printable ASCII character (codes 0x20-0x7e
) is assigned the same code as the ASCII character it types (without shift or caps lock active). Other keys are assigned the following codes:
Code | Key |
---|---|
0x01 |
Up arrow |
0x02 |
Down arrow |
0x03 |
Left arrow |
0x04 |
Right arrow |
0x05 |
Shift (either key) |
0x06 |
Caps lock |
0x07 |
Control (either key) |
0x08 |
Backspace |
0x09 |
Tab |
0x0d |
Return/Enter |
0x10 |
Alt (either key) |
0x1b |
Escape |
mouse device (0x40-0x4f
)
The mouse device provides an interface to a mouse. The mouse device only recognizes the left, right, and middle mouse buttons. The implementation must ignore any presses/releases involving unsupported mouse buttons.
Port | Name | Description |
---|---|---|
0-1 |
vector |
Stores a vector which is executed when the mouse is moved, a mouse button is pressed, or the mouse's scroll wheel is moved. |
2 |
x |
Stores the X position of the mouse. |
3 |
y |
Stores the Y position of the mouse. |
4 |
buttons |
Stores a bitmap encoding which mouse buttons are being pressed. A bit is 1 if that button is pressed, 0 if not. Bit 0 (the least significant bit) is the left button, bit 1 is the right button, bit 2 is the middle button. |
5 |
scrollx |
Stores the amount that was just scrolled horizontally, as a signed integer. Positive is to the right, negative is to the left. This port is reset to 0 after the mouse vector has been executed. |
6 |
scrolly |
Stores the amount that was just scrolled vertically, as a signed integer. Positive is upwards, negative is downwards. This port is reset to 0 after the mouse vector has been executed. |
disk device (0x50-0x5f
)
The disk device may eventually provide an interface to some sort of sandboxed long-term storage. However, it does not yet exist.
time device (0x60-0x6f
)
The time device allows programs to read the current local date and time.
Port | Name | Description |
---|---|---|
0-1 |
year |
When read, returns the current year. |
2 |
month |
When read, returns the current month from 1 to 12. |
3 |
day |
When read, returns the current day of the month from 1 to 31. |
4 |
hour |
When read, returns the current hour from 0 to 23. |
5 |
minute |
When read, returns the current minute from 0 to 59. |
6 |
second |
When read, returns the current second from 0 to 60. |
7 |
weekday |
When read, returns the current day of the week from 0 to 6, where 0 is Sunday. |