Building an entire CPU from scratch has been the "Holy Grail" of my electronics hobby for at least the last ten years or so.  Now, after a lot of time, research, and effort, I have finally succeeded!

This project showcases a simple 8-bit one-instruction CPU that I built over the course of about 4 months in the summer of 2025.  The CPU runs the subleq instruction set, which, surprisingly enough, achieves Turing-completeness with just one instruction.  Some goals for this project were:

- Build as much of the CPU out of discrete components as feasible (minimize the use of integrated circuits)
- Make getting output from the CPU as painless as possible
- Get a "Hello world" program running

I think I did a decent job accomplishing these goals.  For those of you who don't want to read through all the details of this project, I do have a video up on my YouTube channel that explains how I made this processor.

For those who wish to continue reading, a detailed overview can be found below.

The subleq instruction takes three arguments, A B and C, each representing a location in memory.  The subleq instruction first subtracts the value at address A from the value at address B, then stores the result back at address B.  Then, if the result of the subtraction was less than or equal to zero, then the program jumps to address C.  With some clever permutations of this one instruction, it is possible to simulate any CPU operation we want.

Because the CPU uses such a simple instruction set, it was able to be implemented with very simple hardware.  The machine only has 4 8-bit registers, three of which are asynchronous and hence much simpler to build than would ordinarily be required.  The only synchronous register is the program counter.  A list of all of the components of the CPU is outlined below:

- The A and B Registers:  two asynchronous registers that store operands for the subtractor from the data bus
- The Subtractor:  subtracts the value of the A register from the B register, and optionally outputs the result to the data bus
- The Branch Unit:  decides if the program should branch based on the output of the subtractor
- The Address Register:  an asynchronous register that acts as a pointer for fetching A and B operands for the subtractor
- The Program Counter:  a synchronous counting register that is used to fetch instruction operands during execution
- The Control Sequencer:  contains a 6-stage ring-counter and an instruction decoder to activate control lines in the correct sequence to execute the subleq instruction
- RAM:  the only Integrated Circuit on the machine (not counting the LCD display)
- Data bus and Address bus:  two 8-bit busses, one for data and one for memory addresses; the machine can therefore access 256 bytes of memory

A block diagram of the CPU's architecture can be found below:

On this architecture, it is possible to execute the subleq instruction in six steps:

1) Using the program counter, load A into the address register, and increment the program counter
2) Using the address register, load the value stored at address A into the A register
3) Using the program counter, load B into the address register, and increment the program counter
4) Using the address register, load the value stored at address B into the B register
5) Using the address register, store the output of the subtractor at address B
6) If the program needs to branch, then use the program counter to load C into the program counter.  Otherwise, increment the program counter to move to the next instruction.

Once the CPU architecture and instruction sequence had been designed, I decided to simulate the CPU at the logic gate level in Logisim before beginning the physical build.  A screenshot of the Logisim circuit can be found below, and the full .circ file for the Logisim circuit can be found on my GitHub if you want to tinker around with it.  Link to GitHub:  PolymathUnlimited/Subleq-CPU: This repository contains files relating to my subleq processor build.

After verifying that the CPU design would work, at least in theory, I set out to physically build the machine out of transistors.  I decided to use NMOS for all of the logic gates.  All of the gates were built with 2n7000 n-channel mosfets with 51k-ohm pull-up resistors.  This gave a good balance of performance with power consumption, though if you want to run at higher clock speeds, a lower value for the pull-ups may be warranted.  I'm only running at a max clock speed of around 1kHz, so these resistors were fine in my case.  The entire circuit uses a few watts of power, most of which is used by the indicator LEDs, so I suspect you could go quite a bit lower on the resistor values without too much issue.

I built the entire CPU on a cardboard substrate with point-to-point wiring using 22 gauge solid-core insulated wire.  While this approach did result in a working CPU, some of the wiring can be a bit finicky at times, and it does sometimes randomly decide not to work until I poke at it in just the right spot.  In the future, I may design some PCBs and rebuild the machine on those to help mitigate this issue.  Pictures of the completed build can be found below:

Unfortunately, I don't have complete circuit schematics available for this CPU, since I was just translating the Logisim circuit into NMOS on-the-fly while I was building.  However, I do have the general order of operations I followed during the build below.

For the first stage of the build, I built the power regulator.  This was just a simple voltage-following regulator using some Zener diodes and a TIP31-C NPN transistor to regulate the input voltage to around 5-volts.

