Introduction
I know, I know. Last time I said I'm going to delve into more CPU details and automated translation from RTL to transistors. I lied. Or, to be more charitable, I've changed my mind. I'm going to go there eventually, but I decided to have a little detour (or a long one as it may be) to the other big section of any computer: memory.
I mentioned in passing in previous articles that my Disintegrated Machine will use magnetic core memory for storage. That of course means that I have to design and build a core memory. Well, almost. I bought a core memory module on eBay that was originally part of a PDP-11. Specifically it is an H-217D module, which is a 32kByte (16kWord x 16 bits) core memory matrix. But just that. Well, almost. There are some diodes on it as well as something, called a precharge circuit.

There is however a lot more to core memory then just the cores. All sorts of interesting drivers and amplifiers and current sources and switches and diode matrices are needed to make it tick. I don't have any of those things and even if I did they would be no good: the PDP-11, being a '70-s era design, uses integrated circuits all over the place. In my design, I decided to go without any of those frills and do everything with transistors.
All this means that I have a mighty design project in front of me. This chapter will introduce some concepts and the general operation of core memories without delving into what I intend to do as far as implementation goes too much. That will come later.
I will heavily rely on the magnificent ferrite core memory project by Andrew Starr over on Hackaday.io as well as Jeff McBrides project. I'm also going to lift a lot of material from the PDP-11 documentation, especially the ones I've archived in my project here: [https://github.com/andrastantos/the\\\_d\\\_machine/tree/main/docs/core\\\_memory](https://github.com/andrastantos/the_d_machine/tree/main/docs/core_memory). These all came from the unparalleled bitsavers archive, but it might be a bit hard to find the right documents in their treasure trove of PDP-related documentation. Finally, there's the Square Loop Ferrite Circuits book by C. J. Quartly. This one is a fascinating read and goes into all sorts of wonderful circuits related to ferrites and transformers that I was completely oblivious to. I probably should write something up just about those... But that's not why we're here today. We're here to discuss core memories, and let's start our journey with a single core!
A single core
The basic storage element of a core memory is a ferrite ring. Ferrite rings never went out of fashion really, they are still are widely used as EMI suppression devices. However the ones used for core memories are distinct and special: these rings were made of a special ferrite material, something that can be permanently magnetized.
This is the basic idea behind core memory (and really all magnetic storage, including tapes, floppy disks and hard drives): get a material that can be magnetized, then use an electromagnet (really a coil) to magnetize them.
All we need to do is to loop some wire around the ferrite bead, and, boom, storage!

In fact, if we wanted to go simple, we want to loop as little as possible. And as little as possible means that we simply thread a wire through the hole of this magnetic doughnut:

Still works, just needs more juice to drive. But how much juice? Well, that's ... complicated. The reason these cores (or any ferromagnetic material) can act as a permanent magnet is that their response to magnetic field contains a hysteresis:

The way you read this is as follows: for any given magnetic field value (H axis) the core will have some magnetization value (M axis). With some materials, specifically ferromagnetic material however, the correspondence is not unique. For any field strength, the core can be in two possible states:

As the magnetic field changes the magnetization of the core will respond by 'sliding' on the curve to the left or right, depending on the direction in the change of the field. Notice two important things: if set the external field to 0, (look at the Y axis intersections with the curve), the core can still be in two distinct places. These correspond to the two possible magnetization states of the core, and this is what we call a permanent magnet: some magnetization is there even if the external field is removed. This is what allows us to use this device for storage. We just need to designate the 'top' point, magnetized in the 'up' direction as '0' and the 'bottom' point, magnetized 'down' as '1'. Or the other way around, it doesn't really matter as long as we stick to it.

The second point is about control: you see how this curve is closed. Let's imagine that we start at the 'bottom' point, in the '1' state. Now, let's start increasing the magnetic field (move the core to the right on the curve). Eventually we reach all the way to the top-right corner, where the two parts of the curve merge.

At this point, we've flipped the magnetization of the core. Actually, the flip happened when we've crossed the H axis, on the really steep part of the curve.
During this flip, we remove any remnant magnetic field, and it's associated energy. This is going to be important later, so remember it. After the flip however, we're at the top-right corner. Now, let's start decreasing the external magnetic field, sliding backwards on the diagram to the right. The core is going remain magnetized in the 'up' direction, so it's going to follow the top part of the curve:

By the time we completely removed the magnetic filed, the core landed in the '0' state. We successfully flipped its magnetic field.
Of course we can flip it back by applying and removing a sufficiently strong field in the opposite direction.
Before we move on, let's consider three other cases:
First, let's imagine our core in the '0' state and start applying a positive external field (moving again to the right in the diagram). Even if we move all the way to the point where the two parts of the curve meet, we didn't really change anything: the core stayed in its 'up' magnetized state all the way. There was no flip, there was no energy release and, if we start removing the field, it will return to the '0' position.

Now, let's imagine the same setup. Our core is in the '0' state and we start applying some external field that shifts the state to the right. However, the maximum field strength is much lower then previously, we don't move the core all the way to the intersection point. Just a little bit to the right and back.

In this case, just as before, no magnetic flip will happen, the core will return to its '0' state when the field is removed.
Finally let's apply the same weak field to a core, but this time make sure that the core starts in the '1' state. We move it a little to the right, but not all the way to the intersection point, then back.

Our core didn't flip this time either! It didn't release its energy and returned to the '1' position when the field is removed.
All of these are going to be important soon. A few final comments on the shape of these hysteresis curves.
It should be obvious that the reliability and noise immunity of this storage system depends on the size of the opening of this curve: the larger the better. Materials with wide hysteresis openings are called hard magnets. So we want those.
The second point may not be immediately obvious: all of what I've described is really only an approximation and works only when the hysteresis curve is largely rectangular. Again, the closer, the better.
All of these things are properties of the material the ferrite ring is made of, and these exact properties make core memory rings special. You can't just order some ferrite rings from Amazon and expect them to work. Look for hard magnet ferrite rings, those should fit the bill much better.
Currents and half-currents
Let's now change our diagram a bit. We know that the magnetic field of an electromagnet is proportional to the current flowing through its winding. This linear relationship allows us to redraw the hysteresis curve in terms of current (I) on the X axis:

The current corresponding to moving the core all the way to the right (or left) to the intersection point is normally denoted as 'Im'. I've also marked the half of this current on the diagram and shifted to a more idealized, rectangular hysteresis curve.

What is important to notice is this: the full 'Im' current is capable of flipping the magnetic field in the core into the desired state (0 or 1 depending on polarity), while the half of that current can't. It is actually going to be way more important to know what the value of this half current is than the full value of 'Im'. So important, in fact, that everyone is just referring to 'Im/2' and call it the 'half-current'. I'm going to use that name from now on as well.
Why is it so important? It has to do with addressing.
Addressing
Let's go back to one of the first drawings I included in this chapter:

You see two wires going through the core. The idea is that if we apply the half-current in the right direction to both of these wires, their magnetic fields add up, pushing the core to the 'corner' of the hysteresis diagram. If we apply the half-current to only one of the wires (or both, but in opposite directions) we didn't build up a strong enough magnetic field and the core will stay in its original magnetization state.
This is a very neat property, one that we can exploit for addressing. Imagine a lattice of wires in an X and Y direction. Now, imagine a magnetic core strung up on both wires in every intersection point:

What we can do in this setup is to apply a 'half-current' to one X line and one Y line. The cores on the selected X line will see half of the magnetic field needed to set their magnetization, so they will not be bothered. Same for the cores on the selected Y line. Of course cores on unselected lines see no current at all, so they don't care either. Only a single core, at the intersection of the selected X and Y lines will see the full magnetic field and set its magnetic field accordingly.

This scheme allows us to select any core in a large 2D matrix without using enormous amounts of electronics to do the selection. For instance, in the memory that I'm working with, there are 16384 rings in these matrices, yet all can be addressed by just 128 X and 128 Y lines.
Write operation
We pretty much covered all the pieces needed for writes to work, but there are some details worth revisiting and summarizing. To write a particular bit to '1', we do the following:
- Determine which X and Y lines select the core storing the bit in question
- Apply a half-current to the appropriate Y line in the negative direction (this is arbitrary at this point, but later it becomes an important distinction)
- Some time later, we apply a half-current to the appropriate X line in the negative direction.
- We wait a little to ensure that the core has enough time to adjust its magnetic field
- We turn off the currents
The reason we don't turn on both X and Y currents at the same time has to do with noise and more important for reads than writes; don't worry just yet, it's not a terribly important detail for now.
Notice however that this technique only allows us to write a '1'. We could write a '0' by reversing the current directions, but we won't do that for reasons that we will come to soon. For now, the important point to realize is that with what I've described so far we can only set a bit to '1', we can't clear it.
Read operation
For reads, we need to be a bit more tricky: we need to thread a third wire through the cores. This is going to be our 'sense' wire. This wire snakes through every core in the array. There are many patterns used, here's one:

My core uses a different pattern, but for now it doesn't matter. In order to read a bit in this memory, we follow the exact same steps that we did for the write, except reversing the X and Y current directions:
- Determine which X and Y lines select the core storing the bit in question
- Apply a half-current to the appropriate Y line in the positive direction
- Some time later, we apply a half-current to the appropriate X line in the positive direction.
- Sense operation
- We turn off the currents
The magic happens in step 4 above. To understand what we will need to do, let's imagine what happens in the memory! As we ramp the Y current towards its maximum value, the magnetic field in the selected core (and only in the selected core!) reaches beyond the tipping point and travels to the top-right corner of the hysteresis curve:

This happens independent of whether we started from the '0' position (red path) or the '1' position (green path). There is a big difference though between the two. On the red path, the core needs to change its magnetic field, while on the green path it doesn't. When the magnetization changes, the core needs to release its stored energy. This energy is released around the time when the magnetic field flips, that is, when the core transitions through the intersection of the red arrow and the 'I' axis.
This release of energy comes in the form of a magnetic field change, which will induce a voltage in any winding on that core. There are three: the X, and Y lines and the sense wire. It's not really possible to detect this voltage on the X and Y lines as we are busy trying to keep the current under control on them, but we can hook up a sensitive amplifier to the sense wire and detect this voltage spike. Of course, if the core was in the '0' state at the beginning of the read operation, no re-magnetization happens, so no pulse will be generated. The timing is critical: we need to know when to expect a spike. Because of that we need to 'gate' the sense amplifier to only look for the spike in the window of time when it may happen.
To summarize: we can implement read by looking for voltage spikes on the sense wire in step 4 of the read operation.
Before we go into more details of what that might look like, one important observation: whatever the initial state, the end result of a read is that the core will return to the '0' state when we remove the currents: the read is destructive. Keep that in mind, it'll come up again soon.
Now, back to the details of reading: the sense wire through the core doesn't only detect magnetization changes in the core. It also forms a transformer with the X and Y lines. What that means is that as long as the current changes in those wires, we should expect an induced voltage in the sense wire as well. The amplitude of that voltage is proportional to the rate of change of the current. This is the reason we don't turn on the X and Y drivers at the same time: by staggering them, we lower the rate of change of the current and thus the coupled noise into the sense wire.
Either way, what we should expect then is a voltage spike, especially towards the beginning of the current ramp in the X lines. If the core was in the '1' state before the read, we should also expect a second spike towards the end of the current ramp. If we tune things appropriately, the first spike should be smaller and come sooner then the second. This should help discriminate the two. Here's a diagram from the PDP11 manual describing this relationship:

I have doubts that the actual voltage numbers quoted are accurate, they seem to be reusing this exact same graphics for several generation of products, but I'm not in the position just yet to verify. At any rate, the logic of the diagram is still correct.
Control
Now, let's return to the comment about reads being destructive. This fact obviously means that if we were to keep the data in the memory, we should write back what we've read. You can be arbitrary smart about this, but the simplest way to go is to immediately follow-up every read with a corresponding write. Notice that reads guarantee that the content of the addressed ring is '0'. So, the fact that our write mechanism can only write '1'-s is not a problem. If we want to write a '0', we can simply do nothing.
What about a simple write with no read preceding it? That surely is a problem if the core contains a '1' and we want it to be '0'. Yes, yes it is. Again, the simplest solution is to always do a read first. That zeros out the memory location, and then we can write back our new '1' if that's what we desire or skip the write if a '0' is to be written there.
The result of all this is that core memory reads and writes always come in pairs: first a read, then a write (or nothing, if we want a '0' to remain in the bit).
Multi-bit memories
We so far have only discussed a single-bit-plane memory. That works of course, but it's much more interesting to create byte-wide or even wider memories. We can obviously just replicate the whole circuit we have so far as many times as needed, but there's something more clever we can do: we can use the same X and Y drivers to drive all the bit-planes (sometime referred to as mats) in series (not in parallel, as we need to control the current rather precisely). We just need separate sense wires for each bit plane:

This results in a 3D structure, something that you can even see in some older memory designs:

You can also flatten it out into a single 2D plane, which is how my core memory is done:

Each pair of those rectangles is one bit-plane, 16 in total. The DIP packages should give a sense for scale: the total capacity of this memory is 0.25Mbit, in other words, there are more than a quarter of million ferrite rings in those rectangles. I pulled out my webcam microscope and snapped some close-ups:

Each of those rings has an outer diameter of 18mils and is 4.5mil thick. That's roughly 0.5mm by 0.1mm. judging from the picture, the wires should be in the 45AWG range.
Talking about the wires, the loopy pieces are parts of the sense line, the ones that exit the core matrix are the Y lines. The way the sense wire loops shows how this memory uses a different sense wire pattern than what I've shown above and this is a great time to start talking about why.
In a multi-bit memory, sharing X and Y lines presents a problem for writing: for an 16-bit memory every X and Y line has 16 intersections; one on each bit-plane. If we want to write a '1' to only some of the bits, how can we do that? After all, we apply a common current, enough to flip any core in any intersection!
The solution is an 'inhibit' circuit. This circuit, during write operations, can put some current through yet another wire, the 'inhibit wire'. This one also snakes through each core of a bit-plane, and just as sense-wires, we have individual inhibit wires for each bit-plane.
Let's imagine that we send a half-current through this inhibit wire in the positive direction (remember, writes used currents in the negative direction) during a write operation! This current will flow through every core in the bit-plane, but alone will not do anything: it is just a half-current, not enough to flip any bits. For cores that are not on either the select X or Y lines, that's the end of the story. For cores that are on either the selected X or Y lines, but not in the intersection, the two fields generated by the inhibit and the select currents cancel each other out: these cores don't see a magnetic field at all. For the single core at the intersection of all the current-carrying wires, the inhibit current cancels one of the select currents, leaving only one half-currents worth of effective magnetic field in the place; not enough to cause a flip.
If you add this up, by applying this inhibit current we inhibited (thus the name) the write from taking place in this particular bit-plane. All we need to do is to create a separate drive circuit for each inhibit wire in the bit-planes and now we can selectively enable or disable (inhibit) the writing of the '1' in the desired pattern.
Putting this all together, round 1

The above diagram is from the Andrew Starrs project and shows a single bit-plane. You should imagine several of these stacked in a 3D fashion 'behind' the current one, daisy-chained on the X and Y directions.
You see a set of drivers, one for the horizontal wires (the Y drivers) and another set for the vertical ones (the X drivers).
You also see two wires (one in blue, one in red) that snake through all ferrite beads; these are the sense and the inhibit lines. There's a sense amplifier for reading and an inhibit driver for writing. You can also see a flip-flop storing the read-out data (remember, reads are destructive) and feeding it back to the inhibit driver for the re-write cycle.
As we saw before, a read operation would be performed in the following way:
- The requested address is split into an X and Y part (lower bits to X, higher ones to Y for instance).
- Both X and Y values are converted from binary to one-hot encoded, in effect determining which X and Y line to drive
- A half-current source is switched to the selected Y (and soon after) X lines
- Some time later (this time is a predetermined constant, based on the ferrite characteristics), the sense amplifier output is latched into the flip-flop
- The current sources are turned off, the read is complete
A write operation would have similar steps:
- The requested address is split into an X and Y part (lower bits to X, higher ones to Y for instance).
- Both X and Y values are converted from binary to one-hot encoded, in effect determining which X and Y line to drive
- A half-current source is switched to the selected Y (and soon after) X lines in the opposite polarity from read. At the same time, a half-current source is connected to the inhibit wire if a '0' is to be written. If a '1' is written, the inhibit wire is left floating.
- Wait some time to allow the ferrite core to respond to the current and develop a permanent magnetic field
- The current sources are turned off, the write is complete
Putting this all together, round 2
An astute observer might notice something interesting: we never use the inhibit wire and the sense wire at the same time. They both snake through the whole array. Would it be possible to combine the them together?
Indeed it is possible and my memory from DEC is built like that. This layout needs some modification to the weaving pattern: the combined sense/inhibit wire runs in parallel with the Y wires, but crosses half way through the array for noise-reduction purposes:

The crossing in the middle is the reason for each bit-plane being split into two rectangles and the weaving between columns - which is not shown here - is responsible for the loops you've seen on the close-up picture.
Of course the sense amplifier now needs to be able to survive the inhibit drive applied to its inputs and the inhibit driver, when disabled should not interfere with the operation of the sense amplifier.
What is needed then?
My array is 16kWord large with 16-bit words. This means that each bit-plane is constructed from a 128x128 array. It is a three-wire design with combined sense/inhibit lines. The half-current is about 0.5A. So, here's what is needed:
- 16 sense amplifiers, one for each plane
- 16 inhibit drivers, one for each plane
- 256 half-bridge drivers for the X wires
- 256 half-bridge drivers for the Y wires
- 7-bit to 128-line one-hot decoder for X decode
- 7-bit to 128-line one-hot decoder for Y decode
- Several ~0.5A current sources
- Some control logic
A few extra notes:
The ramp-rate of the current through the X and Y wires have a large impact on the noise in the system. Being able to control that is important. This can be done at the individual half-bridge drivers or at the current source. If the later, I would need multiple current sources as the ramps are staggered on the X and Y directions.
The current through the X and Y wires needs to be bi-directional: read and write currents flow in opposite directions. This is why half-bridges are needed on each end.
In my particular design, I don't have the register from the previous diagram: instead the read-out values are stored in the CPU and the bus-timing of the CPU guarantees the write-back operation after every read.
The timing of the signals can be derived from a clock, if needed, but at least the sense pulse location is dictated by physical details and cannot scale with the operating frequency of the machine. I'm not yet sure how I will deal with this, maybe it will be a combination of a clock and some other timing mechanism (R/C delay is probably too crude and temperature-sensitive for this purpose); we shall see.
Temperature compensation is a headache. The wires in my memory are extremely thin. They also are long, somewhere around a foot (30cm). Pushing half an amp through these wires will heat them up, which in turn will heat up the cores. The magnetic characteristics of the ferrite cores is temperature dependent, crucially, the "opening" of the hysteresis curve shrinks with increasing temperature. This means that the drive current will need to be adjusted with temperature. Mercifully, the timing at least can remain the same. The way DEC dealt with this in the PDP-11 was to include a thermistor in the core array (so I have it in mine as well) and incorporate that into the current source design.
Can we do better?
If you look at the list of components, it is a long one: 512 half-bridges mean 1024 transistors alone, not including any other stuff that's needed to make them tick.
So, can we do better? Yes, yes we can. In fact we can do dramatically better. In fact, in fact, I will be forced to do better: the wiring of my core matrix requires this particular trick.
This article is getting long however, so I'll have to leave these details to another time. How's that for a cliff-hanger?
Parting thoughts
The memory subsystem is going to be challenging to implement. The documentation from DEC is very detailed, but I still expect quite a bit of experimentation and fumbling before arriving at a working design. Because of that, I will start small: just a few X and Y drivers, a single sense amplifier and inhibit driver. This should allow for rigging up something that goes through the motions of the read and write sequences and will allow me to observe (with the judicious application of an oscilloscope) the behavior of the circuit in real life. There's just so much I can simulate and reality has the nasty habit of disagreeing with theory.
Next time, I will detail my ventures into designing a sense amplifier circuit. Till then, I bid farewell.
Sources:
- http://madrona.ca/e/coremem/index.html
- https://hackaday.io/project/11179-ferrite-core-memory-module
- https://bitsavers.computerhistory.org/www.computer.museum.uq.edu.au/pdf/EK-MF11U-MM-003%20MF11-U%26UP%20Core%20Memory%20System%20Maintenance%20Manual.pdf
- [https://en.wikipedia.org/wiki/Magnetic-core\\\_memory](https://en.wikipedia.org/wiki/Magnetic-core_memory)
- https://jeffmcbride.net/core-memory/single-bit/
- https://ub.fnwi.uva.nl/computermuseum/CoreMemory.html
- https://ed-thelen.org/comp-hist/Byte/76jul.html
- https://www.pdp-11.nl/pdp11-35/memory/core-intro.html
- [https://github.com/andrastantos/the\\\_d\\\_machine/tree/main/docs/core\\\_memory](https://github.com/andrastantos/the_d_machine/tree/main/docs/core_memory)
Leave a comment
Comments are reviewed before they are published, so yours will not show up right away.
You can use
**strong**and*italic*text,`code`, and lists (lines starting with-or1.). Everything else, HTML included, is shown as it was typed.