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.out));
}
-----Progress: I have only started this project yesterday, so have completed 1 out of 7 hardware projects so far