Indie game storeFree gamesFun gamesHorror games
Game developmentAssetsComics
SalesBundles
Jobs
Tags

Jack/Nand2Tetris VM inside Chip-8 (XO-Chip)

A topic by Josh Goebel created Sep 24, 2022 Views: 141 Replies: 1
Viewing posts 1 to 2
(2 edits)

For anyone interested I’ve been working on a Jack VM (16-bit) to run into Octo. When reading the stack source linked from this years jam I got the idea of building my own automated testing suite as well… and this is working out MUCH better than I ever thought… you just add tests cases and they are auto-magically registered with the test runner…

Ref: https://www.nand2tetris.org/

Here is an example of the kind of thing I have working so far:

test "simple add"
	vm_start
		o_push_constant 1 # 0x80 0x00 0x01
		o_push_constant 2 # 0x80 0x00 0x02
		o_add # 0x04
	vm_stop

	pop_into_reg vC # returns a 16 bit value in vC-vD
	assert_equal vC 0
	assert_equal vD 3
	success
;

Note, that vm_start and stop wire up the VM runner… so o_* instructions are NOT generating CHIP-8 code, they are generating the binary shown - Jack VM bytecode. The course itself never gets into defining bytecode for the Jack VM - but it wasn’t too hard to conceptualize. And of course we’re adding our own opcodes as well so that we still have low-level access to things like clear, etc… The Jack VM is stack based, so our opcodes vary from 1-4 bytes (mostly 1 or 2). With things like function dispatch being the most complex and ops like add being a single byte.

vm_start preserves the address, pushes the org forward 2 bytes (to save room to a jump we’ll insert later to skip over the VM code and jump past vm_stop… then vm_stop inserts a o_halt opcode (that instructs the VM to stop and return) and then sets the VM’s PC to the start of the mini-program and then boots it. Oh, it also quickly goes back and adds the jump now that we know where we need to jump TO.

So now each time a test hits a VM block the VM fires up, runs the code, and then we test the results to verify correctness. And of course there will be a tiny little UI for this as well.

The overall idea is we fit the VM engine into the first 4.25kb of RAM (we have a jump opcode table at the end of 4k for opcode dispatch) and then that leaves 60kb or so for program code (since it’s now abstracted by the VM). Right now I have most of the Jack “basic” opcodes implemented (other than flow control) along-side my test suite and I’m sitting at 2.6kb of low RAM used - so I think this doable.

I still need to figure out:

  • delay
  • sound
  • inputs (memory mapped?)
  • sprites

Step two would be writing (or repurposing) a Jack compiler in JavaScript to generate Octo flavored Jack bytecode… even better would be pairing it with an editor so all this was available on a simple web page where you could write a Jack program, compile it, paste it into Octo and run it. :-) A “compiled” program would just be a blob of binary/data + the VM runtime.

If anyone is interested (or wants to help) I’d be happy to share more or answer questions.

(1 edit)

And if you’ve never heard of Jack it’s a simple high-level language syntactically similar to C or Java:

class Main {
 function void main(){
   var Fraction a, b, c;
   let a = Fraction.new(2,3);
   let b = Fraction.new(1,5);
   let c = a.plus(b); // compute c=a+b
   do c.print(); // should print the text: “13/15”
   return;
 }
} 

It has has classes, objects, arrays, booleans, looping constructs, pointers, etc…