I found project 8 to be the most difficult so far (by a large margin), and when I was near completing it I found this cool trick. One of the hardest parts about debugging it is that if you run the .vm file(s) in the VME, each "step" does not correspond with one step in your ASM file. So if you have the ASM file open in the CPU emulator, and the .vm file open in the VME, you have to click "step" many times in CPU emu (i.e. anywhere from 5x - 50x) to match every single "step" you click in the VME (the reason you'd want to keep the CPU emu and VME in sync is to watch all your RAM locations and see where your problem is occurring).
So, the trick is that you have your asmWriter program add something like this at the beginning of every new .vm line that it translates:
The idea is that 7777 is a RAM location you know is not being used for anything. Now in the CPU emulator you can set RAM=0 as a debug breakpoint, and every time you hit the "fast forward" button in the CPU emu, it corresponds with one "step" in the VME. Helped me a lot, hope it helps someone else!
I agree that the VM is the hardest project in the book. There are many new concepts and you need to learn the Hack architecture and instruction set in great detail.
I learned programming in the days of mainframes, punch cards, and line printers. By reflex, I wrote my Hack assembler to generate a listing file:
// This file is part of the materials accompanying the book
// "The Elements of Computing Systems" by Nisan and Schocken,
// MIT Press. Book site: www.idc.ac.il/tecs
// File name: projects/06/max/Max.asm
// Computes M = max(M, M) where M stands for RAM
0 0 @0
1 FC10 D=M // D=first number
2 1 @1
3 F4D0 D=D-M // D=first number - second number
4 10 @OUTPUT_FIRST
5 E301 D;JGT // if D>0 (first is greater) goto output_first
I have my VM translator write the VM source lines as comments in the ASM output. Armed with the listing file generated by assembling my VM output I could quickly find the generated code by address, and set breakpoints as needed.
It would have been much quicker to use your trick.
It might be handy to include the VM source line number to make it easier to relate ASM and VM code:
Though I like the look of your asm file, pretty nifty with the line numbers and hex codes all neatly aligned!
Also, if you're sure it's unused, then 255 can be used in place of 7777 and you will always be looking at the bottom of your stack (rather than have to scroll through 7000 lines to get back to the RAM that matters, since it focuses on a RAM location every time it modifies it).
You can actually use a simpler technique. Just set a breakpoint on the value of A. So you just need one line to set a break point, for example:
So instead of setting a breakpoint when a specific RAM location gets a certain value, just set it when A gets a specific value. It is less intrusive (a single line per break point), and you do not get the "jumping" back and forth in the emulator.
// <VM filename>:<source file line #> <text of VM source line> <line number seen in CPU emulator> : @<incrementing break number> // <help text> <line #> : <ASM blob for this VM line> <line #> : <ASM blob for this VM line> .... etc etc
Break number increments per VM->ASM blob (aka each source VM line), so breaking on A=# will stop before that instruction blob is executed. Just copy and paste the break number into the break window.
All of these variables are easy to track as you translate each line of VM source, ASM blob #, and output ASM line number. You can match source code to VM lines as .ASM blob lines in the CPU emulator very quickly.
I set it up so my VM translator normally returns a .VMOBJ along with the .ASM object code, and a "--debug" flag turns on the break numbers.
You just have to make sure that your .VMOBJ and .ASM files are generated at the same time! :)
FYI: my version of the VMOBJ file is just a helper and can't be assembled by itself, unless you strip out the line numbers
The output of all of my tools embedded comments in the output code that the subsequent tools could access (if it was there) and was also human-readable. These comments gave put sufficient information into the final assembly file that you could actually reconstruct the source code (including comments) as well as see which address each assembly instruction ended up at. This made both debugging the tools and the code written using them MUCH easier. Organizing it so that it wasn't a mish-mash took some thought, but the process is so inherently hierarchical that it wasn't that bad.