I ported the Nand to Tetris Emulator to the Web

classic Classic list List threaded Threaded
16 messages Options
Reply | Threaded
Open this post in threaded view
|

I ported the Nand to Tetris Emulator to the Web

Arch
This post was updated on .
I am proud to present my solo hobby project NAND. This year-long undertaking follows the completed Nand to Tetris course, but ported to the web with its own runtime, user interface, and IDE. You can try out some example programs I wrote in Jack on NAND such as 2048, a genetic algorithm, and a manual stack overflow to corrupt the screen.

Check out NAND at https://nand.arhan.sh and its repository at https://github.com/ArhanChaudhary/NAND

Additionally, I've authored an extensive writeup about the project. Read about it on the README.
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

dolomiti7
Wow, great work! Really well done!

I see that you are also using compressed constants for the charset to reduce the size of Output - so I'm not the only one doing this :-) In addition to packing bits of multiple characters into a constant, I am using a simple RLE encoding. Depending on the type of encoding, I ended up with 285 or 315 constants for the whole charset (one version for "normal" translation and one version which works better with threaded code. If I see it correctly, you are using 96*4=384 constants instead of the 96*12=1152 constants of the original implementation, which gives you already a significant reduction in size.

One thing I don't understand: why are you poking a value into the Keyboard I/O address on Sys.halt? I guess that is for debugging purposes, but shouldn't that address be read-only by default (and consequently that RAM address shows still 0 even after the halt)?

Other than that: I also used green on black for my own emulator as default, though you have to be aware that it implicitly inverses the Hack specification. I only realized that with the Chess program, where the board and piece colors were flipped... Perhaps you could add a color select option.

And finally: if there was an option to load ASM or BIN code directly into the emulator, this would become my favorite nand2tetris IDE ;-)
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

Arch
I'm poking the value 32767 into the Keyboard address on Sys.halt as a poor-man's flag to tell the runtime when to stop. The runtime polls for this value at this memory address, and I didn't want to introduce another memory address to remain spec-compliant. After the halt, it's instantly set back to 0 when this flag is enabled to reset it.

I'm not sure if a color selector is really needed, but I could definitely look into an ASM or BIN loader which would be trivially easy to implement in terms of code.
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

Arch
In reply to this post by Arch
Also, woohoo for Nand to Tetris! My project was #1 on Hacker news for half a day and the repository has gained over 300 stars. I'm so grateful for this course and its opportunities
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

Renslay
In reply to this post by Arch
That is a marvelous emulator! Love it <3 Excellent job!
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

Arch
In reply to this post by dolomiti7
Awesome news!

I had a bit of free time to spare. I implemented exactly that into https://nand.arhan.sh/ — a JACK/VM/ASM/BIN loader, along with an option to clear the RAM.

:-)
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

dolomiti7
Looks great, just tried it out. Works fine with most programs. There is a small bug (which is also in the Java based tools). The A register is updated before the jump. Therefore the following command will falsely jump to address 0:

@1234
A=0;JMP // should jump to 1234 and set A to 0 on the fly

See also here:
http://nand2tetris-questions-and-answers-forum.52.s1.nabble.com/Subtle-different-behaviors-between-the-CPU-emulator-and-the-proposed-CPU-design-tp4034781.html

In the new Web-based tools, this error has been corrected.
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

Arch
Noticed the bug, and took a look at my code. I played around with it for a while, but for the life of me I couldn't get the corrected CPU to work without making it unnecessarily complicated. I haven't touched the code in half a year, so I was a bit disoriented trying to figure out how everything worked again. xD

For now, I'll put that bug fix on hold until I'm spontaneously motivated again or someone else helps

If anyone happens to be curious, here's what I've been trying to fix https://github.com/ArhanChaudhary/NAND/blob/main/src/core/architecture.rs#L30
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

Arch
NVM, I managed to fix it :D
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

dolomiti7
I didn't have the time to work on a patch myself. Tested it just now and it works perfectly.
Great job!


Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

