CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

This board is to show, discuss and archive useful combinator- and logic-creations.
Smart triggering, counters and sensors, useful circuitry, switching as an art :), computers.
Please provide if possible always a blueprint of your creation.
Altarus
Burner Inserter
Burner Inserter
Posts: 9
Joined: Sun Jun 16, 2024 7:13 pm
Contact:

CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Altarus »

Here's something I've been working on for the past few months.
I wanted a good CPU to help automate tasks which are too complex for the more usual kind of Factorio combinator logic - but I couldn't find a nice design that I particularly liked, so I made my own CPU instead.
Screenshots:
Screenshot of CPU
Annotated version
Notice how the program ROM occupies much less space in the blueprint (and uses a lot fewer combinators) than the data RAM, despite the former having 12x more capacity than the latter... such is the power of packed ROM storage.
Blueprints:
These had to be added as attached text files, since they are too large for the forum post size limit.
EDIT: Replaced the blueprints with a slightly modified version that is now compatible with Space Age. The functionality of the CPU has not been affected in any way.
BP - CPU with blank program ROM.txt
(46.52 KiB) Downloaded 17 times
Overview:
Long story short, here's a quick outline of the design I've ended up with:
- 57 powerful instructions; most of the arithmetic and logic ones execute in 6 ticks (10 IPS),
- x86-like addressing modes; most opcodes accept m32/r32 for the destination and imm32/m32/r32 as the source,
- 8 general purpose registers (including ESP),
- separate program (code) and data address spaces,
- packed program ROM: holds 10 instructions (lines of code) per each constant+decider combinator pair,
- memory-mapped I/O registers; easily extendable to any size,
- as-blueprinted it comes with 1200 lines of code ROM and 100 cells of data RAM, as well as 10 inputs and 10 outputs,
- microcode pipeline allows very complex and powerful instructions to be created (for example, POPAD reads 7 registers from the stack in a total of just 9 ticks),
- Factorio 1.x compatible: none of this fancy new Space Age stuff; it's some of the finest, time-proven, old-fashioned vacuum tube technology right here,
- now also compatible with Space Age.

Of course, there are also some important differences from the real x86.
After all, my design goal was not to create an exact reproduction of the x86's capabilities, but rather to create an efficient and effective CPU; completely sufficient for the kind of tasks that could be reasonably expected of it in Factorio.
To that end, I considered it counterproductive to try and exactly copy every single aspect of the x86, since attempting to do so would hugely increase the CPU's complexity for very little practical benefit.

