Archived

This topic is now archived and is closed to further replies.

Please be aware that the content of this thread may be outdated and no longer applicable.

Arnadath

A Computer in your Game in your Computer

Recommended Posts

Arnadath    69

So the promised moment is here. After:

a million hours of designing and debugging,

490 overlapping ports

408 XOR gate

250 AND gate

96 memory latches

56 NOT gate

25 OR gate

~7 coffees

1 BUFFER gate

1 FILTER gate

and countless bugfixing, landscapping, pipelining and labeling to make the game OCD friendly, 

i am proud to present to you, the ARN Vortex 1.490 (a.k.a somebody made a gfx in factorio, so i felt like i had to make a cpu in ONI),

an= 8-bit microprocessor, inspired by the Arm Cortex structure

Spoiler

stable149auto.thumb.png.0ae5fd4255365f5bc8c9cbd961657790.png

stable149elec.thumb.png.3aecd9ca72d64db4bfa375f52d6766ab.png

 

As of yet, it can read/write from memory to registers, load registers, load from immediate and it also has a 2-digit BCD display

If you go to the power overlay, everything is tagged with heavy watt-wire and a weird character set.

How do you turn it on? Go on the top left of the map. There is an assembly with an "IMMEDIATE", a "CTRL"  a "CLK" and a power button with a switch. Turn it on and enjoy.. Before you press it, i suggest you leave the game at slow speed, because even so it will be too fast for what you'd normally expect from an automation circuit. I am not kidding. At the fastest speed, the automation speed is far faster than my game fps.

Anyway, what i have set up as a demo, is a simple program. It will do those things:

enable ouput from immediate and ldB (load to register B)

it will perform ldA but on an empty Bus, thus will clear itself.

Then it will loop those actions

======================

LDA + ADD

write enable A + RW

=====================

What this means is that it 

will load A and enable the output of ADD, thus assuming it's value

then it will enable writing to memory and will store it in whichever is the selected cell.

The BCD decoder has latches that will latch onto the data bus and periodically poll it's value. It works asynchronously to the cpu and that's because doors have an animation speed and the CPU will do all the things i said, almost 3 times faster that that (powered airlocks mind you).. It has it's own clocking mechanism that demonstrates perfectly why i avoid buffer and filter gates (the display is prone to problems and faulty latching if you have the clock off-screen).

The rest of the system is totally bug free and independent of automation of game time, so it will NEVER miss an automation step. Also you can do the trick with waiting the system to trigger and then see it step by step by building automation cable on top of already existing one.

