Building a Computer from Scratch
“What the fuck am I fucking doing here?!”
Charli XCX
It's a question that goes surprisingly well with learning how computers work.
What actually happens to the code we write? How does software control physical hardware? And how can something made from silicon become a programmable machine?
CPU8 is my attempt to find out. A computer reduced to its core components, built from basic logic.
ALU
An 8-bit combinational Arithmetic Logic Unit. A four-bit control selects its operation. Its output feeds RESULT, which only captures it at COMMIT for an ALU instruction.
Doing Math on a Rock
The half adder and full adder form the foundation of CPU8. They're the first step where basic logical operations become something more: a circuit that can actually perform arithmetic.
A & BA | BA ^ B~AOne Unit, Many Operations
The interesting part isn't just adding more operations. It's using control signals to choose what the same hardware does. This is where a collection of circuits starts feeling like a computer.
error0
ADD/SUB signal unsigned overflow or borrow, or signed overflow. TWC signals signed overflow only. This wire is not stored as a CPU flag.
Making a Processor
Rather than studying existing CPU architectures, I started with a simple question: What actually needs to happen for a computer to execute an instruction?
First, it needs to know what to do. Then, it needs to perform the required operation. Finally, it needs to preserve the result.
I figured it might be time to fire up the good ol' brain instead of asking AI or the internet to do the thinking for me. I spent several days trying to figure out how information should move through the processor, where it comes from and where it needs to go, visualizing everything with pen and paper to the point where I was even dreaming about the problem in my sleep.
Eventually, I arrived at a three-phase execution cycle: FETCH, COMBINE and COMMIT.
It was definitely not the fastest way to design a CPU, nor did it lead to the most efficient outcome. But that wasn't really the point. I wanted to understand the problem well enough to solve it myself, rather than just understand someone else's solution.
And that's what made this step so special to me. For the first time, it felt like I wasn't just connecting circuits anymore. I was actually building a computer, and I had figured out its fundamental architecture on my own.
ALU
An 8-bit combinational Arithmetic Logic Unit. A four-bit control selects its operation. Its output feeds RESULT, which only captures it at COMMIT for an ALU instruction.
Find out what the program says to do.
Decode the instruction and let the required paths respond.
Capture the stable result on the clock edge.
A Computer, One Step at a Time
LDA brings data into register A. ADD writes A + B into RESULT. Step through the phases to see what changes, and when.
Ready for GET 0x20.
- FETCH
- COMBINE
- COMMIT
Things GET complicated.
I wanted to keep the instruction set simple and easy to understand. An instruction like LDA already has a clear job: load a value into register A. Adding memory addressing to the same instruction felt like doing too many things at once.
So I introduced GET, a separate instruction whose only job is to bring a memory address into the CPU.
However, GET turned out to be one of the more complicated parts of the project. Unlike most instructions, it needs an additional byte from program memory, meaning the program counter has to advance within the instruction rather than just following the normal execution cycle.
In a way, GET combines two bytes into one operation. It also connects two parts of the system that had previously been developed separately: the CPU and its memory.
Since memory integration wasn't what I wanted to focus on, I handed that part over to AI. My main interest was understanding and designing the CPU architecture itself, not spending time getting the surrounding hardware integration to work.
Giving the Machine Instructions
Writing the assembler was another interesting moment. I finally understood what my code actually gets translated into.
An assembler is basically just a translator between human-readable instructions and a memory image made up of nothing but ones and zeros.
And that's what the CPU eventually reads. No magic, just a bunch of bytes telling the hardware what to do.
asm
RAM
- 0x20
- 0x21
- 0x22
Reference
One byte per instruction, except GET: opcode + address byte. JMP is indirect: PC ← DATA[MAR]. STR stores RESULT. Unary ALU instructions use A; binary ones use A and B.
Demo RAM starts with the selected example’s data; other bytes start at zero. Hardware RAM is unspecified without an initialization image.
Unused demo ROM bytes are NOP (00000000); the original assembler emits only supplied bytes. CPU8 has no HALT opcode. Stepping stops outside this 16-byte view. Assembling resets execution.
0000000000000001000000100000001100000100000001010000011000000111000010000000100100001010000010110000110000001101000011100000111100010000Program ROM
- PC
- 0x000
- IR
- 0x000
- MAR
- 0x000
- A
- 0x000
- B
- 0x000
- RESULT
- 0x000
What's Next?
The next big goal is to develop a compiler for CPU8.
So far, programming the processor means writing assembly instructions directly. A compiler would make it possible to write programs in a higher-level language and translate them into instructions that my CPU can actually execute.
I'm especially interested in understanding how that translation works, from parsing source code to generating machine instructions.
Like the CPU architecture itself, this is something I want to figure out mostly on my own.
The project is still a work in progress, so I'll leave the final conclusions for when it's actually finished.
- Higher-level
source - CompilerPlanned
- Assembly /
machine instructions - Program ROM
- CPU8