Show HN: WIP NandToTetris Emulator in pure C – logic gates to ALU to CPU to PC (github.com)
* OPEN TO CONTRIBUTIONS *
NandToTetris is a course which has you build a full computer from:
Logic gates -> Chips -> RAM -> CPU -> Computer -> Assembler -> Compiler -> OS -> Tetris
All this is done via software defined hardware emulation.
I'm building an emulator for this entire stack in C.
How is my approach different to other projects that build emulators?
- No external dependencies (so far)
- Start with a single software defined NAND gate in C
- EVERYTHING is built from this base chip
- Don't use certain programming utilities: boolean logic
operators, bitwise logic operators etc
Instead we leverage the gates/chips to implement such logic
I build more and more base chips from the NAND gate
- Simple gates: OR, AND, NOT, XOR (5 total "gates")
- Simple chips: DMux, Mux (11 so far, "combinatorial"
chips
- 16 bit variants
For comparison, most emulator projects start right at the CPU level and don't sequentially build primitive structures. So a lay-person, or person unfamiliar with PC architectures is missing some lower-level information.Typical emulators look at CPU truth table / Instruction set and implement that logic directly in code.
More straight forward, way fewer lines of code - but you skip all the gates/chips fun.
------
Confused? Heres example code for my NAND gate:
void nand_gate(Nand *nand)
{
nand->output.out = !(nand->input.a & nand->input.b);
}
From this gate I build a NOT gate (note, no boolean operators) void not_gate(Not * not )
{
Nand nand = {
.input.a = not ->input.in,
.input.b = not ->input.in,
};
nand_gate(&nand);
not ->output.out = nand.output.out;
}
Then OR / AND / XOR / MUX / DMUX ..... and their 16 bit versions.Heres a more complex chip, a 16bit Mux-8-way chip
/*
* out = a if sel = 000
* b if sel = 001
* c if sel = 010
* d if sel = 011
* e if sel = 100
* f if sel = 101
* g if sel = 110
* h if sel = 111
*/
void mux16_8_way_chip(Mux16_8_Way *mux16_8_way)
{
Mux16_4_Way mux16_4_way_chip_a, mux16_4_way_chip_b;
Mux16 mux16_chip_c;
// Mux a
memcpy(mux16_4_way_chip_a.input.sel, mux16_8_way- >input.sel, sizeof(mux16_4_way_chip_a.input.sel));
memcpy(mux16_4_way_chip_a.input.a, mux16_8_way->input.a, sizeof(mux16_4_way_chip_a.input.a));
memcpy(mux16_4_way_chip_a.input.b, mux16_8_way->input.b, sizeof(mux16_4_way_chip_a.input.b));
memcpy(mux16_4_way_chip_a.input.c, mux16_8_way->input.c, sizeof(mux16_4_way_chip_a.input.c));
memcpy(mux16_4_way_chip_a.input.d, mux16_8_way->input.d, sizeof(mux16_4_way_chip_a.input.d));
mux16_4_way_chip(&mux16_4_way_chip_a);
// Mux b
memcpy(mux16_4_way_chip_b.input.sel, mux16_8_way->input.sel, sizeof(mux16_4_way_chip_b.input.sel));
memcpy(mux16_4_way_chip_b.input.a, mux16_8_way->input.e, sizeof(mux16_4_way_chip_b.input.a));
memcpy(mux16_4_way_chip_b.input.b, mux16_8_way->input.f, sizeof(mux16_4_way_chip_b.input.b));
memcpy(mux16_4_way_chip_b.input.c, mux16_8_way->input.g, sizeof(mux16_4_way_chip_b.input.c));
memcpy(mux16_4_way_chip_b.input.d, mux16_8_way->input.h, sizeof(mux16_4_way_chip_b.input.d));
mux16_4_way_chip(&mux16_4_way_chip_b);
// Mux c
mux16_chip_c.input.sel = mux16_8_way->input.sel[2];
memcpy(mux16_chip_c.input.a, mux16_4_way_chip_a.output.out, sizeof(mux16_chip_c.input.a));
memcpy(mux16_chip_c.input.b, mux16_4_way_chip_b.output.out, sizeof(mux16_chip_c.input.b));
mux16_chip(&mux16_chip_c);
memcpy(mux16_8_way->output.out, mux16_chip_c.output.out, sizeof(mux16_8_way->output.ou...
11 comments
[ 2.0 ms ] story [ 39.2 ms ] threadI have learnt more about computers in 2 weeks of this course, than years of software development.
Particularly if you are a web-developer, I challenge you to look deeper into PC architectures and low-level programming.
C is a great language to get started with.
After I build this project, I want to move on to a more powerful emulator (maybe a GameBoy or similar, but probably starting at the CPU level rather than the logic gate level!)
[0] - https://www.nand2tetris.org/
You could definitely do the Nand2Tetris course alongside :)
A really simple way to start contributing would be to write some truth table tests for the nand_gate, then the other gates too.
EG: For the NAND gate, it would be as simple as the following (pseudocode):
Oh and for anyone who looks at this sort of low level project (hardware etc) and feels its outside their ability, its not!I say this because thats how I have always felt seeing such projects.
Its actually very, very simple code to understand and start yourself, and working through the NandToTetris course, you could probably do better than my code within a few days.
You could write such an emulator in any programming language
fwiw, i did the nand2tetris from coursera, and it is indeed great fun.
not for the faint-of-heart though.
(If you want to do your own thing that's fine too - just wanted to make sure you're aware of what's out there.)
Second project idea
As an extra exercise for the reader, it suggests connecting it to the internet somehow. I've no idea how I would do this and I really wish they'd add it as a proper chapter.
Be sure to check out the lispmachine folder; its essentially a fork I made after finishing the course. It implements a Lisp on the nand-gate level :)