In particular, the following x86 features didn't make the cut:
- interrupts of any sort (both hardware and software); I know that this will make at least some people unhappy, but I have no need for interrupts in my intended use cases, and adding support for them would entail significant extra complexity,
- everything involving the FPU (and floating point numbers in general) is not available,
- carry and overflow flags are not implemented (as most Factorio automation tasks don't really need to manipulate 32+bit numbers anyway),
- memory (RAM) addressing modes are significantly reduced compared to the real x86 (with realistically feasible amounts of RAM, you sure won't be working with complex data structures here anyway),
- some of the string handling instructions, such as INS/OUTS/SCAS, as well as REPZ/REPNZ prefixes (but not REP), are also gone,
- also not available are the CMOVcc instructions, as well as bit scan/test/set ones; all of those would be quite complex to implement and slow to execute within the limits imposed by Factorio's combinator logic (at least, as of 1.x).

But, enough about all that boring stuff - now let's take a look at what interesting things we can do with this.
Nuts & bolts:
First off, the most important part - instruction encoding and instruction set:
Details of instruction encoding
Instruction set - opcodes
Next up, flags:
Details of flags and related logic
Further technical details:
CPU reset and initialization
Expanding ROM and RAM capacity
HELL WORLD:
Ah yes, of course there is a HELL WORLD example program included with the CPU. Sure enough, I mostly play deathworld, why do you ask?
But, more seriously now:
The example code is nothing special, it's just a rather simple implementation of a prime number generator which checks every consecutive odd positive integer if it is prime, and outputs it to the display if that is the case.
Because it is just an example program, I've deliberately avoided implementing some performance improvements in the code in order to showcase a wider variety of the available opcodes.

Code: Select all

Address	Opcode	Data	Mnemonic
entry_point:
1	80027	601	MOV	EDI,601
2	52	2	STOSD	2		;deal with the special case of 2 being a prime
3	30027	1	MOV	EBX,1
4	60027	600	MOV	EBP,600
5	21	7	JMP	.main_loop

.output_result:
6	210327	1	MOV	[EBP+1],EBX	;output result to display

.main_loop:
7	30002	2	ADD	EBX,2		;only check odd numbers - since by definition, evens >2 can't be prime
8	335	-	PUSH	EBX
9	4	22	CALL	isqrt
10	40227	-	MOV	ECX,EAX		;setup loop counter
11	40010	2	IDIV	ECX,2		;we only check odd divisors, so halve the loop counter
12	90027	1	MOV	ESI,1

.trial_loop:					;check every possible odd divisor from 3 to isqrt(x)
13	90002	2	ADD	ESI,2		;start with ESI=3
14	90307	-	CMP	ESI,EBX		;deal with the special case of input = 3, which would otherwise fail to be recognized as prime
15	20327	-	MOV	EAX,EBX
16	19	6	JZ	.output_result	;optimization: not having Jcc directly following CMP causes no extra delay to be inserted into the pipeline
17	20926	-	MOD	EAX,ESI		;does it divide exactly (without remainder)?
18	20255	-	TEST	EAX,EAX
19	25	13	LOOPNZ	.trial_loop	;loop while it doesn't
20	20	6	JNZ	.output_result	
21	21	7	JMP	.main_loop


;find integer square root using Heron's method, input is passed on the stack, output in EAX
isqrt:
22	60731	-	PMOV	EBP,ESP
23	22127	2	MOV	EAX,[EBP+2]	;s
24	40227	-	MOV 	ECX,EAX		;s
25	20043	1	SAR 	EAX,1		;x0=s/2
26	50427	-	MOV 	EDX,ECX 	;s
27	50210	-	IDIV	EDX,EAX		;s/x0
28	50202	-	ADD	EDX,EAX		;x0+(s/x0)
29	50043	1	SAR	EDX,1		;x1=(x0+(s/x0))/2

.loop:
30	50207	-	CMP	EDX,EAX		;x1,x0
31	16	38	JGE	.loop_end
32	20527	-	MOV	EAX,EDX 	;x0=x1
33	50427	-	MOV 	EDX,ECX 	;s
34	50210	-	IDIV	EDX,EAX		;s/x0
35	50202	-	ADD	EDX,EAX		;x0+(s/x0)
36	50043	1	SAR	EDX,1		;x1=(x0+(s/x0))/2
37	21	30	JMP	.loop

.loop_end:
38	60032	-	POP	EBP
39	38	1	RET	1		;EAX contains x0 (result)
Closing remarks:
Last but not least, some important notes about practical usage of this CPU:

1. Normally, only one of the operands in an opcode can be a memory address; it is not possible to specify different memory addresses for the destination and the source for any instruction.
Note that technically it is valid to specify both the destination and source to point to the same memory address, however it would be rather unhelpful in most situations.
However, I can nonetheless see a few meaningful use cases, such as:
- using IMUL to square a variable stored in RAM,
- using XOR to clear a RAM variable in 1 instruction,
- using ABS on a memory location.

2. It is not possible to have both a memory operand destination and an immediate source value in the same instruction. Technically, this does work when used - however, the base memory address will be inevitably the same as the immediate value, which is generally quite unhelpful (even more so than in the preceding point).

3. Reading data values from program ROM is not possible, as there is no mechanism to allow program ROM contents to be accessed as data - and conversely, it is also not possible to execute data stored in RAM as code, either.
When using program ROM for the storage of constants (strings, lookup tables, etc) this has to be done by embedding the data values in valid instructions and executing them at an appropriate time.
If random access to the data is needed, storing the constants in RAM is one potential course of action. Also see example code below.
Another option would be to use the I/O logic with an array of inputs wired to constant combinators, to effectively implement a "data ROM" to use for lookup tables or other similar uses - without needing to use valuable RAM space for the storage of arrays of constants.
Example of storing data constants in program ROM
4. Unless otherwise specified for a particular opcode, all opcodes accept m32/r32 for the destination and imm32/m32/r32 as the source.

5. For the abovementioned purpose, r32 means any register other than EIP. As with the x86, there is no way to directly read the value of EIP.

6. I/O registers are mapped to the data RAM address space, and accessed in the exact same way as any other data RAM location. In the blueprint, it uses addresses 601...610 for the outputs and 801...810 for the inputs.
The "Raw fish" signals in the I/O block are obviously meant as placeholders; replace them with whatever signal you want to be using for any particular input or output, such as with the "Blue" signal used as an output to the display.

7. HCF instruction actually has a valid use, as a crude but functional form of a debugging breakpoint. Simply replace any instruction with HCF and the CPU will spinwait when it reaches that point, until it is replaced with another opcode.

8. I have not yet tested this in Space Age, only in 1.x version. However, I don't think there's anything in Space Age (based on the new features I know about) that should cause anything to break here.

9. While I have done a fair amount of testing on this CPU - including some rather obscure "corner cases" I could think of - and it appears to work fully as intended, nonetheless I cannot reasonably claim that it is totally entirely 100% bug-free; it is quite possible that there are still some weird corner case bugs lurking in there, waiting for the unwary victim.
Last edited by Altarus on Wed Apr 09, 2025 2:19 pm, edited 1 time in total.
eugenekay
Filter Inserter
Filter Inserter
Posts: 404
Joined: Tue May 15, 2018 2:14 am
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by eugenekay »

I quit my dayjob to play Factorio: Space Age. It has been fun, I have built some crazy contraptions along the way.


But you, sir, are crazy. Bravo! :shock:
Premu
Fast Inserter
Fast Inserter
Posts: 147
Joined: Wed Nov 13, 2019 4:40 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Premu »

Wow, that's surprisingly compact!

Did you set up this all manually, or did you use some scripting as support?
Altarus
Burner Inserter
Burner Inserter
Posts: 9
Joined: Sun Jun 16, 2024 7:13 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Altarus »

Thanks.
No scripting was involved, the only "tool" I used was MS Notepad for writing down all my notes.
Well, that, and of course Factorio's built-in map editor - to allow single-stepping through the simulation ticks while verifying the correct propagation of signals. Would've been a heck of a task without being able to use that.

Design wise, easily the hardest part was figuring out the overall basis for the CPU's operation. By this, I mean the underlying architecture, as in - how the functional blocks should interact with each other.
The instruction decoder was what I spent the most time on, as it took me some months to figure out how to allow arbitrary opcodes to manipulate arbitrary registers/memory... without having to hardcode every possible combination as a separate instruction (a clearly intractable proposition, considering all the possible opcode/input/output combinations); and also without incurring a lot of ticks of delay in the process and/or requiring 100's of extra combinators.

It's kinda funny in a way, because I ended up doing basically nothing in-game with this for a good few months, after placing hardly anything in terms of logical processing at first (initially only part of the program ROM and data RAM) - and then finished the whole thing in only about 2 or 3 weeks at the end.

And no part of that process was something where using some kind of script would've been of much help, anyway.
I suppose that in principle, designing the opcodes - specifically, calculating the exact microcode timings without resorting to at least some trial-and-error - was the one part that could've been made faster with a script - but, then again...
...it would've probably taken far longer to write and test+debug a suitable script than just to design and test all the 57 opcodes manually, as XKCD 1319 conveniently illustrates.

Oh, and I wasn't joking about the part where I mentioned MS Notepad as a design tool. Here, I'll show you with an example yanked straight from my scratchpad, of what is likely the most complex opcode in terms of the amount of microcode involved:

Code: Select all

PUSHAD:

Signal:			Function:			Binary representation of microcode signal value:				Decimal value:
Modular armor 		Decrement ESP			00000000000000000001010101010101   (it is one tick behind the other signals)	5461
Grenade 		Write to [ESP]			 00000000000000000111111111111110						32766
C signal 		Clear current memory cell	  00000000000000000101010101010100 (it is one tick ahead of the other signals)	21844
Rocket control unit	Read EAX			 00000000000000000000000000000100						4
Rocket fuel		Read ECX			 00000000000000000000000000010000						16
Nuclear fuel		Read EDX			 00000000000000000000000001000000						64
Low density structure	Read EBX			 00000000000000000000000100000000						256
Uranium-235		Read EBP			 00000000000000000000010000000000						1024
Used-up uranium f. cell	Read ESI			 00000000000000000001000000000000						4096
Uranium fuel cell	Read EDI			 00000000000000000100000000000000						16384

Blue signal		Fetch next instruction		 00000000000000000000001000000000						512
Storage tank		Increment EIP			00000000000000000000001000000000						512
mmmPI
Smart Inserter
Smart Inserter
Posts: 4252
Joined: Mon Jun 20, 2016 6:10 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by mmmPI »

Hey sorry if this sounds like a criticsm, or an unwanted suggestion, i wanted to ask if you could maybe provide a little something on "how to use" in a way that makes its obvious it is working, like : "do this, this, this, and it should look like this, (and this is what happened)". I'm saying this because regarding :
Altarus wrote: Thu Apr 03, 2025 9:22 pm 8. I have not yet tested this in Space Age, only in 1.x version. However, I don't think there's anything in Space Age (based on the new features I know about) that should cause anything to break here.
Unfortunatly i am unable to tell if it's preventing the machine to work, because i don't know what i should be seeing/not seeing. But there's an error message referering to the removal of the rocket unit control as item in space age when importing the blueprint that has a display connected to it, so it may be required to use another signal instead .

You may need to explain in quite simple word or as chat gpt puts it "Please explain it like I’m a cat in a room full of laser pointers". how and where is the HELL WORLD supposed to be visible to confirm proper installation :?
Altarus
Burner Inserter
Burner Inserter
Posts: 9
Joined: Sun Jun 16, 2024 7:13 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Altarus »

mmmPI wrote: Tue Apr 08, 2025 11:51 pmUnfortunatly i am unable to tell if it's preventing the machine to work, because i don't know what i should be seeing/not seeing. But there's an error message referering to the removal of the rocket unit control as item in space age when importing the blueprint that has a display connected to it, so it may be required to use another signal instead .
Oops. Thanks for the feedback, though - currently I do not have the means to test anything in Space Age, only in 1.x/2.x at this time.

It's quite ironic, really, considering that a couple months ago I had this exact same kind of problem when trying to import a blueprint of piriform's old CPU design from around version 0.15 - in that case, the issues were with the long-since-missing "alien artifact" signal (purple goo) and the "raw wood" signal.
I really wish that the Factorio devs (and in fact, also a lot of other game devs) would stop doing this kind of thing with regards to backwards compatibility, but... oh well, it is what it is, and complaining about it probably won't accomplish anything of value.

Anyway, back to the more important things - here's an updated blueprint, try this one instead. If it works in Space Age now, I'll update the OP as well to reflect the change.
I replaced the offending signal with the pistol signal, it was only used in a few places so it didn't take long to fix.
Now, to answer your other question:
There isn't really any trick to using the blueprint - plop it down, have bots place all the entities, then connect the power after everything's been built.
I would not recommend attempting to build the BP with powered power poles already present within wire range, as I know for a fact that certain parts of the CPU can cause funny behavior when it's powered up before having been fully placed first.

In any case, once it's built and powered up, you should be able to see it run after about 1 second if there's any valid program present in ROM - the most obvious indication that it's working are the rapidly flashing lamps in the middle. Also if you hover mouse cursor over the decider combinator labelled EIP, the output value changes quite frequently when everything is working correctly.

Oh, and the HELL WORLD example program doesn't print any text - in fact it can't, as the display only works with numbers and not text.
But when it is working correctly, you should see the display update every couple of seconds with the next prime number in the sequence (2,3,5,7....)
mmmPI
Smart Inserter
Smart Inserter
Posts: 4252
Joined: Mon Jun 20, 2016 6:10 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by mmmPI »

Altarus wrote: Wed Apr 09, 2025 3:37 am Oops. Thanks for the feedback, though - currently I do not have the means to test anything in Space Age, only in 1.x/2.x at this time.
I haven't tested much of 2.x , mostly Space Age, so i'm not too familiar with the state of 2.x, but i understand from this and after checking again the BP quickly that it doesn't appear to use what i "think" is present in 2.x in terms of new functions given to circuits. That would break with 1.x saves though. the 1.X compatibility is the "retro-hard mode" x).
Altarus wrote: Wed Apr 09, 2025 3:37 am Now, to answer your other question:
There isn't really any trick to using the blueprint - plop it down, have bots place all the entities, then connect the power after everything's been built.
I would not recommend attempting to build the BP with powered power poles already present within wire range, as I know for a fact that certain parts of the CPU can cause funny behavior when it's powered up before having been fully placed first.
My usual way is to use the /editor, so everything is pasted at once, added the electric energy interface and it worked much better than last time ! :lol: I can confirm it was broken last time without rocket unit.

Altarus wrote: Wed Apr 09, 2025 3:37 am In any case, once it's built and powered up, you should be able to see it run after about 1 second if there's any valid program present in ROM - the most obvious indication that it's working are the rapidly flashing lamps in the middle. Also if you hover mouse cursor over the decider combinator labelled EIP, the output value changes quite frequently when everything is working correctly.

Oh, and the HELL WORLD example program doesn't print any text - in fact it can't, as the display only works with numbers and not text.
But when it is working correctly, you should see the display update every couple of seconds with the next prime number in the sequence (2,3,5,7....)
absolutely ! ok now i can proceed reading the rest of the manual x)
Altarus
Burner Inserter
Burner Inserter
Posts: 9
Joined: Sun Jun 16, 2024 7:13 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Altarus »

Thanks for confirmation, I've updated the OP with the fixed blueprints.

And yeah, you are correct - the CPU does not use any Space Age or even 2.x new features. In fact, it was developed and tested by me in 1.x.

There is in fact a very good reason why I'm staying with 1.x for the time being - and no, this has nothing to do with my opinions about certain changes in 2.x/Space Age.
No, said reason is actually closely related to 1.x being "hard mode" as compared to 2.x, in more than one way - but I won't be bringing this up again until these differences become directly relevant to what I'm currently working on, which probably won't be finished for another good few months yet.

I've taken a quick look at the new 2.x circuit network features, but nothing in there seems like it would be particularly helpful for the CPU, anyway.
Maybe I'm overlooking something as it was only a cursory examination, but it doesn't appear that the new selector combinator would be of much (any?) benefit here.
About the only thing I've found in 2.x that would be at least minimally helpful here is this: "Decider combinator output constant can be changed." This would've been nice for the direction flag logic (saving some 1 or 2 combinators as a result), but it's still nothing that would be in any way truly groundbreaking in the overall big picture.
mmmPI
Smart Inserter
Smart Inserter
Posts: 4252
Joined: Mon Jun 20, 2016 6:10 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by mmmPI »

That's surprising to read for me :shock:. I would have thought the 2.x feature would seem more meaningful the more expert you/your creations. I can understand 1.x functions are very strong/ turing-complete, and maybe that's all you needed, but to me they ease things out and i can't imagine you wouldn't find a way to take advantages of them.

I could understand if you don't feel like doing it after all the hardwork , but if you DO feel like it, i would appreciate if you could provide a little more information, on "how to use" , not the CPU itself, but your documentation . To add more context to the cat chasing laser pointer metaphora, i don't know why i'm doing it but i try to follow :) , you provided engineer documentation about your machine, but i'm no engineer, nor technician, i feel like i'm presented a complicated machine that i can only "try to understand" but it feel monolithic and i'm not sure where to start.

To illustrate its capabilities in a way that would be obvious to me, the "how to use" if you could guide / explain through a list of ordered steps like a A=>B=>C list where i would maybe , try to replicate what you did to make the "blank" blueprint into the "demo" one.

That may seem dead simple to you, but i know if i have like a tutorial with steps to follow and i wonder WHY i'm doing the thing at the moment i'm doing it, it helps understanding.

I thought it would also be the steps to understand to see where to "load" the program , "how" , "what represent the program that count the prime number", in a chronological order, in little steps and in a way for anyone to see for themselves if they had success/ when it is like the demo at the end. So one feel more confident trying to then replicate with "another program".

Maybe it sound backward to gives instructions to use the CPU to someone that doesn't understand how it works, but i'd be happy if i can use something even if don't (fully) understand it and i expect that little "feel good" to fuel my thirst for curiosity, like the laser pointer moving somewhere else suddenly or something :)
Altarus
Burner Inserter
Burner Inserter
Posts: 9
Joined: Sun Jun 16, 2024 7:13 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Altarus »

What I had in mind is that the 2.x new features (selector combinator, mostly) wouldn't be of much benefit in the context of this particular design.

Would it be possible to design a different(better?) CPU that relies on the selector combinator? Quite likely - but I haven't done any analysis of that idea yet, and it is rather unlikely that I would be taking that into consideration anytime soon.
For the time being I'm done with designing Factorio CPUs, I have a working design now and that's good enough for what I want to do with it. I'd rather not spend the next few months on starting back from square 1, when the whole idea of a working CPU is not "the end" but rather it is the "means to an end".

Ok, now for some Factorio CPU programming 101:

1. Clearly, the first step is to write your program in something like Notepad or Excel.
At this point only use the mnemonics and operand names (do not yet worry about converting the instructions to the needed format) - it should look almost exactly like you're writing assembly code for the real x86.
Hopefully you already know how to write functioning assembly code for the real x86 - if you don't, no amount of my explanations will help you make this work, and in any case teaching x86 assembly from scratch would be way way beyond the scope of a forum post (or even a few dozen of them).

Of course, you need to use opcodes that are valid for this CPU - they are mostly the same as the x86, but there are some important differences as well, so make sure to refer to the opcode list to see what usage is and is not allowed for each specific opcode.
For example - on the real x86 opcodes such as IMUL/IDIV are hardwired to use specific registers only, but on this CPU they can use any valid register or even a memory location as the destination operand. Also IDIV does not compute the remainder, you need to use MOD for that... etc, etc.

Let's use an extremely simple program as an example, basically a very primitive form of elapsed time counter:

Code: Select all

Mnemonic:
entry_point:
XOR EAX,EAX	;initialize EAX to 0 (using MOV EAX,0 instead would work just as well)
.loop:
ADD EAX,1	;increment EAX
MOV [601],EAX	;send the value of EAX to the display (wired to memory address 601, using blue signal in the output combinator)
JMP .loop	;infinite loop

2. Next step is to add line numbers (which will correspond 1:1 with program ROM addresses) and convert labels in operands to numerical values of the respective addresses (note how JMP .loop became JMP 2 as a result):

Code: Select all

Line #:	Mnemonic:
entry_point:
1	XOR EAX,EAX	;initialize EAX to 0 (using MOV EAX,0 instead would work just as well)
.loop:
2	ADD EAX,1	;increment EAX
3	MOV [601],EAX	;send the value of EAX to the display (wired to memory address 601, using blue signal in the output combinator)
4	JMP 2	;infinite loop

3. The final step at this stage is to convert the mnemonics to the numerical representation used by the CPU (a pair of opcode:data values for each instruction).
The details of instruction encoding are explained at length in the OP, hopefully I don't need to repeat that here.
Note that in cases where the data value is irrelevant (not used by the particular instruction) I have listed it as "-", it means it can take any value; personally I set the data value to 0 in such cases for clarity.

Code: Select all

Line #:	Opcode:	Data:	Mnemonic:
entry_point:
1	020257	-	XOR EAX,EAX	;initialize EAX to 0 (using MOV EAX,0 instead would work just as well)
.loop:
2	020002	1	ADD EAX,1	;increment EAX
3	010227	601	MOV [601],EAX	;send the value of EAX to the display (wired to memory address 601, using blue signal in the output combinator)
4	21	2	JMP 2	;infinite loop

4. Ok, now we are ready to input the program into the CPU.
The way the ROM is laid out, each constant combinator holds 10 instructions (addresses "X"0 ... "X"9), and they are connected to decider combinators which hold the X value.
So to calculate the actual address of any cell, the equation is:

Code: Select all

Address = (X signal * 10) + (number of signal in constant combinator)
Important: The 0000 address (0th signal of 0th X combinator) is cursed and must never be used. It is marked with RED signals in the blueprint, do not assign any values to those.

Anyway, here is how it should look like after entering the program from this example. Because it is a very short program, we are only using the X=0 combinator which contains ROM addresses 01 ... 09. (compare this with the HELL WORLD program, which uses addresses 01 ... 39)
Click to expand image

5. Finally, you need to set up the I/O to allow interfacing the CPU to the outside world.
In this case, we only need 1 output signal (at address 601), so set up the OUTPUT combinator (rightmost one) with the correct output signal - the display used in the blueprint needs to receive the value on the Blue signal, instead of the placeholder Raw Fish signal:
Click to expand image

6. The CPU is now set up and ready, connect power to it and it should start counting upwards from 1 on the display.
mmmPI
Smart Inserter
Smart Inserter
Posts: 4252
Joined: Mon Jun 20, 2016 6:10 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by mmmPI »

Altarus wrote: Thu Apr 10, 2025 3:18 pm What I had in mind is that the 2.x new features (selector combinator, mostly) wouldn't be of much benefit in the context of this particular design.
Clarified !

The possibility to do each red-wire / each green-wire and the possibility to add much much signals/data onto a single constant combinator seemed to me so powerful that "it would be easy to find a spot where it could be used", but when studying a bit more the CPU, ehh it's not that clear anymore given what seem to be the relation between the biggest number the circuit can handle and the number of different signals/channels used to transmit.
Altarus wrote: Thu Apr 10, 2025 3:18 pm Hopefully you already know how to write functioning assembly code for the real x86 - if you don't, no amount of my explanations will help you make this work, and in any case teaching x86 assembly from scratch would be way way beyond the scope of a forum post (or even a few dozen of them).
Sorry to disappoint but no , i had never done such a thing as what you mention. I was nonetheless able to follow your later instructions which must be proof of their precision ! I had already watched some videos tech related and had some vague idea what a CPU was doing but only as hobbyist. I was taught many things on this very forum which i can use in relation with more advanced videos or tutorials i can find online that would seem abstract without "doing something", i do not intent to stop learning ^^and appreciate what seem to be a simplified environment with whom i'm already (very) familiar to take on more complex task gradually. I wanted to report that so far it is went fine :)
Altarus wrote: Thu Apr 10, 2025 3:18 pm so make sure to refer to the opcode list to see what usage is and is not allowed for each specific opcode.
Don't worry i will :lol:
Altarus wrote: Thu Apr 10, 2025 3:18 pm Let's use an extremely simple program as an example, basically a very primitive form of elapsed time counter:
Perfect ! not too much, only the bare minimum and it's already a lot to wonder about:)
Altarus wrote: Thu Apr 10, 2025 3:18 pm 2. Next step is to add line numbers (which will correspond 1:1 with program ROM addresses) and convert labels in operands to numerical values of the respective addresses (note how JMP .loop became JMP 2 as a result):
Thank your for taking little steps !
Altarus wrote: Thu Apr 10, 2025 3:18 pm 3. The final step at this stage is to convert the mnemonics to the numerical representation used by the CPU (a pair of opcode:data values for each instruction).
The details of instruction encoding are explained at length in the OP, hopefully I don't need to repeat that here.
I wouldn't have had the knowledge to do that all by myself just from the OP for sure but i use it as reference a lot to understand.
Altarus wrote: Thu Apr 10, 2025 3:18 pm Note that in cases where the data value is irrelevant (not used by the particular instruction) I have listed it as "-", it means it can take any value; personally I set the data value to 0 in such cases for clarity.
That tripped me, i had all the "data" offset by one because the first was "-" i missed it . I only realized later when trying to plug the screen that it was here i did a mistake, it was easy to fix and taught me a good lesson.
Altarus wrote: Thu Apr 10, 2025 3:18 pm Anyway, here is how it should look like after entering the program from this example. Because it is a very short program, we are only using the X=0 combinator which contains ROM addresses 01 ... 09. (compare this with the HELL WORLD program, which uses addresses 01 ... 39)
Very useful picture. ! x)
Altarus wrote: Thu Apr 10, 2025 3:18 pm 5. Finally, you need to set up the I/O to allow interfacing the CPU to the outside world.
In this case, we only need 1 output signal (at address 601), so set up the OUTPUT combinator (rightmost one) with the correct output signal - the display used in the blueprint needs to receive the value on the Blue signal, instead of the placeholder Raw Fish signal:
So in my life , half of the time i was told "you won't have enough time to read all the questions / details of the exam before starting to think" the other half i was told " you should read the whole exam/list of question and then plan a strategy", which doesn't help when faced with a new situation x). I'm saying this because to me, knowing there is this step helped a lot not wondering in the unknown while doing the previous. I had in mind the whole time " i'll know if i messed up ONLY WITH THE SCREEN AT THE END".
Altarus wrote: Thu Apr 10, 2025 3:18 pm 6. The CPU is now set up and ready, connect power to it and it should start counting upwards from 1 on the display.
It shouldn't have been connected all along ? I couldn't do with the flashing icons, i had it connected all along. At this point i just "cut" the whole CPU (ctrl X) and then "undo" in editor, so it was all reset with the proper very simple program, and it started to count the numbers 1 by 1 on the screen !
04-12-2025, 00-22-35.png
04-12-2025, 00-22-35.png (1.12 MiB) Viewed 617 times
Thank you for the complementary informations, i feel like i learned new things in a video game that is also real life knowledge, and that is exactly why i stick around factorio !!

