Your First Computer
Introduction
This guide is for anyone who is interested in learning how computers work, and wants to learn by building a computer for themselves, but doesn't know where to start.
This guide assumes the reader has a basic understanding of Logic Worlds building mechanics, and has some experience building some of the more intermediate components, such as adders, memory, and decoders. As well as at least some understanding of the binary numbers system.
The first part of this guide will go over the hardware of a very basic computer system. This guide will not be covering things like text and image processing, internet browsing, operating systems, file systems, windows and graphical user interfaces and other systems generally associated with personal computers. These systems are all built using software, and covering them, as well as software in general, is an entire subject unto itself. This guide will only be focusing on creating hardware that can execute software. Furthermore, this guide will only be focusing on core hardware, the bare minimum required to run software. This guide will not be covering peripheral hardware such as screens, graphics processor units, storage devices, co-processors, or multi-core setups. Nor will this guide be covering the topic of source code compilation. These are all topics that require their own study and as such are outside the scope of this guide.
The second part of this guide will be covering the process of designing your computer. It will bring up key details you should consider when planning your computer build, as well as providing a basic layout that you can use if you don't want to design everything from scratch yourself.
The third part will of this guide will cover building your computer.
Hardware Theory
All computers, regardless of complexity, need at least these 4 components. Program memory typically made using read-only memory, an ALU, working memory typically made using individual registers and random-access memory, and a control unit.

Program memory is where the program to be executed is stored. It takes the form of a list of binary numbers called machine code, and each number is called an instruction or opcode. The computer will step through this list one instruction at a time, grabbing each instruction, interpreting it, and performing an action corresponding to that instruction. This process is referred to as the fetch-decode-execute cycle.
Actions that a computer can perform can vary greatly, but two of the most common actions are moving data around, and performing operations using data.
The ALU is what allows a computer to perform these operations, and working memory is where the data being operated on is stored.
Moving data around may seem pointless at first, but often ALU's don't have direct access to working memory, but instead must use whatever is stored in more local registers. Data must therefore be juggled between working memory and registers in order to give the ALU access to the entire dataset. It's also useful for organizing data into larger data structures.
While it might be hard to wrap your head around this at first, everything that computers do can mostly be reduced to these two operations: moving data around, and operating on data. How these two operations can be structured to do things like browse the internet or play games requires a study of software, and is thus outside the scope of this article. But under the hood, millions of these operations are happening at a rate of billions a second, and it's this size and speed that allows computers to perform much more complicated tasks.

