Skip to content

Building a Computer

Tags: permacomputing, projects

I have recently blah blah blah… What are the reasons for doing this? Here:

Design Decisions

With that out of the way, I want to build an 8-bit computer out of the least amount of resources I can. Ad infinitum; I would start with a wooden stick and start constructing from there. However, this is impractical not only because it’s boring but because it literally is not possible for me unless I have access to a fabrication laboratory, also called a fab-lab but here I’ll call it a “fab”, where I can make things. To be fair, I don’t think the university I currently study at has this kind of machinery. Because of this, I have to limit myself to things that I can actually make without a fab. Some of those things are

I am also allowing myself some niceties because although it is technically possible to do it this way, it would just take me longer and the effect would be the same:

Now, things I can make by myself:

In order to consider this project COMPLETE:

Wanted Specifications

The things that say TBD here are because I don’t know enough:






Building an 8-bit Computer from Discrete Transistors

A multi-year guide to building a working 8-bit computer from discrete BJT transistors, capable of running Forth, with enough understanding to rebuild from scratch.

Framing: buy your transistors for now. Everything above that layer, you build. The full design — ISA spec, gate schematics, microcode table — lives in your notebook. That documentation is the durable artifact. A future person with transistors and your notes should be able to rebuild it.


Table of Contents

  1. Design Decisions
  2. Materials and Costs
  3. Phase 0 — Design on Paper
  4. Phase 1 — Gate Library
  5. Phase 2 — ALU
  6. Phase 3 — Register File and Buses
  7. Phase 4 — Control Unit
  8. Phase 5 — Memory Interface
  9. Phase 6 — UART
  10. Phase 7 — Forth
  11. Timeline
  12. The Collapse Scenario Property

Design Decisions

Fixed parameters for this build. Document deviations in your notebook.

Why Forth

Forth is the right software target for a hand-built 8-bit machine because:

Honest Performance Ceiling

Perfboard wiring adds capacitance; every gate adds propagation delay. The realistic clock ceiling for a hand-wired discrete transistor machine is 100 kHz – 2 MHz. At 1 MHz you can run simple Forth programs interactively. This is approximately 1975-era computing — genuinely useful and programmable.


Materials and Costs

Transistors and Passive Components

ItemQtyUnit CostTotalNotes
2N3904 NPN BJT500$0.02$10Bulk from LCSC or AliExpress
2N3906 PNP BJT200$0.02$4Level shifting, pull logic
1 kΩ resistor 1/4W500$3Base resistors
10 kΩ resistor 1/4W500$3Pull-ups and pull-downs
4.7 kΩ resistor 1/4W200$2Collector loads
100 Ω resistor 1/4W100$1Current limiting
100 nF ceramic capacitor200$3Decoupling, one per gate cluster
10 µF electrolytic capacitor50$3Power rail decoupling
1N4148 signal diode100$0.01$1Wired-AND logic, protection

Memory and I/O ICs

These are the only ICs in the CPU itself. All logic is discrete transistors.

ItemQtyUnit CostTotalNotes
AS6C62256 SRAM 32KB×84$2.50$10128 KB total (A15 selects ROM/RAM)
28C256 EEPROM 32KB×82$4.00$8Bootstrap ROM, reprogrammable
MAX232 or SP32322$0.50$1RS-232 level shifter for UART
1 MHz crystal oscillator module2$1.50$3One active, one spare

Construction Materials

ItemQtyUnit CostTotalNotes
Perfboard 24×18 cm (large)20$2.00$40One board per major module
Perfboard 9×7 cm (small)10$0.80$8Sub-modules, experiments
30 AWG wire-wrap wire, 3 colors3 spools$8.00$24Red/black/signal color
Wire wrap tool (manual)1$12.00$12Powered version ~$25
Solder 60/40 thin 0.5 mm1 spool$8.00$8
Soldering iron, temp controlled1$25–50$40Hakko 888D clone is fine
Flux pen1$5.00$5
IC sockets DIP-28 and DIP-820$0.20$4For SRAM, EEPROM, MAX232
40-pin header strips10$0.50$5Inter-board connectors
Ribbon cable + IDC connectors2 m$6.00$68-bit and 16-bit buses
Backplane (large perfboard)1$8.00$8Holds module boards together

Test and Measurement

ItemCostNotes
Oscilloscope (DS1054Z or clone)$150–$300Non-negotiable above Phase 1
Multimeter$20–$40You likely have one
Logic analyzer, 8-channel USB$10–$15Invaluable for bus debugging
Bench power supply 0–15 V, 3 A$30–$60Adjustable, current-limited
EEPROM programmer TL866II+$25Programs your bootstrap ROM
USB-serial adapter CP2102$4Connects UART to your machine
Breadboards ×4 (prototyping)$16For each phase before committing to perfboard
Helping hands / PCB vise$15