Once I had power on the board, I built the data bus and the control bus.  I added some DIP switches to both busses so that I could control them manually for testing during the build, and programming after the build was completed.  I also added some LED indicators to the data bus (The row of yellow LED's on the bottom left).

Next, I built the A and B registers.  These were just simple asynchronous registers built from D-latches.  I attached the input-enables of both registers to the control bus, and wired some blue LED indicators to both registers so I could see the stored values.  I then verified that both registers were working by using the data and control busses to store some values in them.

With the A and B registers built, I could then move on to building the subtractor.  This was just a simple adder with the A-input inverted in order to perform subtraction, and a few other changes to account for the need to add one while doing two-s complement for the inverted A operand.  The output of the subtractor was connected to the data bus via some open-drain NAND gates.  This let me control via the control bus whether or not the subtractor output would get loaded onto the bus (I used similar open-drain logic to connect all of the components of the processor that need to communicate with the bus).  I then verified that the subtractor was working by loading some values into the A and B registers, and verifying that the subtractor was outputting the correct value to the data bus.

I then proceeded to build the address register and address bus.  The address register was identical to the A and B registers, except that it also needs to selectively output to the address bus via open-drain NAND gates.  I connected some red LED indicators to the address register so I could verify that it was storing values properly.

Next, I built the clock.  Most CPU builds start with the clock, but I was able to put this off for a while because everything I had built to this point was asynchronous.  However, in order to build the program counter, I needed a clock, so I couldn't put it off any longer.  The clock I made was a simple relaxation oscillator made from a discrete NMOS Schmidtt-trigger, a resistor network, and a timing capacitor.  I wanted to make the clock speed continuously variable, but I didn't have the right potentiometer on-hand, so I just wired up a few different resistors to the circuit selectable by a DIP switch.  The clock has 4 speeds, ranging from about 1.3 Hertz to just over 1kHz.

Next, I built the program counter.  This was arguably the most complex part of the entire build, and took the longest to complete.  I made the program counter out of a master-slave D flip-flop, with its output fed into an incrementor, which was in turn fed back into the data inputs via a multiplexer.  I also added a "reset" button that asynchronously sets the program counter to zero.  The multiplexer lets the computer decide whether to increment the program counter, keep the value in the program counter the same, or load the value on the data bus into the program counter on each rising edge of the clock.  This behavior is controlled by the control bus, similarly to how all of the other CPU components are coordinated.  I connected the program counter to the address bus with some more open-drain NAND gates, and connected some more red LED indicators to verify its behavior before proceeding.

The next step was to start building the control sequencer.  I started with a six-stage ring-counter that steps through all six stages of the instruction sequence in order.  The green LEDs on the far right of the board show the state of the ring-counter, and hence which instruction step the CPU is on.  Then, I built the instruction decoder, which activates the different parts of the CPU by outputting a control-word onto the control bus for each stage in the instruction sequence.

Next, I added the RAM chip to the CPU.  I opted for a 64k x 8 SRAM chip (mostly since that is what I already had on-hand) with the highest bits of the address hard-wired to one, and the lower 8-bits wired to the address bus.  This lets my CPU access up to 256 bytes of RAM.  Not a lot, but more than enough to run some cool programs!  The RAM chip is also the only integrated circuit on the entire machine (unless you count the LCD that I added later).  Everything else is made out of individual transistors.

After verifying that everything was working by running some simple test programs, I decided to wire up an LCD display so I could get some text output from the machine.  I used a 1602 2-line LCD since I had one on-hand already.  I mapped the LCD instruction codes to memory address 0xFE and character codes to address 0xFF so that I could simply write to the appropriate address when I wanted to send data to the LCD.  I tested it with a "Hello world" program, and it works very well!  (the "Hello world" program was also a good excuse to test my homemade assembler).

To program the CPU, I use the DIP switches connected to the data bus and the control bus to manually write machine code into RAM one byte at a time.  This process is pretty tedious, but it does work.  If the memory space was any larger than it is, I would probably have considered building some kind of dedicated programmer using an Arduino or an EEPROM.  I have some larger CPU designs in the works, so that is probably what I will do there.

This project is technically completed, but that doesn't mean that I am done working on it.  There are lots of improvements I hope to make in the future, like getting some PCBs designed so I don't have to worry about the less-than-reliable connections on the backside of the cardboard.  I also would like to write some more complex programs for this CPU to run.  There's definitely lots more stuff I can do with this in the future!