Subtitles section Play video Print subtitles Hi, Iβm Carrie Anne and welcome to Crash Course Computer Science. So last episode, using just logic gates, we built a simple ALU, which performs arithmetic and logic operations, hence the βAβ and the βLβ. But of course, thereβs not much point in calculating a result only to throw it away - it would be useful to store that value somehow, and maybe even run several operations in a row. That's where computer memory comes in! If you've ever been in the middle of a long RPG campaign on your console, or slogging through a difficult level on Minesweeper on your desktop, and your dog came by, tripped and pulled the power cord out of the wall, you know the agony of losing all your progress. Condolences. But the reason for your loss is that your console, your laptop and your computers make use of Random Access Memory, or RAM, which stores things like game state - as long as the power stays on. Another type of memory, called persistent memory, can survive without power, and itβs used for different things; We'll talk about the persistence of memory in a later episode. Today, weβre going to start small - literally by building a circuit that can store one.. single.. bit of information. After that, weβll scale up, and build our very own memory module, and weβll combine it with our ALU next time, when we finally build our very own CPU! INTRO All of the logic circuits we've discussed so far go in one direction - always flowing forward - like our 8-bit ripple adder from last episode. But we can also create circuits that loop back on themselves. Letβs try taking an ordinary OR gate, and feed the output back into one of its inputs and see what happens. First, letβs set both inputs to 0. So 0 OR 0 is 0, and so this circuit always outputs 0. If we were to flip input A to 1. 1 OR 0 is 1, so now the output of the OR gate is 1. A fraction of a second later, that loops back around into input B, so the OR gate sees that both of its inputs are now 1. 1 OR 1 is still 1, so there is no change in output. If we flip input A back to 0, the OR gate still outputs 1. So now we've got a circuit that records a β1β for us. Except, we've got a teensy tiny problem - this change is permanent! No matter how hard we try, thereβs no way to get this circuit to flip back from a 1 to a 0. Now letβs look at this same circuit, but with an AND gate instead. We'll start inputs A and B both at 1. 1 AND 1 outputs 1 forever. But, if we then flip input A to 0, because itβs an AND gate, the output will go to 0. So this circuit records a 0, the opposite of our other circuit. Like before, no matter what input we apply to input A afterwards, the circuit will always output 0. Now weβve got circuits that can record both 0s and 1s. The key to making this a useful piece of memory is to combine our two circuits into what is called the AND-OR Latch. It has two inputs, a "set" input, which sets the output to a 1, and a "reset" input, which resets the output to a 0. If set and reset are both 0, the circuit just outputs whatever was last put in it. In other words, it remembers a single bit of information! Memory! This is called a βlatchβ because it βlatches ontoβ a particular value and stays that way. The action of putting data into memory is called writing, whereas getting the data out is called reading. Ok, so weβve got a way to store a single bit of information! Great! Unfortunately, having two different wires for input β set and reset β is a bit confusing. To make this a little easier to use, we really want a single wire to input data, that we can set to either 0 or 1 to store the value. Additionally, we are going to need a wire that enables the memory to be either available for writing or βlockedβ down --which is called the write enable line. By adding a few extra logic gates, we can build this circuit, which is called a Gated Latch since the βgateβ can be opened or closed. Now this circuit is starting to get a little complicated. We donβt want to have to deal with all the individual logic gates... so as before, weβre going to bump up a level of abstraction, and put our whole Gated Latch circuit in a box -- a box that stores one bit. Letβs test out our new component! Letβs start everything at 0. If we toggle the Data wire from 0 to 1 or 1 to 0, nothing happens - the output stays at 0. Thatβs because the write enable wire is off, which prevents any change to the memory. So we need to βopenβ the βgateβ by turning the write enable wire to 1. Now we can put a 1 on the data line to save the value 1 to our latch. Notice how the output is now 1. Success! We can turn off the enable line and the output stays as 1. Once again, we can toggle the value on the data line all we want, but the output will stay the same. The value is saved in memory. Now letβs turn the enable line on again use our data line to set the latch to 0. Done. Enable line off, and the output is 0. And it works! Now, of course, computer memory that only stores one bit of information isnβt very useful -- definitely not enough to run Frogger. Or anything, really. But weβre not limited to using only one latch. If we put 8 latches side-by-side, we can store 8 bits of information like an 8-bit number. A group of latches operating like this is called a register, which holds a single number, and the number of bits in a register is called its width. Early computers had 8-bit registers, then 16, 32, and today, many computers have registers that are 64-bits wide. To write to our register, we first have to enable all of the latches. We can do this with a single wire that connects to all of their enable inputs, which we set to 1. We then send our data in using the 8 data wires, and then set enable back to 0, and the 8 bit value is now saved in memory. Putting latches side-by-side works ok for a small-ish number of bits. A 64-bit register would need 64 wires running to the data pins, and 64 wires running to the outputs. Luckily we only need 1 wire to enable all the latches, but thatβs still 129 wires. For 256 bits, we end up with 513 wires! The solution is a matrix! In this matrix, we donβt arrange our latches in a row, we put them in a grid. For 256 bits, we need a 16 by 16 grid of latches with 16 rows and columns of wires. To activate any one latch, we must turn on the corresponding row AND column wire. Letβs zoom in and see how this works. We only want the latch at the intersection of the two active wires to be enabled, but all of the other latches should stay disabled. For this, we can use our trusty AND gate! The AND gate will output a 1 only if the row and the column wires are both 1. So we can use this signal to uniquely select a single latch. This row/column setup connects all our latches with a single, shared, write enable wire. In order for a latch to become write enabled, the row wire, the column wire, and the write enable wire must all be 1. That should only ever be true for one single latch at any given time. This means we can use a single, shared wire for data. Because only one latch will ever be write enabled, only one will ever save the data -- the rest of the latches will simply ignore values on the data wire because they are not write enabled. We can use the same trick with a read enable wire to read the data later, to get the data out of one specific latch. This means in total, for 256 bits of memory, we only need 35 wires - 1 data wire, 1 write enable wire, 1 read enable wire, and 16 rows and columns for the selection. Thatβs significant wire savings! But we need a way to uniquely specify each intersection. We can think of this like a city, where you might want to meet someone at 12th avenue and 8th street -- that's an address that defines an intersection. The latch we just saved our one bit into has an address of row 12 and column 8. Since there is a maximum of 16 rows, we store the row address in a 4 bit number. 12 is 1100 in binary. We can do the same for the column address: 8 is 1000 in binary. So the address for the particular latch we just used can be written as 11001000. To convert from an address into something that selects the right row or column, we need a special component called a multiplexer -- which is the computer component with a pretty cool name at least compared to the ALU. Multiplexers come in all different sizes, but because we have 16 rows, we need a 1 to 16 multiplexer. It works like this. You feed it a 4 bit number, and it connects the input line to a corresponding output line. So if we pass in 0000, it will select the very first column for us. If we pass in 0001, the next column is selected, and so on. We need one multiplexer to handle our rows and another multiplexer to handle the columns. Ok, itβs starting to get complicated again, so letβs make our 256-bit memory its own component. Once again a new level of abstraction! It takes an 8-bit address for input - the 4 bits for the column and 4 for the row. We also need write and read enable wires. And finally, we need just one data wire, which can be used to read or write data. Unfortunately, even 256-bits of memory isnβt enough to run much of anything, so we need to scale up even more! Weβre going to put them in a row. Just like with the registers. Weβll make a row of 8 of them, so we can store an 8 bit number - also known as a byte. To do this, we feed the exact same address into all 8 of our 256-bit memory components at the same time, and each one saves one bit of the number. That means the component we just made can store 256 bytes at 256 different addresses. Again, to keep things simple, we want to leave behind this inner complexity. Instead of thinking of this as a series of individual memory modules and circuits, weβll think of it as a uniform bank of addressable memory. We have 256 addresses, and at each address, we can read or write an 8-bit value. Weβre going to use this memory component next episode when we build our CPU. The way that modern computers scale to megabytes and gigabytes of memory is by doing the same thing weβve been doing here -- keep packaging up little bundles of memory into larger, and larger, and larger arrangements. As the number of memory locations grow, our addresses have to grow as well.