~micklemeal/taro

A 16-bit virtual machine written in Rust.
Added pc increment, quick test in sprite_edit.asm.
Added cpy and set ops.

refs

main
browse  log 

clone

read-only
https://git.sr.ht/~micklemeal/taro
read/write
git@git.sr.ht:~micklemeal/taro

You can also use your local clone with git send-email.

#Taro

A 16bit virtual stack machine.

#Inspiration

This project is largely inspired by (and very similar to) uxn. Some key differences include a different set of assembler mnemonics, more limited instruction set, and larger data types in general (RAM is 24 bit addressable vs. 16 bit addressable in uxn, the stack operates on 16 bit values vs. 8 bit values in uxn). Another significant inspiration is PICO-8.

#TODO

  • Build sprite editor
  • Add audio system
  • Add UDP system

#Taroasm Assembler

#Invoking

taroasm [<rom/sym output name>]

If a rom name is provided, a binary rom will be created with that name. Additionally, a symbol file will be created with ".sym" added to the name.

#Data Format

All values are treated as little endian unsigned integers. The data stack and call stack both have 16 bit cells. Addresses are 24 bits and will occupy two cells. The top 8 bits are ignored.

#Directives

.<label>          define label
,<sublabel>       define sublabel
@<address>        set current address
;<number>         insert literal byte
:<number>         insert literal word
!<label>          insert label address (3 bytes)
#<color>          insert 24-bit rgb translated to 15-bit rgb word (16-bit word, ignores high bit)
%<name>{ <ops> }  define macro (invokable by name)

#Ops

123         ( -- 123 ) decimal
0x123       ( -- 0x123 ) hexadecimal
&<label>    ( -- lo hi ) push label value to data stack
DROP        ( a -- ) 
DUP         ( a -- a a )
SWP         ( a b -- b a )
ROT         ( a b c -- b c a )
OVR         ( a b -- a b a )
<RTS        ( a -- ) to return stack
>RTS        ( -- a ) from return stack
+           ( a b -- a+b )
-           ( a b -- a-b )
*           ( a b -- a*b )
/           ( a b -- a/b )
AND         ( a b -- a&b )
OR          ( a b -- a|b )
XOR         ( a b -- a^b )
SHL         ( a b -- a<<b) 
BRK         stop execution
JMP         ( lo hi -- ) jump to address
JMP?        ( a lo hi -- ) jump to address if a > 0 
JSR         ( lo hi -- ) jump to address and put current address + 1 on return stack
RET         remove address from top of call stack and jump there
EQ?         ( a b -- a==b ) 
NEQ?        ( a b -- a!=b )
GT?         ( a b -- a>b )
LT?         ( a b -- a<b )
LW          ( lo hi -- a ) load word (16 bits) at address to data stack
LB          ( lo hi -- b ) load byte at adress to data stack
SW          ( lo hi a -- ) store a to two bytes starting at address
SB          ( lo hi a -- ) store a to byte at address
IN          ( port -- ? ) perform in operation on port
OUT         ( port -- ? ) perform out operation on port
CPY         ( from to num ) copy num bytes from one address to another
SET         ( addr val num ) set num bytes to val starting at address

#Ports

Ports are how taro communicates with entities outside of the virtual machine. The IN/OUT ops take the top of the stack, consuming it, and using it to determine which port to talk to. The entity is able to manipulate taro's state completely, so it is important to know how that entity communicates.

#Console

Console writes/reads from the console. It's default port # is 0.

#Out Examples
( lo hi 1 0 -- )  Print string at address
( bool 2 0 -- )   Enable/disable debug printing
( 3 0 -- )        Clear the screen
( len lo hi 4 0 -- ) Print the len bytes from ram at lo hi
#In Examples
( max lo hi 1 0 -- chars_read ) Read up to max chars from user and place at address in ram.

#Random

Random provides rng services. It's default port # is 1.

#In Examples
( 0 1 -- n )  Get a random u16.
( lo hi 1 1 -- n ) Get a random u16 n s.t. lo < n < hi

#Filesystem

Filesystem provides services for reading and writing files. It's default port # is 2. There may only be one file open at a time.

