Write documentation for first part of course

This commit is contained in:
2020-11-15 14:38:56 -05:00
parent 742db6d102
commit 16fa19d034
5 changed files with 186 additions and 18 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# ---> N2T # ---> N2T
tools/bin/Hardware Simulator.dat tools/bin/Hardware Simulator.dat
tools/bin/CPU Emulator.dat
# ---> Python # ---> Python
# Byte-compiled / optimized / DLL files # Byte-compiled / optimized / DLL files
__pycache__/ __pycache__/

View File

@@ -1,6 +1,94 @@
# N2T # N2T
Nand to Tetris solutions building a general-purpose computer from first This repository contains my solutions for Nand to Tetris.
principles. [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) ![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.

BIN
gifs/vim_hack_syntax.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@@ -4,7 +4,6 @@ import sys
import re import re
def preprocess(lines): def preprocess(lines):
lines = remove_whitespaces(lines) lines = remove_whitespaces(lines)
lines = replace_symbols(lines) lines = replace_symbols(lines)
@@ -36,7 +35,7 @@ def replace_symbols(lines):
"R15": "15", "R15": "15",
"SCREEN": "16384", "SCREEN": "16384",
"KBD": "24576", "KBD": "24576",
} }
# Find all labels, remove them, and add them to symbol table. # Find all labels, remove them, and add them to symbol table.
address = 0 address = 0
@@ -116,6 +115,7 @@ def assemble_c_instruction(line):
ins_str = "111" + comp_lookup(comp) + dest_lookup(dest) + jump_lookup(jump) ins_str = "111" + comp_lookup(comp) + dest_lookup(dest) + jump_lookup(jump)
return ins_str return ins_str
def comp_lookup(comp_str): def comp_lookup(comp_str):
return { return {
"0": "0101010", "0": "0101010",
@@ -176,26 +176,28 @@ def jump_lookup(jump_str):
if __name__ == "__main__": if __name__ == "__main__":
try: if not sys.argv[1:]:
hack_asm_file = sys.argv[1] sys.exit("Call: ./assembler.py <hack_asm_file>")
for hack_asm_file in sys.argv[1:]:
if not hack_asm_file.endswith(".asm"): if not hack_asm_file.endswith(".asm"):
sys.exit("Hack asm file must have a .asm file ending.") sys.exit("Hack asm file must have a .asm file ending.")
hack_asm_ns_file = hack_asm_file.replace(".asm", ".nosymbol.asm") hack_asm_ns_file = hack_asm_file.replace(".asm", ".nosymbol.asm")
hack_bin_file = hack_asm_file.replace(".asm", ".hack") hack_bin_file = hack_asm_file.replace(".asm", ".hack")
except IndexError:
sys.exit("Call: ./assembler.py <hack_asm_file>")
with open(hack_asm_file, 'r') as f: with open(hack_asm_file, 'r') as f:
assembly_lines = f.readlines() assembly_lines = f.readlines()
preprocessed_lines = preprocess(assembly_lines) preprocessed_lines = preprocess(assembly_lines)
binary_lines = assemble(preprocessed_lines) binary_lines = assemble(preprocessed_lines)
with open(hack_asm_ns_file, 'w') as f: with open(hack_asm_ns_file, 'w') as f:
for line in preprocessed_lines: for line in preprocessed_lines:
f.write(line + "\n") f.write(line + "\n")
with open(hack_bin_file, 'w') as f: with open(hack_bin_file, 'w') as f:
for line in binary_lines: for line in binary_lines:
f.write(line + "\n") f.write(line + "\n")
print(f"{hack_asm_file} -> {hack_bin_file}")

77
vim/hackasm.vim Normal file
View File

@@ -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"