Software Tools (all free, all NixOS-compatible)

Total Cost Summary

CategoryLowHigh
Transistors and passives$30$40
Memory and I/O ICs$22$25
Construction materials$100$130
Test equipment$250$450
Total~$400~$650

If you already have an oscilloscope and bench supply, build materials are under $200.


Phase 0 — Design on Paper

Duration: 1–2 months. No soldering. This is the most important phase.

0.1 — Define Your ISA

Write these decisions in your notebook and do not change them lightly. This document is the constitution of your machine.

Registers:

NameWidthPurpose
A, B, C, D8-bitGeneral purpose
SP8-bit (or 16-bit)Stack pointer
PC16-bitProgram counter
IR8-bitInstruction register (internal)
MAR16-bitMemory address register (internal)
FLAGS8-bitCondition codes (Zero, Carry, at minimum)

Instruction format:

Fixed-width opcodes are simplest. Use 8-bit opcodes; a second byte carries immediate values or addresses. So instructions are 1 or 2 bytes.

[opcode: 8 bits] [optional operand: 8 bits]

Minimum instruction set:

Data movement:   MOV, LOAD, STORE, PUSH, POP
Arithmetic:      ADD, SUB, INC, DEC
Logic:           AND, OR, XOR, NOT, SHL, SHR
Control flow:    JMP, JZ, JC, JNZ, JNC, CALL, RET
System:          HALT, NOP
I/O:             IN, OUT  (map to UART registers)

That is ~24 opcodes. Fits in 5 bits; leaves room to grow to 32.

Memory map (example):

0x0000 – 0x7FFF    RAM      (32 KB, AS6C62256)
0x8000 – 0xFFFF    ROM      (32 KB, 28C256, bootstrap + Forth kernel)

The high address bit (A15) drives chip-select logic: A15=0 → RAM, A15=1 → ROM.

0.2 — Draw the Block Diagram

Sketch each module and how they connect. Use pencil; you will revise this.

                        ┌─────────────┐
                        │   Clock     │
                        │  (1 MHz)    │
                        └──────┬──────┘
                               │
         ┌─────────────────────┼─────────────────────┐
         │                     │                     │
    ┌────▼────┐          ┌──────▼──────┐       ┌─────▼─────┐
    │   PC    │          │  Step       │       │    IR     │
    │(16-bit) │          │  Counter    │       │ (8-bit)   │
    └────┬────┘          └──────┬──────┘       └─────┬─────┘
         │                     │                     │
         │              ┌──────▼──────┐              │
         │              │  Microcode  │◄─────────────┘
         │              │    ROM      │
         │              └──────┬──────┘
         │                     │ control signals
    ┌────▼──────────────────────▼────┐
    │         16-bit Address Bus     │
    └──────────────┬─────────────────┘
                   │
    ┌──────────────▼─────────────────┐
    │    SRAM / EEPROM               │
    └──────────────┬─────────────────┘
                   │
    ┌──────────────▼─────────────────┐
    │          8-bit Data Bus        │
    └──┬───────────┬─────────┬───────┘
       │           │         │
  ┌────▼───┐  ┌────▼───┐  ┌──▼────┐
  │  Reg   │  │  ALU   │  │ UART  │
  │ A,B,C,D│  │        │  │       │
  └────────┘  └────────┘  └───────┘

0.3 — Simulate Critical Modules in Verilog

Before soldering anything, write Verilog for your ALU and control unit. Simulate with iverilog. This finds design bugs for free.