#In Examples
( lo hi 1 2 -- b ) Open file with name stored at lo hi for reading/writing, b is success indicator
( x lo hi 2 2 -- n ) Read x bytes from file to address lo hi in ram, n is number of bytes read
( n 3 2 -- ) Seek n bytes into the file from the current position
( n 4 2 -- ) Seek n bytes backwards in the file from the current position
( 5 2 -- ) Seek to beginning of file
#Out Examples
( 1 2 -- ) Close the currently open file
( b lo hi 2 2 -- n ) Write b bytes from lo hi in ram to the file, n is number of bytes written

#Graphics

Graphics provides pixel rendering services. It's default port # is 3. The screen size is 320 pixels wide and 200 pixels high. The screen is refreshed at a 60hz rate.

#Palette

Colors are palettized, with 15 possible colors plus one additional background color. Palette item 0 is the background color, and is rendered to the background every frame. Palette items are specified by 16-bit words, as 15-bit rgb, with the high bit ignored.

#Sprites

The user may define 128 sprite definitions, and 128 sprites which may utilize those definitions. Each sprite may be up to 64x64 pixels. However, the size must be divisible by two. Each pixel in a sprite definition is represented as an index to the palette, with 0 being ignored. Sprite definitions are read from memory after the appropriate call (see examples below). A sprite definition in memory looks as follows:

[sprite def id        ] 8 bits
[width                ] 8 bits
[height               ] 8 bits
[pixel 0 palette index] 4 bits
[pixel 1 palette index] 4 bits
...
[pixel n palette index] 4 bits

A sprite is read from memory after the appropriate call (see examples below). A sprite in memory looks as follows:

[sprite def id to use] 8 bits
[x position          ] 16 bits
[y position          ] 16 bits
[vertical flip       ] 8 bits
[horizontal flip     ] 8 bits
[visible             ] 8 bits
#In Examples
( lo hi 1 3 -- ) set routine at address lo hi to be triggered every frame before screen refresh (routine must end with BRK)
#Out Examples
( col 1 3 -- ) set background color (set 0 palette color )
( lo hi 2 3 -- ) set palette by reading 32 bytes at address lo hi
( lo hi n 3 3 -- ) set sprite definitions by reading n sprite definitions at address lo hi
( lo hi n 4 3 -- ) set sprite n to have properties specified at address lo hi

#Mouse

Mouse provides information about the user's mouse. It's default port is 4. Coordinates retrieved may be outside of the screen, but are always unsigned 16 bit values. For instance, if the mouse is left of the window, the amount returned for x would be 65536 - (x distance from window).

#In Examples
( 1 4 -- x y lb rb ) retrieve mouse position and wether the left or right button is pressed
#Out Examples
( lo hi 1 4 -- ) set callback for mouse updates (motion and buttons)

#Keyboard

Keyboard provides keyboard information. It's default port is 5. The user may read which keys have been pressed and set a routine to be executed on keypress. Numeric keys, letter/symbol keys, control, shift, alt, capslock, enter, backspace, space, escape, and arrow keys are supported.

#Keycodes

When reading the keyboard, the stack is loaded as follows:

[ mod flags (ctrl, shift, arrows etc. )        ]
[ n: count of keys appearing next on the stack ]
[ key n utf8 value                             ]
[ key n-1 utf8 value                           ]
[ ....                                         ]
[ key 0 utf8 value                             ]

The mod flags are as follows (lsb to msb):

Keyname       |  Bit
---------------------
UP            |  0
DOWN          |  1
LEFT          |  2
RIGHT         |  3
LEFT CONTROL  |  4
RIGHT CONTROL |  5
LEFT ALT      |  6
RIGHT ALT     |  7
LEFT SHIFT    |  8
RIGHT SHIFT   |  9
ENTER         | 10
SPACE         | 11
TAB           | 12
CAPS LOCK     | 13
BACKSPACE     | 14
ESCAPE        | 15
#Out Examples
( lo hi 1 -- ) Set callback for keyboard events to the address at lo hi.
#In Examples
( 1 -- key0 key1 key2 ... keyn n modflags ) Read keyboard