PC 8-bit CPU

Haku

Skeletron Prime
I spent quite a while working on this huge 8-bit CPU, I tried to make it as powerful as I could. It has an 8-bit data bus, a 32-bit instruction bus, 32 bytes of data memory, and 1KB of program memory. It can handle a 1-tick clock so it has a max speed of 30 instructions per second, this halves my framerate with frameskip off, and sends it straight down to 1fps with frameskip on.

I haven't found the perfect clock speed for it yet, it's a choice between sacrificing speed for framerate or framerate for speed.


Since this CPU is capable of a lot more than just simple fibonacci, I thought I'd share the world & instruction set text file incase people want to make their own programs for it. Eventually I'm going to make a collection of minigames that I or other people have made for it to run, so you can select which game you'd like to play when you turn it on.
 

Attachments

  • CPU.wld
    6.6 MB · Views: 365
  • 5743.txt
    4 bytes · Views: 226
Super Cool! Thanks for sharing and good job! The instruction file (I think it is 5743) only contains 5743. Am I missing something or is that unintentional?
 
Not sure why it says 5743, it looks like the file was open as I uploaded it so something went wrong. Here's what I meant to upload:
 

Attachments

  • Instruction Set.txt
    3.2 KB · Views: 249
Not sure why it says 5743, it looks like the file was open as I uploaded it so something went wrong. Here's what I meant to upload:
Interesting.
Instead of saying "that the instruction bit is persistent" in my opinion it would make more sense to say "this thing is stored in a register, and you can only change it by XORing it with some other constant".
This a huge pain though especially with the memory address...
Also, as far as I can tell just by looking at that text file, there is no way to do any sort of indirect memory access on this. You can't do subroutines either. Not even with a Wheeler jump, cause the program memory is read only. This is a bit of a bummer...
But anyways, still, you did a very good job with this. :) Not bad at all for the first ever logic gate computer in terraria. I look forward to writing some code for this.
 
Last edited:
The branching commands only jump to a fixed address, so function call is not possible. Anyway I should study how you build your data path. I have put my computer plan for a while since building the data path is painful.
 
I have made smaller CPUs in Terraria, but they've all been using a very basic RISC and none have really had enough memory to write a proper minigame, as far as I was concerned if it could do fibonacci it was good enough. I plan to keep making more, building on them as I understand how they work in real life. This is the first one I've made that can handle a 1-tick clock with a good amount of program memory designed for more complex things.

As for the subroutines, that's definitely something I'm going to add in the next one. Now I can use this as a baseline and improve the programming side of it rather than the scale of it, I was getting a bit stuck with space and thinking about what I needed to add.
So, as a list of things I should add for the next one; I would want to add a command allowing you to write to the program memory, allow you to branch to an address in the data bus or a data register, add a subroutine that allows you to save data in the data bus to the next empty RAM location, use one of the expansion bits in the instruction structure to decide whether or not to save the data/executed instruction in a register, and also increase the size of the data memory.

Thanks for the feedback both of you, I appreciate it. 😁
 
So, I spend my afternoon reverse engineering this CPU, which I did enjoy a lot. Though it would have been helpful if you actually labelled some of the components of the CPU.... Here's what I figured out:

wires.png

I've also reworded some of the stuff you wrote in "Instruction Set.txt" and came up with an assembly language. It"s a little weird, since basically every single instruction can apply modifications to the MEM register, put things into DATA for one cycle, branch and do another thing. So, it's like every instruction is actually 4 instructions.

EDIT: To avoid some confusion, I should say the text file I attached also contains the Fibonacci numbers program, translated into assembly. This is the program @Haku included in the world download, I didn't come up with it myself.

In reality the machine also store a lot more "states" than it appears to. I guess this is just a thing that terraria wiring does in general, but it really makes it hard to wrap your brain around this thing. I kind of fixed this by calling DATA and ALU "pseudo registers". Cause they have a state, they can store things, therefor, they are registers.

I drew a little chart about, how you can actually move data between all these registers. The fact that some operations set registers and some XOR them, is really confusing. It also means you have to give out a lot more instructions to do simple things.

Making the memory address part of the instruction actually set the address would make things a hell of a lot more understandable. I don't think it would too difficult to wire that up either. The XORing might make some room for reusing code for accessing different places in memory, but that's not very useful without more powerful branching facilities.

The other major weirdness is with the output of the ALU (what I call the ALU register) and the accumulator. Having ACC there as an extra buffer is pretty redundant. Also it means you have to pretty much do ADD twice, every time you want to do an addition, just to reset the ALU register. What would make more sense would be to have what is now called ALU be the new ACC. Basically connecting the ALU directly to DATA through a gate. So, ST.ACC would no longer be necessary. Instead you can have an extra instructtion to clear, what we now call ACC, but used to be the old ALU register.

I don't know if that makes any sense. What I mean to say, is instead of this:
Code:
                                   MEM[MEM] --(XOR)--> |      | --(SET)--> MEM[MEM]
                                         IN --(XOR)--> | DATA | --(SET)--> A
                                                       |      | --(SET)--> B
(A + B) --(XOR)--> ALU --(SET)--> |  ACC | --(XOR)--> |      | --(SET)--> OUT
You should have this:
Code:
                                   MEM[MEM] --(XOR)--> |      | --(SET)--> MEM[MEM]
                                         IN --(XOR)--> | DATA | --(SET)--> A
                                                       |      | --(SET)--> B
                (A + B) --(SET)--> |  ACC | --(XOR)--> |      | --(SET)--> OUT

The whole manually enabling the output of certain components to DATA is a bit non standard, this really fells like something the instruction decoder should figure out. But it does work pretty well, and it cuts down on the number of opcodes you need. So, yeah, it's not a bad idea actually.

Another weird thing I can see is with the carry bit of the ALU... It's not actually connected to the carry input of the ALU. The point of this bit in a real CPU, apart from being a branch condition, is to lets you easily add numbers that are bigger than the registers and the ALU can handle. Here to do that, you'd have to like do a branch based on the carry, set the carry in if it's one, and then also reset it at the end somehow.

Other than the occasional weirdness, this does seem like a pretty capable little CPU. :) I do understand though that these sort of conventions, and the reasons they are this way, are probably not self evident to most people. Like, the only reason I know about this is because I had several university courses specifically about this, so I had no choice but to learn it...
 

Attachments

  • HakuASM.txt
    10.3 KB · Views: 265
Last edited:
Wow, that's a lot more in depth than the quick instruction set a whipped up before! It's a lot more clear too (you do need to branch to one address before where you want to go, branching takes place at the end of one instruction and incrementing the PC happens at the start of the next instruction). I also had an idea about having two opcodes per instruction, and two decoders, where 0000 does nothing so that you could read from mem 0 and write to reg A in one instruction, then read from mem 1 and write to reg B in another, because currently you can only do one thing at a time, even when doing two at once would work fine.
Everything on your diagram was labelled correctly too by the way. 😉
 
Back
Top Bottom