// Example: 8-bit ALU skeleton
module alu(
  input  [7:0] a,
  input  [7:0] b,
  input  [2:0] op,    // 000=ADD 001=SUB 010=AND 011=OR 100=XOR 101=NOT 110=SHL 111=SHR
  output reg [7:0] result,
  output reg zero,
  output reg carry
);
  always @(*) begin
    case (op)
      3'b000: {carry, result} = a + b;
      3'b001: {carry, result} = a - b;
      3'b010: result = a & b;
      3'b011: result = a | b;
      3'b100: result = a ^ b;
      3'b101: result = ~a;
      3'b110: {carry, result} = {1'b0, a} << 1;
      3'b111: {result, carry} = {a, 1'b0} >> 1;
    endcase
    zero = (result == 8'b0);
  end
endmodule

Simulate every edge case before you wire a single gate.


Phase 1 — Gate Library

Duration: 2–3 months

Build and characterize your standard cell library on perfboard. These are the atoms everything else is built from.

1.1 — NAND Gate

The universal gate. All other gates derive from this.

         Vcc (5V)
          │
         4.7kΩ  ← collector load
          │
          ├──── OUTPUT
          │
         Q2 (2N3904) collector
         │
    A ──1kΩ── Q2 base
         │
         Q2 emitter / Q1 collector
         │
    B ──1kΩ── Q1 base
         │
         Q1 emitter
          │
         GND

Truth table: output is LOW only when both A and B are HIGH. Verify with multimeter. Measure propagation delay with oscilloscope. Target: under 100 ns per gate.

1.2 — All Gates from NAND

Build and verify each. Document transistor count.

GateConstructionTransistor count
NOT1 NAND with inputs tied2
ANDNAND + NOT4
ORThree NANDs (De Morgan)6
NOROR + NOT8
XORFour NANDs8
XNORXOR + NOT10

1.3 — SR Latch and D Flip-Flop

SR latch: Two cross-coupled NANDs. Set and Reset inputs, Q and /Q outputs. This is the fundamental memory element.

D flip-flop from SR latch: Add two steering NANDs: one passes D to S, the other passes /D to R, both gated by the clock. On the rising clock edge, D is captured and held.

This is your 1-bit register cell.

1.4 — 8-bit Register Module

Eight D flip-flops sharing a clock line, with an output-enable gate on each output. The output-enable is critical: it allows the register to be disconnected from the shared data bus so other modules can drive it.

Use open-collector outputs: the collector is the output, with a pull-up resistor to Vcc. When output-enable is low, the transistor is off and the bus pull-up holds the line high. When output-enable is high, the transistor pulls it low.

Only one register may have output-enable asserted at any moment. Enforcing this is the control unit’s job (Phase 4).

Build this module completely and test it before moving on. You will build approximately 6 of these (A, B, C, D registers, MAR high byte, MAR low byte).


Phase 2 — ALU

Duration: 3–5 months

The ALU is the most self-contained complex module. Build it first because it is fully testable in isolation — you can feed it inputs manually and read outputs with LEDs without needing a clock or control unit.

2.1 — 1-bit Full Adder

Sum   = A XOR B XOR Cin
Carry = (A AND B) OR (Cin AND (A XOR B))

Verify all 8 input combinations against truth table.

2.2 — 8-bit Ripple Carry Adder

Chain 8 full adders: carry-out of bit N feeds carry-in of bit N+1. Carry-in of bit 0 is tied to GND for addition.

Test cases to verify:

Measure worst-case propagation delay: input changes on A/B to stable output on result, with carry rippling through all 8 stages.

2.3 — Subtractor

Invert the B input (XOR each bit with a control line SUBTRACT) and force carry-in of bit 0 high when subtracting. This implements two’s complement subtraction by reusing the adder:

A - B = A + (~B) + 1

The SUBTRACT control line simultaneously:

2.4 — Logic Unit

Eight parallel single-bit operations on inputs A and B:

OpGate
ANDA AND B
ORA OR B
XORA XOR B
NOTNOT A (B ignored)

Each is just 8 copies of the corresponding gate operating in parallel.

2.5 — Function Select Multiplexer

A 3-bit opcode selects which result (adder, logic unit output) reaches the output. Implement as a multiplexer tree from NAND gates.

For 2 sources and 1 select bit: OUT = (A AND /SEL) OR (B AND SEL) Extend to 8 sources with a binary tree of such muxes.

2.6 — Flags Register

Two D flip-flops that latch on each ALU operation:

Zero flag (Z): NOR of all 8 output bits. High when result is 0x00.

Z = NOT(result[7] OR result[6] OR ... OR result[0])

Build as a tree of NOR gates.

Carry flag (C): Carry-out of bit 7 of the adder.

Both flags latch on the same clock edge that completes each ALU operation. They feed the conditional jump logic in the control unit (Phase 4).

At the end of Phase 2 you have a complete standalone 8-bit ALU. Connect it to switches and LEDs and compute some arithmetic by hand. This is a genuine milestone.


Phase 3 — Register File and Buses

Duration: 2–3 months

3.1 — Build Registers A, B, C, D

Four 8-bit registers from your Phase 1 module design.

Each register has:

The load-enable and output-enable signals come from the control unit (Phase 4). For now, wire them to manual switches for testing.

3.2 — Data Bus

An 8-wire ribbon cable running to every module. Each wire has a 10 kΩ pull-up resistor to Vcc at one end. Default state (nothing driving) is logic high.

Bus discipline rule: At any clock edge, exactly one module has its output-enable asserted. All other modules’ outputs are in high-impedance state. Violations cause bus contention — measure with oscilloscope as sagging voltage.

3.3 — Address Bus

A 16-wire ribbon cable driven by the PC or MAR. Not bidirectional. Only the PC and MAR drive this bus.

3.4 — Memory Address Register (MAR)

Two 8-bit registers (high byte and low byte) that hold the address to be accessed. Loaded from the data bus in two operations (high byte first, then low byte), then presented to the address bus.


Phase 4 — Control Unit

Duration: 4–8 months. The hard part. Be patient.

The control unit reads the current opcode and step counter, then asserts the correct control signals each clock cycle to move data through the machine and execute the instruction.

4.1 — Choose: Hardwired vs Microcoded

Hardwired: Control signals generated by combinational logic from opcode + step. Faster, fewer parts, harder to modify.

Microcoded: Each instruction maps to a sequence of micro-operations in a ROM. More flexible, easy to modify by reflashing EEPROM.

Recommendation: microcoded. For a first build, being able to fix bugs by reflashing instead of rewiring is invaluable.

4.2 — Step Counter

A 3–4 bit binary counter built from D flip-flops. Increments each clock cycle, sequencing through the micro-steps of each instruction.

At the end of each instruction’s sequence, a RESET signal (from microcode) returns the counter to 0 to begin the next instruction’s fetch cycle.

4.3 — Instruction Register

An 8-bit register (from your Phase 1 module) that latches the opcode byte from the data bus during the fetch micro-step.

4.4 — Microcode ROM Layout

Address: [opcode: 8 bits][step: 4 bits] = 12-bit address → 4096 locations
Value:   16-bit control word (one bit per control signal)

Example control word bit assignments:

BitSignalMeaning
0REG_A_OUTRegister A drives data bus
1REG_B_OUTRegister B drives data bus
2REG_C_OUTRegister C drives data bus
3REG_D_OUTRegister D drives data bus
4REG_A_INRegister A latches data bus
5REG_B_INRegister B latches data bus
6REG_C_INRegister C latches data bus
7REG_D_INRegister D latches data bus
8ALU_OUTALU result drives data bus
9ALU_OP[2:0]ALU function select (3 bits, 9–11)
12MEM_READMemory read strobe
13MEM_WRITEMemory write strobe
14PC_INCIncrement program counter
15PC_LOADLoad PC from MAR (jump)

Program this table into your 28C256 EEPROM using the TL866II+.

4.5 — Fetch Cycle (Step 0 for Every Instruction)

Every instruction begins with the same two micro-steps:

Step 0: PC → address bus, assert MEM_READ, data bus → IR, PC_INC
Step 1: decode opcode, begin execution micro-steps

After step 0, the IR holds the opcode. The remaining steps depend on it.

4.6 — Example: MOV A, B (copy register B to register A)

Step 0: fetch (universal)
Step 1: REG_B_OUT=1, REG_A_IN=1  (B drives bus, A latches it)
Step 2: RESET step counter

4.7 — Example: ADD A, B (A ← A + B)

Step 0: fetch (universal)
Step 1: REG_A_OUT=1, ALU_A_LATCH=1   (A → ALU input A)
Step 2: REG_B_OUT=1, ALU_B_LATCH=1   (B → ALU input B)
Step 3: ALU_OP=ADD, ALU_OUT=1, REG_A_IN=1, FLAGS_LATCH=1
Step 4: RESET step counter

4.8 — Conditional Jump: JZ addr (jump if zero flag set)

Step 0: fetch opcode
Step 1: fetch low byte of address → MAR_LOW
Step 2: fetch high byte of address → MAR_HIGH
Step 3: if FLAGS[Z]=1 then PC_LOAD=1 else RESET
Step 4: RESET

The conditional in step 3 is implemented by AND-ing the FLAGS[Z] line with the PC_LOAD control signal in hardware — a single gate.


Phase 5 — Memory Interface

Duration: 1–2 months

5.1 — Connect SRAM

Wire the AS6C62256 to:

5.2 — Connect EEPROM

Wire the 28C256 identically, but:

Your bootstrap program lives at address 0x8000 (the reset vector).

5.3 — Bootstrap Program

Write a minimal test program in hex, burn it to EEPROM with TL866II+.

Minimum viable bootstrap:

  1. Initialize stack pointer to top of RAM (0x7FFE)
  2. Output character > over UART (proves CPU and UART work)
  3. Enter loop: read character from UART, echo it back

This proves end-to-end operation. When you see > on your terminal, the machine is alive.


Phase 6 — UART

Duration: 1–2 months

6.1 — Baud Rate Generator

Divide the 1 MHz system clock down to 9600 baud.

1,000,000 Hz ÷ 9,600 baud ≈ 104 cycles per bit

Build a binary counter from D flip-flops that resets at count 104. The reset pulse is the baud clock.

6.2 — UART Transmitter

A shift register (8 D flip-flops in series) clocked by the baud clock.

Transmit sequence:

  1. Load byte from data bus into parallel-load shift register
  2. Assert start bit (logic 0) for one baud period
  3. Shift out 8 data bits, LSB first, one per baud period
  4. Assert stop bit (logic 1) for one baud period
  5. Signal CPU: UART_READY flag set

Output pin → MAX232 → DB9 or direct to CP2102.

6.3 — UART Receiver

Sample RX line at baud clock rate. Detect falling edge (start bit). Wait 1.5 baud periods (to sample center of first data bit). Shift in 8 data bits. Set UART_RX_READY flag.

CPU polls UART_RX_READY or is interrupted by it to read the byte.

6.4 — Connect to Your Machine

Computer USB → CP2102 → 3.3 V/5 V TTL → MAX232 → Your UART circuit

Open a serial terminal:

picocom -b 9600 /dev/ttyUSB0

When your machine outputs its first character, you have a working computer.


Phase 7 — Forth

Duration: ongoing, begin during Phase 5

Forth is implemented in two layers: a small assembly kernel, then Forth builds itself on top.

7.1 — Write an Assembler in Python

A few hundred lines. Takes your assembly mnemonics and produces hex output to burn to EEPROM or load into RAM.

# Example minimal assembler structure
opcodes = {
    'MOV':  0x01,
    'ADD':  0x10,
    'JMP':  0x40,
    'JZ':   0x41,
    'HALT': 0xFF,
    # ... etc
}

7.2 — Forth Memory Layout

0x0000 – 0x00FF    Zero page / scratch
0x0100 – 0x01FF    Return stack (grows down from 0x01FF)
0x0200 – 0x02FF    Parameter stack (grows down from 0x02FF)
0x0300 – 0x7FFF    Dictionary (Forth words defined here)
0x8000 – 0xFFFF    ROM (kernel primitives)

7.3 — The NEXT Loop (inner interpreter)

The entire Forth engine is this loop:

; W holds address of current word's code field
; IP holds address of next word in thread
NEXT:
    LOAD  W, [IP]    ; fetch next word address from thread
    INC2  IP         ; advance IP by 2 bytes (16-bit addresses)
    JMP   [W]        ; jump to the word's code

Approximately 5 instructions. This is the heartbeat of the system.

7.4 — Primitive Words in Assembly

Implement each of these as an assembly routine ending in JMP NEXT:

WordOperation
DUPcopy top of stack
DROPdiscard top of stack
SWAPexchange top two items
+add top two items
-subtract
ANDbitwise AND
ORbitwise OR
=equality test, push 0xFFFF or 0x0000
<less-than test
@fetch word from address
!store word to address
EMIToutput character over UART
KEYread character from UART

7.5 — Dictionary Structure

Each Forth word is a header followed by code:

[length + name string][flags][link to previous word][code field][parameter field]

The FIND routine walks this linked list searching for a word by name.

7.6 — Outer Interpreter

Once you have EMIT, KEY, FIND, and a number parser in assembly, you can write the outer interpreter in Forth itself:

: INTERPRET
  BEGIN
    WORD FIND
    IF EXECUTE
    ELSE NUMBER
    THEN
  AGAIN ;

From here, the system is self-extending. You define new words in terms of existing ones. Your machine is now a programmable interactive computer.


Timeline

PhaseOptimisticRealistic
0 — Design1 month2 months
1 — Gates2 months3 months
2 — ALU3 months5 months
3 — Registers and bus2 months3 months
4 — Control unit4 months8 months
5 — Memory1 month2 months
6 — UART1 month2 months
7 — Forthongoingongoing
Total to working computer~14 months~25 months

Part-time, with life — 3–4 years is a realistic and not discouraging estimate. The ALU and control unit are where most time goes, and also where most learning happens.


The Collapse Scenario Property

The machine is secondary. The documentation is the durable artifact.

Keep a physical notebook with:

A future person with access to discrete transistors and your notebook should be able to rebuild this machine from scratch. That is the property you are preserving.

The EEPROM microcode and your Python assembler should also be printed or copied into the notebook in hex — if those files are lost, a machine with the notebook can still be rebuilt by hand-entering microcode.


References and Further Reading