Now time to try and make the number goes up 2 by 2 ! x)
Altarus
Burner Inserter
Burner Inserter
Posts: 9
Joined: Sun Jun 16, 2024 7:13 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Altarus »

mmmPI wrote: Fri Apr 11, 2025 10:31 pmThe possibility to do each red-wire / each green-wire and the possibility to add much much signals/data onto a single constant combinator seemed to me so powerful that "it would be easy to find a spot where it could be used", but when studying a bit more the CPU, ehh it's not that clear anymore given what seem to be the relation between the biggest number the circuit can handle and the number of different signals/channels used to transmit.
Looks like I missed that part about the red/green filtering when I was looking at the update notes (still haven't actually tried playing in 2.x yet).
Again, this is something that probably has deeper implications for a potential new CPU design - but even in this one, it could be utilized to at least clean up some of the wiring; there are a few parts where I had to split certain signals onto separate red/green wires to prevent signals from interfering with each other or causing other kinds of problems.
Data RAM is one example of this, the Z signal (address used for reading RAM values) had to be split to a separate green wire - because otherwise it was getting fed through the other green wire and combining (adding) with the Z signal already present on the red wire "main bus", causing wrong RAM cells to be addressed at wrong times and making a huge mess as a result. That would not have been an issue if the read combinators could have been set to use "red Z" only, instead of "green Z + red Z" as it always works in 1.x.

mmmPI wrote: Fri Apr 11, 2025 10:31 pmSorry to disappoint but no , i had never done such a thing as what you mention.
In that case I'd suggest you find some guides and try writing some of your own code for the real x86 first, so you can figure out the basics first: how the stack works, how function calls work, how to test and debug your code, etc.
Another reason to learn on the real x86 before messing around with writing complex code for a Factorio CPU, is that the real x86 allows you to easily generate executable code (.exe file) without doing all that stuff by hand, and it also lets you use a debugger to step through the code while being able to better keep track of the CPU's internal state.
I mean - sure, Factorio also allows single-stepping with /editor, however you can't step over function calls, can't set proper breakpoints, there's no stack trace ("call stack"), etc. You can still do a lot with /editor, but it's far slower and more tedious than using a real debugger on a real x86.

If you want to give that approach a try, you'll need an assembler to work with. Personally I'd recommend FASM, it's nice to use and there are no "assembler directives" to complicate things. (in FASM, instead of utilizing assembler directives, all the information needed to generate the .exe is part of the project files)
Unfortunately I can't recommend any particular x86 assembly guide, though - I've learned this stuff decades ago using actual paper books, so I doubt that would be of much help even if I could remember what they were called (and I don't).

mmmPI wrote: Fri Apr 11, 2025 10:31 pmIt shouldn't have been connected all along ? I couldn't do with the flashing icons, i had it connected all along. At this point i just "cut" the whole CPU (ctrl X) and then "undo" in editor, so it was all reset with the proper very simple program, and it started to count the numbers 1 by 1 on the screen !
Generally speaking, it is not a good idea to mess with editing the program code on a powered CPU unless you know very exactly what you are doing - and this very much holds true both for the real x86, as well as this version of x86 as built in Factorio.
Although this CPU has the watchdog timer to force a reset if the internal state gets totally messed up, that only reinitializes the state of the registers (including flags); whatever garbage might have been unintentionally written to RAM (and/or the output registers) as a result of messing up the program code will stay there unchanged through a reset.

Incidentally, this also means that the "XOR EAX,EAX" in the 2nd example program is actually redundant here: during reset, EAX and the other registers are cleared anyway, so there is no need to explicitly clear them at the start of the program.
But on the real x86 (as used in the average PC) you basically never execute any program from CPU reset, so there it is very important to take steps to ensure that your code starts in a consistent initial state: either by XORing the registers, or by writing specific initial values to them before they are used for anything else.
mmmPI
Smart Inserter
Smart Inserter
Posts: 4252
Joined: Mon Jun 20, 2016 6:10 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by mmmPI »

Altarus wrote: Sat Apr 12, 2025 11:21 am In that case I'd suggest you find some guides and try writing some of your own code for the real x86 first, so you can figure out the basics first: how the stack works, how function calls work, how to test and debug your code, etc.
To make the CPU count 2 by 2 that was not necessary. I was able to change the single digit in the ROM :)

I then tried to make a program that would count up to 314 and restart for a few hours.

I have come to appreciate your advice :)