Coordinating these instruction fetches, data moves, and ALU operations is the control unit. It's job is to decode the instruction fetched from program memory and decide what control signals to send out and when.
While this module can get very complicated, the simplest control unit consists of a lookup table that takes the current instruction and the value of a counter as a combined input, and produces all the read, write, and function signals the computer needs as an output. A clock is used to increment the counter, and to a pulse for the write signals through the use of a falling edge detector, while the table is configured to read from program memory and write to an instruction register when the counter is 0, then increment an instruction pointer register when the counter is 1. From there, the table can be configured to do different things depending on the value of the instruction register. The counter register is reset when the instruction is complete, and the fetch-decode-execute cycle repeats.
On top of being able to move data around and perform operations, there is a third common type of action a computer can perform called a control flow instruction. These consist of instructions like jumping, conditional branching, calling and returning, and software interrupts. Though we'll only be focusing on jumping and conditional branching here. These all have one thing in common, and that is to break the linear sequence of the program.
Normally, as part of the fetch-decode-execute cycle, the instruction pointer used to index the program memory is incremented after each instruction fetch. Setting up the next instruction in the list to be executed when the current instruction is complete. By writing a new value to the instruction pointer, we queue up a different section of the program memory, and the first instruction in that section is primed to be executed next.
This is often used to create infinite loops in your program, or finite loops if you use a conditional branch that resumes a normal execution sequence if a condition is met, like two numbers being equal or one being greater than the other.
These three action types are the minimum required to create a turing complete computer - a computer that can calculate anything.
Planning A Computer
While you might be inclined to jump head-first into building, computers are complicated things, as the previous section shows. A little bit of forethought is required before you start building in order to help guide your building decisions. Otherwise you will end up with a computer that's too messy to modify, and if it ends up malfunctioning, it'll be too messy to troubleshoot as well. Documentation is key.
Before you begin, think about what you want this computer to do. What ALU functions do you want it to have? What special instructions or special hardware? You'll also want to consider key details such as the width of your ALU, the width of your working memory and program memory, as well as the size of these memory modules, as this will determine the width of your address bus and indexing registers.
Example Computer
Since you'll likely not know what to add, this section will provide for you a very basic plan for you to follow.
Component Sizing
Our computer will be very basic. We'll be using an 8 bit ALU and 8 bit working memory. We can do a lot with 8 bits without needing to make the hardware too big.
We'll also have 256 addresses in our program memory, so an 8 bit address bus and 8 bit indexing register is in order, since 2^8 is 256. The width of the program memory is determined by our computers instruction set.
Instruction Set
This is the most influential part of planning. This will not only dictate what your computer does, but also how programs will be stored in program memory.
You can design your own instruction set from scratch if you want something more complex. But for our instruction set, we will be adding 4 types of instructions: load immediate, operate, move, and jumping/branching. These will be organized with the prefixes 00 01 10 and 11 respectively.
We will be squeezing our instructions into 8 bits, making our program memory 8 bits wide. With 2 of those bits being used as a prefix, that leaves 6 bits to be used as instruction arguments.
Below is the instruction set we will be using:
| Name | Opcode (8 bits) | Operands | Description |
|---|---|---|---|
| Immediate | 00 xxx xxx | x: The immediate value. | Puts a value given by x into register 0. |
| Add | 01 000___ | Adds the values in register 1 and 2, and puts the result in register 3. | |
| Subtract | 01 001___ | Subtracts the value in register 1 from register 2 and puts the result in register 3. | |
| And | 01 010___ | Bit-wise ANDs the values in register 1 and 2, and puts the result in register 3. | |
| Or | 01 011___ | Bit-wise ORs the values in register 1 and 2, and puts the result in register 3. | |
| XOR | 01 100___ | Bit-wise XORs the values in register 1 and 2 and puts the result in register 3. | |
| Not | 01 101___ | Bit-wise NOTs the values in register 1 and 2 and puts the result in register 3. | |
| Move | 10 xxx yyy | x: The register to move data from. y: The register to move data to. | Moves the data from register x into register y. |
| Test | 11 000___ | Tests the values in register 4 and 5 against each other, putting the result into the flags register. | |
| Jump zero | 11 001___ | Jumps to the instruction in register 6, if the zero flag is ON. | |
| Jump carry | 11 010___ | Jumps to the instruction in register 6, if the carry flag is ON. | |
| Jump negative | 11 011___ | Jumps to the instruction in register 6, if the negative flag is ON. | |
| Jump equal | 11 100___ | Jumps to the instruction in register 6, if the equal flag is ON. | |
| Jump | 11 101___ | Jumps to the instruction in register 6, regardless of an flags. |
Notice that most instructions only use 3 of the 6 argument bits. Move immediate uses all 6, allowing the user to load an immediate value of 0-63, and move also uses all 6 to index 2 registers.
Registers
Since we have 3 bits available to index registers with, our computer will include 8 all-purpose registers.
If you design your own computer, you will need to consider what registers to include, and what will they be used for.
In our case, while all of our registers will be general purpose, meaning you can use them for whatever purpose you like, most of them will be used in a specific way for specific instructions. The registers and their purposes are as follows:
- Register 0 - destination for immediate values
- Register 1 - source of one argument of an operation
- Register 2 - source of the other argument of an operation
- Register 3 - destination of the result of an operation
- Register 4 - source of one argument for a comparison
- Register 5 - source of the other argument for a comparison
- Register 6 - target address for a jump
- Register 7 - general purpose
Note that the instruction pointer and instruction register are separate to these all-purpose registers, so our program will not be able to directly edit the values in these registers.
Editing of these registers is done by the control unit either during the fetch-decode-execute cycle, or through the use of a jump instruction.
Flags
If you decide to include conditional branching in your instruction set, you will need to decide what conditions (called flags) you want to check for and how you're going to check them.
In our case, we will have the following 4 flags in our computer:
- Zero flag - This flag will be ON only if the first value from the last test instruction was equal to 0.
- Carry flag - This flag will be ON only if that last add (or subtract) instruction carried/overflowed.
- Negative flag - This flag will be ON only if the first value from the last test instruction was negative (had its most significant bit ON).
- Equal flag - This flag will be ON only if the values from the last test instruction were equal to each other.
We will also be storing the states of these flags in another register called a flag register. Allowing us to capture the state of these flags with one instruction (test) and use the state of these flags in another instruction (jump).
You do not need to store the state of these flags in a register, or perform a test and branch in two separate instructions. You can perform your comparison and use the results immediately, if you want. Doing so will just require a more complex execution sequence, and possibly bigger instructions.
Example Program
Putting everything together, we can use our instruction set to write a Fibonacci program:
; Initialise registers
Immediate 0 ; 00 000000
Move 2, 0 ; 10 000 010
Immediate 1 ; 00 000001
Move 1, 0 ; 10 000 001
;
; Main loop
Add ; 01 000 ___
Move 2, 1 ; 10 001 010
Move 1, 3 ; 10 011 001
Immediate 4 ; 00 000100
Move 6, 0 ; 10 000 110
Jump ; 11 101 ___
Note that in this notation, the instruction name comes first, and subsequent numbers are the operands of the instruction. Instructions such as the Move instruction have two operands; The first is the destination register, which describes the register to save data to, the other is the source register, which describes the register to load data from. For example, the Move instruction moves the data from the source register, and copies it to the destination register directly.
Visualizing the process, we can imagine the first instruction being copied into the instruction register, the instruction pointer being incremented, then the value being copied from the instruction register to register 0.
Then the step counter resets, and we begin again, fetching the instruction, incrementing the instruction counter, and moving the content of register 0 to register 2. Reset the step counter and on and on it goes.
This is what our computer should do when it's complete.
With the planning done, it's now time to move on to building.
Building the Computer
Mental Preparations
Depending on the size and complexity of your computer, the building phase will take several days to several months to complete. This is a full-scale project, so be aware of what you're committing to. Our example computer is simple, and should only take 1-3 days to build depending on your skill and availability. Even still, you will need to mentally prepare yourself if you're not used to working on larger scale projects. Things won't work the first time, you will need to troubleshoot things. You will learn how to do things better as you work on this, give yourself time and space to tear things down and start again if you deem it necessary. Prototypes aren't just small-scale quick-and-dirty versions of the final product that you assemble to get something done now. They're practice runs. They give you the experience needed to determine what works and what doesn't. Be patient. Be willing to try when you don't know what to do, and be willing to throw it all away when you find out it doesn't work.
If you want to know what it takes to carry out a big project, the book "How Big Things Get Done" by Bent Flyvbjerg and Dan Gardner is an invaluable resource. If you read it, you might get the impression that the "correct" way to handle a project like this is to plan every little detail on paper before you start building.
Do not do this!
The key idea you should take away from this book is there are some things you just don't think about until you start putting shovels in the ground. Rather than waiting until this moment, instead try putting a spoon in a sandbox first. Do it for real, but on a smaller and cheaper scale.
Nothing is cheaper than a digital sandbox, so Logic World has you covered there. But you'll still have to build something, make your mistakes, learn, and try again. No amount of forethought will ever prepare you for what you've never tried before. Though a bit for forethought is still good practice.
Start With The Major Components
What components do you know your computer will need? What are their inputs and outputs? What do they do? These are the best places to start when building a computer.
In our example computer, we know our computer needs an 8-bit ALU. We know this ALU will have 6 functions, and we know it will produce 4 flag outputs along with the normal result output. Start by building that. Use sockets to make this component modular and to make connecting to other components easier.
We also know this computer will need registers. We'll need to be able to read from and write to these registers, but things like the ALU can also access the state of these registers directly. Building a single register with a data in/out connection, a read, write, and enable input, and a state output will allow us to duplicate the design and build our register file much faster.
We can also reuse this design for other registers like the instruction register.
The flag register, instruction pointer register, and step counter will all need to be custom built, as their behavior differs slightly from our register template. But these too are components who's inputs, outputs, and behaviors are known, and thus can quickly be built in isolation.
Test as you go!
While it may seem like extra work, you'll want to know that each component is in working order before you use them in your final build. This isn't strictly necessary, but as a general rule of thumb when building things, it never works right the first time. When it comes time to put everything together, it's better to only have to deal with issues that arise at that level, rather than to run around trying to fix every problem with every component at every level.
Continue to build each of the remaining components in isolation. Moving on to a small portion of switch-based program memory, and any other major components you might discover you need along the way, such as multiplexers, decoders, and smaller lookup tables for controlling things like translating portions of the instruction register into control lines for the general purpose registers or the ALU. Save the control lookup table for last! As the inputs and outputs of this component will change as you begin to define what components you have and what their inputs/outputs are.
For example, we may discover that it'd be much easier to send a single "register read/write" line to the registers, and then use a decoder to translate bits from the instruction register into enable lines to exact fine control over the individual registers.
Or, we may discover that our lookup table needs to take the state of our flag register into account when performing certain instructions.
These are things we discover as we build, and as such, it's best to leave things like the control unit for last.
Putting It All Together
Alright! You have all your components built, and they're all working in isolation. Pat yourself on the back.
Now comes time to wire it all together.
Don't be afraid to make a mess. Remember, we haven't actually tested everything together as a whole yet. We have no idea if it will work, or if we overlooked something. Doing our fine-polishing work now if something isn't working properly will just be a waste of time and lead to frustration as we try to squeeze patches into what was once our finest masterpiece.
This is also why we built a small portion of switch-based program memory. The smaller size means we don't invest too much time building a massive component that we just don't need right now, and making it switched based will eliminate the need to reprogram our program memory every time we want to conduct a test.
You can even make multiple copies of your program memory and load different test programs on each one for a more thorough testing procedure.
Lay out all your major components as best as you can. It helps to have them surround a large open area. Use buses to make the connections, and when you feel like everything is ready, load your program memory with a small test program, start the clock and see what happens.
More than likely, things will not work as you had expected. Here's where you're going to need to slow down and troubleshoot things. Use the simulation controls to slow down, stop, and step the logic simulator. At this level, most issues you will experience are related to timing. Register inputs are not being held during the duration of a write. The ALU output is not stabilizing in time. Things like that. Though it is also possible that one of your components isn't working as it should, or its behavior needs to change in some way.
This is why we made our components modular. If this does happen, take the defective component out, fix or rebuild it, and plug it back in.
Be sure to run all of your test programs again after making a change. Sometimes changing one thing can fix one issue, and create another. Don't assume previous tests will pass just because they were passing before the change.
Refine and Repeat
Eventually, after many cycles of testing, troubleshooting, and repairing, your rough prototype will be in working order.
Congratulations! You just built your first computer!
Now comes time to polish things up, if you want to.
You can replace your test program memory modules with a full sized program memory module, if you want. Or you can begin the process of organizing your components.
Compress them down, rearrange them, stack them in a neat package. Just be sure to do it one step at a time, and test after each change.
When you're happy with how things look, you can call it done.
Again, be sure to replace your test program memory modules with a full sized module, and for easy programming/using of your computer, consider adding a programmers interface where you can punch in programs and read the state of your registers.
Whatever you do, make it yours.