General-purpose computer from scratch

The Nand to Tetris saga

In the "Nand to Tetris" course, I completed a series of 12 projects based on Elements of Computing Systems. This comprehensive course guided me through building a computer system from the ground up, starting with basic logic gates and culminating in the creation of a fully functional computer system. Each project tackled a fundamental aspect of computing, including hardware design, assembly language, and operating systems, providing a deep understanding of the entire computing stack.

In the "Nand to Tetris" course, I completed a series of 12 projects based on Elements of Computing Systems. This comprehensive course guided me through building a computer system from the ground up, starting with basic logic gates and culminating in the creation of a fully functional computer system. Each project tackled a fundamental aspect of computing, including hardware design, assembly language, and operating systems, providing a deep understanding of the entire computing stack.

Organization

The University of Chicago

Core Technologies

Python Hardware Simulator CPU Emulator VM Emulator Assembler Compiler

Domain

Computer Systems Architecture

Date

October 2023

CPU Architecture
CPU Architecture
CPU Architecture

Technical highlight

The first half of the course culminated in building a functional computer system by integrating the following omponents which were built during the earlier weeks: Memory: 1. RAM16K: This is the computer's short-term memory, where data and programs are temporarily stored while the system is running. 2. Screen and Keyboard: These components interface with the user, displaying output and receiving input. CPU (Central Processing Unit): 1. A Register: Acts as a temporary storage area for data that the CPU is currently processing. It's crucial for handling operations like calculations and data manipulation. 2. D Register: Holds the result of operations performed by the CPU, such as calculations or data fetched from memory. It ensures that the results are available for subsequent steps. 3. ALU (Arithmetic Logic Unit): Performs all the arithmetic and logical operations needed by the CPU, such as addition and comparison. Computer System: 1. ROM32K: Stores the program code that the CPU executes. It can load programs from a file, allowing the system to perform various tasks based on the instructions given. By combining memory, CPU, and instruction storage, I learned how each part contributes to the overall functionality of a computer- from executing programs to interacting with users.

Assembler
Assembler
Assembler

Technical highlight

I developed an assembler, a tool that converts programs written in Hack assembly language into binary code that the Hack hardware platform can execute. The assembler handles two main types of instructions: A-instructions and C-instructions. 1. A-instructions (@value) are used to load a specific value or memory address into a register. 2. C-instructions define computations, such as arithmetic operations or data transfer between registers and memory. The project was completed in two stages to ensure comprehensive functionality: 1. Symbol-less Assembler: The first stage involved creating an assembler that could translate programs without symbols. I focused on converting A-instructions and C-instructions directly into binary code, which the Hack computer hardware understands. 2. Symbol Handling: In the second stage, I extended the assembler to manage symbolic references. Symbols are placeholders in the code that represent memory addresses or variables. The assembler was designed to first scan the entire program, building a symbol table that records the locations of all labels (which mark positions in the code) and predefined variables. During the second pass, the assembler replaced each symbolic reference with its corresponding binary address or value, ensuring that the program could be correctly executed by the Hack hardware.

I developed an assembler, a tool that converts programs written in Hack assembly language into binary code that the Hack hardware platform can execute. The assembler handles two main types of instructions: A-instructions and C-instructions. 1. A-instructions (@value) are used to load a specific value or memory address into a register. 2. C-instructions define computations, such as arithmetic operations or data transfer between registers and memory. The project was completed in two stages to ensure comprehensive functionality: 1. Symbol-less Assembler: The first stage involved creating an assembler that could translate programs without symbols. I focused on converting A-instructions and C-instructions directly into binary code, which the Hack computer hardware understands. 2. Symbol Handling: In the second stage, I extended the assembler to manage symbolic references. Symbols are placeholders in the code that represent memory addresses or variables. The assembler was designed to first scan the entire program, building a symbol table that records the locations of all labels (which mark positions in the code) and predefined variables. During the second pass, the assembler replaced each symbolic reference with its corresponding binary address or value, ensuring that the program could be correctly executed by the Hack hardware.

Compiler Architecture
Compiler Architecture
Compiler Architecture

Technical highlight

In this project, I developed a compiler designed to translate high-level code written in a procedural programming language into machine-readable instructions. The project was divided into two main phases: 1. Implementing a Procedural Programming Language: a. Variables: Managed the declaration and usage of variables within the code. b. Expressions: Implemented the evaluation of mathematical and logical expressions. c. Statements: Enabled the execution of various control structures, such as loops and conditionals. 2. Adding Object-Based Programming Features: a. Objects: Introduced the ability to define and manipulate objects, allowing for more complex data structures. b. Constructors: Implemented methods for initializing objects. c. Methods: Enabled the definition and execution of functions tied to specific objects, enhancing the language's capabilities. Throughout the project, I employed several key techniques to ensure the compiler's functionality: 1. Parsing: Handled the syntax analysis of the code, breaking it down into manageable components (initially provided). 2. Symbol Tables: Used to keep track of variable and function names, ensuring correct usage and scope management. 3. Compilation Engine: Responsible for the overall coordination of the translation process from high-level code to machine code. 4. Code Generation: Produced the final machine code that could be executed by a computer, ensuring that the translated code accurately represented the original source.

In this project, I developed a compiler designed to translate high-level code written in a procedural programming language into machine-readable instructions. The project was divided into two main phases: 1. Implementing a Procedural Programming Language: a. Variables: Managed the declaration and usage of variables within the code. b. Expressions: Implemented the evaluation of mathematical and logical expressions. c. Statements: Enabled the execution of various control structures, such as loops and conditionals. 2. Adding Object-Based Programming Features: a. Objects: Introduced the ability to define and manipulate objects, allowing for more complex data structures. b. Constructors: Implemented methods for initializing objects. c. Methods: Enabled the definition and execution of functions tied to specific objects, enhancing the language's capabilities. Throughout the project, I employed several key techniques to ensure the compiler's functionality: 1. Parsing: Handled the syntax analysis of the code, breaking it down into manageable components (initially provided). 2. Symbol Tables: Used to keep track of variable and function names, ensuring correct usage and scope management. 3. Compilation Engine: Responsible for the overall coordination of the translation process from high-level code to machine code. 4. Code Generation: Produced the final machine code that could be executed by a computer, ensuring that the translated code accurately represented the original source.

Takeaways

This project allowed me to visualize the inner workings of a computer at the low level, from writing a complete game to seeing it run on a computer I built. It also deepened my skills in both procedural and object-based programming paradigms.

This project allowed me to visualize the inner workings of a computer at the low level, from writing a complete game to seeing it run on a computer I built. It also deepened my skills in both procedural and object-based programming paradigms.