I think it could be done in 10 lines of instructions, it didn't sounded that hard but when attempting to do so, i realized just making a comparaison is not straightforward, you need to understand the different way the data are stored, the S and F flags, their regular use and the particular case here where there is a little delay for them to be set after one instruction, so you need a dummy instruction, and then use a jump instruction for the comparaison, that's a lot of possibility to mess up a opcode, too much for me to hope it has a chance to work when i'm only 50% sure a few time in a row x).

I will probably give it another go using the demo program you provided, and after documenting or following tutorials. When i feel i'm not trying to guess but don't understand why it doesn't work when it should instead, i may update my mnenonics and attempt at converting in opcode.

I also appreciate the other advices regarding the ease of use and rationnals behind trying on a regular x86 before trying on yours, but i fear the ease of use would be gained at the expense of the feeling of playing a game. It's something i do keep in mind though and will not hesitate to use an excuse to ease the burden of having failed a very simple program if things should escalate this badly x)
Altarus
Burner Inserter
Burner Inserter
Posts: 9
Joined: Sun Jun 16, 2024 7:13 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Altarus »

Don't worry about inserting the "dummy" opcode as you called it, that's just an optimization thing. The end result is still the same if you don't use that, it'll just take a few ticks longer because the CPU inserts an extra delay to allow the flags to properly update in that case.

