A small 16bit Virtual Machine with assembler to create small programs or games
Memory Visualization
Example of 'Hello, World' in Pixel language
;************************************
; Program: Hello, World!
;************************************
include print
clear | jmp [main]
:var "Hello, World!"
:main
push #20
push #20
push [var]
call [@print]
end
Instructions
Function | Parameters | Description
--------------------------+---------------------------------------------+---------------------------------------------------
push | (#<num>|0x<hex>|[label]) | Loads value onto the stack, labels will translate as label address
pop | no params | Pops value from the stack
load | ([label]) | Loads variable onto the stack
store | ([label]) | Pops value from stack and stores to variable
storev | ([label]) (#<num>|0x<hex>) | Stores value to variable
jmp | ([label]) | Jumps to label location
call | ([label]) | Call procedure
return | no params | Returns from procedure
rsp | no params | Reset stack pointer to zero
add | no params | Performs an addition of the two stack values
sub | no params | Performs a subtraction of the two stack values
mul | no params | Performs a multiplication of the two stack values
div | no params | Performs a division of the two stack values
mod | no params | Performs a modulus of the two stack values
inc | ([label]) (#<num>|0x<hex>) | Increases variable by given number
dec | ([label]) (#<num>|0x<hex>) | Decreases variable by given number
and | no params | Performs a bitwise AND of the two stack values
or | no params | Performs a bitwise OR of the two stack values
xor | no params | Performs a bitwise XOR of the two stack values
shr | no params | Performs a bitwise shift right of the two stack values
shl | no params | Performs a bitwise shift left of the two stack values
stov | ([label]) | Stores overflow value to variable
addressOf | ([label]) | Pushes memory address of label onto the stack
writeAt | ([label]) | Writes stack value to memory address
readAt | ([label]) | Reads memory address and pushes value onto the stack
seed | (#number) | Number used to calculate a starting value for the pseudo-random number sequence (0 = default)
random | (#<num>|0x<hex>) | Pushes random number, starting from 0 and the parameter defines max range
if | [label] (#number) | Condition test x == y, executes next instruction if true
ifn | [label] (#number) | Condition test x != y, executes next instruction if true
ifg | [label] (#number) | Condition test x > y, executes next instruction if true
ifl | [label] (#number) | Condition test x < y, executes next instruction if true
ifv | [label] [label] | Condition test x == y, executes next instruction if true
ifnv | [label] [label] | Condition test x != y, executes next instruction if true
ifgv | [label] [label] | Condition test x > y, executes next instruction if true
iflv | [label] [label] | Condition test x < y, executes next instruction if true
draw | [x][y][label] | Draw sprite to vram from defined label at x and y
print | [x][y][index][label] | Writes character index to vram from label as string with x and y coords
printv | [x][y][var] | Writes numeric value of a variable as string, same as 'print' without the index
stcol | ([label]) | Stores collision value to variable (1 or 0)
strlen | ([label])([var]) | Stores string length in variable
strcmp | ([string])([string]) | Performs a condition test string == string, executes next instruction if true
input | ([label]) | Stores keystroke value to variable
clear | no params | Clears vram memory (blanks screen)
end | no params | Terminates execution of program
--------------------------+---------------------------------------------+---------------------------------------------------
_drawa | [x][y][address] | Internally used opcode for drawing a sprite given by address
_print | [x][y][address] | Internally used opcode for printing a string given by address
_printv | [x][y][address] | Internally used opcode for printing a variable number given by address
_strla | [address][variable] | Internally used opcode for obtaining a string length given by address
--------------------------+---------------------------------------------+---------------------------------------------------
include | <print|common> | Import build-in functions
The 'if' and 'strcmp' type statements assume the skipped instruction to be 3 bytes, this means instructions that take up more bytes will not work.
The following instructions do not work with if/strcmp -statement:
- printv
- draw
- inc/dec
- if-types
- strcmp
- storev
Pixel has build-in functions that simplify the making of certain procedures, these functions are included into the user assembly.
[@print] Simplified version of the print.
Params: x, y, [address]
include print
:string "Hello!"
push #10
push #10
push [string]
call [@print]
[@printv] Simplified version of the printv.
Params: x, y, [address]
include print
:number 100
push #10
push #10
push [number]
call [@printv]
[@sleep] Delays the current procedure by given number of cycles.
Params: int
include common
push #60
call [@sleep]
[@scroll] Scrolls display to specified direction and steps
Params: direction steps
include common
push #0
push #1
call [@scroll]
Sprites can be made from a binary array that is 8x6, the format is shown below.
jmp [program]
:x 10
:y 10
:program
draw [x][y][letter_A]
end
:letter_A
.00011000
.00100100
.01000010
.01111110
.01000010
.01000010
This would produce the letter 'A' drawn on the screen at position variables x and y
Variables can be defined with labels followed by a number, the compiler assumes an UInt16 value. The value of a variable must be in straight forward format, no hexadecimals or such.
:program
load [foo]
inc [foo] #5
end
:foo 5
Pseudo code:
declare foo = 5
foo += 5
exit
String data is decoded by the assembler into font address values, the 'print' instruction can then enumerate for each character index and draw them on the given x and y position.
include print
:foo "Hello!"
:program
push #10 ;push x
push #10 ;push y
push [foo] ;push address
call [@print] ;call built-in print function
end
This process can also be achieved manually by doing the following
:x 10 ;variable block
:y 10
:i 0
:len 0
:str "Hello!"
:program
strlen [str][len] ;store string length
:loop
if [i][len] end ;condition test for length, if reached, end program
print [x][y][i][str] ;print out character on i in string
inc [i] #1 ;increase index
inc [x] #6 ;increase x
jmp [loop] ;loop back...
In order to print out a value of a variable we use a different print command called printv
include print
:number 100
:program
push #10
push #10
push [number]
call [@printv]
end
This process can also be achieved manually by doing the following
:x 10
:y 10
:number 100
:program
printv [x][y][number]
end
Keys can be defined with { and }, the assembler will convert the keys to memory address values that corresponds with an address offset within the memory data. The format is the same as defining variables except you set your keys between the braces.
The instruction 'key [var]' will store the current keystroke that is equal to the defined key variable. If no keystroke was recorded, it will store a 0 instead.
Supported characters:
abcdefghijklmnopqrstuvwxyz
1234567890 +-*/[|]!?,.:<>\'↑↓←→
Example of a simple key hook
:program
input [key]
if [key] [exit] end
jmp [program]
:exit {Q}
:key 0
Psuedo code:
declare exit = key{q}
declare input = 0
do
input = key_press()
if (input == exit) { exit }
Loop