First blog post

This commit is contained in:
Shylie 2024-01-14 12:39:51 +00:00
parent 347fab9ccd
commit a576c947d1
9 changed files with 236 additions and 27 deletions

View File

@ -3,7 +3,7 @@ layout: default
---
<h1>
{{ page.title }} - Shylie - {{ page.date | date_to_string }} - <a href="javascript:window.history.back();"><-</a>
{{ page.title | downcase }} - shylie - {{ page.date | date: "%b %Y" | downcase }} - <a href="javascript:window.history.back();"><-</a>
</h1>
{{ content }}

View File

@ -0,0 +1,91 @@
---
layout: post
tags: eight-bit-cpu
title: "eight bit cpu: part 1"
---
During my architecture of computers course, I decided I wanted to go a little bit deeper than the course did on how a CPU works.
Obviously, this meant i needed to build my own, gate-by-gate.
# simple beginnings
I started out by picking a simulator to build a CPU with (because the real world sucks), and eventually settled on [Virtual Circuit Board](https://store.steampowered.com/app/1885690/Virtual_Circuit_Board/): it's a great simulator, and it looks nice too.
Then, I went ahead and built some basic circuitry that I knew I'd need, like an 8-bit adder and registers. This all was fairly straightforward-- the adder, well, added numbers. It wasn't too complicated. However, I still had no idea where to start the CPU itself.
# where is the start
So, without any clue as to how to structure the CPU, I was lost. So, I googled. And googled. You know, the internet has everything these days.
However, most of the documents I found online were either too complicated for me to understand when I'd never built a CPU at all before, or it didn't really have enough detail to be useful. Not many fell in the middle.
I ended up consulting someone I knew that understood this stuff and they told me that I should begin by making an instruction set architecture, and then a finite state machine for the control circuitry.
# instruction sets and architecture
I knew that I wanted my CPU to be simple, so I stuck with 8-bit data and 16-bit addresses. I don't remember much else (this blog is a recap of everything that's happened), but the overall instruction set ended up looking like this:
* `ASTL` - Set accumulator low to constant
* `ASTH` - Set accumulator high to constant
* `MSTL` - Set memory pointer low to accumulator
* `MSTH` - Set memory pointer high to accumulator
* `OPER` - Perform ALU operation given by the constant with accumulator as input A and temp register as input B, output to accumulator (LT = overflow; EQ = 0)
* `JUMP` - Offset program counter to the contents of the accumulator - 0x7F
* `JMPZ` - Offset program counter by accumulator - 0x7F if status register signals 0
* `JMPO` - Offset program counter by accumulator - 0x7F if status register signals overflow
* `LOAD` - Load the contents of memory location X into the accumulator, where X is the contents of the memory pointer + constant
* `STOR` - Store the contents of the accumulator into memory location X, where X is the contents of the memory pointer + constant
* `SWAP` - Swap the contents of the accumulator and temp register
* `MADD` - Add the accumulator to the memory pointer
There are two 8-bit registers and one 16-bit register accessible to the programmer, respectively the accumulator, temporary register, and memory pointer.
The accumulator serves as one of two operands for the OPER instruction, with the temp register being the other.
The memory pointer is used as a base address register for memory operations, such as `LOAD` and `STOR`
Each instruction uses the upper 4 bits to determine what to do, and the lower 4 bits are constant data.
# finite amounts of state in the machine
The purpose of the finite state machine for the control circuitry is to determine which control lines should be active at any time. Each state has an output bit for each control line and rules to determine which state to transition to on the next clock cycle.
Given the general CPU order of operations, I knew the states had to go something like
`fetch instruction` > `increment program counter` > `execute instruction`.
Fetching the instruction was fairly simple and consisted of three states, aptly A, B and C.
State A told the program counter to output to the address bus, and that the memory address in should read from the address bus.
State B sets the memory to output to the data bus.
State C keeps outputting the memory to the data bus, but also tells the instruction register to read from the data bus.
Incrementing the program counter was pretty similar.
# state transitions
The transition logic looks like this: `A` > `B` > `C` > `INCPC A` > `INCPC B` > `??`, where `??` are the instruction-specific states. Now, since these states vary based on what the instruction register is, the instruction register is an input to the state machine. Most of the time, it doesn't care about the instruction register. The one exception is when the current state is `INCPC B`. This is the state right before the instruction starts to execute.
After an instruction is selected, the state machine goes through each state required to run the instruction. For example, the `ASTH` instruction has two states `ASTH A` and `ASTH B`.
# into the matrix
With the finite state machine complete, I then had to get it into the simulator. Of course, I wasn't going to place every gate by hand for the state transition lookup table, so I found a program to do it for me: [vcbtool](https://github.com/JC3/vcbtool). However, the lookup table generator in this application doesn't allow "don't care" bits, so I wrote a short program to convert the truth table to not use "don't care" bits. This lazy workaround cost a lot of space in the simulator, though, since every possible combination of "don't care" bits needed to be explicitly set as 0 or 1 in the lookup table.
For reference, here's a screenshot of the entire CPU. The long strip of gates is the transition lookup table.
![VCB CPU](/resources/images/vcb_cpu.png)
Generating the state machine output was easier since there were no "don't care" bits in the output, and any invalid states would output 0 if not in the lookup table.
# wiring it all together
All that was left was to wire together the components I made at the very beginning with the control circuitry.
At least, I thought.
The timing was off for some instructions, and they didn't work properly as a result.
This took a while to debug as I had to step through the simulation and watch the individual gates flip to figure out how to adjust the timing.
# extra goodies
You can get the latest version of the design [here](/files/8bit.vcb).

View File

@ -1,10 +0,0 @@
---
layout: post
tags: test2
---
This is a test post but 2.
# here is a heading
Here is a link: [wow]()...

View File

@ -1,14 +0,0 @@
---
layout: post
tags: test1 test2
---
This is a test post.
# here is a heading
Here is a link: [wow]()...
some code? more likely than you think
`code`

136
files/8bit.vcb Normal file

File diff suppressed because one or more lines are too long

View File

@ -7,6 +7,6 @@ title: Posts
<ul>
{% for post in site.posts %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
<li><a href="{{ post.url }}">{{ post.title | downcase }}</a></li>
{% endfor %}
</ul>

Binary file not shown.

After

Width:  |  Height:  |  Size: 274 KiB

View File

@ -97,3 +97,9 @@ ul li {
ul li a {
left: -20px;
}
img {
box-shadow: 4px 4px 0px 1px #300;
max-width: 100%;
max-height: 100%;
}

View File

@ -9,7 +9,7 @@ title: Posts by Tag
<h2>{{ tag[0] }}</h2>
<ul>
{% for post in tag[1] %}
<li><a href="{{ post.url }}">{{ post.title }}</a></li>
<li><a href="{{ post.url }}">{{ post.title | downcase }}</a></li>
{% endfor %}
</ul>
{% endfor %}