Besides, I wouldn't agree with calling that a "dummy" opcode in the first place.
The extra delay due to the flags update mechanism is 5 ticks, whereas the fastest instructions take 6 ticks to execute, so it's still slightly faster to not use the extra opcode if it would not otherwise serve a useful purpose - and it also doesn't use up extra ROM space if you don't do that.
Or, in other words - you should only be placing extra instruction(s) before a conditional jump (or conditional set) if they perform some useful work for your program, for example reading or updating a variable (or I/O) stored somewhere.
But it's quite pointless to put something like "MOV EAX,EAX" or any other form of NOP just for the sake of adding delay, in this particular case.

Oh, and welcome to the world of Jcc/SETcc instructions.
For the most part you don't need to worry about the specific flags and only consider the type of comparison you want to use.
That's why the mnemonics are named the way they are, for example JGE = jump if greater or equal. In the case of a preceding CMP instruction, that would be "jump if destination operand >= source operand".

Also, you do not need to always use CMP instruction specifically; many other arithmetic/logic instructions also set the flags.
For example if you use SUB ECX,1 to decrement a loop counter near the end of a loop, that already sets the flags, so there is no need for an extra CMP or TEST if you want to use JZ/JNZ as the jump condition.
And incidentally, CMP is the same as SUB except that it doesn't write the result of the subtraction back, but rather discards it. Same as on the real x86.
mmmPI
Smart Inserter
Smart Inserter
Posts: 4252
Joined: Mon Jun 20, 2016 6:10 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by mmmPI »

