Source
All source code for this project and its documentation can be found at: https://gitlab.com/transistories/dlc/dlc-0
All source code for this project and its documentation can be found at: https://gitlab.com/transistories/dlc/dlc-0
DLC-0 is a proof-of-concept to showcase that it is possible to design and built a computer from scratch using only logic gates. From the start the design goal was to build a simple, yet capable computer that was Turing complete.
Although learning and exploring everything related to designing and building a computer took quite a bit of time, the actual computer was designed and implemented in a simulator over a weekend.
There are multiple reasons why I wanted to do this project. The biggest reasons for me were because it presented itself as a challenge and it seemed fun (and it was a lot of fun!). On top of that, it is also a really good project if you want to learn a lot about electronics and about how computers work, on a more fundamental level.
This project will provide a lot of insight into how computers work and how programs are processed on it. By building your own computer you will get a really good understanding of how data and instructions flow through a computer.
This project is also historically relevant, as almost all the early computers were built this way; from countless IC's. In the 60s many computers were built using 74-series logic ICs (or equivalents). As time went on, components became smaller and computers became more powerful. At the same time, "older" components became cheaper, the 74-series ICs were no different. Like that computers found their way into hobby electronics. As hobbyists started experimenting more and more; the homebrew computer was born.
The following table contains the entire instruction set for DLC-0. The two instructions marked with NI are not implemented in this design.
opcode | hex | name | description |
---|---|---|---|
0000 | 0x0 | NOP | No operation |
0001 | 0x1 | JMP | Jump to address |
0010 | 0x2 | JPO | Jump to address on overflow |
0011 | 0x3 | OUT | Output accumulator value |
0100 | 0x4 | LDA | Load accumulator |
0101 | 0x5 | SWP | Swap the values in the accumulator and swap |
0110 | 0x6 | INV | Invert accumulator |
0111 | 0x7 | JMZ | Jump to address on zero (NI) |
1000 | 0x8 | JME | Jump to address if equal (NI) |
1001 | 0x9 | NND | Bitwise NAND |
1010 | 0xa | NOR | Bitwise NOR |
1011 | 0xb | XOR | Bitwise XOR |
1100 | 0xc | ADD | Addition |
1101 | 0xd | SUB | Subtraction |
1110 | 0xe | ADS | Addition from swap |
1111 | 0xf | SBS | Subtraction from swap |
A few remarks:
OPR | Flag | T0 | T1 | T2 | T3 | T4 | T5 | T6 | T7 | T9 | T10 |
---|---|---|---|---|---|---|---|---|---|---|---|
NOP | CNT | ||||||||||
JMP | fetch | ||||||||||
JPO | fetch | CNT | |||||||||
JPO | OVF | fetch | |||||||||
OUT | fetch | ||||||||||
LDA | fetch | CNT | |||||||||
SWP | fetch | SWP | |||||||||
INV | fetch | ||||||||||
JMZ | fetch | NI | |||||||||
JMZ | ZERO | fetch | NI | ||||||||
JME | fetch | NI | |||||||||
JME | AisB | fetch | NI | ||||||||
NAND | fetch | CNT | |||||||||
NOR | fetch | LDB | |||||||||
XOR | fetch | LDB | |||||||||
ADD | fetch | LDB | |||||||||
SUB | fetch | LDB | |||||||||
ADS | fetch | ||||||||||
SBS | fetch |
There is no schematic per-se for this project. However there is a logisim simulation, which can be considered a schematic. The logisim file can be found in the repo.
Direct link: DLC-0.circ
There is no specific language requirement for DLC-0, since there is no compiler available. We ourselves are acting as the compiler, and as such we transform the written code to values that have to be placed in the memory.
In order to make it easier for ourselves to compile our own code, it is easiest if the code is written is assembly or something closely resembling that. Assembly itself is already close to machine code, making the translation a lot simpler for us.
As later versions and iterations will get more and more complex, the need for a compiler will become self-evident. Not only will the programs become larger and more complex, the computer itself will become more capable. So on top of being able to write large programs, we will also have access to more complex instructions, with varying numbers of arguments... As all of us know, writing programs in higher-level languages is far easier than writing programs in assembly. However, for the time being, programs are written and programmed into the computer like in the good ol' days; with pen and paper and one bit at a time!
To reduce the work involved in and risk for mistakes while translating our code into the bytes that the computer will understand. An assembler was written, it can output the resulting data to the screen and to a binary file that can be understood by logisim.
The source code for this assembler is written in python and can be found at: https://gitlab.com/transistories/dlc/dlc-0/-/blob/release/assembler/assembler.py
This is a simple program that increments a value and outputs it each time. When an overflow occurs the program resets and starts over again. This program was written to test the basic functionality and to check if the control unit was designed without mistakes.
The program also showcases the normal jump instruction and the conditional jump instructions.
0LDA15Load the ACC with the value at address 15
1OUTOutput the value in ACC
2ADD14Add the value at address 14 to ACC
3JPOJump on overflow to instruction 0
4JMPJump to instruction 1
The following table show the values that have to be programmed into each address of the ROM. The cells marked with n.u. are not used, but can be programmed with 0x0 for safety reasons. This is the NOP instruction. If the computer would ever get here at least it will move along the zero's and eventually get out of the block of zero's.
adr | 0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xa | 0xb | 0xc | 0xd | 0xe | 0xf |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
code | LDA | 15 | OUT | ADD | 14 | JPO | 0 | JMP | 2 | n.u. | dat | dat | ||||
val | 0x4 | 0xf | 0x3 | 0x9 | 0xe | 0x2 | 0x0 | 0x1 | 0x2 | 0x0 | 0x0 | 0x0 | 0x0 | 0x0 | 0x1 | 0x0 |
This is a slightly more advanced program that calculates and outputs the numbers in the Fibonacci sequence.
It will output the numbers: 1,2,3,5,8,e(13); and then start over.
This program makes use of the SWP instruction, swapping the result in the ACC and SWP register. This was one of the original design goals!
0LDA15Load the ACC with the value at address 15
1SWPSwap the values in ACC and SWP
2LDA15Load the ACC with the value at address 15
3OUTOutput the value in ACC
4ADSAdd the value of SWP to ACC
5JPO0Jump on overflow to instruction 0
6OUTOutput the value in ACC
7SWPSwap the values in ACC and SWP
8JMP4Jump to instruction 4
The following table shows the values that have to be programmed into each address of the ROM.
adr | 0x0 | 0x1 | 0x2 | 0x3 | 0x4 | 0x5 | 0x6 | 0x7 | 0x8 | 0x9 | 0xa | 0xb | 0xc | 0xd | 0xe | 0xf |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
code | LDA | 15 | SWP | LDA | 15 | OUT | ADS | JPO | 0 | OUT | SWP | JMP | 6 | n.u. | dat | |
val | 0x4 | 0xf | 0x5 | 0x4 | 0xf | 0x3 | 0xe | 0x2 | 0x0 | 0x3 | 0x5 | 0x1 | 0x6 | 0x0 | 0x0 | 0x1 |
So what is next?
As this is a mere proof-of-concept created in a simulation, the logical continuation of this project would be a physical build, using the appropriate logic IC's and discrete components.
Beyond that, there are two paths that I would like to explore further;
Ragardless of where this road takes me; you can stay up to date by following the Transistories RSS feed: Keep up to date