Much more to come. I will add SUB, NOT (with 1's and 2's complements, bitwise XOR and comparator, bitwise AND, bitwise OR, SHL,SHR,ROL,ROR and a program counter, oh and some ROM.

Anyway have fun, hope you like it. Also a tip, if you can't see gates in a place that is supposed to have them, toggle the automation overlay off and on again.

As a little introduction to computing, switch the maching off and put another value in the immediate,then start it again

 

 

p.s.I won't be making a guide as of yet because it is unfinished. If you experiment with it, then know that every single component in there, is there for a reason. Study what excactly happens before you touch anything and give a second thought on what you are about to do to, and don't, no, i mean DONT save if you don't verify what you did works,or you may end up redownloading the map

 

Arn Vortex v1.490 stable.sav

Share this post


Link to post
Share on other sites
Gurgel    610

Probably should go for a 4 bit CPU instead, like early calculators had. Memory will probably remain the limiting factor though. The 4004 had only 2300 transistors.

Share this post


Link to post
Share on other sites
Arnadath    69
49 minutes ago, Gurgel said:

Probably should go for a 4 bit CPU instead, like early calculators had. Memory will probably remain the limiting factor though. 

I think the complete opposite of those is true.8 bytes are already more than enough memory for our practical applications. And you can double it just by copy pasting and increase the address bus length. On the second topic, 4-bit is far too limiting. But at least it would make pipelining it, a walk in the park.

I plan to see it's applications in digital signal processing in game. After that i might make a 4-bit version of it that spans like 1/8th of the map and can be modularly fitted to be useful in you actual game

Share this post


Link to post
Share on other sites
Saturnus    2,710
Just now, Arnadath said:

They do.

Good. So they changed.

Ages ago I made a two bit adder long before automation came out with only liquid pumps and liquid sensors. I've tried but I don't seem to be able to find it anymore as It was only my previous work computer.

Share this post


Link to post
Share on other sites
Arnadath    69
5 hours ago, Saturnus said:

Good. So they changed.

Ages ago I made a two bit adder long before automation came out with only liquid pumps and liquid sensors. I've tried but I don't seem to be able to find it anymore as It was only my previous work computer.

Here's food for thought for you if you are into weird experiments. How about an analogue computer out of a closed air loop and gas valves? We do flow splits into pressure divisions. Sometimes i wished the game had a gas pipe pressure sensor. Anyway i'll be doing my digital stuff. I added the sub function and now i'm debugging it . Next up, bitwise operators. Also i have to thank you. Your aproach, inspired me to really think out of the box and it just, single-handedly, eliminates once and for all, all memory problems. Let me present you:bot.thumb.png.6a33a2931af46a4ee2bb6f7f645bf2ae.png

A 32 byte programmable rom made out of pipes. I could potentially expand it up to the kilobyte region by filling the bottom part of the map. I think that if we filled the whole map with liquid and gas piping, we could potentiall store 1-2 megabytes of information inside our own save game. Isn't that funny? Also electricity could be potentially VERY useful for fast data transfer within this game. More on that after i do my experiments.

Share this post


Link to post
Share on other sites
impyre    87

This is pretty incredible. Once you add some memory and a few more functions I'll be playing around with it. I have some experience working with x86, I can't imagine it's all that different from ARM on the software side. A load is a load after all, right? I guess the only real question is big-endian or little-endian lol.

Share this post


Link to post
Share on other sites
Saturnus    2,710

I'm assuming you're using the water pipes to inject a string of liquids when one type of liquid represent zero and another represent one, and reading the value back with an element sensor. Potentially with a 3rd liquid type to indicate byte stop.

But I'm wondering why you're doing it as a serial string when it really doesn't save space compared to having them in parallel which would also be easier to construct a programmer for. All you'd need is a 9th bit timing loop that just alternates between one and zero.

Share this post


Link to post
Share on other sites
Arnadath    69
3 hours ago, impyre said:

This is pretty incredible. Once you add some memory and a few more functions I'll be playing around with it. I have some experience working with x86, I can't imagine it's all that different from ARM on the software side. A load is a load after all, right? I guess the only real question is big-endian or little-endian lol.

You asked for it, you got it. I just put in a comparator (>==<) , bitwise NOT (also im going to be doing complements substraction instead of hardcoding a substractor), bitwise AND, bitwise XOR,and bitwise OR. I mean this thing starts looking like something, kinda reminds me the mascot for the sydney olympics or something like that.bot.thumb.png.1ab1043ee3cbfda4aaea6a6c6bd7b359.png

 

Btw i set the bitwise not so that it can feed directly into the expresion we are making. We can now say A XOR (NOT B)

3 hours ago, Saturnus said:

I'm assuming you're using the water pipes to inject a string of liquids when one type of liquid represent zero and another represent one, and reading the value back with an element sensor. Potentially with a 3rd liquid type to indicate byte stop.

But I'm wondering why you're doing it as a serial string when it really doesn't save space compared to having them in parallel which would also be easier to construct a programmer for. All you'd need is a 9th bit timing loop that just alternates between one and zero.

Yes, that's what im doing. I had in mind a serial and a parallel version, the parallel to represent our rom and the serial, to represent generic IO. For the parallel ones though, i think it will be harder to make every pipe equidistant. I mean the whole thing so far has been an architectural nightmare.

Share this post


Link to post
Share on other sites
impyre    87
3 hours ago, Arnadath said:

im going to be doing complements substraction instead of hardcoding a substractor

2's complement is easy to do, and even if you added a subtractor it would just do the same thing, just might save a cycle.

Share this post


Link to post
Share on other sites
Arnadath    69
7 minutes ago, impyre said:

2's complement is easy to do, and even if you added a subtractor it would just do the same thing, just might save a cycle.

Yeah, it will be something like:

NOT BX

ADD AX,BX

INC AX

so a hardcoded subsrtactor would save 2 cycles. Simply not worth it, ill hardcode this function and decompile as SUB

edit: "hard-wared" in this case.

Share this post


Link to post
Share on other sites
Arnadath    69
55 minutes ago, SakuraKoi said:

Can you play ONI with it?

No. But now ONI can play computer with you.

Share this post


Link to post
Share on other sites
Arnadath    69

UPDATE: v1.599 is live. I added the shifter. It can now rol ror shl and shr. This concludes the functions of the ALU and up next will be the controls of it.

By now i think i should take a small brake and maybe write a little guide. To start things of, i feel i should tell you some brief stuff about my background..

I don't do digital design proffesionally. I have studied it. I was briefly a professional on the field, but because of stuff, i had to return to my homeland, and in here i have no hope of finding a job on the field. Currently i work as a sound guy in a theater and i love this job.

Since i returned to my country, i also had to join the military (it's compulsive in here). It was a terribly boring experience, but i think it taught me something important. Resolve. I usually start small things, make them nice but my problem always was finishing them. I left my ideas there. hanging. It is the first time i do something for myself that i plan to finish, to see to fruition. And what better resolve to finish this thing, than promising a whole bunch of people you will do it. Another goal would be to teach you. To share my world view about a specific thing.  It isn't something easy. Captivating peoples minds? But some of us were lucky enough to have teachers like that. Somebody who made the whole damn classroom shut up for 45 minutes and let everyone see his passion for the subject through his own eyes.I aspire to cultivate this kind of personality trait. to try to trully share with you my love for computer science and electronics and to make you aware of it's importance. Well, at least it won't be from a 50 year old academic babbling to disinterested students, but from a 27 year old sound guy, openly conversing with other interested people. To concude, i think it's also important to understand computers a little bit more.Computers,, like it or not,shaped most of the world around you, And given enough time, they'll be shaping it, constantly. 

So let's see where this will go:

1. What does a computer do?

Let's ask ourselves here. After all we share the same thing, we all own one. Well. You can play video games in it. I like video games, and a computer is generaly important for this task. So, what do you do with a computer? -Uh, i kinda move the mouse around and press keys on the keyb... AHA so a computer is something that let's us interact with it. We distinctively understand that it isn't something fictitious, it is something that belongs in the sphere of reality. Nice. And what else? -Uh, i can see stuff happening on the screen. HOLD IT RIGHT THERE. Are you describing something that we could define as a living being? -Uh no? So it is an object? -Yes. -And you said that you do stuff to it and it does stuff back? - Yes. 

-Sooooo. we are describing some kind of machination? A thing that we are meant to interact with? -I'd say so.

And here we have a description of some sort, something with which we could go to let's say a little kid and provided that the kid knows some semantics, we could let it be aware that there is a thing called "computer" and it is a thing, and specifically a machine. What else about the computer? How does it work? -It seems it has some sort of brain, called the CPU... -So there is a thing inside this computer called a CPU that is setting this whole thing in motion. And so far, we collected all the definitions we need to ourselves that serve ourpurposes. And for our purposes "a CPU is  the acctuator for a machine called the 'computer'". The computer's purpose is to interact with us in some fashion. It is for us to give it input in some way, of things that we want it to do, and expect back results..

To this point, with the internal dialogue and all, you might think that i consider you dumb and that im talking down on you in some form, but let me tell you this. I am not. I was 20-21-22 years old once. I liked knowledge but i felt like teachers were talking down on us (as in the students), and it was, because of this reason. They wanted to be 'perfectly clear' in a sense, securing for themselves in a position in which later they can tell you "it's your fault for not listening". Teachers are humans, they would love to speak plainly but they also have a responsibility. That is to teach. And the first thing you have to do if you are about to educate anyone really, should be to be exact with your descriptions . It's the way to speak to people who are motivated to listen. You give them something important. Clarity. And the rest, better payed attention. In our case, we are delving in a really complicated world. I mean really complicated. I mean, this mess of cables would give me migraines back then, when i was young and impatient. I wanted to know

When?Now!

I opened diagrams and blueprints of devices, i would bother with something were i saw words and abbreviations that i knew but if it became aparent that it was a little too advanced for me i quickly shut it down and move on with my life. I comforted myself by saying it was my dyslexia.But i was lying to myself. It was just that. My impatience. And the thing is that you never understand the flaws of impatience until you have taught yourself patience. And that's why it is important to make simple definitions and keep them in mind. It's so that we build structures of information inside our brain and don't get lost in the process. Getting lost in the process brings impatience and impatience is not the way to untangle tangled webs. And hey, who doesn't like helpful floor layouts after all?

But how about we don't go in a big tangent? That's another way to completely derail your train of thought and become impatient.

So we have this childlike definition of a computer. It is a starting point. Finding a childlike definition for the cpu is a little bit harder. So

.2.What is a cpu?

I mean, we childlishly described it as the "brain" of this computer thing, but what is a brain anyway. Let's try this. Let's go down the philosophic path for this one.For starters, let's add a fact to this whole "what is computer" thing that we have going on. The fact is, that there are many different computers. The computer, isn't only a concept , it's an every day object. There exists a plethora of them. Same goes for the cpu. In concept, it's the brain of the computer, but then there must exist as many of them, as there exist computers. So  we have a distinction for a CPU object and a CPU concept.

Plato, made once a statement. In this statement, he talked about this world of ideas. In this world, there exist the god form, for every object. This god form is the appearance of this object when we bring it to mind. Think of a mug. Right now i thought of a very plain white mug with a handle, somehow lit in an otherwise totally dark and absent space. It is simply a shape This shape, this apparition is the definition  of the concept mug. It exists as an object but it also exists somewhere within our brain in it's most devoid form so that we recall  when we think of a mug. So what would be the concept for the object CPU? 

In our ongoing fashion of philosophy let's unlid the cpu object. Let's remove all those protective shells and amd/intel/cyrix logos and see what would it be to be a cpu.

Darkness. nothing. For a little bit of convenience, we feel our humanlike characteristics  and our touch, but we have nothing else. There is absolutely nothing to see or hear from anywhere In fact we don't even have eyes we can only walk and we can sense by touch. There is a ground but it's irrelevant. It's only there so that we can walk. There is a tape on the floor and it is seemingly tethered in both directions. We touch the tape. We can feel that it has dots and stripes. seperated by boxes. On one direction it has only dots. There is no change in information. Those dots are irrelevant. If we follow the other direction, we get something more than just dots. It's dots and dashes sometimes in a seemingly organised fashion and sometimes not. There seems to also be an end to this with dots for the rest. . We go back to the first thing that we found that wasn't a dot. This is our start Everything before it is irrelevant. We get a new sense of purpose. We are not meant to escape by going forward or backwards, we are meant to play a game, layed out to us by this long stripe. At the end of this game, we can sleep. And that's the only way to return to the certain nothing from the something we are in.

Now, our brain also works in a cryptic fashion. For starters, our whole alphabet, consist of only those two things, those dots and those dashes.The rules of our language, are those:

Words are made only of those two letters, but can span however long we need.

The first word you read, will tell you to do something.

Based on what you have to do, consider the next words, as your materials to complete the task that you've been told to do. Sometimes you will need to read the next word to complete it. Sometimes the next two, sometimes you won't even need to read the next word. (There will be for example, cases were the instruction word will explicitly say to do nothing and move on to the next word).

If you consider yourself done with the task, go on to the next word which you would consider a word that tells you to do something and do as foretold.

If you end up in the end, or you read a specific instruction that tells you that you're done, then fall in a deep and restless slumber. (don't worry. You'll wake up again with a whole new fun riddle to solve)

You also have some kind of notebook. were you prickle the pages to create words on your own and keep your own notes.Some instruction words will tell you to make the notes, and other ones to read them.

In the same way, our brain is kinda weird. Wehave a concious brain. With this concious brain, we can only picture words to ourselves. When we read and understand what the instruction word wants us to do, we have a fast but unusual way of producing results. We have a very fast unconcious brain. This brain is tremendously fast. The instant we have pictured words in our head, even before asked to do something with them, our unconcious brain has done most of the things that we would have asked him to do. Our concious brain needs to just tell the unconcious one what he wants. The catch here is that we will also have to forget one of the things we picture in our concious brain, to replace with the answer from our unconcious. Forgetting one of the two words will almost always be a necessary scenario, as our concious brain, is the only thing in this entire universe, fast enough to communicate with the unconcious one. And also one rule of this universe is that nothing else is aloud to read this mind  (well maybe except for us)

For the rest, there is a special part of this brain,whose sole purpose is to keep track of were excactly we are on the tape at this moment of time. It also controls the motor functions. We can tell this brain that we are done with what we currently did and to step forward so that we read the next thing, or go that many steps backwards and forwards, or to go directly on this word upon the strip. 

And this is what a cpu would be, relative to a brain.

 

 

Next part, we'll get into a bit more technical stuff, both game related, and real world related. Also some usefull guidelines on how to use this thing.

 

I must say this though. The implementation of automation in this game, is fantastic. It is not real world, but it approximates it in a very fun and informative fashion. Also, know that the nature of the systems we'll be making are not of the conventional, inuitive automation you experience  in this game so far. We are designing, based on the game's "real-life" approach mechanic to get the most out of each thing we make. One more thing is that if you decide to go down this road, your view on automation and on the creation of systems in this game, will likely change, radically.

Anyway, have fun. Also if you feel like making a video about it, totally feel free to do so. I will try to make one, as a general guide, when i finish the whole thing, but recording video and playing oni at the same time is a no-no for my little computer. (i should totally do a video on pipelining though. It has a certain nice aesthetic to it, like trimming down a bonsai tree). I totally added an easter egg. It's... relevant although ended up being too obvious.

  

===TL;DR===

Here is map. Tutorial next post

 

 

Arn Vortex v1.599 stb.sav

Share this post


Link to post
Share on other sites
impyre    87

That was a really nice description of a CPU.

For anyone interested in learning, the language of hardware is generally referred to vaguely as "assembly" language, and there are a few games that can help you learn how to approach solving problems with its limited capabilities:

TIS-100 and Tomorrow Corporation are good examples.

(If mentioning other games is prohibited I will happily edit this out)

https://schweigi.github.io/assembler-simulator/

Above is an assembly language simulator that simulates what a cpu does when it's working, and it comes with a preloaded example script. It may seem tough to decode at first, but it's not that difficult once you figure it out. For the first run through, just scroll to the bottom and hit "assemble" and the script is loaded into the "memory". By default, instructions are highlighted blue in the memory panel so you can see where the instructions are. Also, the labels are shown below the memory panel and clock speed settings. 

Labels (and the DB thing at the top of the script that looks like an instruction) are *not* instructions and are *not* loaded into memory. These are notes to the assembly compiler (or they would be if it existed... since this is really just a simulation) so that it knows when you are just loading up data to be referenced later and so that it knows where it should tell the cpu to go in memory when you reference a label. This is for ease of use, so that way you don't have to keep all those memory locations in your head. All you need to do is write JMP start, and the compiler says "okay, let's see where that will end up being put in memory so I can put the correct instruction", in this case 1F is the JMP instruction, and 0F is where the compiler has told it to go. 0F is hexadecimal for 16, and the memory panel conveniently arranges the memory addresses into rows that are 16 bytes wide, so 0F is the last one on the first row. If you look over there you see 06 is the next instruction, this is the MOV instruction which will move something somewhere. I'm sorry if that's vague because what gets moved where can be very dependent on architecture. In the example we are moving the location of the hello label into register C, it's written as MOV C, hello; however, it's not putting hello into that register, it's putting the location of the hello label into the register (which is 02). Looking at the memory panel will also reveal that the memory location of register C is 02. With a bit of hunting you can find out that register A is 00, B is 01, and D is 03. These register values (A,B,C,D) are interpreted by the compiler also, and inserted into memory where needed.

Some clarifications: MOV in the simulator has several instructions available: 06, 03, and 05 are *all* different MOV instructions.

06 is what is called a "literal" assignment, it means whatever comes next... put that value in the register, it's used like MOV A, 3. This will store the value 3 into register A.

03 is a memory load, it takes the form MOV A,B or MOV A,label. In this case they used [C] to show that it's a pointer. This means "Go to the address location specified in register B, and put that data into register A"

05 is a memory store, it takes the form MOV A, B or MOV label, B. In this case they have used [D] to show that it's a pointer. This means "Take whatever is in register B, and store it at the location held in register A."

If a register contains something other than an address location but you treat it like it has an address in it(which is possible if you aren't careful) then this does amazing and wonderfully unexpected weirdness... like your program re-writing itself and combusting.

Now that you know how to identify instructions, you can figure out the rest by examining the sample code and documentation.

Above the memory but below the display you see several things. A, B, C, and D should be obvious by now, but if it isn't... those are your four registers.

IP is a register that contains the "instruction pointer". This is the thing the cpu looks at to tell where it needs to go next. JMP 3F actually does the same thing as MOV IP, 3F. All it does is change the ip so that the cpu goes to the new place next (because by default the ip just gets incremented by one each time it does something). Basically the cpu will do the instruction at location A3, then it will go to A4 and do that instruction, and then A5 and so on, but JMP allows you to change that flow.

SP is a register that contains the "stack pointer". The stack pointer is a handy thing that lets you store data for later, and easily retrieve it. Everytime something is "PUSHed" onto the stack, the sp is moved by one, and the data is stored, this all happens automatically. When something is "POPped" from the stack, the sp is moved the opposite direction. In this simulator the SP grows into lower address space (and this is not uncommon), this is to reduce the risk of accidentally overwriting your code by pushing to the stack too many times. So the sp is decreased by 1 when you push, and increased by 1 when you pop. The key thing to remember is that the stack works just like a stack of boxes, you can't get to the thing on bottom. Whatever was the last thing put on top, has got to be the first thing to come off.

Condition codes:

These are represented by the Z, C, and F. They are extremely important and there's really no way to get around using them.

Z is the zero flag, C is the carry flag, and F (I can't be sure about this) is the overflow flag. You shouldn't need the overflow flag much, and if it comes on there's a problem somewhere.

The condition codes are automatically updated after any instruction that performs any kind of operation on the data in the registers. For example: Let's say I take whatever is in register A, and I subtract 0x48 from it (bearing in mind that hex 48 = 72 decimal), and then the Z flag becomes true. This means that the value in register A is 0x48, which is ascii for 'H'. You might wait for the user to hit a key, then decided whether or not to do something based on what they press. Get the value they enter, store it in a register, and test it against other known values and/or literals to find out exactly what they entered, then do something based on that result. A Z result is useful when searching for an exact match, but what about comparing two values? The easiest way is to subtract them from one another. If you subtract register B from register A, you can discard the result and check the C flag. If C is set, it means that the result was negative, so B is larger. If it isn't set then they are either equal or A is larger. If you need to know which you would check the Z flag also. By subtracting then check both Z and C, you can check if two things are equal, or if not which is greater. This is usually implemented by the architecture automatically in the form of various different types of JMP instructions. JNZ is a common instruction which means JMP to a given address if the Z flag is false. JNZ is sometimes called JNE, but they work the same. These conditional jump statements allow your program to make decisions based on changing data.

To bring it all back around, above he mentions ldA instruction, this is basically like a MOV instruction.

Share this post


Link to post
Share on other sites
Arnadath    69

@impyre Nicely put. I'd add shenzen.io on this list, but i consider it, more dsp oriented.

 

Before we begin, i should make a not here. You'll see me sometimes refer to neutral objects, as is they had a masculine/feminine characteristic to it. That's because of the structure of my mother language. We refer to some objects, like the brain, as a masculine thing. And that's why sometimes you'll see me write stuff like "the concious brain tells 'him' (the unconcious brain)".

 

So... last time, i had promised you a guide, on the whole thing. The problem is, that my job scheduling is reaaaaaaallly fluid. What i mean by that, is that apart from performances, in theaters they also do rehearsals. A rehearsal , pretty much means: sit in a corner, don't speak unless spoken to, press the play button once or twice if you are told and just watch 7 young, half-naked actresses, play Helen by Euripides.I thought too this was the dream part of the job, but you eventually get desensitised to human phisique. Anyway, let's get back on track:

 

Intrinsics of the game:

I must tell you, i debated a little whether i should start with the very basics. You know, the usuall "making a good strating basis without making the audience feel dumb" conundrum. So i'll just do that really brief, only as reference, for any intents and purposes:

 

intro1.thumb.png.76df04d1e72f073fe07116d218542a4d.png

It's them. From left to right we have the AND gate. It's on, if both inputs are on. Then the OR gate, it will be ON, if any of the inputs is ON. The XOR gate turns on, if it's inputs are both different than each other, and the NOT gate will inverse the state of it's input. Note that the cable that connects all the outputs, will be on if at least one of the outputs is on, as we would kinda expect to happen in real life. Also note, that i didn't add the 'buffer' and the 'filter' gate. That's because they are not really gates and we won't be using them. Anywhere. On the whole map i made, there is 1 buffer and 1 filter gate. Their purpose is to teach you as to why you shouldn't use them.

 

Now. Let's take another look at the same things, only this time from my world view:

5c5ad51ce41b4_intro2.thumb.png.154f3aa7c3288f888a040effbbe7cad6.png

So now, from left to right, we have:

the AND gate  nononononononono, i said MY worldview. Let's start again

A THINGY, that let's it's one input be propagated, if the other input agrees to it.  

A THINGY that always propagates any of it's inputs regardless

A THINGY that ill explain what does in a little while.

 

Let's check them out. But before we do so, we have to do something really fundamental. That is, pause the game.

And Whoooosh, we froze time. Now we can see what excactly those tinsy little electrons, do within our cables, at our own pace and leisure. Let's start acctually with our second thingy. The thingy that always propagates what's incoming

intro 4.png

=======FRAME 1                                       FRAME 2==========

Well. the sands of time are stuck in the huge hourglass, and now we can manually let some of it fall. How you might ask, well i wrote in a previous thread, about my findings of mechanics. In particular there's two little tricks, that the developers were kind enough, to let inside this game, that are exactly meant for people like me. One of them, is that if you try to build 1 unit of automation cable, anywhere really, the automation will progress by one frame.

What did i do here? i turned the switch on, progressed (admitedly accidentally) by 1 frame and took a snapshot.

We are now in frame 1. Time is frozen. It's input is in the 'ON' state. Why isn't the output 'ON'? \

We begin to panic. Our whole world, everything we knew is wrong, oh the horr...

And then, somebody smart, remembers that the sands of time are stuck, and decides to let another grain of san fall.

At the next frame, the output is 'ON'. Phew, what we knew about the world, didn't change.

Quite the contrary, we learned a new fact which we can add to acctually all the gates. 

IF you decimate your signal, in quantized time periods, or steps, or frames or whatever yuo wanna call them,the normal behavioural of all the gates, is to buffer(to delay) the change state of their inputs by 1 frame, before they output. And why is the input buffered and not the output, you may ask? Why wouldn't the output be the one thing that is delayed. You might be correct, but i have REALLY good reasons to believe it's the input. That just a small detail though, their behavioural description, didn't change much. What you know about those gates, didn't change much. BUT we added a fact, a VERY relevant fact, and that is, that the state change, is delayed, by the least ammount of time possible.

This is the first observation that i made, while playing this game normally. I have played this game normally you know? Like you, the first time i started, everything went to hell by cycle 60 or something. It took me 2-3 restarts, but then i started making more stable systems. Also watched a couple of youtube videos by brothgar and Grind this game that helped me get mostly my priorities right. You see, i went for my aesthetic first, and this caused impracticalities.

Kinda more roleplayed, than acctually administering a colony. "What does this guy do? He's good at cleaning? Well, i have this bed in the cellar. The previous guy died out of asphixiation with a 100% stress somehow, so it's all yours. You also died from asphixiation and with a 100% stress? Well, let's find a new cleaning guy. You are good at combat? You should be a knight. Here, go sleep with the rest of the knights, in this beatifull, well oxygenated area. I wonder why my peasants die so much though."

Something like this you know? I had all this architectural layout of a small castle inside the earth, and i wanted to bring it to fruition. The only problem, is that we humans, made more hygienic modus of accomodation since the medieval times. This kinda changed my perspective about what i'd like to do. Well, i still roleplay a little in this game, but at least now, i am a HAL9000 like contraption, that is tasked with the survival of 28 clones. And i learned how to build stable bases that last 1000+ cycles.

Let's not stray too much though, i have still one hour before i go to the mundane task of pressing play buttons  for the ladies in the theater.

So, this 1 frame delay thing? Well, in the normal world, we call it a propagation delay. The good thing in this game, is that this propagation delay, is 1 time frame for all gates. In the real world, you might see on the same microchip, 2 different XOR gates, were one has a propagation delay of 10 nanosecond and the other one 12, a NOT gate with a delay of 6ns 70% of the time and 7 or 5 the other 30%. Now, try to synchronise some million of them, were every one befalls on it's own statistical probabilities about what it's set time and propagation delay would be, and... Well long story short, modern electronics are literally magic and a little miracle that humankind made popssible. OK? Let's just say that.

So, according to our findings, that there IS a least possible ammount of time frame for the automation in this game, and it is possible to see it frame-by-frame, let's check out the 3rd thingy, the one i said that i'd explain later

5c5b431927507_intro3.png.dfbb78863c5d33e53e789987345c345c.png

What happens in here? Let me explain. The switch was on. At the first frame, we turned it off. What happens next, is entirely up to the system and is no on our hands. At the second frame, both the XOR and the NOT gate, are at their buffering phase. the XOR gate, still retains it's original state since the first frame. Everything changed, though and this XOR gate should know better. At the very next frame, the xor gate has changed the state of the output. Only this time, the state of it's input is again changed and therefore it's outputing again the wrong value. From another perspective, it outputs the correct value, but at the wrong time. The not gate, also takes 1 frame to buffer. And there stops the loop for this little gizmo. What does it do? When the state is changed, it will output 'true' FOR 1 AND ONLY 1 FRAME. This is really important moving forwards with this, so take serious notes.

'So this contramption, creates a small impulse. It will output green for just long enough to tell another contraption something, and then lay silent.

It could be usefull in this kind of arrangement:

5c5b43479e9d7_intro5.thumb.png.05bc23b59222fc0321f72177c02f7652.png

We have something in the latch, that we want to propagate. What is in the latch, is information. It is irrelevant. We observe the rest of the mechanism.And it is simply this. "output the output of the latch, for 1 frame, when i toggle the state of this switch. This is an example of a well structured and reliable system that we would like to be using in our design.

Those already are big boy tools, and maybe you already gotten some inspiration to do something yourself with this newfound knowledge. But no, not yet. I have one more important thing to tell you before you trully bring out your 'mad scientist' self.

Let's talk a little about the memory toggle. Let's see it's order of priorities.

5c5b436141a0f_intro6.thumb.png.42c2e807af6a0ea5be861e84240a4aa0.png

 

What is the behavioural of it's inputs? Well it seems  that if i set and reset at the same time, the latch will turn off. That's pretty reasonable. I'd expect a latch to do this in the real world. So, let's see what about it's priorities, in a world of frozen time:

5c5b438c55347_intro7.thumb.png.0b8387aa01b70b10b250a8d072d7ecd0.png

We have a simple contraption here. There is the latch, and then there is all the rest of the system. In this case, we set up to produce a short pulse. We also put an OR gate before the input and we connected the rst. What this means, is that the pulse wil get first to the rst and then to the set. So it will reset first, and then set. At the end of this long journey, we see that the latch is correctly set to true, because we reset it first, and then we set it.

 

5c5b43cf6d5b3_intro8.thumb.png.2be09f24af36d27a2cdafe4e548e5447.png

Same kind of thing. This time, we set first and then we reset. Since we reset after the set, the input to the set is irrelevant. The output of the latch, once the system settles, will always be false.

Okay. That should be all the cases so far. you know? Let's add a small case that we didn't check. What would happen if we set and reset but for only 1 frame. In the first example, it was a switching, not an impulse.

5c5b43f2aa48a_intro9a.thumb.png.7eadd4b2408836aa10953855388db606.png

What we expected, pretty much. Same as in the first case scenario, we turn the circuit on and this xor contraption, produces an impulse. The inputs of the toggle become 'true', and then 'false' in the next frame. Mehhh. I kinda wished they had implemented the real life scenario, where the latch would reset at not clk and it would be set at clk timing..........

 

AND SUDDENLY...

 

 

5c5b43f8ade92_intro9b.thumb.png.694a3858d6aea319226082a17df748d1.png

WHAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAATTTTTT???????????

To be continued...

Share this post


Link to post
Share on other sites
Arnadath    69

Well, i should tell you this. The answer to what exactly happened, it has to do with the cable length. If a letch has to serve both priorities, it will serve the one furthest from the source impulse. So again the inertia in the system, would be the same  as in the 2nd example of checking the priorities of the latch, only this time, we achieved the same, but with just cable length. In fact, we checked for both cases with just cable length. So let's make a general rule, that we can use here. In all cases (be it a simple  edge change, or the fastest possible edge change, ), the behavioural of the latch changes, according to the length of it's wire. Or, as liquid would do in a liquid pipe, considering that both the set and the reset, would behave like liquid inputs, were the flow of liquid, if stopped, will always end up on the last consumer.

(and a little note here: Electricity adopts the same behaviour, as water in pipes in some way. also smart batteries, are the perfect way to bridge stepped automation with game time automation. This means:

in-game "analog" to "digital" converters and also "natural" logarithms, Coming soon... :)

Spoiler

5c5ca8758f429_superexcite.thumb.png.9c7c9ac1f5f1c9a5586b3eea6aeef85a.png

).

So now *cough cough*

The Digital Designers toolkit, for Oxygen not included:

_________________ OUR TOOLS_________________

toolkit.thumb.png.8bd76cf73ed6e5d5726347f200b6621d.png

From left to right:

1. Clock(clk). Yes, it's a NOT gate connected to itself. Yes, It'll help synchronise stuff in our circuit.Yes it's very fast, and yes, it'll get annoying even faster..

2. Clock_enable(clk_en). For when the clk get's annoying

3.Signal injector. Just because we have something pulsating in our cable, doesn't mean we will allways have something pulsating in our cable

4.Positive Edge detector(*) Sends a fast 'true' pulse if it detects that the signal has risen from 'false' to 'true'

5.Negative Edge detector(*) Sends a fast 'true' pulse if it detects that the signal has fallen from 'true' to 'false'

6.Clock detector/Edge detector(*) Sends a fast 'true' pulse if it detects that the signal crossed edge in any direction. Also will become 'locked' to output 'true', if the signal  in it's input is as fast blinking as the 'clk' type signal

7.Clock "reciprocator". You may call it a PLL for all i care. Reciprocates a "clk" type signal. Otherwise stays off. 

8.Clock "reciprocator"/buffer. You may call it the opossite of a PLL for all i care. Reciprocates a "clk" type signal. Otherwise it's the inverse of it

9. Signal blocker Will block all kinds of signal this direction.

(*)Those can prove extremely useful for you in your survival base. More on that later

 

 

The Clock (clk)

In electronics, we use clocks to synchronise  our systems, remember this propagation delay thing? Well, with clocks in the circuit, we can do some verification as to if our input and output has 'settled' down before we accept it as correct and proceed to use it.

 Clock enable (clk_en)

As i said before, in this game, connecting a not gate with itself, get's annoying really fast. Good thing is we won't need a clock all the time.This has real world applications as well.

Signal injector

Now you might've noticed something. For the clock to be stopped, it needs to have a 'true' signal accros it. And it will be normally 'on' after that.  So, we can now send our own thing down the line by flicking this switch.

(Positive/Negative) Edge Detector

Was this signal changed? Was the signal green and became red? Was it the opposite? Well, those 3 little thingies will give you the answer. The edge detector is also a clock detector, in the sense that it's mechanism is 'locked' in a way with the rythm of the clock and therefore it's output will stay green if there is a clock signal down the line, otherwise bussiness as usual

Clock "reciprocator". This is also kinda 'locked' to the clock frequency. It will output it 1 frame later though, so it will do a 90 degree phase shift to it. It's one thing to keep in mind, later when we get to interfacing synchronous with asynchrounous things.

Buffer. This will do roughly the same thing as the clock reciprocator. Only this time, it won't 'block' the output if the signal it receives isn't output. Either way, it will, like the 'reciprocator' phase shift it by 90 degrees (or 1 automation frame)

Signal blocker Why is this even there?

 

Those things? You can even test them in your survival build, i mean like right now. And you can also find a lot, and i mean A LOT of uses for the edge detectors, for a million small concepts you'd like to bring out. Take this for example:

Spoiler

5c5ce3b43349b_Duplicantdirectiondetector.thumb.png.4bdaca70f2324daee6cd70227097aa2d.png

This is a DDD. A Duplicant Direction Detector. I'll just leave this silly little thing here. Why would you wan't one? It's up to you

Anyway, more stuff next post. 

Laters

Share this post


Link to post
Share on other sites
Arnadath    69

Update v1.864 is now live. And It's a HUUUGE update.

====================changelog===================

Added a Σ register to accumulate the output of the ALU.

Added carry, zero, equality and greater than flag

Quadrupled the ram, from 8 to 32 byte and a 6-bit address bus which means we can address up to 64.byte ram.

Added controls and decoders for all the circuitry so far. Also added a control matrix that reads program code. Also added a control bus (opcodes will be explained).

Added synchronous disable for all operand outputs

Removed the bcd decoder, but pretty soon, we'll be decoding through cpu.

================================================

The whole thing, looks like this now:

Spoiler

v1_864.thumb.png.7d9408a7edb68a0dc9e9a9bbcfdf9834.png

 

Now, i have to warn you. The intensity of the switching, depending on what parts you are looking, can and will bring the audio engine down to it's knees. a.k.a if you switch from slow to fast speed at the wrong moment, you will hear some audio buffers overload. If you're lucky, this will stop after 3-4 seconds. If not, a black hole will eat your game. I didn't even bother reporting those crashes because i'm kind of sure as to why they happened.

If you want to try to write your own program, here's the mnemonic table that i constructed so far:

Register/flag actions 

BITMASK

6:   X (Enable/ Disable out if refering to a register, Don't care if refering to a flag)

5:   X(0 = Read, 1 = Write if refering to a register/ 0 = Rst, 1 = Set if refering to a flag)

4:   X(0 if a register, 1 if a flag. Might swap in the future)

3:   X(addr2)

2:   X(addr1)

1:   X(addr0)

0:  1 (for actions regarding an internal register or flag 

registers: addr2  |  addr1  |  addr0

     0             0             0            0        Σ register

      0            1             0            1        AX

      0            1             1            0        BX

      0            1             1            1        IO Buffer (Sets direction from/to Ram. Is not acctually a register)

 

flags:       addr2  |  addr1  |  addr0

  1                0             0            0       Zero (ZF)

  1                0             0            1       Equality (EF)        

  1                0             1            0        Greater than (GF)

  1                0             1            1        Carry (CF)

  1                1             0            1       AX to data bus (implies that AX is enabled and in 'read'. Won't produce serviceable interupt if set)

  1                1             1            0       BX to data bus(implies that BX is enabled and in 'read'. Won't produce a serviceable interrupt if set).

Interrupt and overflow flag TBD

 

Arithmetic/Logic actions 

BITMASK

6:   X (Synchronous disable of all ALU outputs. If set, it is implied that all other bits in the data bus are 0. Otherwise... problems)

5:   X(1 = arithmetic function, 0 = logic function)

4:   X(func1

3:   X(func0

2:   X(param1)

1:   X(param0)

0:  0 (for actions regarding arithmetic or logic functions) 

Arithmetic |   func1   | func0 

     1                0           0       NOT A to externals (data bus)  

                                             param1 = Don't Care

                                             param0 = (Disable if 0, Enable if 1)

 

     1                0           1       NOT B to externals(data bus)

                                             param1 = Don't Care

                                             param0 = (Disable if 0, Enable if 1)

 

     1                1           0       ADD

                                             param1 = Don't Care

                                             param0 = (Disable if 0, Enable if 1)

 

    1                 1           1       BITSHIFT

                                             param1 = (rotate if 1, shift if 0)

                                             param0 = (left if 1, right if 0)

Note: The bitshifter won't work yet because i forgot to connect a cable and i cannot find which one right now. But it works properly, i swear.

 

Logic        |   func1   | func0 

     0                0           0       NOT A to internals (bitwise)  

                                             param1 = Don't Care

                                             param0 = (A if 0, NOT A if 1)

    0                0           1       NOT B to internals (bitwise)  

                                             param1 = Don't Care

                                             param0 = (B if 0, NOT B if 1)

    0                1           0       CMP

                                             param1 = Don't Care

                                             param0 = (Disable if 0, Enable if 1)

Note: After you perform compare, write to Σ to get Equality and Greater than Flag. The Zero flag serves as a Less than indicator this way. The carry flag is raised if there is a Cout from the adder and will also produce a serviceable interrupt which i'll use in the future. Also the carry flag acts as the Cin for it. So if you want to increase by one without touching the other register, you can disable the register, raise the flag and perform the addition.

 

    1                 1           1       BitWise

                                             param1,param0

                                            00 = Nothing

                                            01 = OR

                                            10 = XOR

                                            11 = AND

 

 

Won't get into further detail yet. The demo should be just the same counter it was the last time. Only this time it is an executable program running.this code in loop

read A 

enable add

write Σ

disable add

read Σ

write Α

 

Remember to always set your source first and then your destination when playing with registers to avoid accidental writes wherever you don't want to.

Have fun. :)

 

 

 

 

Arn Vortex v1.864.sav

Share this post


Link to post
Share on other sites
Arnadath    69

v1.899 is now out and about.

I changed a little the design philosophy to enable us to have better synchronisation among components.Specifically, the control bus now is composed of 9 lines. The upper 5 bits, enumerate operands and registers within our cpu, the 2 next bits are for passing parameters, the 8th line is for internal interrupts and the 9th (the one that has the jagged shape) is the clock

Spoiler

comp_ctrl_bus.thumb.png.d50354ed09cb50648616d09922e84049.png

Now, as of the current state of the machine, we don't yet fetch opcodes from memory, although we are in a state that we could do so. That is, because i haven't programmed yet all the procedures. But then again, programming the procedures is more time consuming rather than brain consuming. Let's see how it's done

Step one would be to fetch an instruction from the ram address, pointed to by the program counter. (The fetch structure is existant, but not yet utilised and i haven't programmed the procedure as of yet).

Next step is to decode the instruction into internal procedures. Those internal procedures are like a hardcoded 1-4 step algorithm that tells the machine what to do exactly. And it makes constructing a mnemonic table, more intuitive.

Instructions are 8 bit words.

 

MOV STRUCT

MOV instructions start with a 00. The next 3 bit's correspond to the enumerated source register and the last 3 bits correspond to the enumerated destination register. The register enumeration, is as following:

ENUM       ||      REGISTER

00 000              NULL

00 001              ALL FLAGS

00 010              AX

00 011              BX

00 100             Σ (sigma accumulator)

00 101             IObuf (Not a register, just denotes direction of the data bus as input/output from/to ram and peripherals)

00 110             IP(index pointer)

00 111             NULL (reserved for SP)

10 00X            PC*

*Note: This is a special register. We cannot address it directly with a mov command. When fully implemented in the future, it's default behaviour will be to increase it's value by one before every fetch step, except if the last instruction was a conditional jump that succeded, in which case we load the value from the IP to which we have loaded the address beforehand. The full functionality is not yet implemented, because i have to consider some small timing details.

Behavioural of registers: 

A register that is to be a source, should be set so,during negative clock. At the positive edge of clk, it will enable output to the data bus and will disable itself at the next positive edge.

A register that is to be a destination, should be set so during positive clock. At the negative edge of clk, it will write into itself whatever value it get's from the data bus. Unlike being a source, the procedure of writing lasts one tick instead of the input being enabled untill the next negative edge         

To set anything as source/dest, set your enumerator, and set parameter1 to '1' for dest or '0' for source. Give it 3 ticks to settle

In the AX/BX register, set parameter0 to '1' to set them as sources to DATA bus. Also the behaviour of setting them as sources for the ALU is inverted. The output is normally enabled. If you set parameter1 to '0', the output will be disabled untill the next positive edge. This is to output only one register in some specific instances.


               

 

 

ARITHMETIC/LOGIC STRUCT

Components inside the ALU, are also enumerated in the same way registers are enumerated

ENUM       ||      FUNCTION

01 00X             "CONCATENATED NOT'S" (NOT A/NOT B/ INTERNAL NOT A/INTERNAL NOT B)

01 010              ADD

01 011              CMP (as numeric)

01 100              BITWISE

01 101             BITSHIFT/BITROTATE

01 110             NULL (reserved for special operands in future)

01 111             NULL (reserved for special operands in  future)

Components in the ALU are ALWAYS destinations, since the input to all of them is automatically AX and BX. Remember the part of the extremely fast unconcious brain? Well, this is the ALU. It knows everything beforehand and it's up to us which answer we want to route to AX/BX or Σ. Ιν μοστ ψασεσ, ςε ςαντ το λοαδ ιντο ΑΧ ορ ΒΧ, βθτ ςηεν ςε ηαωε α ΞΜΠ φθνψτιον ιν ηανδ, ςε ςαντ το λοαδ ιτ ιντο τηε S register so as to set our flags.

This is also the difference between Comparing to get a numeric answer and comparing to prepare a jump.

For adding and comparing, we just need to set parameter1 to '1' (no need to normally, but some error correction never hurt anyone ;)) and the ouput will be enabled from positive edge until the next positive edge, just like being a destination register.

For bitwise operations, param1 and param0 select the operation. 00 is null, 01 is or, 10 is XOR and 11 is AND

For the bitshift operation, param1 selects wether we Roll or Shift and param0 selects direction left/right. The bitshifter is set so that it bitshifts directly the value of A by the last 3 bits of B (much faster than shifting one by one for x times). When it's down, it will output an internal interrupt to let the controller know it's done.

 

JMP's STRUCT

Note:The jmp struct and it's mnemonics, will be more thoroughly explained in next update were it will be acctually implemented, but you can use it right now if you can write the procedure for it!

If you follow the output path of the flag's, you'll see it ends up in some gates and then in a bunch more cables that end up in the program counter part

Zero flag raised = Lower than

Zero or Equality flag raised = Lower than or Equal

Equality Flag = Equal

Not Equality Flag = Not Equal

Equality or Greater Flag raised = Greater than or Equal

Greater Flag raised = Greater than

Carry flag raised = Jump Carry (to implement Jump Not Carry, what we do is to Jump Carry back to the start of the function and if there isn't carry and we can continue, we load another value in the IP and jump uncoditionally in the next step. For Now, because later on i'll make a direct Jump not carry opcode)

Unconditional Jump = Always '1'.

 

FETCH AND PROCEDURE DECODING

Let's assume that we just fetched an instruction from RAM. How do we decode it?

comp_op_fetch.thumb.png.db361a02c5ab10f8ce465db429a53e5b.png

Locate this part. The fetch register isn't connected to anything yet so it'll never interact with the data bus. You can set the hypothetical opcode values through those switches. You can also see the "MOV" procedure. The logic behind the procedures, is that we feed a 'step' line with 'HIGH' for 4 ticks and at the 3rd tick we toggle the state of clk so that the enumeration decoders have ample time to be set and can start routing the way we instruct them to route. 4 ticks is the minimal set time to have certainty, but you can increase it. Just append more OR's on every collumn of the "PLL"

The way we generate the clock and the proc line will change, because there will be some cases were we need to slack it to have information certainty, but the way it is now, it'll give you the rough idea of our timing constraints. (Every enumeration decoder can, and should be isochronous with a set time of less than 4 ticks. Just something to keep in mind if you decide to change the enum decoders around.)

 

But in the case depicted, we don't decode to MOV (all MOV's start with 00. Also 00000000 decodes to moving null to null and essentially means NOP).In this case, we decode for INC, which is this proc:

comp_op_inc.thumb.png.a2aea0438d6de0e7368083b7675db0e2.png

We get 0101010X as fetched instruction. This decodes to INC. X is the register to put the answer to (0 = AX, 1 = BX)

The first row contains two AND gates with one leg normally on. When they receive a signal that it's their step, they'll output '1' to Bit 2 and 0 of the control bus. This means 00001-01 which translates to (ENUM:All flags, PARAM: Raise Carry flag.)

The next step is to 0001(NOT X)-01 which translates to (ENUM:The other register, PARAM: Disable output to internals)

Next up is 0101010 which translates to (ENUM:ADD, PARAM:Enable)

And lastly 0001( X )-10 which translates to (ENUM: The defined reguster, PARAM:WRITE).

After we execute those steps, we are supposed to increase the program counter and fetch a new thing, but this is not yet implemented as i said before. So here you have it, The INC demo that we started with, but in it's final form. 

This is not the final form of the controller though. We need a better clock generator, because in some cases that have to do with arithmetic/logic functions, we need to give some more time for them to set. In edge cases, the ripple adder and the comparator, will need 7 ticks to set to the correct value and the bitshifter will do it's own thing and tell us when it's done. So we will need to 'slack' the clock in those steps. But this is for another time.

 

That's all the new stuff i added. Those might seem more confusing than when we started, but trust me, it will all make sense in the end.

Now, i will be kinda busy for the forseeable future (like 1-2 weeks) so i'll have little to no time to work on the cpu, but what can we do... If i want to continue doing cool stuff in ONI, i must secure food on my plate first. But by all means, you can add your own opcode decoders if you understood how to program a procedure. Hell, even post it here if you verified that it'll work. I'll be more than happy to credit you even for the smallest contribution. We are after all at the phase where everything is easy but just tedious and time consuming.

This for now. See you later

Arn Vortex v1.899.sav

Share this post


Link to post
Share on other sites