Hey thanks for the advices. Wanted to send an update because i had attempted a bit more, but it didn't work right away and then got lost watching assembly tutorial for noobs which didn't help all that much as it felt too disconnected to things i would later use.

This is more of a skeleton because the OPCODE and data are somewhat random, i thought it could illustrate what someone who has no idea what he's doing can end up with :

Code: Select all

Addr	Instruction	OPCODE	Data (Operands / Constants)	Notes

1	MOV ECX, 0	020257	0				Initialize counter to 0
2	ADD ECX, 1	010201	1				ECX ← ECX + 1, sets S/Z flags
3	MOV EAX, ECX	020257	4				Copy ECX to EAX for comparison
4	CMP EAX, 314	010314	314				Compare EAX to 314, sets S/Z flags
5	MOV EBX, EBX	020357	3				Dummy op to avoid flag delay before JG (doesn't affect flags)
6	JG 9		150800	9				If result > 314 (i.e., !S & !Z), jump to RESET at address 9
7	MOV [601], EAX	010227	601				Send the value of EAX to the display at memory address 601
8	JMP 2		020002	2				Jump back to increment loop (address 2)
9	MOV ECX, 0	020257	0				RESET: Clear counter
10	JMP 2		020002	2				Restart loop (jump to address 2)
Attempting to validate that the instructions are making sense, and that the 10 lines as written would work is difficult, because to test it using only factorio, it would require that no mistakes are made in the transcription of the mnemonics into step 2 and 3 but what i done is closer to a placeholder, waiting to be corrected "after i learned how to in videos", but that later part didn't occured.

It was pleasing to try though, and i feel like i learned a few things, so i would recommend curious players with much times of their hand to try ! Do not expect good result right away but try it ! It didn't feel frustrating to not be able to use the CPU to do something that can be done with a single combinator, it felt like doing rocket science, a whole new world to discover, and quite exciting. ( i'm the kind of player who always had thought this fCPU mod looked super interesting, but never dare tried it, i feel like toying with your CPU opened up my perspectives, gauging the depth of my ignorance :) , and i wanted to say thanks ! , now one day if i dare try this mod, i would have better chances for sure ,or at least i will be able to evaluate relative difficulty x).
Panzerknacker
Filter Inserter
Filter Inserter
Posts: 286
Joined: Mon Aug 22, 2022 5:27 am
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Panzerknacker »

