Let's start by looking at what I first designed back in 0.16: a simple memory cell.
Let me explain what's going on:
- The leftmost decider is the actual storage. It simply sends through its input, and loops back on itself, so the signal is persisted.
- The rightmost decider is the input. If a certain control signal drops, it will send its input to the storage. This would usually cause the old and new values to be added together, if not for...
- The arithmetic combinator in the middle. This one inverts the stored value, and sends it back to the input. So whenever we read, we both add the new value and subtract the old value from the old stored value. old+new-old = new. Simple as that.
- good: seamless output. The storage updates from the old value to the new one within a single tick, making the output fit for e.g. being displayed on nixie tubes (its original purpose).
- good: noiseless output. no control values to be filtered out later, what you stored is what you get.
- bad: can't be updated every tick, the cell needs an extra tick to stabilize. This wouldn't be a huge issue, if not for...
- bad: the control signal. If it drops for a signal tick, the cell will read. If it drops for more than one tick, though, (e.g. by being disconnected), the cell will start glitching out.
It was in the process of reacting to one of the comments that I "invented" memory addresses. You can find that intermediate design in the reddit post. Once I realized what I had done, I refined it to have adressable read access, and this is the result:
The blueprint is for a more compact version. Let's explain its inner workings once more (apologies for my image editing skills):
- The middle arithmetic combinator determines the adress of the cell. It subtracts one from the adress of the row above, resulting in an adress one bigger than the row above. Since these are negative numbers, sending the positive adress across either the write or read control cables will result in the sum being 0 on those combinators, triggering a read or write action.
- Speaking of writing, the three leftmost combinators are nothing new. The writing combinator now checks if A=0, so whether the sum of the incoming (positive) adress and the local (negative) adress is 0, and updates the storage if this is the case.
- The second combinator from the right merely serves to combine the storage and the adress, as we don't have three inputs on the rightmost combinator, the read acces point. These three signals (the local adress, the adress sent across the read control line, and the storage) need to all be kept separate. Any A value leaking into the storage would corrupt the cell, and we can't attach the local adress to the read control line as that would mess up all other cells. So when the local adress matches the adress sent across the read control line, the read combinator outputs the stored value across the read output line.
- It takes three ticks for an updated value to reach the read combinator. Any read requests sent less than three ticks after a write will get the old value.
- Any signal can be stored, save A, which designates the adress. If you desperately need the entire alphabet, use fish as adresses.
- Once again, leave one tick between two writes to the same cell.
- You could combine the read control and read output line, but that would force you to either leave one tick between two reads, or filter out adresses from the output. With two wires, our output is sparkly clean.