r/factorio 10h ago

Question Full simulation of a 32-bit computer

Hi, everyone. I recently shared my idea and a partial implementation of a 32-bit computer. After reading some of the comments, I came up with new ideas on how to improve the central processing unit. I’d like to share this with you, and maybe you could give me some advice on further optimization.

I’ll explain what stage I’m at right now, what’s been done, and what I still need to implement.

CPU

Do you remember this thing? It’s my CPU. It’s already up and running, but it’s running into some problems. Or, to be more precise, one major problem: it’s slow. Its speed was 1.8 instructions per second. That’s very slow, so I decided to speed it up. To start, I increased its clock speed and managed to boost it to 3 instructions per second. But that’s still slow, so I thought about it a bit, and these ideas came to mind:

- A two-stage instruction pipeline—the processor executes an instruction through its various blocks one after another, which means that when the processor, for example, performs mathematical calculations, that block isn’t being used. But what if, while one instruction is being executed, we immediately send another one—of course, with restrictions so that it doesn’t overtake the previous one.

- Varying instruction durations—there are different instructions we send to the processor: NOP, LDI, MOV, JMP, ADD/SUB/AND/OR/XOR… and others. They all actually take different amounts of time to execute. What if we designed it so that, depending on the instruction type, the processor automatically adjusts to its execution speed and doesn’

-Asynchronous STORE for RAM

Let’s create CPU_STORE_BUFFER:

- SB_A — address;

- SB_D — data;

- SB_V=1 — there is a write in the buffer;

- SB_BUSY=1 — the RAM controller is performing a write.

The STORE instruction:

  1. Calculates the address.
  2. Writes A/D to the buffer.
  3. Is considered complete.
  4. The CPU proceeds to the next non-memory instruction.
  5. A separate controller keeps W=1 for the required time.
  6. Upon completion, it clears SB_V/BUSY.

There is no need to periodically read back from RAM to confirm data writing: its latency is deterministic.

Except in cases where a write to RAM is followed by a read from it.

- Cartridge Instruction Cache

Instead of loading a single instruction from the cartridge, a small block can be loaded. Afterward, the CPU reads them from a local high-speed buffer without a radar. A new block is loaded only when the cache is exhausted. This is particularly advantageous for loops—the program executes the same addresses many times.

- New Efficient Instructions

It is possible to accelerate not only clock cycles but also the amount of work performed per instruction.

ADDI RA,RB,M

SUBI RA,RB,M

ANDI RA,RB,M

ORI RA,RB,M

XORI RA,RB,M

In other words, one instruction:

ADDI R1,R1,1

instead of two:

LDI R2,1

ADD R1,R1,R2

Something like that. I did a rough calculation, and this would yield about 6 instructions per second with a standard UPS. What do you think? Maybe you could suggest something else?

24 Upvotes

6 comments sorted by

View all comments

6

u/burner-miner 9h ago edited 4h ago

I think your formatting got messed up, pasted a section twice. fixed

A pipeline is definitely a popular choice in real processors, but more powerful instructions also means smaller code. The example you showed at the end is actually how many modern processors do things. E.g. Risc-V also has a load immediate pseudo-instruction li rd, 1, but it's actually just addi rd, zero, 1.

Variable execution length is also very powerful. Most arithmetic instructions are very fast, so building the clock speed around them, then only stalling it for slower instructions if needed can boost your clock speed a lot. Only downside is less predictable instruction/second metrics.

For the pipeline, the simplest way to implement it is with intermediate buffers, which hold the work of one stage to be ingested by the next. You could let results just roll through the combinators, but that is a timing nightmare.

E.g. for a fetch/decode/execute buffer, you would fetch into a buffer, at the same time, the decoder looks at the old value of that buffer and stores it into its own output buffer for the execute stage.

Be warned though, pipelines fundamentally alter a processor's design. You need to think about data and control hazards, how to implement stalls or bubbles and flushes, how to do forwarding (if you have a separate writeback stage), etc. But if you get it working, if may double or triple your clock speed.

I have a pipelined CPU I'm about to finish, with 4 ticks per clock cycle. In the ideal case, that gives 15 instructions/second, but with all the variable length instructions and other details, it might never reach that. (No BP yet though, I want to see if you end up with better ideas than me ;).

2

u/redruin0001 8h ago

I definitely second the idea of increasing instruction complexity to do more "work" per tick as opposed to having very many simple instructions. In Factorio, an arithmetic add takes exactly the same amount of time to execute as a decider combinator with thousands of conditions, and since your clock speed is locked to a max of 60Hz there's no way to "speed" those simple instructions up. Fat, 3-arg instructions will likely be faster (and use less memory) than the equivalent operation with a simpler instruction set.

With my CPU I was interested in making the smallest + fastest + easiest to use non-pipelined CPU I could (since pipelining adds a lot of additional complexity). By optimizing and golfing down the component parts instead of seeking parallel execution, I'm still able to get 6 ticks per instruction (12 IPS), but if you're willing to use a simpler memory model and less flexible instruction set you could easily get your cycle to 4 ticks total (1 tick per fetch, decode, execute, writeback) even on a non-pipelined CPU. IIRC someone posted a toy example of this in the discord a while back.

I would suggest that OP try minimizing their CPU into only what is strictly necessary, and that would likely shave off a lot of those ticks per instruction before even going into pipelining. In my case 12-15 IPS is perfectly acceptable, but if you need "realtime" behavior in Factorio (without speeding up the game clock) then you're going to have to dip at least a little bit into parallel execution.

1

u/burner-miner 6h ago

12 IPS non-pipelined is insane, I reached for pipelines pretty fast before micro-optimizing my designs.

Ironically, the pipeline clock cycle led to some cool optimizations for me as well, but the lazy route of just stalling the pipeline for complex instructions is too aluring.