Awesome work mate, crazy project
Nidan
Filter Inserter
Filter Inserter
Posts: 306
Joined: Sat Nov 21, 2015 1:40 am
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Nidan »

Interesting work.

Re 2.0 combinators: The changes made in 2.0 really shine when you're frequently working with Each. Stuff that's rather cumbersome in 1.x reduces to a single combinator in 2.0. With the goal of sticking close to x86, these improvements become mostly meaningless. The selector combinator has, besides max/min (which, again, helps applications using Each), mainly niche uses, making specific pieces of game data available to combinators; useless for a generic CPU.

Altarus wrote: Thu Apr 10, 2025 3:18 pm Important: The 0000 address (0th signal of 0th X combinator) is cursed and must never be used. It is marked with RED signals in the blueprint, do not assign any values to those.
Since you're (mostly) not using Each, I'm surprised a zero signal is giving you trouble.

mmmPI wrote: Thu Apr 24, 2025 12:13 pm Attempting to validate that the instructions are making sense, and that the 10 lines as written would work is difficult, because to test it using only factorio, it would require that no mistakes are made in the transcription of the mnemonics into step 2 and 3 but what i done is closer to a placeholder, waiting to be corrected "after i learned how to in videos", but that later part didn't occured.
The question is, what is it supposed to do? Validating something without a goal to validate against is … difficult. Without looking up the opcodes in the OP I can tell there are discrepancies/inconsistencies between your opcodes and instructions & notes. Ignoring the opcode and data sections and optimizing the obvious bits (Your reset code repeats instruction 1 and then jumps to 2, you can jump to instruction 1 directly instead. ECX and EAX have the same value after instruction 3, but neither gets clobbered within the loop, so there's no need for the copy. Dummy instructions are not needed according to OP, only good places to put something unrelated if you have something you can put there):

Code: Select all

Addr	Instruction	Notes
1	MOV ECX, 0	Initialize counter to 0
2	ADD ECX, 1	ECX ← ECX + 1, sets S/Z flags
3	CMP ECX, 314	Compare EAX to 314, sets S/Z flags
4	JG 1		If result > 314 (i.e., !S & !Z), jump to RESET
5	MOV [601], ECX	Send the value of EAX to the display at memory address 601
6	JMP 2		Jump back to increment loop (address 2)
Repeat writing 1 through 314 to address 601 forever.
mmmPI
Smart Inserter
Smart Inserter
Posts: 4252
Joined: Mon Jun 20, 2016 6:10 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by mmmPI »

Nidan wrote: Thu Apr 24, 2025 10:36 pm The question is, what is it supposed to do? Validating something without a goal to validate against is … difficult.
This =>
mmmPI wrote: Sat Apr 12, 2025 7:02 pm I then tried to make a program that would count up to 314 and restart for a few hours.
Where "few hours" is time spent trying, not the amount of time the program is expected to run ^^

Which i guess isn't far from :
Nidan wrote: Thu Apr 24, 2025 10:36 pm Repeat writing 1 through 314 to address 601 forever.
Now that's what you pretend ! But my point is that i have no way to tell, because in order to validate what you made, i need to also have the OPCODE correct, all of them, and see ingame if it works. Given my current understanding of the registers and stack, there is a lot of risks that i fail transcripting the OPCODE, this is because there is many situation when i wonder if it's a 1 or a 2, and i feel i'm guessing at 50%, and it repeats 10 times before it's done, and then i have to input them in the game.

have i understood properly the instructions ? l suppose not fully
have i used them efficiently ? I suppose no
did i try be efficient ? no x) i tried to make something that would be the most straightforward.

If i had the opcode correct for the set of instruction i wrote, it would have worked in a inefficient way ? or the loop logic wouldn't yield expected result due to misunderstanding of how to use them ?

I thought it was necesary to use EAX in line 3 somehow,( same on line 5) when i read what you wrote i can't fully follow why it would work, i couldn't have come up with something similar because when i read it i don't recognize the logic. (also what does clobbered mean in this context ? ).

I wanted to use a dummy instruction to see if it was going to work as i understood, which was roughly that i could put anything that doesn't change what's used by the rest of the logic, then changing a thing here at a time would allow me to test "does it break now ?".
Nidan
Filter Inserter
Filter Inserter
Posts: 306
Joined: Sat Nov 21, 2015 1:40 am
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Nidan »

mmmPI wrote: Fri Apr 25, 2025 12:22 am
Nidan wrote: Thu Apr 24, 2025 10:36 pm The question is, what is it supposed to do? Validating something without a goal to validate against is … difficult.
This =>
mmmPI wrote: Sat Apr 12, 2025 7:02 pm I then tried to make a program that would count up to 314 and restart for a few hours.
Where "few hours" is time spent trying, not the amount of time the program is expected to run ^^

