diff --git a/.gitignore b/.gitignore index 6774fd6..40f21f1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ # ---> N2T tools/bin/Hardware Simulator.dat +tools/bin/CPU Emulator.dat # ---> Python # Byte-compiled / optimized / DLL files __pycache__/ diff --git a/README.md b/README.md index eb9500e..2898952 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,94 @@ # N2T -Nand to Tetris solutions building a general-purpose computer from first -principles. +This repository contains my solutions for Nand to Tetris. +[N2T](https://www.nand2tetris.org/) is a course that teaches how +to build a fully functioning general-purpose computer from first +principles. It starts with the hardware design, including its own +instruction set for which you program an assembler. You finish the +course with a two-step compiler that translates a high-level programming +language called Jack into assembly code. + +Even though I have a solid understanding of microcontrollers based on my +experience in the embedded industry, I still learned a couple of new concepts +and improved my general knowledge of how a computer works. Going through all +the steps to build a computer starting from first principles helps to facilitate +a deep understanding. If you are a person that needs hands-on examples to grasp +a concept, you will love this course as much as I do. + +In the following, I explain how to use my solutions, mainly if I want to revisit +this class later. If you haven't done the course yet, you should not look at the +answers, but you can try to play the game I wrote in the Jack programming +language. It is not a game but a 1D cellular automaton simulator. + +## Project 1: Boolean Logic + +In this project, we start building the basic gates required for the computer. +Our basic building block is a Nand gate from which we make 15 additional gates. +We implement the gates in a simplified hardware description language (HDL). To +test the HDL files, run `./tools/HardwareSimulator.sh` and open one of the test +scripts located in `./projects/01`. + +## Project 2: Boolean Arithmetic + +Based on the previous projects' basic gates, we build arithmetic chips: +a half-adder, a full adder, a 16-bit adder, and a 16-bit incrementer +based on the simple adders. Finally, we create the ALU (arithmetic-logic +unit), which is the heart of the CPU that we make in the later projects. +The ALU takes two 16-bit inputs and computes an output depending on a +couple of control-bits' status. To test the arithmetic chips, use the +hardware simulator equally to the first project. + +## Project 3: Memory + +Till this point, all gates are stateless. To build a computer, we need +memory. For this purpose, the course introduces a DFF (data flip-flop). +We can create a one-bit register and build up from there to a 16k chip +with the DFF. + +It found it rewarding to build memory from first principles, but even more +rewarding was how easy Vim makes it to write the HDL code for these chips. The +following picture shows how I create a 64-bit register from 8-bit registers in a +matter of seconds. ![Create HDL for 64-bit RAM in Vim](gifs/vim_ram64.gif) + +## Project 4: Machine Language Programming + +In project 4, we get familiar with the Hack machine language - our +computer's assembly language. We write two basic projects: fill the +screen when the user presses a button, and a second one that +multiplicates two input arguments. To try the scripts, start the CPU +emulator by executing `./tools/CPUEmulator.sh`. You can then open the +script located in `./projects/04/fill` or `./projects/04/mult`. + +I have created a Vim syntax file for the Hack assembly language. Copy the file +`hackasm.vim` from the vim directory into your Vim installation's syntax +directory. You can then set the filetype to hackasm by running `:set ft=hackasm` +from within Vim, and you should see highlighting as shown on the following +screenshot. + +![Hack Asm syntax highlighting in Vim](./gifs/vim_hack_syntax.png) + +## Project 5: Computer Architecture + +In this project, we assemble all prior building blocks into the main memory, +CPU, and finally into the full hack computer. There are test scripts similar to +project one to three to validate that the computer works as designed. Seeing it +all come together is incredibly rewarding. Even if you stop the course at this +point, you have developed a great intuition of how a computer works. + +## Project 6: The Assembler + +With the computer working, we now need a way to assemble the hackasm code into +machine instructions. The purpose of this project is to build the assembler in +the programming language of our choice. + +My Python version has 203 lines of code and relies on Python 3.8 features. We +can test the assembler by changing the directory to `./projects/06` and then +running `python assembler.py pong/*.asm`. Note that my assembler can only +translate individual asm-files and does not search a directory. + +Load the resulting hack file into the CPU emulator to verify that the +assembler works correctly. + + diff --git a/gifs/vim_hack_syntax.png b/gifs/vim_hack_syntax.png new file mode 100644 index 0000000..35e8cbd Binary files /dev/null and b/gifs/vim_hack_syntax.png differ diff --git a/projects/06/assembler.py b/projects/06/assembler.py index 6379838..2f25849 100755 --- a/projects/06/assembler.py +++ b/projects/06/assembler.py @@ -4,7 +4,6 @@ import sys import re - def preprocess(lines): lines = remove_whitespaces(lines) lines = replace_symbols(lines) @@ -36,7 +35,7 @@ def replace_symbols(lines): "R15": "15", "SCREEN": "16384", "KBD": "24576", - } + } # Find all labels, remove them, and add them to symbol table. address = 0 @@ -116,6 +115,7 @@ def assemble_c_instruction(line): ins_str = "111" + comp_lookup(comp) + dest_lookup(dest) + jump_lookup(jump) return ins_str + def comp_lookup(comp_str): return { "0": "0101010", @@ -176,26 +176,28 @@ def jump_lookup(jump_str): if __name__ == "__main__": - try: - hack_asm_file = sys.argv[1] + if not sys.argv[1:]: + sys.exit("Call: ./assembler.py ") + + for hack_asm_file in sys.argv[1:]: if not hack_asm_file.endswith(".asm"): sys.exit("Hack asm file must have a .asm file ending.") hack_asm_ns_file = hack_asm_file.replace(".asm", ".nosymbol.asm") hack_bin_file = hack_asm_file.replace(".asm", ".hack") - except IndexError: - sys.exit("Call: ./assembler.py ") - with open(hack_asm_file, 'r') as f: - assembly_lines = f.readlines() + with open(hack_asm_file, 'r') as f: + assembly_lines = f.readlines() - preprocessed_lines = preprocess(assembly_lines) - binary_lines = assemble(preprocessed_lines) + preprocessed_lines = preprocess(assembly_lines) + binary_lines = assemble(preprocessed_lines) - with open(hack_asm_ns_file, 'w') as f: - for line in preprocessed_lines: - f.write(line + "\n") + with open(hack_asm_ns_file, 'w') as f: + for line in preprocessed_lines: + f.write(line + "\n") - with open(hack_bin_file, 'w') as f: - for line in binary_lines: - f.write(line + "\n") + with open(hack_bin_file, 'w') as f: + for line in binary_lines: + f.write(line + "\n") + + print(f"{hack_asm_file} -> {hack_bin_file}") diff --git a/vim/hackasm.vim b/vim/hackasm.vim new file mode 100644 index 0000000..7c639e8 --- /dev/null +++ b/vim/hackasm.vim @@ -0,0 +1,77 @@ +if exists("b:current_syntax") + finish +endif + +" Computation instructions +syntax match hackasmFunction "\v0" +syntax match hackasmFunction "\v1" +syntax match hackasmFunction "\vM" +syntax match hackasmFunction "\vD" +syntax match hackasmFunction "\vA" +syntax match hackasmFunction "\v-" +syntax match hackasmFunction "\v\+" +syntax match hackasmFunction "\v\|" +syntax match hackasmFunction "\v!" +syntax match hackasmFunction "\v\&" + +" Jump instructions +syntax match hackasmConditional "\v;null" +syntax match hackasmConditional "\v;JGT" +syntax match hackasmConditional "\v;JEQ" +syntax match hackasmConditional "\v;JGE" +syntax match hackasmConditional "\v;JLT" +syntax match hackasmConditional "\v;JNE" +syntax match hackasmConditional "\v;JLE" +syntax match hackasmConditional "\v;JMP" + +" Assignment instructions +syntax match hackasmOperator "\vnull\s*\=" +syntax match hackasmOperator "\vM\s*\=" +syntax match hackasmOperator "\vD\s*\=" +syntax match hackasmOperator "\vMD\s*\=" +syntax match hackasmOperator "\vA\s*\=" +syntax match hackasmOperator "\vAM\s*\=" +syntax match hackasmOperator "\vAD\s*\=" +syntax match hackasmOperator "\vAMD\s*\=" + +syntax match hackasmNumber "\v\@[0-9]+" + +syntax match hackasmIdentifier "\v\@[a-z][a-z_]*" + +syntax match hackasmLabel "\v\@[A-Z][A-Za-z0-9:._]*" +syntax match hackasmLabel "\v\([A-Z][A-Za-z0-9:._]*\)" + +syntax match hackasmError "\v\t+" +syntax match hackasmError "\v\([A-Z].*[^A-Za-z0-9:._].*\)" + +syntax match hackasmError "\v\@[A-Z]+[^A-Za-z0-9:._].*" +syntax match hackasmError "\v\@[a-z]+[^a-za-z0-9:._].*" + +" Computation instruction errors +syntax match hackasmError "\vM\s*\+\s*D" +syntax match hackasmError "\vA\s*\+\s*D" +" TODO Missing invalid computation instructions + +syntax match hackasmKeyword "\v\@R[0-9]" +syntax match hackasmKeyword "\v\@R1[0-5]" +syntax match hackasmKeyword "\v\@SCREEN" +syntax match hackasmKeyword "\v\@KBD" +syntax match hackasmKeyword "\v\@SP" +syntax match hackasmKeyword "\v\@LCL" +syntax match hackasmKeyword "\v\@ARG" +syntax match hackasmKeyword "\v\@THIS" +syntax match hackasmKeyword "\v\@THAT" + +syntax match hackasmComment "\v//.*$" + +highlight link hackasmError Error +highlight link hackasmComment Comment +highlight link hackasmFunction Function +highlight link hackasmConditional Conditional +highlight link hackasmOperator Operator +highlight link hackasmKeyword Keyword +highlight link hackasmNumber Number +highlight link hackasmIdentifier Identifier +highlight link hackasmLabel Label + +let b:current_syntax = "hackasm"