dolomiti7
Just found another program that doesn't work... After having a look at the code, I believe the issue is related to ADDRESS_M which is updated inside cpu before the a register is updated. Therefore the next read (of the following command) is still done with the old address. I'm actually surprised that this is working at all. Anyway, it is not possible to move the ADDRESS_M update behind the a register update either, because then a potential memory write would already go to the new A instead.

The most pragmatic solution could be to move the ADDRESS_M update completely out of cpu and inside computer() instead:

fn computer(reset: bool) {
    let (out_m, load_m) = cpu(
        memory(0, false, unsafe { ADDRESS_M }),
        memory::rom32k(unsafe { memory::PC }),
        reset,
    );
    memory(out_m, load_m, unsafe { ADDRESS_M });

    // update address line for next instruction
    unsafe {
        ADDRESS_M = slice16_0to14(alu_y1);
    }
}

I don't have an environment to test this code myself however...
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

Arch
This post was updated on .
Could you provide example assembly that doesn't work so I could debug this myself?

BTW, if you want to run the environment for yourself, execute

cargo install wasm-pack@0.13.0
npm i
npm run rwbuild
npm run dev
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

dolomiti7
The bug seems to be more subtle than I thought, and it took me a while to narrow it down. It appears to be a combination of AM= and Keyboard access. This sample code will fail:

(LOOP)
@KBD
D=A

@THAT    // just for demonstration, could be any address
0            // NOP, this doesn't help, so it is no timing issue
AM=D     // FAILS!!!
0            // NOP, this doesn't help, so it is no timing issue
//
// This would work instead of AM=D
//    M=D
//    A=D

D=M       // read KBD state
@LOOP
D;JEQ     // loop until key pressed

// The program will exit the loop immediately with
// D==24576 here ?!

@END     // halt
(END)
0;JMP


I couldn't reproduce it with any other address than KBD, so it seems it is somehow related to the keyboard access...
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

Arch
Thankfully the AM=D bug you pointed out was just a simple typo

pub fn keyboard(in_: u16, load: bool) -> u16 {
-    if load {
+    if load && unsafe { CLOCK } {
         unsafe {
             PRESSED_KEY = in_;
         }

In other news, your program inspired me to implement a step-by-step debugger into my emulator. After pausing a program during execution, click "Step" to execute one instruction at a time. I think it is a really useful feature 8)

If you don't see the feature immediately, the old version might be cached on your device; reload and it should work.
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

dolomiti7
Very nice work. I have tested it with a couple of programs and they all work fine. Another idea for additional features: For my own emulator (Java-based, local) I recently added a profiler.

On the VM level, profiling is straightforward (and has been implemented here for example), but for ASM code it is a bit tricky on the Hack platform. Specifically it is not trivial to identify if a jump was actually a call (or a return) without relying on label naming. The standard way of tackling this is to add debug information to the binary, but I wanted to be able to profile other existing Hack binaries as well. One of the challenges is for example that the final jump of the call is not necessarily done from within the caller itself, but from a global call handler instead. In the end my profiler takes various data points into account and detects the call structure quite reliably. A simple example output with the Pong.asm from project 06 is shown below (not pressing any key and just waiting until Game Over and Sys.halt has been reached).


Alternatively the output can be shown as a tree:


Please note, that the official Pong.asm doesn't follow the naming conventions and has all class names in lower case. That asm also contains loop labels which are valid function names and could potentially cause naming conflicts (another challenge for the profiler...).
Reply | Threaded
Open this post in threaded view
|

Re: I ported the Nand to Tetris Emulator to the Web

Arch
A general purpose hack profiler is a very interesting idea. I doubt I could ever figure out how to profile raw binaries. But you since you already mentioned debug info, I wanted to point out that NAND’s assembler already encodes debug info in its output to map the PC to the VM instruction in the ROM memory view. It doesn’t look like an assembly profiler for my assembler specifically would be too hard, but again an all purpose assembly profiler is a huge technical challenge.

Mostly likely, I won’t work on this for some time to focus on other technical projects. I’m definitely putting this in the backlog though.