Which i guess isn't far from :
Nidan wrote: Thu Apr 24, 2025 10:36 pm Repeat writing 1 through 314 to address 601 forever.
I see the objective was hidden in an earlier post. Well, at least the assembly matches your goal, but, as I wrote in the last post, the opcodes look inconsistent, even when accounting for stuff like "mov x, 0" being interchangeable with "xor x, x"
Now that's what you pretend ! But my point is that i have no way to tell, because in order to validate what you made, i need to also have the OPCODE correct, all of them, and see ingame if it works. Given my current understanding of the registers and stack, there is a lot of risks that i fail transcripting the OPCODE, this is because there is many situation when i wonder if it's a 1 or a 2, and i feel i'm guessing at 50%, and it repeats 10 times before it's done, and then i have to input them in the game.
The instructions in the OP for transcribing the assembly look pretty straightforward to me; "dumb" manual labor, no thinking or guessing involved. Likely influenced by our different perspectives. Sentences like "set these bits in that register to the values given in this table" are not hard to find in the datasheets and protocol specifications I occasionally have to work with.

Anyways, I added the opcodes and put them into a constant combinator:

Code: Select all

Addr	Instruction	Opcode	Data	Notes
1	MOV ECX, 0	040457	-	(as XOR ECX, ECX) Initialize counter to 0
2	ADD ECX, 1	040002	1	ECX ← ECX + 1, sets S/Z flags
3	CMP ECX, 314	040007	314	Compare ECX to 314, sets S/Z flags
4	JG 1		000015	1	If result > 314 (i.e., !S & !Z), jump to RESET
5	MOV [601], ECX	010427	601	Send the value of ECX to the display at memory address 601
6	JMP 2		000021	2	Jump back to increment loop (address 2)

If i had the opcode correct for the set of instruction i wrote, it would have worked in a inefficient way ? or the loop logic wouldn't yield expected result due to misunderstanding of how to use them ?
Your assembly looks correct, it would just be a bit slower. (My main reason the optimizing was simplifying it so the function of the code is easier to see.)
I thought it was necesary to use EAX in line 3 somehow,( same on line 5) when i read what you wrote i can't fully follow why it would work, i couldn't have come up with something similar because when i read it i don't recognize the logic. (also what does clobbered mean in this context ? ).
There are instructions, both in OPs CPU and irl, that have the registers they work with hardcoded, but none of the instructions we used fall into that category. clobber ~ overwrite, destroy. An instruction, section of code or function is said to clobber a register when, after the function has finished, the value of that register has changed, but that value is not (part of) the result of that function.
Altarus
Burner Inserter
Burner Inserter
Posts: 9
Joined: Sun Jun 16, 2024 7:13 pm
Contact:

Re: CPU with x86-ish instruction set, 10 IPS [Factorio 1.x compatible]

Post by Altarus »

Nidan wrote: Thu Apr 24, 2025 10:36 pm
Altarus wrote: Thu Apr 10, 2025 3:18 pm Important: The 0000 address (0th signal of 0th X combinator) is cursed and must never be used. It is marked with RED signals in the blueprint, do not assign any values to those.
Since you're (mostly) not using Each, I'm surprised a zero signal is giving you trouble.
The problem is that in Factorio, any signals with a value of 0 are in a bizarre quantum-sort-of-state where they both exist and don't exist at the same time.

Or to be more specific, Factorio does not provide proper means to distinguish the lack of presence of a signal at all, as opposed to a particular signal being assigned a value of 0 explicitly.
(this is along similar lines as the concept of Void "value" for unassigned/uninitialized variables used in certain programming languages, for example)

This issue is not limited to using "Each", either.
Value of 0 causes havoc even when used with a specific comparison, such as "Raw fish = 0".
Of course in "normal" gameplay this usually does not matter at all, for example when you are checking for an absence of a specific item in logistic storage then everything works as expected.
But in situations where the distinction between having a value of 0 vs an unassigned value is important, this issue becomes a major design concern.

In the case of this CPU, and the specific instance you brought up, the nature of the problem is very simple - because of the abovementioned issue, program ROM address 0000 is effectively always addressed/active when no other address is being explicitly invoked.
And since the CPU relies on asynchronously fetching opcodes from program ROM in a strict logical sequence, having any valid opcode at address 0 would totally screw up the operation and ruin everything.

There are also other places in the CPU where I used a workaround for this - by adding a constant value of 1 (with a constant combinator) for purposes of a comparison, you can see that in the instruction decoder where values of 0 to 9 are actually mapped to values of 1 to 10 in the signal selectors, since that would never properly work otherwise because of this zero value problem.

In principle this kind of workaround might also work for the case of program ROM, but it would involve a bunch of extra complexity for very little gain, just to save the waste of 1/10th of a constant+decider combinator pair... clearly, adding even 1 extra combinator to solve the "problem" already results in a net negative trade. Which is why I instead decided to just stick the red signal in there as a warning to others.

mmmPI wrote: Fri Apr 25, 2025 12:22 am I thought it was necesary to use EAX in line 3 somehow,( same on line 5) when i read what you wrote i can't fully follow why it would work, i couldn't have come up with something similar because when i read it i don't recognize the logic. (also what does clobbered mean in this context ? ).
On the real x86, there are some instructions which are hardcoded to always use specific registers or specific register pairs (such as EDX:EAX for both IMUL and IDIV opcodes).
But despite how much I like the real x86 assembly, this is the one part I find quite annoying - because forcing the programmer to use specific registers obviously lacks flexibility, in addition to requiring extra effort to carefully manage the assignments of registers for different purposes in the code, and sometimes also wasting both time and space to move/push/pop the affected register values around.

One of the few things that rather annoys me on the real x86 is the DIV/IDIV instruction: a lot of the time when it is used, you only need one of the 2 results that it generates (either the result or the remainder), but the unwanted result still ends up in the other register and destroys its previous value.
This is one of the main reasons why in this CPU I went for separate IDIV and MOD opcodes.
Another major reason was that by separating the opcodes they can now use the instruction decoder to write to an arbitrary destination, instead of a fixed register pair.

On this CPU, because of the instruction decoder logic it was trivial (in a design sense) to make these opcodes use arbitrary destinations instead.
But that's only true for general arithmetic/logic type instructions, which only use the destination location to read/write some value.

However, even here there are still some opcodes (such as LOOP, MOVSD, etc.) where fixed registers or pairs of registers are used - which is because these instructions require specific additional logic (that is, combinators) attached to these particular registers, and it would require a significant amount of additional complexity (extra combinators) to allow arbitrary registers to be used instead... and even worse, that would totally fail to work when a memory location is selected as an operand instead, since it is clearly impractical to attach a whole bunch of extra combinators to every single RAM cell.
Post Reply

Return to “Combinator Creations”