 Okay, so hello everyone. I'm Pete. I'm a bachelor of electrical engineering. That doesn't really That's very much, but it's a title I can use so what the hell And as the name of this talk suggests, I know are the slides up yet. Can we get the slides? Okay, so as the title of this talk suggests in the next 50 minutes, I will Explain to you how to build a processor out of discrete transistors Now we need a little bit of a disclaimer for that. I'm compute of science in a huge field So obviously it's impossible to explain how to build a processor from transistors in 15 minutes But since we're at a hacker camp, we're gonna do it anyway So to do this the first thing that we need to know is what exactly is a transistor and If we ask Wikipedia the source of all human knowledge Then it says a transistor is a semiconductor device used to amplify or switch electronic signals and power For us, there's really three important words in the definition. There's to switch electronic signals We are working with digital signals. So we only want to switch them the amplifying and the electrical power That's really for analog design engineers, and that's a lot more complicated. So we try to stay far far away from it So There are multiple types of transistors and if we want to make digital logic Then we typically only use one and that's the MOSFET or the metal oxide semiconductor field effect transistor Which is a ridiculously complicated name Which goes back to how the device is physically constructed and how it physically operates But all that we need to know from it is that it is a device and that it has three connectors either on the physical package or on the circuit diagram that you can see on the left and That there are two main types of it The first type is an in type MOSFET and we also called as an infet and it has three pins a gate a drain and a source And what it does is very simple When you put a one or a logical high signal on the gates Then it makes a connection between the drain and the source and when you put a zero onto it Then it breaks that connection The second type of MOSFET is called a p-type MOSFET or a p-FET and it does exactly the opposite When you put the one on the gates, then it breaks the connection between the drain and the source And when you put a zero on the gates, then it makes the connection and to indicate that it does exactly the opposite We put little dots on the gates in the in the symbol so now we have this transistor so if we can switch signals with them and We have to use this in some way to make logic So we do that by making logic gates And we will discuss three the three main important logic gates during the stock But this is the end gate the or gates and the not the gates The simplest one is the not gate and it looks like this. It has one input on the left one output on the right and what it does it Inverts the signal that you put on the input. That's why we also call it an inverter So if you would put a logical high signal or a one on the input Then a zero will be on the output if you put a zero on the input then the one will be on the output And we can put it in a nice table and when we do that we call it a truth table and then We can look for a way to implement this with transistors So we can do this like this. We take truth and two transistors one p-FET one in fat We put the p-FET at the top and we connected to a logical one You put in fat at the bottom we connected to a zero Then we connect both of the gates together and we connect it to the input and we connect both of the drains together And we connected to the output So what you will see now is if we would put a one on the input Then the p-FET will be off and the input will be on so the output will be connected to zero and Every put a zero on the input, then the p-FET will be off and the input will be on so we have a one on the input This is still very simple. Also notice that we use a PFAT at the top and an INFAT at the bottom. And we do that because physically a PFAT is much better at switching once and an INFAT is much better at letting through zeros. So that is why we always keep the PFATs at the top and the INFATs at the bottom. So the next gate is the end gate. It's a bit more complicated. It has two inputs. We call it AMB. And what it does, it only makes the output one when both of these two inputs are one. And in all the other cases, it's a zero. So again, the next question is, okay, how do we make this with transistors? And the answer is this. I remember that the first time that I saw the circuit, I had no idea what I was looking at, but in fact, it's not really all the difficult. And if we look closer at it, then you see that very near the output, you have two transistors there that are arranged exactly like the node gates on the previous slide. And that means that at the input of that node gate should be the exact opposite signal as at the output of the end gate. So if we take away those two transistors, then we have a simpler circuit. And we can check that circuit and see if it does what we want. And then if it does, then we know that we have a functional end gate. So we'll do that. I took out the two transistors and I changed the true table. So now wherever there used to be zero in the output table, there's one and the other way around. And now we can check, does this, do these four transistors really do what we want them to do? And the easiest way to do that is just go over the true table line by line and check whether it matches. So for the first line, we put a zero on the A input and a zero on the B input, which means that both of the PFATs are on, both of the INFATs are off. So we see that the output is connected to one. So for the second line, we see that the APFAT is on, the A INFAT is off, and for the B, it's the other way around. So you can see now that the output is still connected to one, and that the A INFAT is blocking the zero from coming through to the output. And if we go to the next line, we see that it's exactly the other way around, but still the output is one. And only if both A and B are one, then both of the PFATs are off, and both of the INFATs are on, so our output is zero. So now we have this exactly opposite AND gate. We actually call it an AND gate or an AND gate with an N in front of it, from NOT. But if we put a NOT gate after that, then we have an AND gate. So we can do the same thing for the OR gate. We can make a truth table for that. What it does is the output is always one, except when both outputs are zero, or another way to put it is that the output is one, when either one of the outputs is one. And if we make that with transistors, we get something very similar, but only now the PFATs are connected in series, and the INFATs are connected in parallel. Okay, so now we have these three basic building blocks. We have an AND gate, an OR gate, and a NOT gate, and this is actually enough to make any logical circuit that you want. So for example, we could look at this one. We want to make a logic circuit to turn on the fan when the switch is on, and it is the warm or it is the humid. So if we wanted to make a circuit that does this, the first thing that we have to do is remove ambiguity, because this is English and it's always ambiguous. So what we do is we put in some parentheses, and we say we turn on the fan when the switch is on, and it is the warm or it is the humid. So when we want to construct this circuit, we start by constructing it from the inside out. So first we look inside of the parentheses, and then outside of them. So what we see then is in the first parentheses, it says the switch is on, but that is just a simple statement. So if we get a signal from the switch, we immediately have that. And in the second parentheses, it says it is the warm or it is the humid. So that award trigger says that we should use an OR gate, and that we should take the signal it is the warm and it is the humid, put them through an OR gate, and now at the output, we have the statement that's within the second parentheses. And then we have to end that with the other parentheses, so the switch is on. So we get this circuit, and now we have a very basic circuit that can enable our fan when it's the warm, and we want it to be on. So to be honest, this is a little bit of a silly example because, yeah, who would ever need something like that? So the next thing that we're going to look at is a bit more practical. It's about adding numbers. And to do that, it's useful to first look at how we would add numbers if we would be doing it ourselves in the decimal system and by hand. So if we want to add 27 and 15, then the first thing we would do is look at the rightmost column and add the numbers 5 and 7, and we know that's 12. So we'd write 2 below the bar, and we carry over 1 to the next column. So because of this, we call the 2 the sum and the 1 the carry because we carry it over. And then in the second step, you would add the 1, the 2, and the 1, and the result would be 42. Now, if we want to do this in a computer, then we basically have to do the same, but in binary. And luckily for us, binary is much simpler because you only have two possible values, so 0 and 1. So we can look at two binary numbers that we want to add, A and B, and then look how the carry and the sum signal should look like. When we add the 0s, then the result is just 0. When either of one of these two numbers is 1, then the result is 1. And when both of them is 1, then the result would be in binary 1, 0, or 2 in decimal. So that means that the sum is 0, and in the next column, we should put a 1. So we make the carry 1. So now we want to make a logic circuit for this. There are very structured ways to do this, but those are quite elaborate. And here we can really just eyeball it a bit. So we'll start with the easiest one. We want to find a way to make the carry signal. And if you look in the table, then you can see that the carry is only 1 when both A and B are 1. So to generate the carry signal, we can just take A and B, put them in an end gate, and then we have a carry signal. The sum is a bit more difficult because you see that it should be 1 when either A or B is 1, but not both of them. And if we go ahead and do the same thing that we did with the example of the fan, then we can find out that the circuit looks like this. So what the two end gates do is they make the signals A is 0 and B is 1, or A is 1 and B is 0. And then we order those together with the OR gates, and we output that to the sum. So this is a circuit that we use all the time because in digital logic, we add numbers all the time. So what we do is we put a box around it, and we call it the half adder. And now we can make a box that has two inputs and two outputs, and we give them a name, and then we just say it's a half adder, and we don't really care about what's inside anymore. We just know that it will follow the true table that is on this slide. So now we can add two numbers. But if we look back at this slide, then you see that in the last step, we actually have to add three numbers. You have your two original numbers, and you have the carry. So we need a way to add three numbers. And to do that, we take two half adders, and we use the first one to add the first two signals, or the first two numbers, or the original ones. Then we use the second one to add in third number, or the carry, from the previous operation. And then if you would make a truth cable, then you would see that the carry has to be one when either of two of the carries from the half adders is one. So now this circuit allows us to add three numbers. And again, this is something that we very often do. So we draw a box around it, and we call it a full adder. So now we have this full adder, and we can add three bits. But what we would actually like to do is add real numbers. So we'd want to be able to say to check how much is 3 plus 1. And for that, we can come up with this fancy circuit. And to see how it works, it's really the easiest to just do an example. So let's say that we want to add 011 and 001. So we want to add 3 and 1. What we do is we connect the signals or these numbers to the adder as on the slides. So to each one of the full adders, we connect a corresponding bit of the number that we want to add. So then we see for the first adder, we're adding 2 1s and a 0, because it's a fixed 0. It's the first number. So the result should be 2 in decimal, or 1 0 in binary. So the sum should be 0, and the carry should be 1. Then the second adder, we are again adding 2 1s and 1 0. So the output is the same. The sum is 0, and the carry is 1. And then for the last adder, we see that we have 1 and 2 0s. So the result is 1. So the sum is 1, and the carry is 0. And if we now look at the number that we have on the right side, and we read it from bottom to the top, then we have 0 1 0 0, or 4 in decimal. And that's exactly the number we were looking for. So we've already went from basic transistors to being able to add numbers. But now, if we do a calculation, it would be nice if we could store the result. So for that, we need memory. And the simplest form of memory is this. It's called a latch. And it has two inputs, reset and set, and one output, out. And what it does, when you make the reset pin high, then the output will become 0. And when you make the set pin high, then the output will become 1. And when you make both of these two pins low again, then it will remember whatever value you put in there. So let's check that. Let's say we make reset 1 and set 0. Then the output of the first OR gate is 1. So we negate that with the inverter. So the output is a 0. And then the second OR gate, the lower OR gate, also results in a 0. The inverter makes it 1 again. And we see that the circle matches. The same thing happens when we take the set pin. The set pin forces the output signal of the bottom OR gate to be 1. So the input, the output of the bottom inverter is a 0, which means that the output of the top OR gate is a 0, which means that the output of the top inverter is 1. And we see again that we have a stable circuit. So now what happens if we make both reset and set 0? Let's check that. Well, if out was already 1, then the output of the lower OR gate will be forced to 1. The output of the lower inverter will be 0. So the output of the top AND gate will be 0. So the output of the top inverter will be 1. So again, we have a stable system. We're enforcing the state that we already had. If the output would have been 0 at that time, then you see that the output of the lower OR gate is 0. So the output of the lower inverter is 1. Lower higher OR gate is 1. And the higher not gate is 0. So again, we have the stable system. So we can remember two states. Now, this is nice. But a big disadvantage of this is that whenever you would write something to set or reset, the memory circuit will immediately copy that value to its output. And initially, you might think that's nice, because then everything goes nice and fast. But this can actually give you some problems. For that, two examples. The first example is called spikes. When you look at the circuit on the screen, from what we just talked about, you would think that the output is always 0. Because you have an end gate, and you put a signal onto it, and the inverse of its signal. So whatever happens, there can never be two ones on the input of the end gate. However, these things are physical devices. So it takes some time for any gate to react on a change at its input. So let's say, for example, that a gate takes 10 seconds to update its output when the input updated. That means that if we would change our input signal to 1, at time is 0, then the not gate would see, hey, my input changed. But the end gate would also see, hey, my input changed. So the not gate will change its output from 1 to 0. But before that has happened, the end gate still sees a 1 at the output of the inverter and sees a 1 that we have just put there. So it says, hey, I see two ones. So my output should be 1. And it does that. And then only at that time, at the same time that the inverter makes its output 1, the output of the end gate makes its output 1. At the same time that the end gate makes its output 1, the inverter changes over to a 0 at the output. And the end gate notices this and says, hey, I have to go back to 0. And what you get is that in your logical signal, you have this spike. And if you would just look at it statically, then you would never expect it. But in real life, it actually happens. And when this happens in more complicated circuits, it can really make your day horrible. So a second problem could be oscillations. And the simplest form is this. You take three inverters and you make them into a little chain. And because there's three of them, this circuit has no stable state. Because if the leftmost point would be 1, then the point next to that towards the middle would be 0. The output of the second inverter would be 1, which means that the output of the last inverter would be 0. But that output is also the input of the first inverter. And we just said that was 1. But now it's 0. So what this circuit will do, it will actually constantly change every point from 0 to 1, from 0 to 1, from 0 to 1. And sometimes you want this. For example, if you want to generate the clock signal. But if this is hidden somewhere deep into your circuit, and all of a sudden your circuit starts changing randomly from 1 to 0 all of the time, then you probably won't be happy. So how do we solve this? Well, some very clever people came up with the idea of synchronous circuits. So what we do is we take our logical gates. And then after that, we put a magical clock memory circuit. And what this does is the memory circuit listens to a special signal that we call the clock. And only when that clock says, OK, now you should copy what's on your input and remember it, then it will do so. And typically, a clock signal will look like this. So it's an oscillating signal. And what we typically do is we say, whenever the clock signal goes from 0 to 1, then our memory circuit should copy the value that is at their input and place it at the output. And during all the other time in the cycle, we give the logic circuit the time to process its inputs and to let the value ripple through to the output so that it's nice and stable. So we solved the problem of spikes and oscillations. But we only solved it theoretically because we say we need this magical circuit, so now we have to build it. So how do we do that? Well, we start by taking our original latch in black. And the first step is we put two end gates on the set and reset signals and connect it to an enable signal. So this enable signal will now only if the enable signal is high, we will be able to set and reset the latch, change its state. So if we would connect this enable signal to a clock, then the latch would already or would be responsive during half of the clock cycle. So this is already better than what we initially had that it was changing all of the time. So a next thing to notice is that if you look at this point in the circuit, then you will always get the exact opposite value as it's on the output. If you would actually go through all of the states again, then you would see that it's true. So what we do is, again, we make a box. We call it a latch. So our original latch is now a box. We give it a set and a reset input. We give it a queue. That's the name for the output. And we give it a queue accent or a queue prime. And that's now the inverse output. We also put that at the interface of the box. So then we put our end gates in front of that. And we connect the enable signal to the opposite of the clock signal. So originally, when we connected it directly, we would be able to change the value of the latch only when the clock was high. Now, because we put an inverter in the clock line, we can only change it when the clock is low. OK. Next thing we would like to do is we would not have to set and reset the signal or the latch, but we just want to give it a value and say, remember that value. So we can do that by this. We take a data signal. We connect it to the set input. And we invert it and connect it to the reset input. So now if the signal is high, the latch will get a set signal. If data is low, then the latch will get a reset signal. OK. But we still have the problem that this circuit will copy the value during half of the clock cycle. So when the clock is low. So to fix that, we put exactly the same thing behind it. But we put another inverter in the clock line. So the second part of the circuit will only copy the value of the first part when the clock is high. So if we would then look at the waveform. So what I've drawn here is different signals in the circuit. And when the line is at the top, it's a logical high over 1. And when it's at the bottom, it's a 0. So the clock keeps ticking. And originally, all the signals are at 0. But then all of a sudden, at the first rising edge of the clock, so that would be, oh, oh, nope. So that would be here. Then we make the data input high. So during the first half of the next clock cycle, nothing happens, because the clock is high. And our first latch will only copy its inputs when the clock is low. But when after that, the clock goes low. Then the first latch says, OK, so I can copy my input now. And it makes its output, so point A, high. But still, the output doesn't change, because now the clock signal is low. And the second latch only copies when the clock signal is high. So when the clock goes from low to high, then the second latch will say, OK, so I can copy what was an A now and put it on my output. So in that way, we get at the point that the clock signal goes from low to high. So we have a rising edge. We actually see the value that we put at the input appearing on the output. And like this, we can make the magical clock circuit, or the magical memory circuit that we wanted. So we can use it for memory, as we did before, and just connect logic to it. But we can also do other funky things with it. For example, we can make a counter out of it, like this. And what you see here is that we have connected the inverse outputs of all of the flip-flops. Oh yeah, forgot to mention. This circuit that we had on the previous slide, we call it a flip-flop. And again, we draw a box around it, and then we have what's here, then we have three of those boxes here. So we have a data input, we have a clock input, and then we have a data output and an inverse output. So what we do now is we connect the inverted outputs to the data input. So now whenever the flip-flop receives a clock signal, so our rising edge on the clock, then it will copy what is on the inverted output to its output. So it will basically toggle. If the output was a 1, it will now be a 0. If it was a 0, it will now be a 1. And what we do then is we also connect this inverted output to the clock signal of the next flip-flop. So if we would look at the waveforms now, then we see that originally both signals, A, B, and C, so that's here, they're all 0. But when the clock has its first rising edge, then the first flip-flop sees this and says, oh, I should toggle now. So it makes its output a 1. So now A, B, and C form 1, 0, 0, or 1 in decimal. At the next rising edge of the clock, the first flip-flop again says, OK, so I should toggle now. And it was 1, so it goes to 0. But the inverted signal of this flip-flop is connected to the clock of the second flip-flop. So when the output of the flip-flop goes from 1 to 0, then the inverted output goes from 0 to 1. So the second flip-flop says, OK, I see a rising edge on my clock inputs. I should toggle now. And we see that then A becomes 0, B becomes 1, and C becomes 0. And this goes on and on. And in the next step, A and B will be 1, and C will be 0. And after that, C will be 0, and A and B will be 1, and A and B will be 0. So we see that our circuit is actually counting, 1, 2, 3, 4, 5, 6, 7, 8, and going around and around. And by adding more flip-flops, we can count to higher numbers. So by now we took our transistors. We made logic gates out of them. Then we kept adding things to it. We can now store memory. We can count. And there's actually only one thing still missing to make a processor. And that's what we call a selector. That's this little circuit. So what you see there is in the middle. We have two transistors, infet and a pfet. And we connect them in parallel because, as I mentioned earlier, a pfet is very good at switching once. And infet is good at letting through zeros. But what a selector does is you can put it on a signal line. And when you enable it, then it actually lets the signal through. And when you disable it, it stops the signal. So if you would disable the selector, so if you would make the enable pin low, then the output would not be connected to anything because both of the transistors would be off. And it's basically just the same like having a floating piece of wire. And as you can see, we put an inverter in the enable line because the pfet is enabled exactly at the opposite time as the infet. So by doing this, we can put a signal on the input and connect a bunch of things together to one bus. And we can choose which circuitry is allowed to connect to a bus. So if you would have multiple circuits writing to the same point and some put ones to the point and other put zeros, then you would have a short circuit. So by using this circuit, you can actually disconnect parts of your circuit. And now that we can do this, we can disconnect parts of the circuit. We actually have all the building blocks that we need to build a very basic simple processor. And the processor that we'll look at is the Delta 1. It's developed at Delft University of Technology. And it's really made to be super simple. So it's easy to explain. But still, it's a real processor. So it's true and complete. And it can make any calculation you would like. So this is how it looks like. And to step through it, all of these blocks are actually quite simple. So you can see that this entire design is based around this big horizontal line, which we call the data bus. And the data bus is a way that we use to allow data to move from one part of the system to another. So one part of the system can write the value to the data bus, and then another part can listen to it. And then connected to the data bus, in the left top corner, we have this, the program counter. This is a counter that keeps track of where in the program that we're executing we are. So when you have a program, whether it's in C, Python, or assembly, you always have lines, and you just do it step by step. And sequentially, you execute every operation. So this counter keeps track from where we are. Then next to that, we have a ROM. And what that does is it stores the operations that we have. And to make this ROM, you give it a bunch of inputs, which we call address lines. And we say, when the counter is at that value, then it will put this value on the address lines. And then making a truth table, you can say, OK. So when this value is on the input lines, then we should show disinstruction on the output. And then similarly to what we did before, you can make a logic circuit that does that. That will always show this value at the output when you give a certain address at the input. And it's a lot more complicated than what we did before. But like I said, you have systematic methods to do this. And these days, we usually just let a computer do it. So your ROM gets a value from the program counter. And on the output, it gives the current instruction or the current step that your processor should perform. It gives this value to the instruction decoder, which looks at the instruction and then figures out, based on the instruction, what the different parts of the microprocessor should do. And how this works exactly, we will see in a couple of slides. But for now, we can just assume that it can take in an instruction and then figure out how the other blocks should behave. Then we see from the instructor, we have this block that connects to the data bus. And this is a block of selectors that we had previously. So this block allows us to connect or disconnect the instruction decoder to the data bus. So for example, if you would have an instruction that says add 5 to the number you already had, then you can use the instruction decoder. We'll take that value 5 out of the instruction because that's a constant. And then it will put it on the data bus. So somewhere else in the processor, something can read. OK, so I should add this value to that number. And you see that there's a little bar through the line with an 8 next to it. And that means that instead of having one signal flowing there, we actually have 8. So we want to operate on bytes and not bits. So we just put 8 bits in parallel. And not to clutter or drawing too much, we just draw this one line and we say that's 8 lines. So then here we have registers that we can use to store things. And what the register is, it's a bunch of flip flops next to each other. So as we said, most of these lines are 8 bits wide. So that means that if you want to store a value, you don't have to store one value, but you have to store 8. So you put 8 flip flops next to each other, and then you can do that. So here we have a bunch of registers that are also connected through the data bus. So they can read the value from the data bus. And then through the selectors, they're also connected to the data bus. So they can also put a value on the data bus. And then next to that we have input and outputs registers. And these are very similar to the original registers. But instead of putting their own value back onto the data bus, now they have signals going out and into the processor. So what you could do is you could, for example, connect LEDs to the outputs. And then you could see what value the processor stored to that register. And you can also add switches to the microprocessor so that you can dial in the number and then read from those switches and put that value on the data bus. And then we have the actual calculation unit of the microprocessor here. We call it the ALU, or the Arithmetic Logic Unit. And this is what actually does all of the additions and logical operations. So you can see that there is a line going from the instruction decoder to the ALU that tells the ALU, OK, now you have to do an addition or you have to do an end operation. And then the ALU is also connected to the data bus. So it can read the number that we put on there. For example, if we want to add five, then we put five on the data bus. And the ALU can read that from the data bus again. And it also has this special register here, A, which stands for accumulator, that stores the value or that can store the value that is output by the accumulator and also feeds it back. And then we also, again, have the possibility of putting the value of the accumulator back onto the data bus. So in order to control all this, we have a bunch of control signals. So what you saw now are really signals that have the values that we're actually calculating with it. So that's why we call it the data path, because the actual data is on there. Then we also have a bunch of other signals shown in red here, which are control signals. And these are generated by the instruction decoder, and they tell all of the other blocks how they should behave. So for example, they tell which of the buffers should be on, so which components are allowed to write their value to the data bus. So now, how can we do operations with this? I have a couple of examples. For example, load a number. So what this operation does is it loads a certain number that is fixed in the instruction into the accumulator. So when the instruction decoder sees this instruction, it will know that at the fixed place in the instruction is the number that we actually want to load into the accumulator. So it will put it on the data bus, so the instruction buffer output enable signal will be high. And then it will tell the ALU, copy the value of the data bus into the accumulator, and it will tell the accumulator, store value that is given by the ALU now. So and then when the clock has the next rising edge, then the accumulator will copy the value that is given by the ALU, and we will now have stored it there. Second possible instruction is load the value from a certain register. So here, instead of letting the instruction decoder write the value on the data bus, we actually load the value that is in one of the registers and put it on the data bus, and then the ALU does exactly the same. So the data bus always has one person writing or one thing writing to it and one thing listening. We could also store a value to the data bus. For example, we have done some calculations, and we have a useful number in the accumulator. We can connect the accumulator to the data bus and then tell a register to store the value that's now on the data bus. And then we can also do actual math. So we can have an instruction that says add the number 5 to whatever is currently in the accumulator. So the instruction decoder will put the number 5 on the data bus and tell the ALU, take the value from your accumulator, and add the number 5 to it, and then tell the accumulator to, again, store the value that is given by the ALU. So by the next log cycle, the value in the accumulator will have been increased by 5 or whatever number you put on there. And then a last type of instruction is a jump instruction. And this is a totally different class of instructions than what we saw before because this actually allows you to change the program counter. For example, if in your code you would have an if statement or a loop, then you have to be able to make jumps in your program. And that's what this instruction does. And what it does, it says take whatever value that I give you in the instruction and put that value into the program counter. So instead of increasing the program counter by 1, the next log cycle, go to that value that I give you now. And so that's just an overview of how these things work. And this is a full set of instructions that work on or that the delta 1 uses. So you see, oh yeah, I forgot to mention this. If you look at the ALU, then you see that it has a C and a Z next to it. Those are flags. And what they do is the C flag will be 1 when the last addition you had done created the carry. So it will remember that, like the last number that you have created or that I have put in the accumulators actually 1 to little because I had a carry at the end. And then we have a Z. And that says the last logic operation you did resulted in 0. And we can use that to do checks in our program. So when we look back at this instruction set, we can set and clear the C flag so that we are sure that it's in a certain state when we start something. And then we have a bunch of load instructions. So we have load a fixed number, load a register, store to a register, add a fixed number to the ALU, store a fixed number in the ALU in the accumulator, and then do an XOR operation. What an XOR operation does, it looks at two numbers. And when either one of them is 1, but not both of them, then it will output a 1. So we have that operation that we can do. And then we have end operations that we can do again with a fixed number or with a register. So those are the mathematical operations. And then we have a bunch of flow control operations. So we can jump to a fixed number. We can jump to a number that is stored in a register. We can do BZ, so that's a branch based on the Z flag. So what we do there is we check the Z flag in the ALU. And we say, if that Z flag is 0, then we want to make a jump. And the same for BC, but there we look to the C flag. And what that allows you to do is check whether a value or whether a number has certain properties. So isn't number 0 or isn't number negative. You can do things with that. And then the last step is the instruction decoding. So until now, our instruction decoder has been a magical box. But it's actually also not all that difficult. So what you do is every instruction, every type of instruction, you give it an opcode that uniquely identifies that type of instruction. So we say, whenever we want to load a constant, then we give it the opcode 0011. And then we make place for an operand. So for example, the constant or the number of the register that you want to read or write to. And then you just go over all the control signals in your processor. And you say, OK, when we do this operation, then this control signal should be 1 or 0. And then again, this is basically a true table. And you can synthesize a logic circuit from this. Or you can let your computer synthesize a logic circuit from this. And that's basically all there is to it. So this processor that we just talked about, we actually implemented it. So then it looks like this. And you can see the different parts that we talked about. So like the repetitive structure on the left here, these are registers. Then what you see here at the top, this is the instruction decoder. Then I think here you have the ALU. And we also made this. So then it looks like this. And every line that you see, or if you can really see the individual transistors here. So they're just connected, as we would connect them with our logic gates. And this design, so this very simple processor, it uses about 16,000 transistors, which is, in today's terms, is basically peanuts. But it's also very simple. And what the stock should really tell you is that even though it has 16,000 transistors, none of it is actually really complicated. Because just like in software engineering, we just take something very simple and then we abstract it into a box and then we add a little bit to it. And we keep on doing that. And in that way, every step of the way is actually really not that complicated. And just by doing this abstraction, you keep the possibility of designing really complex things from basic systems, which you would never be able to do if you would have to think about how to wire each of these 16,000 transistors by hand. So yeah, that was it. That was how you build a processor from discrete transistors. So yeah, I had some bonus slides. But oh, yeah, all I wanted to say is, thanks. And if you're interested, you can always contact me. Thank you.