Understanding Binary Numbers: The Foundation of Computing
Why Computers Use Binary
- Computers operate using electricity, representing absence (0) or presence (1) of electrical signals
- Electrical signals are inconsistent, making it easier to use simple on/off states rather than multiple voltage levels
- Binary (base-2) uses only two digits (0 and 1) per position, unlike decimal (base-10) which uses ten digits (0-9)
Converting Between Binary and Decimal
Binary to Decimal Conversion:
- Each digit position represents a power of 2 (20, 21, 22, 23, etc.)
- Multiply each digit by its positional weight and sum the results
Example: 10112 (binary) = ?
- 1×23 + 0×22 + 1×21 + 1×20
- = 8 + 0 + 2 + 1 = 1110 (decimal)
Decimal to Binary Conversion:
- Find the largest power of 2 that doesn't exceed your number
- Subtract it and repeat with the remainder
- Fill in 0s for missing powers
Example: 2210 = ?
- 16 + 4 + 2 = 1×24 + 0×23 + 1×22 + 1×21 + 0×20
- = 101102
For a hands-on implementation of these conversions, see Understanding Number Systems and Binary Conversion in Python.
Binary Arithmetic: Addition and Subtraction
Adding Binary Numbers
Binary addition follows the same principles as decimal addition, with carry-over when digits exceed 1:
Basic rules:
- 0 + 0 = 0
- 0 + 1 = 1
- 1 + 0 = 1
- 1 + 1 = 0 (carry 1)
- 1 + 1 + 1 = 1 (carry 1)
Example: 22 + 9 = 31
- 101102 + 010012 = 111112 (3110 ✓)
Representing Negative Numbers: Two's Complement
- The leftmost digit has a negative weight
- This allows computers to represent both positive and negative numbers efficiently
- Simplifies subtraction by converting it to addition of a negative number
This representation is foundational to how programming languages store integers; learn more in Understanding Data Representation in C Programming. Binary arithmetic directly determines the range of integer types, as explained in Understanding Integer Data Type: Size, Range, and Number Systems Explained.
Building a CPU from Scratch
The video progresses from basic binary concepts to designing a complete CPU, covering:
- How computers execute a single program
- How operating systems enable multitasking (running multiple programs simultaneously)
- The relationship between low-level hardware principles and high-level programming decisions
For a deeper exploration of processor design and instruction sets, refer to the Comprehensive Guide to Computer Architecture and RISC-V Overview. This foundational knowledge is part of a broader curriculum; the CS50 Harvard: From Binary Basics to Building Interactive AI Chatbots and Games course covers similar topics from the ground up.
So, in this video, I'd like to try and explain just exactly how a computer works. And I know that sounds like an
incredibly open-ended and difficult question to answer. And I'm going to go ahead and guess that the length of this
video will make that self-evident that it's not any trivial question in itself. But, uh, this is something that I
wondered a lot growing up. And, uh, even when I got to college, I knew I was going to study computer science. But it
wasn't until halfway through my degree where I took the course which answered this question and I found everything I
learned really interesting. Ended up TAing that course for a few semesters afterwards and uh find the really
low-level stuff of computing to be fascinating. And I've been surprised at uh a the lack of direct resources online
that can answer this question for just an average person who's curious and b how many uh friends I have working
technical jobs or who studied computer science or related degree in college and are not too sure how a computer actually
works. And I think um there's a lot of benefits to be held from this knowledge. Uh if you are just curious like I am, I
want to cater this video to be completely digestible even if you have no mathematical or technical background.
And I also want this video to be useful for someone who might already be uh doing an engineering relating job
because uh a lot of these lower level principles might actually affect how you structure code or just uh lay out your
programs at a higher level. Um so in this video I'd like to start with no assumption of what a computer work how a
computer works. uh just basic mathematics will be assumed which I'm assuming if you clicked on this video
you have a somewhat of an understanding of um we're just going to be starting simple with like addition, subtraction,
multiplication, division uh and like exponents. I think that's probably all the background you need to be able to
follow along for this entire video. So I'm going to answer this question by making a CPU from scratch. Um
and then we're going to go ahead take that processor uh which can run one program and build an operating system or
just explain kind of at a higher level how an operating system can be built for our processor uh and what an operating
system actually is because that's also a great question in itself. Um but without getting into too much detail now the
computer you're watching this on even if it's a phone uh is is doing multiple things at once. And if you're like me,
uh you probably have this YouTube video open, iMessage in your background, um and maybe a few other tabs behind those
two. And uh it's a really interesting question cuz we're going to get to a point in this video where you design a
CPU and it can execute one program. And a really common and good follow-up question is how can my computer run
multiple programs at the same time? Um, and so we're going to go ahead and dive into what that looks like as well
because it's a it's interesting to know how a computer works. Um, but I also want to make it pragmatic and get to
something more modern uh, which is an operating system. So, uh, I also would like this video to be useful um, even if
you don't watch it all the way through. I have no idea how long this is going to be. I am guessing it will not be short
though. And what I would hope is um for those of you who don't watch it all the way through, which is the vast majority,
I'm assuming, uh the individual segments uh will be of value themselves. And maybe hopefully you learn something
interesting along the way. Even if you didn't take it all the way, you learned something. Um and what we're actually
going to start at is one of the most interesting topics I think personally uh that anyone can know. And if you only
watch this first segment of the video and nothing else, you've at least learned something really interesting.
Um, so I'm going to start by uh explaining how computers represent numbers and letters. Uh, how do we how
can computers count? How can they add numbers? Um, and you've probably heard of something
along the lines of computers speak in zeros and ones. And uh, this isn't nonsense. It's actually very true.
Computers don't really recognize or process numbers the way we do. They only operate on zeros and ones. And once we
uh understand how computers represent numbers, it's going to make sense why they do it like this. Um without getting
into too much specifics now, but also without being incredibly vague, uh I'll say when we design computers, um they're
built with electricity. And so we can represent the absence of electricity with a zero and the presence of
electricity with a one. Um and with this it it kind of gives us a lot of flexibility to well if we can represent
anything in zeros and ones and we know how to represent zeros and ones with electricity or the absence of it. Then
we can kind of build on top of that and design an entire computer. Um, and you may be wondering, well, why don't we
just use a little a little bit of electricity represents uh a one, a little bit more than that represents a
two, and so on and so on until the the maximum of electricity we could have represents a 10, and then computers
could just work with the the numbers we know. Um, but from an engineering perspective, it's not that easy to
implement. Um, I don't want to get into the specifics of electricity because that's also uh probably a little too low
level for what I want to accomplish in this video, but um, it turns out electricity is is very inconsistent in
in in I guess flickers might be a better word for what I'm trying to say. Um, so we find it easier just to say have a
certain threshold of of electricity, quote unquote, and anything below that we interpret as a zero, and anything
above that threshold we interpret as a one. Um, and again, it's not going to make perfect sense what I'm saying. If
you're already a little bit confused, it should be confusing cuz I'm kind of jumping jumping ahead of myself here.
So, I just wanted to give some context for why we uh represent numbers with just zeros and ones in computers. So,
let's go ahead and understand how computers represent numbers. Um, so I'm going to clear this screen here that
you've been staring at for 5 minutes now. And let's start with uh a quick recap of how we represent numbers. So if
I put the number 724 on the screen, intuitively you know exactly what this is. I mean it's a number you can
rationalize. You can imagine say 724 uh pencils in your hand or something like that. But how do we actually uh how
did we learn this? When you were in elementary school and learning how to count, this number wasn't always easy to
you. In fact, the first time you saw this, it probably made no sense to you, but you're older now, you're smarter,
and you know how to interpret this. Um, so I'm going to redumb this back down to our elementary school level and uh see
how we can build this number by looking at each of the digit positions. So, we know this right here is the ones place.
That's probably not surprising. Um, we know this is called the 10's place. And then we know this is called the
hundred's place. And uh this is something you know and you don't remember when you learned it,
but you know that you learned it at some point. And we know that if we keep going, we have the thousand's place and
then the 10,000's place, then the hundred thousand's place and the million's place, the 10 million's place,
the 100 million's place, and so on and so on. Um and so
what exactly do each of these places mean? And how can we take the values from each of these places and and
generate a composite number? Um, so it turns out that to to get the value of a number, uh, we have to, and this is
going to sound painfully obvious and almost a little bit confusing because of how obvious and true it is to you, but
I'm saying it cuz it's going to make sense when we look at the computer version. um to get the value of a
number, we could look at the value of each of the digits places and kind of sum them all up together. So, we know in
the ones place, this four uh is just going to be whatever value is in that digit spot times the weight of that
spot. So, right here we have 4 * 1, which equals 4. And then over here, we have a two and it's in the 10's place.
So that's going to be time 10 and that's going to be 20. And then over here we have a [snorts] seven in the hundred's
place. So we multiply it by the weight of that place which is a 100 and we have 700. And then we can as I said we could
add all these values together. So once we compute the weight of each digit position we can go ahead and add those
uh those scaled weights together. So then we have 700 + 20 + 4. And I'm going to stack those numbers on top of each
other because that's kind of how we learn how to add numbers. Um, oops, not 24. Uh, and we know exactly that there's
only one non-zero digit in each position. So it's just going to carry right down. Um, and so we kind of
started with 724 and ended with 724. Um, and that's that's nothing too crazy. Um, but let's go ahead and and structure
that out a little more. So, what we saw here is that 724 is equal to 7 * 100 +
2 * 10 + 4 * 1. And where did that come from? Just to recap, it is for each digit
position, we take the value that is in that that spot and then multiply it by the weight of the position. So we know
the very first digit position is the one's place, so its weight is one. Then the next one is the 10's place, so its
weight is 10. And then the next one's the hundred's place, so its weight is 100. Um, and then if we were to expand,
so we've kind of taken our number 724 and written written it out in this expanded way. Um, and hopefully I've
convinced you that this is true and correct, and it it is. Um, unless I've made some error, but let's look at an
even uh more alternative way uh to to to write out this expanded form. And all I'm really going to do is uh replace the
weights um of the position. So, let me write this out and then we'll kind of uh revisit what I'm trying to say.
Okay, so I told you um that this is going to run on basic math. Uh and the exponent is probably as complicated as
we need to get for you to understand everything about how a computer works. Um everything loosely put everything uh
that I'm going to teach you in this video. Um so just to recall that uh 10 uh to the 1 is is 10 10^ squared uh when
we have a a number raised to an exponent it just means we multiply the number by itself that many times. Um so 10 by 10
is equal to 100. And then if we had 10 cubed we multiply 10 by itself three times. um and that's going to be a
th00and. And then uh one caveat to that is um there's this rule that says anything
raised to the zero power is equal one. Um and uh so that means that when we have 10^ the 0, it's going to equal one.
Um, that's just something you probably learned in algebra 2, uh, from high school, which is probably so far out of
your memory at this point, but just just take it to be the truth that, uh, anything raised to the power of zero,
we're just going to say equals 1. Um so what I've done here is I've rewritten our expression uh in this kind of
exponentiated form where each component in our sum is some number multiplied by 10 raised to some power. Um and we also
have it that uh the powers are increasing. So we start with the zero power then we go to power one and then
we go to power two. Uh, and that that's funny enough and that that's actually uh not surprising or the more you think
about it, the more unsurprising it should be because uh 10^ the 0 is 1 and so that's the one's place and then uh 10
to the^ of 1 is 10 and that's the 10's place and then 10 to the^ of two is 100 and that's what we call the hundred's
place. Um so all we've really done here is is taken our number 724 and in write it out in this expanded form. Um and
that is all we will recap about the numbers that you and I know. So now that we know uh or now that I've shown you
just an alternate way to consider the numbers we know um we have the context uh to go ahead and move on and learn how
computers count numbers. So this is how we count numbers. And it turns out computers do it very similarly. So all
of this right here is called decimal. That is our human way of counting numbers. And it's also something that's
called base 10. And what does that mean? Base 10 is worth uh worth probably discussing a
little bit before I move on and and jump to the computer equivalent. But um one way to interpret base 10 is that it
means that for any digit position in a number, there are 10 different values that could go into that digit spot. So
uh in our 724, um let's say I pick this digit position right here. We could either have uh a
zero, a one, a two, or all the way up to a nine. Um so we could have 10 different numbers
could go in this spot. And if you're wondering why it's not nine, it's because uh one through nine is nine
numbers and then the digit zero lets us add one and so we have 10. So we actually have it that in uh any digit
position um there can be 10 different values in base 10. And uh let's also see what happens when we're counting. So for
counting in base 10 i.e. The only way that you and I know how to count for now, um, we start with the number zero,
it becomes one, and then two, three, and then we're going to go all the way up and we get to nine. And each time here,
we're just adding one. So, we're just plus one, plus one, um, all the way down. We get to eight, we add + one, we
get to nine. Um, and we've used all 10 available values that can fit in this digit position. So when we want to add
one to nine um we're stuck. We can't do that in one digit position. So we have this idea of rolling over um where we we
reset this digit back to zero and then carry carry uh a one over to the next digit place. Um and so if we take these
numbers and and write it out in our expanded form, then we know 9 is just equal to or let's start with 10. 10 is
equal to 1 * 10 uh + 0 * 1. Um oops. And then if we have 9 um then we have zero in the 10's position
and a nine in the ones position. Um, and so it's kind of curious that when we we get to the maximum value in a current
digit position and we want to add one to that, then we roll over the current digit position back to a zero and then
add one to the next digit position. This kind of makes sense because what we're doing here is we are when we have this
nine here, um, we know that we want to add one more to our collective value. So what we could do is subtract nine from
this spot and then add one from this spot and that gets us the exact difference. Um that that is the same as
accomplishing uh a net just addition of one. We took all the value from the uh the digit position to the right and then
reset it to a zero and then added one to the next digit position to the left and this is the same number. Um,
so that's how we kind of carry over and and we kind of see this happen when we add numbers. So if I go to a quick
example where we add numbers, let's say we have um 18, we want to add three. Then uh we have 18 + 18 + 3. We start in
the rightmost digit position and we add the two numbers together. So an 8 + 3 is an 11. And you remember from elementary
school that you put the one down here, the remainder, and then you add the carryover up here. And um then you do 1
plus one is two. And that's that is the uh kind of exact same logic that we're showing in this in this uh extended
form. But obviously when you were younger, there was no no reasonable way for you to understand this version of
things. You kind of just learn this rule that you uh you kind of take what you have. if if you if a digit position
exceeds 10, you put the remainder down below and then add one to the carryover. Um, and so that is uh just some more
context that we're going to see this come back very shortly. So we saw that base 10 is this idea that we can have 10
different values in any digit position. And now we're going to come and uh learn what base 2 is. So we said humans speak
in base 10. Um so this decimal is synonymous with base 10. And now I'm going to make this claim
that computers uh operate in base 2. And this is called binary. And so what does this actually mean? Um
well we said in base 10 what that means is that in any digit position you can have 10 different values. And so what we
can translate that to in base 2 is then in any digit position we can have two different values. And those two
different values are zero and one. Um and that is just exactly where the idea that computers only speak in zero and
one come from. Um but let's go ahead and see what that means. So, um, I'm going to give us a binary number. So, we're
going to start with the binary number 1011. Um, and this is a binary number. This is
this is not to be confused with the number 1,11. This is uh which is in base 10. Um, so I
will distinguish it by subsetting it with this two. Um, so I will try to make the distinction as clear as possible. If
we are speaking about a decimal number, i.e. a number that you and I are familiar with, um, I'll write a 10 next
to it. So, if instead I wanted to say, I guess our example above, we had the number 724.
So, from now on, I would say 724 base 10 just so you uh don't get confused. But in general, it's for I'm not going to
try to give you a a complicated example. Um, so if there's just zeros and ones, it's probably safe for you to assume
that I'm talking about base 2. And if there's other digits, then it's the base 10 that we're so familiar with. Um,
and let's go ahead and I'm also going to rewrite our expanded form of 724. So, let me move this down. Um, and for 724,
we said it was 7 * 10^ 2 + 2 * 10 1st power + 4 * 10 0 power. because we start uh with the lowest
power of 10 and then we just keep adding one each time. Um and so now we're I'm I'm kind of we're kind of curious here.
Uh how can we take this binary value and translate it back to a decimal value? What does 1011 mean in binary? Like what
number is that to us? Um and it turns out we're just going to follow the same exact expansion rules. So, uh, we're
going to sum up for each digit position, um, the value in that position, which is either a one or a zero. And then instead
of 10 raised to some power, we're going to use two raised to some power because this was for base 10, but now we're
going to be using base 2. So it'll be we're going to start with um the one spot is going to be two to the zero and
then instead of the 10 spot being afterwards uh we're going to have the two spot which is a little interesting
cuz you know in our in our current uh number we would have um in our in our binary system that we're so comfortable
with we start with the ones position and then go to the 10's position and then the hundred's position. Um, and now
we're going to start with the one's position and then go to the two's position cuz that's 2 to the first
power. And then after that position, we have two to the second power, which is four. Um, and after that would be 8. And
so if I come to our original decimal example, we notice that between each of these, we're multiplying by 10. And now
we're in each of these, we're multiplying by two. Um, we're looking at increasing powers of two, which is is is
pretty interesting. Um, so let me go ahead and erase that. And if this is feeling a little bit confusing, I really
hope it it it should it shouldn't feel intuitive yet. Um, I've said a lot of random things so far in this video. Um,
and now is where we're actually start tying them together. So, let me spill the answer for you on this one, and then
we'll we'll walk through and see what it means. So, um, let's start with the rightmost digit
position. Uh, cuz we start with, uh, 2 to the^ of 0. Um, so I'm going to do 2 to the^ 0. And what do we multiply it
by? We multiply it by the one that's there. And then we have that digit position done. Now we come on to this
digit position. Uh, we have uh, we're not on the 2 to the^ of zero. Now we're on 2 to the^ of one. And then we
multiply that by whatever is in that digit position, which also just happens to be a one. Um, let me move this over
here. Um, and then we then move on to this digit position, which is the 2 to the^ of two position. And what's there
is multiplied by a zero. And then we come on to the very last digit position. And that one is uh the next power to
use. We use powers 0, one, and two. So now we use the power of three um and we multiply it by whatever is there. So now
we have this expression um and you might need to pause here and just read this out loud to yourself to
make sure that you understand exactly how I derived this long form expression. Uh it may not be clear what exactly I'm
doing but how I got this expression should be clear. Um and you should also be able to
understand that uh in base 10 i.e. top row binary that or decimal sorry that we're used to. Uh each digit position is
some increasing power of 10. That's the weight for each digit position. Now that we're in base 2, each digit position,
the weight of that position is some power of two. Um and let's go ahead now. Uh if you need to pause and and just run
through what I explained, do that now. But let's go ahead and uh expand this out. Um so we know that uh we have 1 and
then 2 to the^ of 3. So 2 cubed is 2 * 2 * 2 and 2 * 2 is 4. So * 2 is 8. So we have 1 * 8. And then we have 2 ^ 2 um
which is 2 * 2 which is just equal to 4. And we multiply that by 0. Um and then we have 2 to the 1st power
which is the easy one. And we all know that's just two. Any number to the first power is just saying multiply that
number by by itself uh once not two not two times that's squared. So we just have it once then it's just that number
itself. Um and then we also said earlier that anything to the power of zero is just one. So now we have this expression
and now we're this should look pretty comfortable. Uh no exponents here. So 8 * 1 is 8. Uh 0 * 4 is 0. 2 * 1 is 2. And
1 * 1 is 1. And that's just 8 + 2 which is 10 + 1 which is 11. So and that's base that's uh base 10 11. So the the
number the 11 we're comfortable with. So now I can tell you that this number 1011 in binary is just 11. Um
and that is a totally different way of representing a number uh that we are already familiar with. Um and this is
really interesting because we only had zeros and ones to work with, but we were able to map that back to a number that
we know perfectly. Uh that doesn't need to just use zeros and ones. Um so let's go ahead and look at uh one more
conversion example. Um and uh let's do the number um 1 0 uh 01. Um and kind of go through the
same process again. So uh we know that the weighted positions let's start with that actually. Um let me make this a
little bit wider so we can write on top of them. 1 0 0 1 that's base 2. And we want to know what this equals in base
10. Um and so if we start from the right we know that this is the 2 to the 0ero position then this is 2 to the 1 then
this is 2^ 2ar and this is 2 ^ 3. Um and that's just for our reference. Um so we can go ahead and generate this uh this
long form expression where we know if we start from the very right we know that that is
1 * 2 0 and then we have a 0 * 2 the 1. Um and the the 0ero comes from the fact that that's what's in this digit
position and that's also where the one comes from here. And then for the next digit position we have a 0 * 2 ^ of two.
And then for the very last digit position we have 1 * 2 ^ of 3. Um so then we can go ahead and combine those.
We add those all up. Um so this 1 0 0 1 in base 2 is equal to 1 * 2 cubed + 0 * 2^ 2 + 0 * 2 the 1st power + 1 * 2 0
power. Um, and this time, uh, I could expand this all out, but there's something interesting to note here, and
that's that we have two examples of us multiplying a number by zero. And from basic numbers, uh, as you also learn in
school, anything times 0 is just zero. Um, so there's no point in us doing these expansions of converting 2 ^2 to 4
uh, just to multiply by zero. If we see that it's being multiplied by a zero, let's just ignore it because we know
it's not going to add any any values to our final sum. And that's that that that's going to save us some time later.
Uh if I gave you a super long binary number that was like say a 100 digits wide and told you to convert it um
then you know this would this would be kind of uh a big time saving um for all those zeros that you could drop out. Uh
so then we can simplify this expression. Uh we dropped out those zeros. We just have these two terms left. And then we
know that 2 cubed is 8. So that's 1 * 8. And then uh remember that anything to the power of 0 is just 1. So then we
have 8 + 1 which is 9. So and that's 9 in the decimal we know. So this 1 0 0 1 in base 2 is equal to 9 in the number
system that we know. And this is uh really cool. Um, I remember when I first learned this, I was kind of mind blown
uh at the fact that we could take these random se this seemingly random sequence of zeros and ones and then map it back
to a number that we are so familiar with. Um, and that uh should make it clear how we go
from binary the zeros and ones to decimal which is a number system we speak. Let's look at one other example.
What if I gave you this this big number right here in binary? That's not the number 10,000. That is 1 0 0 0 in
binary. But I would like to know what is this in base 10. Um and uh well, we know let's just go ahead and
count our positions and then generate this expression. Um so we know our digit positions start at 0 1 2 3 and four. So
we have uh that it equals 1 * 2 4 and then + 0 * 2 3 + 0 * 2 ^ 2 + 0 * 2 ^ of 1 + 0 * 2 ^ of 0. And from the rule we
learned in the example before, whenever we're multiplying something by zero, we can just get rid of that term cuz it's
going to equal zero. That's exactly what's happening in all of this expression except for this one digit
position. And then we just know that uh 2 to the 4 is equal to 2 * 2 * 2 * 2. And that's going to be uh 2 * 2 is 4.
Multiply by 2 again you get 8. And then one more time you get 16. So this number right here uh 1 0 0 0 in decimal is
actually going to be equal to 16 or sorry 1 0 0 0 in binary is going to be equal to 16 in decimal um and uh luckily
this is a very popular concept. So if you're feeling a little confused here um on anything I've shown that's totally
fine. Just go ahead and Google it. watch another YouTube video if you need to. Um, I do not want to do more examples
just because this video could drag on forever. Okay, so now we have learned up to this point how to take a binary
number and translate it back into the base 10, i.e. decimal number that we're more familiar with. So, what I'd like to
do now is the exact opposite process. If I have a number like 14, how can I get it to binary? And we're going to almost
take the inverse of the exact process we we had before. And why are we going to learn this? It's going to be because um
we're going to want to program our computer eventually. And we're going to want to be able to store numbers in our
computer. And it's going to be useful then, in fact imperative that we know how to take the number we want to put
into our computer and convert it to binary so we can load it into the language that the computer speaks. So uh
we've been working with this number 724. So let me go ahead and give us the expanded version one more time. So we
said this was 7 * 10 ^ of 2 + 2 * 10 1st power + 4 * 10th power. [snorts] And
I'm also going to go ahead and bring up this binary example from above. Uh where we had let's just say 1 0 1 1.
And I'm going to sub subscript this with two because it's binary. And we said this is equal to 1 * 2 to the 3 + 0 * 2
^ 2 + 1 * 2 1st power plus uh sorry 1 * 2 to the zeroth power. [snorts] And if we notice something here um everything
on our right hand side is decimal numbers the ones we're very familiar with. And we know that we take this this
left-hand side which is unfamiliar to us and we're able to expand it out in a form that is actually only containing
decimal numbers. [snorts] And this is the exact kind of logic we're going to take to get a number from
binary to decimal. So if I have the number 724 and want to convert it to to binary then if there's a way where I
could write it out in this expanded form as uh numbers zeros and 1* 2 to some power then we know we can just read the
the multiplicate the multiplicative coefficients left to right to know our number. Um in this case it's easy. we
have we have a one and then a zero and a one and a one and this is exactly how we end up with this number over here. So
this is a little abstract um and this is probably a little more complicated than what we just learned. Uh it's my
personal opinion that taking a a binary number and converting it to decimal is easier than me asking you to take a
decimal number and convert it to binary. But let's go ahead and do this by working through an example. Um so let's
take the number 15. 15 in base 10 i.e. just the regular 15 we're familiar with. What exactly is that in decimal or in
binary? Um so question mark base 2. What we want to end up with is some expression where we start with 2 ^ 0 and
work up to some power of two and multiply each of those terms by either a zero or a one. uh until we have some
equivalent value that sums up to 15. And as we kind of saw earlier with this binary conversion, we only really need
to care about the powers of two that are multiplied by one. I in our example here, this one uh since it's multiplied
by zero, it drops out. So we kind of know that uh I'm going to move this out the way. Uh this is really just equal to
uh 2 ^ 3 + 2 ^ of 1 + 2 ^ of 0. Um and I'm gonna make this interesting claim uh that any number in decimal any number
that you can think of can be expressed as a sum like this. And what do I mean this? I mean a sum of powers of two. And
there is a exactly one way to represent each number accordingly. Um and I can show you some examples. So if
we have the number one in decimal, then we know that two to the 0ero is just one. And then let's say we have the
number two in decimal. Well, this is easy as well. We know 2 to the^ of 1 is just two. Uh but let's look at let's
look at h what happens when we have three. Um well, we know how to represent one and two in binary. Uh and we can
just add those together because they sum to three. So we would just add their binary representations together and we'd
be left with 2 1 + 2 0 and then four is easy uh because we would know that 2 ^ of 2 is 4. Um and then we'll do one more
which is five uh and we know that uh 5 is equal to 4 + 1. So that's 2 ^ of 2 + 2 ^ of 0. And now uh for each of these
let's uh go ahead and convert it back to binary. Um so let's start with this this five because I think it's the most
interesting case. And we know that the uh we can't skip any digit positions. I.e. the rightmost digit position in our
binary number has to start at 2 to the 0. The power has to be zero. And then we have to keep working up from there until
we we have all the powers that we need in our sum. And that's the same way when you're when you're building out a number
in decimal. You can't skip the 10's place or the hundreds place just because the value in that digit position is
zero. You have to explicitly write a zero to make it apparent that there is no value in the 10's place and then the
next value is the hundred's place. So in our example here what we would need to do is um I'm going to bring out in this
long form. So we we know we have this 2 to the two. We don't have any uh thing multiplied by 2 to the first power. So
we do is we just leave it as a zero. We make the coefficient zero because that means it multiplies out to nothing. And
then we have 1 * 2 to the 0 power. And this is the exact form from earlier where it was pretty easy from there just
to read the coefficients left to right. 1 0 1. And now we have our decimal or our binary number. Sorry. So we took our
five and uh got it to a binary representation. And if you don't believe me, well, the evidence is on the screen.
But if you want to verify for yourself, pose the counter question. What is the decimal representation of the value of
the binary value 101? And if you expand it out like I've already done, you're just going to get five. And uh let's do
this for the example of 42. So we have all we have is 2 ^2. But we know we have to get all the way back down to the
power of zero. 2 to the^ of 0. we just add in 0 * each of those terms. So we have 1 * 2 ^ of 2 and then we don't want
to add anything else. So we just do 0 for the first power of two and zero for the zeroth power of two. And then we
know we just read these coefficients left to right which is 1 0 0. So that's how we represent four in binary. And in
our example here, we have it easy cuz we have every power of two uh in the entire range included. So we know that's 1 * 2
^ of 1 + 1 * 2 ^ of 0. And that's just going to be equal to 1 1 in binary. And we'll just go ahead and finish out what
we have here. Uh we know that 2 ^ of 1 is equal to 1 * 2 ^ of 1. Then we don't want to add anything else. We just
multiply the remaining terms by zero. And there's only one remaining term for us to get back to the power of zero. So,
we're just left with one zero. Uh, and that's supposed to be subscripted with the two.
And last but not least, uh, this one's trivial. Um, we've already worked our way back down to 2 to the^ of 0. So, we
just have that one term and that's just equal to one. Um, so hopefully you notice a little bit of a pattern here.
uh where I have been able to convince you at least that the first five numbers in decimal are are uh able to be
expressed as a sum of twos raised to various powers. And if we can figure out for any number what that sum is, then
I've shown you it's pretty easy once you have that to expand it out and then get the binary representation.
[snorts] So let's try a uh a little bit more difficult example. Um uh let's try 13.
And >> what we're going to do is we want to find the sums of twos to various powers
that sum up to 13. And we're going to do this by starting uh with the biggest power of two that is that can uh
contribute to our sum. So uh I'll go ahead and and write this table out over here. We know 2 to the^ of 0 is one. 2
to the^ of one is two. Then 2^2 is 4. 2 cubed is 8. 2 to the 4 is 16. 2 5th is 32. And I'll do one more. 2 6 is equal
to 64. And what we want to do is just start with the biggest uh x such that 2 to the
x is less than our target value of 13. And that's going to happen uh right here. 2 ^ of 3 is 8 which is the largest
value uh that is less than or equal to our target value. So we know that uh 2 cubed is going to be in this expression
and then when we have uh we kind of have um 13 on this side and we have eight on this side and so that means we need to
get five with some remaining combinations of entries on this list. So we'll do the same process for five. And
if we look at this list, uh we know that one is less than five, 2 is less than five, four is less than five, 8 is not
less than five. So this two 2^2 is the biggest power of two, which is less than or equal to our remainder, which is
five. Our remaining sum, should I say. So then I'll add our two squared in. And now we have a four. And so we know that
13, we have 13 on this left side and we need to get 13 on the right hand side or else they're not equal. And we have 12
on the right hand side. So we just need to get one more. And that's easy enough because we know that 2 to the^ of 0 is
one. So I'll just add that in. And now we've uh done the exact task that we we we said was the difficult part, which is
expressing our target number of 13 as a sum of various powers of two. So now let's go ahead and uh I should have
demarcated this with a base 10. So you know I'm working with the decimal number. Let's go ahead and and bring
this out to the expanded form where we add in the ones and zeros and the missing powers of two. So we would know
we have 1 * 2 ^ 3 + 1 * 2 ^ of 2. We don't have any term with 2 ^ of 1. So we multiply it by zero. So it doesn't
actually add anything. And then lastly we do have 2 to the^ of zero. So we will put a one there. And from there, it's
pretty easy. We just read left to right what the coefficients are. And we have uh our binary equivalent. So 13, the
decimal version of 13, the number that you and I know is 13 is equal to 1101 in binary. And you can expand this out,
which is kind of what we have right here to prove that in fact uh I am not lying to you. It is 13. So let's try one more
example of that just because I think it's a little complicated. Um, let's go ahead and do uh let's say 22. I
just picked it off the top of my head. Hopefully it works out to be somewhat pretty. Um, so we have to find the uh
the sums of powers of two again. And I'm going to go a little bit faster this time. So I'm just going to look at our
list and I can see that 16 is the biggest value here that is is less than or equal to 22. So I know we have a two
to the four here and let's keep things easy. So, I have 22 = 16 + uh we know we're going to need something else cuz
the left hand side does not equal the right hand side. And 22 - 16 is 6. So, we're trying to get a six now. Um, and
four is the next candidate here in terms of largest power of two that doesn't exceed our target of six. So, I'll add a
two squared into our sum and that's going to be equal to four. And now we have 22 on the left hand side and 20 on
the right hand side. So all we need is to get two uh to our expression. And that's easy. We know 2 to the^ of 1 is
just two. And now we know that the 22 is equal to 22. So we've we've con we can convince ourselves that this uh
left-hand expression here is equal to the right hand side of the expression. And so then what we can do is we can go
ahead and write this out in the expanded form which you should be comfortable with now hopefully or it should at least
be becoming apparent to you. And that's equal to 1 * 2 ^ 4. There's no 2 3. So we just write a zero as the coefficient
because it multiplies away. Uh and that's because anything multiplied by 0ero is just zero. So this this term we
just added contributes nothing to our sum. And then we have a 2 ^2. So we'll add the 2^2 in. And we have a 2 to the^
of 1. And we do not have a 2 to the^ of 0. But we need to go all the way down from the largest power of two, which is
four, all the way down to zero. So we stop like this. And then we can we can take these coefficients and just read
them left to right. So that's 1 0 1 1 0. And that means that 22 in decimal is equal to the binary number 1 0 1 1 0.
Cool. So, you've now uh gotten a pretty high level but also complete understanding of
what binary is. I've shown you how to take a binary number, convert it to decimal so you can make sense of it and
also how to take a decimal number and convert it to binary. And there is one final thing that we will want to do and
be able to understand about binary numbers and that is how do we add them? And I talked a little bit about this
earlier when we we talked about decimal, but let's go ahead and and recall our decimal example. Let's say I have 74 +
39. Well, we keep we start at the rightmost digit position and we keep adding the
values in the corresponding digit position. So, we'd add this 49 together and we'd write the sum of them below.
So, let me actually pick a better example. Uh let's do uh 782 plus um 531.
So in our example, we'd start with this rightmost digit position. And we just know 2+ 1 is 3. So we write it down
here. And we move on to the next digit position, which is 8 + 3. And 8 + 3 is 11. Which you should remember this means
we have carryover. So we put the remainder, which is 1, down here. And then we carry over a one up here. And
then now when we get to the next digit position, we add 7 + 5 which is 12. But we also have to factor in this carryover
which makes it 13. And that's exactly what we do. So for decimal, what we did is we started at the rightmost digit
position and for each digit position, we added the corresponding numbers for a and b. If I'm letting this be A and this
be B, our two operands, and I'm going from right to left until we've added all of the digit positions together, and you
have your sum. And it turns out binary is going to be exactly identical to this. So, um let's
see, we have our 22 and we also learned that uh let's take 22 and add uh another random number to it and see what the
result is. So, I'm going to take 22, which we know is 1 0 1 1 0. And let me pick another number. Uh, so we don't
have to reconvince ourselves that this is true. Um, and I'm going to come all the way back up here. And we said that,
uh, 1 0 0 1 is equal to 9. So, let's do this. 1 0 0 1 is equal to 9. So, intuitively, we're adding 22 + 9. And
these are both the decimal numbers we're so comfortable with. We know that's equal to 31. Uh and you know, maybe you
have to to verify that yourself, but you can use your calculator here. Um so we're going to start right to left. And
before I start this, you might have a quick question. And that's uh the two operands we have, this a and b, this 22
and 9, do not have the same number of digit positions. So what do we do here? And there's something beautiful you
learn that carries over and is just as true now. So let's go back to our decimal word for our decimal world for a
second. And if I gave you the number 762 arbitrarily chosen, then you know that this is equal to the same number as
0762, which is equal to the same number as 00762.
And more generally, uh any amount of zeros before our number are equal to the original number. And that's because we
just ignore ignore any leading zeros. And why is this uh why can we do this if that's not clear? Um and that's because
if we expand this 762 in our exponent way, we know we have 7 * 10 2 power + 6 * 10 1st power + 2 * 10 to the 0 power.
And if we add any number of twos, all we're doing is adding 0 * 10 to some increasing power. And we already
discussed why 0 * any number is equal to zero. So this 0 * 10 the cubed is just going to drop out. And if we had the
same thing again where we had a 0 * 10 4, that's just going to drop out. So any amount of leading zeros actually has no
impact on the total value of our number. And so what I'm trying to get at and took a long way of explaining is that
you're able to add number you're able to add zeros to the start of a number without changing its value. So what I'm
going to say in binary is that if we have this number in binary, it's going to be the equivalent of this number in
binary where we add one one or one zero. Or we could add any number of zeros and it's going to be the equivalent
value in binary. So, I'm just going to go ahead and take that logic and make our two operants in this addition
problem uh have the same number of digit positions. Just like that, I added a zero to the front of our number, but did
that does not change the uh numerical interpretation of that value. So,
going to take a sip of water here. Let's go ahead and add these two numbers together then. So we have a 0 and a 1,
which 0 + 1 is 1. And then we have 1 plus 0, which is 1. 1 + 0, which is 1. 1 + 0, which is 1. And 1 plus 0, which is
1. Um, and this is not that interesting. Uh, we didn't actually see carryover, so I kind of picked a bad example in
hindsight, but we'll do another. Don't worry. Um I am going to claim that this number in binary plus this number in
binary equals this number in binary. Um and our if we did everything right, we should hope that this number is actually
equal to 31. So let's go ahead and uh convince ourselves that that's the case. So uh we start with 2 ^ of 0 1 2 3 4. So
we have 1 * 2 ^ 4 + 1 * 2 ^ of 3 + 1 * 2 ^ of 2 + 1 * 2 1st power + 1 * 2 to the zero power. And that's cuz we have one
in all these digit positions which is just equal to 2 4 which is 16 + 8 + 4 + 2 + 1. And 16 + 8 is 24 + 4 is 28 + 2 is
30 + 1 is 31. And uh sure enough this is the exact result we were expecting. Um but what if I wanted to give a more
nuanced example uh one where we had carryover. Well um let's do exactly that. Let's go ahead and do 101
plus one one. Um, and I will go ahead and spill this out for you that this is uh five
plus 7. Um, and we would hope that the result is then 12. And it'd be a good exercise
for you probably to pause this video and convince yourself that those two numbers are uh five and seven. But uh we we need
not do that for the sake of time. So, uh, we add this 1 + 1 and we may be convinced to write a two down here cuz,
you know, there's no, it doesn't sum the more than 10. We don't carry over. But remember, in in binary, we only have
zeros and ones to work with. So, versus decimal where we carry over when we're going from 9 back to to zero to 10. Uh,
in binary, we carry over uh when we're going from one to two, i.e. we roll it back to a zero. So, we put this
remainder down here. um we put a zero down below and then and then carry a one over and now we have 1 plus zero which
is one but we have another one that we have to add the carryover and we're at two and we have the same issue as
before. So we put the the carryover we put a zero below and then carry over once again. Um
>> and now we have 1 plus 1 + 1 which is three. And we're going to go ahead and it's it's interesting. We we put the
remainder uh when we modulo by two. So 3 / 2, the remainder is just then a one and then we carry over a one here. And
uh this is our final result. And uh this is actually equal to the bi to the uh number 12. You can verify this yourself
for it to be true. So basically when we're adding binary numbers, if we have a zero and a one in a position, it
becomes zero. If we have a one and a zero in a position, sorry, I misspoke. If we have a zero and a one, it's a one.
A one and a zero, it's a one. A zero and a zero sums to a zero. And then if you have a one and a one, we know this is
binary two. And so that means we put a zero over here because the two divided by two, there's no remainder. And so we
just carry over a one. And then the uh interesting case that we can't generalize from the remainder of these
is if we have a one and a one and a one and that means we have three and we know that the binary representation of three
is one one but what we do uh to get that if you don't understand is uh we have the three we take the remainder when you
divide it by two 3id two has a remainder of one so we put the one below and then we uh carry over the extra one and uh
these are kind of the four patterns you need to understand um when you're adding binary numbers.
Awesome. Um and this is now uh has become clear how you can take binary numbers and convert between decimal
either binary to decimal or decimal to binary and how you can add binary numbers together. Okay. So now you have
uh learned all but one of the fundamental tricks of binary. So, we learn how to go from decimal to binary.
Then we learn how to go from binary to decimal. And then we learn how to add binary.
But we have yet to learn how to subtract binary. And that is the last thing we need to do
before we have mastered binary. So what we would like to understand is how can we subtract two numbers in
binary and it does follow a very similar process to the subtraction that we know. But it turns out there's a better way to
do it. But before we can even look at subtraction, you may have another question that you wondered at some
point. And that's how do we represent negative numbers? And there is a multitude of ways for us
to represent negative numbers, but the one we're going to go with is a very popular one called TW's complement.
[snorts] And what this is is a very simple rule where all you do is interpret the leftmost digit in your
binary number to have a negative waiting. Now, this is confusing and it's going to require some
digestion, but what we need to agree on now uh is how many digits each of our binary
numbers are going to have. So, in decimal we have uh say the number 7 or it could be 656
or 10,432 and this has one digit, this has three digits and this has five digits. And we
also saw some binary examples. uh we had 1 one which we saw was three and that's two digits and we had something more
complicated uh where we could have like a 1 0 1 0 1 and that's going to be five digits. Um so binary and decimal have
this property in common where you could represent a number and it could take any number of digits to represent that
number. Um for two's complement it gets a little tricky with this variable notation. So, we actually want to agree
on a fixed width for every number. And this may seem like a restriction, and it is. Um, computers uh aren't able just to
arbitrarily represent any digit of any length. Um, and this is one of the design choices that will become more
apparent why that's the case as we design our computer. But for now uh what I would like to do is let's just make
this assumption or write this on the screen that every number in binary. So every binary number
uh shall use four digits. So if our number that we're trying to
represent in binary is less than four digits then we just pad it with zeros. Okay, that's easy. Uh, but what if we
have a number that we want to represent that's more than five digits? We saw some examples earlier. Sorry, more than
four digits. Um, we saw some examples of these numbers earlier and we're actually just going to say our computer is unable
to represent them. We have a upper bound uh on the range of values we can represent. Um, and that's true. If if in
decimal I said you can only use three digits. um then the largest number you could represent is 999 and the smallest
you could represent is zero and that means there's actually 1,000 distinct values you can represent in this range.
Um, and we're going to make the same restriction on binary. And I'm going to
start this off with four. Uh, just so we can learn how to do this choose complement. And it turns out in our
computer, uh, we're going to use eight. In our computer, we're going to restrict every number in binary that we represent
to be exactly eight digits. And this is just one of many design choices that a uh, implementer of a computer has at
their liberty. So, more modern computers, you may have heard of 32bit or 64-bit computers. Um, those are two
very popular representations. Uh, and that is kind of the same rule. If you've heard those numbers before, what that
means is internally that computer is you can say representing every number is exactly either 32 or 64 bits. Um, you
may be wondering, hey, this is kind of a a pretty tight restriction. Like with four binary digits, we can't represent
that many numbers. Um, and it's true. With 32 or 64, however, you are able to represent very large numbers and numbers
larger than most people need to represent for computational purposes. And there are actually uh techniques
that we can use to get around this to represent even larger numbers. Um, but those are uh implemented at a higher
level. So I'm not going to say that each binary number shall use four digits. Um so uh we can't represent the number in
our scheme 1 011 cuz that's five digits. Um cool. So I uh said uh we have this number one 011 and we know this is equal
to let's just I picked this number out of nowhere. It's a four-digit binary number. We can expand this out as 1 * 2
^ of 3 + 0 * 2 ^ of 2 + 1 * 2 ^ of 1 + 1 plus sorry 1 * 2 ^ of 0 and what 2's complement says is we take the most
significant digit position i.e. the left one and its weight is multiplied by negative one. So now instead of this
being a positive 2 cubed, instead of this digit having weight 8, it's actually going to have weightg.
So what this means is we have uh this -1 * whatever is inside our representation of that digit position. Um + 0 * 2^ 2 +
1 * 2 ^ of 1 + 1 * 2 ^ of 0. And so that means that we have8 + 0 + 2 + 1. And that means we have -8 +
2 + 1. Sorry, I don't know why I wrote a six. And that's going to be equal to -5. Um, and this is should be a little bit
confusing because you're you should be wondering, hey, why isn't this equal to 11? And why did we choose it to be like
this? Um so those are two loaded questions. So I want to make sure understand first is how we can interpret
numbers in two's complement. Um and that is that this uh most significant digit position is going to have weight
negative uh negative value. And this actually means that uh for our four-digit values, whenever we have a
one in this most significant digit position, the leftmost one, our number is going to be negative. Um cuz this has
the most weight and uh these all have less weight than it. So as long as this one's this leftmost digits a one, that
means our number is negative. Um and then similarly if that's a zero then we have the original logic before uh where
we're adding sums of weights of twos that are multiplied by either one or zero and so that means the number must
be positive. Um so let's do one more example. Uh let's say I had one one one. So we have
- 1 for this leftmost digit position time 2 ^ of 3 and then we have + 2^2 + 2 the 1 + 2 0 and so that is -8 + 4 + 2 +
1 and that's going to be equal to uh -1 if we sum these all up. And so this is interesting because you know in in what
we learned about less than an hour ago we had uh this would be equal to uh 8 + 4 which is 12 + 2 which is 14 + 1 which
is 15. Uh this should be 15 but now it's negative 1. And that's a little bit confusing. Um but this is actually how
we're going to build our computer. So you should make sure this is clear to you. This isn't something I would
recommend skipping over. uh our computer is going to use two's complement which is the exact binary we just learned with
this one caveat that uh any weighted value i.e. The leftmost digit position has weight negative and
this lets us represent negative numbers. Uh we just saw how to represent negative one. Now so what I'd like to show next
uh well I will tell you that addition works out the exact same. So let's say I had -1 and I want to add -2. So uh if I
wanted to or let's say we have uh -5 and negative 1 and we want to add those together then we have -5 + -1 which is
equal to -6 and we know these binary representations are 1 0 1 1 and then 1 one1. So we can do the same arithmetic
as before. So 1 + 1 we put a zero below and carry a one over here. Then 11 one one we have a one below and a one
carries over. And then one 0 one we have a zero below and a one carries over. And uh then we have 111 which is you know
just three 1. Um and we've kind of made a interesting remark. Uh let's go ahead and ignore
this leftmost digit position because we said we're only working with four bit values now. Fourdigit values. And let's
figure out what 1 0 1 0 in two's complement binary equals. And that's going to be equal -2 ^ 3 plus we skip 2
^ of two. We do have 2 the^ of 1 and we skip 2 to the^ of 0. And that's going to be equal to8 + 2 which is just equal to
-6. And so that's that's that's beautiful. We had a -5 here ag -1. We added them together and we still got the
-6. This does mean however that there are uh some binary values that fit in four
digits but this if we add them together it can't actually fit in four digits. So let's let's let's look at an example of
that. Let's say I have the binary number 011 1 and I will tell you that this is 1 + 2 + 4 which is 7. So we're doing 7 + 7
and we would expect 14 to come out. So let's do the arithmetic. 11 1 is zero. 11 one is one with a carryover. 11 one
is one with a carryover. And then 1 0 0 is just one. Um and this should equal 14. But if we
actually expand this out, uh we well we already know there's some danger because this leftmost bit is this leftmost digit
is a one. Which means as I said earlier that the number is actually negative. We know this can't be the case because 14's
positive. But let's go ahead and and expand it out. Uh we have -2 cubed + 2^ 2 + 2 1 and that's -8 + 4 + 2
and that's going to be equal to -2 which is not equal to 14. So I'm going to take a sip of water
here. This is interesting. Because we have this two's complement
rule, there are actually ranges of the values we can represent in terms of four binary digits
and it's just a limitation and it's something we're have to be careful of when we're designing our computer. Our
computer which I said will operate on 8bit values, eightdigit binary values. If we add two numbers that individually
may be able to be represented in eight digits, uh it's possible their sums not able to be represented in eight digits.
And we have this issue and this is called overflowing computers. You may have heard of it before. Um but it it
means that we can't represent a number in the number of digit positions that we have. And that's just something to be
aware of. um I will try ignore all of those cases and and hide things away so that we we don't have to worry about it
because I don't want to get too technical here. But uh I just wanted to make it something that we aware of. And
then there's one other really interesting uh property of two's complement and that is to negate a
number. So uh in in in decimal it's easy. If we have 10 and we want to negate it, we
just write -10. And if we have -6 and and we want to negate it, we just write six. And it's easy because we just have
this negative sign that we can just throw in front of our number. And we are learning that you can't do the same in
binary. There is no just negative sign we can throw on cuz we don't have a negative sign. All we have is zeros and
ones to work with. And I mean that literally. So there is a really cool property. You
do not need to understand why this works, but it turns out to negate a number, you invert you invert
all the digit positions and then you add one to the result. So, uh let's come up here. Um we had
seven which we know is is actually let's pick let's pick this example 1 0 1 0 we know is -6. So -6 in in decimal is 1 0 1
0 uh in this two's complement. And uh we want to figure out what the negated value is we know it's six but how do we
get that now? And what I'm saying is step one is you invert all the digit positions. So your ones become zeros and
your zeros become ones. So we end up with one becomes 0, a 0 becomes a 1, a one becomes a zero, and a zero becomes a
one and then we add one to the result. So we have 0 1 0 1 and we just add one. And this is the binary addition we
already learned how to do. So we know a one and a one is a zero with a carryover. And then we have 1 0 0 which
is 1, 1 0 which is 1 and 0 0 which is 0. And this should be the negation of this. And we know this is equal to -6. So this
should equal six. And let's go ahead and see if that's true. We have 2 to the^ of two, 2 to the^ of 1, and then nothing
for 2 to the^ of 0, uh, which is 4 + 2, which is six. And so that's that's really cool. Um, so I showed you how to
negate a number in two's complement. But but why did I do that? And that's because if I scrolled back up, uh, I
said we learned how to do addition in binary, but we did not learn how to do subtraction. How can I do a? We know how
to do a plus b. And how do I do a minus b? Interesting question. And uh it turns out you already know how to do it based
on what I just showed you. Because a minus b is equal to a + b. And I know how to I showed you how to
negate a number. I showed you how to take some b and make it negative b. And then I already showed you how to add two
numbers together. So, it turns out you actually do know how to subtract. Um, and this is pretty powerful. So, let's
say I wanted to do 6 minus 2. Well, we know that six in decimal is equal to 0 1 1 0 in our two's complent binary and two
um in in in uh decimal I will go ahead and just tell you is this number right here. So, I
want to do 6 minus 2. And to do that, I'm going to do 6 + -2. So the first we need to do is is is what is -2 uh in in
binary. And so what we do is we take our representation of two. The first thing we do, I'm going to scroll up here. We
invert all the digit positions. And then we add one. So 0 0 1 0 becomes 1 1 0 1. And then we add one to it. And we are
left with 1 1 0. And you can convince yourself this is true, but I'm not going to for
the sake of time. And then what we want to do is 6 to get 6 - 2. Uh, I'll write this out. We want 6 - 2. Uh, we're going
to do 6 + -2. And we now have 6. And we now have -2. And we know how to add. So we'll just add them. So we'll do 0 1 1 0
+ 1 1 0. And let's go ahead and add. And so that's zero here, a zero here, a one with a carryover, and a zero with a
carryover. But we said we were working with fourdigit positions. We just look at the four that we have, and we would
hope that this number is equal to four. And it turns out this is this is it because it is uh we have 2 the^ of 0, 2
the^ of 1, and 2 the^ of 2. We just have a 2 to the^ of two, which is 2 * 2, which you should know is four by now. So
you uh and I know we just went a little quickly and I only gave one example. The website I will link uh I will link a
website below that I've written and it has more examples and is written with a little bit more detail. So if any of
this is confusing to you uh you can just go to that website and get a more complete understanding and some examples
may help work out the gaps. So now uh up to this point let's recap what we've learned. We have learned uh how to well
first off we recaped decimal but I'm not going to include that because intuitively you already knew that we
learned how to go from decimal to binary. We learned how to go from binary to
decimal and we actually did that first. We learned how to negate. We learned two's complement. So how do we represent
negative numbers? We then learned how to add binary numbers.
And lastly, we learned how a a little trick that lets us subtract binary numbers.
And this is all you need to know about how computers represent numbers. So even if you stop watching this video right
now, you have walked away with a a very interesting uh lesson. And that is how do computers represent numbers? And you
may not understand why it needs to be done like this yet because we haven't got any further. But at least you
understand what's happening under the hood. And when you type in uh when you're typing in 5 + 7 to your
calculator, it's not doing 5 + 7. It's doing uh 1 0 1 plus some other binary string. And it's pretty interesting. Um,
so if you've made it this far, you have learned what in my subjective opinion I would say is one of the two most
difficult components of this video. Uh, I think understanding this binary representation is very difficult and
we're going to come back eventually and there's going to be one other really difficult concept uh, which is how we
can store memory on computers. Um, and I think if you can power through both of those, then you will have a full
understanding. And I think everything else in this video will be easier than those two topics. So, even though we're
not halfway done with this video, metaphorically and in terms of difficulty, I would argue we are halfway
through the difficulty. So, what we're going to do next is learn about something called a logic gate.
And, uh, it's going to have nothing to do with what we just did. what I just did. I'm gonna ask that you mentally
push aside and then we're immediately going to bring it back together. Uh and and and so I'm I've taught you binary.
I'm now going to teach you something called logicates which are independent of binary. And then we're going to bring
it together and see how these two topics relate. Okay? So I'm going to ask that you
mentally put everything that we just did aside in your head. Ignore it all. and we're going to learn a new concept and
it's going to seem a little bit familiar as it probably should and you'll see why they are familiar and similar in nature.
But don't worry, uh, what we just learned is not going to be abandoned. Binary, I didn't put that in there just
to torture you. I put that in because it's going to be a very real concept that is almost fundamental to the
entirety of this video. and we're going to tie these things called logic gates back together with
binary afterwards. So, let's go ahead and uh see what a logic gate is. And I actually think that you may find uh the
content in this section pretty interesting. Not to say binary is uninteresting. I just think that the
majority of people will find it a little bit tedious or boring the more specific you go into it. Let us start with the
first logic gate which is called the AND gate and it looks like this.
Uh I will zoom in and draw this a little bit better. So we will say we have a and b and then we will say the outputs y. So
a logic gate takes in two inputs and puts out an output this and gate specifically. So what I will say is that
the output y is a function of a and b. And please do not let this scare you if it's giving
you uh PTSD to your high school algebra classes. It's not as difficult as it seems. And we're also going to add this
rule that a is either a zero or a is equal to one. And we're going to do the same thing for b. B is going to be
either a zero or it's going to be equal to one. And we're going to do the same thing for Y.
Y will either be a zero or it will be a one. So our function here Y is a function. Y will be the output of this
gate and its inputs to the gate are going to be A and B. So whatever A and B are will determine what Y is. and a and
b can only be either zero or one. So there's not that many combinations. So there's two values that we have that are
inputs to this function. And there's two different values that each of those inputs could have. So we're actually
going to we could build out a table to represent each of the inputs and look at what the output's going to be. So I'm
going to uh erase this and make this truth table. So we have a and b as our inputs and then I'll add
this column for y which is the output. So uh our our four different possible cases are a and b are both zero. Maybe a
is a one and b is a zero. Maybe a is a zero but b is a one. Or maybe they're both ones. And and this is uh this is
all the possible input combinations there are. If you don't believe me, please try invent another one and I
guarantee that you will not be able to do so. Now, I'm just going to tell you what the
ANDgate does. Uh, you may have guessed, but it outputs a one if both of the inputs are one and otherwise it outputs
a zero. So, it's an ANDgate because we say it outputs one if A and B are one. Otherwise, it's going to output a zero.
So in our example here, when a and b are both ones, y is going to be equal to one. And in any other case, y is going
to be equal to zero. And that's because the definition of an andgate is that it only outputs one if
both of the inputs are one. Okay, congratulations. You've learned your first logic gate. We've learned
that logic gates take in a a certain number of inputs, but they will always only output one number. I'll tell you
that now. We're going to look at other logicates. Some may take in two inputs, some may only take in one input, but
they're always only going to output one value. And that one value is going to always be determined strictly based the
inputs. So us having an A set to one and B set to one means Y will always be set to one for an ANDgate. And this property
is what we call determinism. And it's a scary sounding word, but to be deterministic, it means that the
output y is is strictly dependent and can be determined based on the value of the inputs a and b. Um, and that's just
a word that if you curiously read around online, you will probably come across. So, I thought it was worth mentioning.
Let's go ahead and look at some more other logic gates that we have. We have the ANDgate. Now, let's go ahead and
look at the ORgate. So the orgate looks like this. We have our two inputs still.
We have a b and y. Uh and it looks different because it has this little curvature on the inside. Uh but this is
a orgate. So let's do the same thing as before. Uh let's build this truth table out.
And we have the same uh same conditions that our inputs and outputs are all binary. I.e. either either zeros or
ones. So this table will be the exact same before up till now. And I will tell you
that the orgate uh you should hopefully be able to guess the andgate outputs one if a if and a and b are both one i.e
both of its inputs are one. The orgate is going to output a one as long as either of its inputs are one. So if a or
b is a one then we're going to output a one. So uh this is going to happen not in our first case. Neither of the inputs
are one. So we output nothing. We output a zero. In the remainder of the cases at least a or b one of them if not both is
a one. So that's alga output a one. And it doesn't require there's nothing wrong with both of them being one. It
just says as long as either of them is a one I will output a one. And that's what the orgate does.
Now we're going to look at the exclusive orgate. And this is called XOR. Exor exclusive or. And it looks like our
orgate, but it has this extra line. And this is going to be the exclusive ORgate. And what this one's going to do
is it's going to output one as long as either A or B is a one. but is with the added condition that both of them can't
be a one. So if A and B are both one, then it's going to output a zero. But if just one of them is a one, then it'll
output a one. So let's draw this truth table out. 0 0 1 0 0 1. And it doesn't really
matter for a truth table what order we put everything in as long as we have all the cases. So AB both zero there. It
means there's no neither of them are zero. So we up a zero. Then a is one and b is zero. So uh either of them is one
but not both of them. So it's a one. And the same can be said for the next case. But in this final case, a and b are both
one. So we say that the output's going to be a zero cuz it has to be exclusive. Either one one of them can be a one but
not both of them. And this is called the uh exclusive orgate. So in English this may translate to exactly one of a or b
is equal to one. And what you're going to kind of see here uh and I've kind of intuitively
hinted it but I'm going to now make it more explicit is that a lot of times we will interpret a zero to be false and a
one to be truthful. Um so we can kind of give some of these values use these truth truth values if you could say. So
if I looked at the output of some logic gate um then if it's a one the output of that gate is saying true. So the
condition based on the inputs is being met. And if it's a zero it means the condition is not being met. So the
output's false. Um and that's just just something worth putting out there. So we've seen the ANDgate, the OR gate, the
exclusive orgate. Let's do a different type of gate and this is going to be called the inverter gate.
And this is interesting because it only has one input. So I mentioned this earlier. I said logic gates have a
variable number of inputs but always only have one output. And that's exactly what we have here. So our truth table uh
this is the knockgate. And all the knockgate does is just invert its input. So if you throw in a zero, it's going to
output a one. And if you throw in a one, it's going to output a zero. So let's go ahead and build a truth table for this.
Well, this is easy. The A can either be zero or one. And then we just invert its value. So if we throw in a zero, we get
out a one. And if we throw in a one, we get out a zero. And this is how the inverter gate works.
So we've seen four gates and there's one other gate we should discuss and this gate is going to be a little bit
interesting because it has a a pretty unique feature about it. And this is going to be called the NAND
gate. So it is the output of the NAND of the and AND gate negated. So it means not
and so as long as not A and B are both one, this is going to output a one. So it's literally going to be the opposite
of our ANDgate. Our ANDgate only outputs one if A and B are both set to one. But our NANDgate is going to ensure it's
going to output one. It's going to tell you if not A and B are both set to one. So, our NANDgate looks like our ANDgate,
but it has this little circle afterwards, and it's going to take in our our same two familiar inputs. I'm
also just arbitrarily calling these A and B. They can be called whatever. And in fact, when you see these logic,
they're usually not labeled letters. I'm just trying to make it clear that we have our our inputs on the left and then
our output on the right. Um, and that's what you should really capture here. So, let's build our truth table. We have A,
B, and Y. And we are going to say this means not and so we will out output a one as long
as the condition met. And the condition is that not both A and B are one. So that happens in our first three cases
and not the last case. In the last case, A and B are both one. Um which means that A and B is true. But we want the
negated version of that. we we we say not A and B. Uh so we output at zero. So there's this really interesting
thing. I'm going to make two pretty shocking claims. So the first is we can build our entire computer out of these
logic gates right here. The computer we're going to design is with one very minor exception is going to be built
entirely out of logic gates. We are going to be able to build storage using logic gates. We're going to be able to
add numbers with logic gates, subtract them, and do everything that a CPU can. It turns out your computer that you're
watching this on is is generally speaking built out of just logic gates. The one exception uh for our computer is
going to be this thing called the clock, which will just synchronize everything. Our clock is just going to alternate
between 0 and one at a fixed pace. And every other component in our computer is going to look at that clock to know what
time it is. Generally speaking, the clock's not going to be built with logic gates. And we're actually not
going to study how the clock is built cuz it's going to be built out of electricity. But, uh, logic gates, uh,
back to the claim, can can be used to build our entire computer. So, we're going to take these very, very simple
logic gates and we can chain them together as in the output of one can become the input of another and we can
build these very complicated circuits. And and the the goal of this video is to build a circuit that's complicated
enough to to be an entire computer. And this sounds like a crazy ambitious goal, but it's it's really cool. And this was
one of the most fascinating things for me when I learned this is that we can build an entire computer out of some
logic gate, which seems pretty trivial. And I'm going to make another claim. I'm not going to back this one up, but it
turns out that this nan gate right here, it's called the universal gate. And any of these other logicates can be
built out of nan gates uh chained together. And we're going to see what chain together means shortly. But what
that means, if you can take that to be the truth, is that you can actually build an entire computer just out of
NAND gates, which is crazy. And that is uh to my understanding actually how most computers are implemented. You have just
have a lot of nan gates. And how are we going to build a computer out of chains together? Uh well, I kind
of mentioned this earlier when we were discussing binary. I said in computers, we either have electricity on a wire or
we don't. If we have no electricity on a wire, we'll treat that as a zero. And if we do have electricity on that wire,
we're going to treat that as a one. And these these these logicates can be built physically. We're not going to see how
they're built physically because that also deals with electricity which I'm not well equipped to teach you about
because I barely understand myself. But uh we can understand at at a level at a higher level that will just treat the
presence of electricity as a one in the absence of it as a zero and build these these these uh logic gates together and
chain them together in such a way that if we did have real electricity and a way of building these logic gates both
of which we do uh it could it could run an entire computer and that's actually something I've done. I've built the
computer that we're going to study uh with wires in the logic gates that you can buy online. And that's something I
hope to demonstrate later on in this video. But let's get back to our goal here cuz I've I've made this crazy claim
that we're going to build an entire computer out of Logicate. So I that's going to be what the next uh next big
segment of this video is about. How do we actually go about doing this? Um, and to do that, I want to start by
explaining how we can chain these logic gates together. So, let's explain how we could we could build a NANDgate by
chaining two of the other logic gates we have together. So, uh, as a reminder, the NANDgate just takes whatever the
output of the ANDgate is and inverts it. It negates it. So, if the ANDgate outputs a one, then we we output a zero.
And if it if the ANDgate outputs a a zero, we should output a one. And uh if if I asked you which two gates
we could combine um the answer would be we could combine an AND gate and a inverter gate together to build this
circuit out. So let's go ahead and see uh what this does. I will spill the answer for you and then we will we will
verify that things are true. So I'm going to take our A and B which are our inputs. I'm going to feed them into this
and ANDgate. And then I'm going to feed the output of that and AND gate into a notgate. This gate that we saw down
here. And then I'm going to let that be the output Y. And so I have this circuit. And this is this is pretty big.
What we just did is we we took the output of one gate and fed it in as the input of another gate. And it's it's a
small example, but we're we're going to see more complicated ones very quickly. So let's go ahead and convince ourselves
that this is actually equivalent to our NAND gate from over here. And the way we can do that is we can build a truth
table uh for what we have here. And what we have here is called a circuit. A circuit is what happens when you chain
logic gates together. And by chaining logic gates together, we can provide we can build circuits that accomplish more
interesting things. So these are these these these uh logic gates on their own don't do very much but by chaining them
together we could actually have them do really interesting things. Uh and I mean the end goal is chaining them together
in such a way that we build an entire computer. Uh and that is totally possible as I will try to convince you
over the next few hours. So let's go ahead and build out our truth table. We have two inputs and one output.
And let us see uh we know our our output cases are as follows. I'll change up the order here a little bit for the second
and third rows. So when a and b are both zero, well we have a is zero here, a zero here. 0 and 0 is zero cuz not both
of them are one. And then we invert it and uh produce a one. So we end up with a one as the output of our our circuit
when the inputs are both zero and zero. And and let me change color here just so it's easier to follow. Then let's
suppose we have zero and one. Then the endgate's still going to output a zero because not both the inputs are one. But
when we invert it, it becomes a one. So uh this is a one here. And if I flip this uh if I say that B is going to if A
is going to be sorry I did that backwards actually. Um, I showed you, let's see, I showed you 0 1. Oh, no, no,
I did that correctly. Now I'm going to show you one zero. And we know the output of the endgate is going to be
unaffected. It's still going to be a zero. But then when we invert it, it becomes a one. And lastly, uh, if we
have a one and a one, then what happens is our AND gate outputs a one, but when we invert it, it becomes a zero. And so
we we have this truth table right here. And I'm going to replace this with white now
that we're done drawing. What we would expect is that if this circuit is equal to the nan gate, then
the truth table should be identical. And if I scroll out, we can see that the truth tables are identical. Uh you can
compare each of the four rows against each other. Um I know I've swapped the order of the middle two, which I
shouldn't have in hindsight cuz it's a little confusing now. Uh, but I was trying to illustrate the point that the
truth table doesn't matter what order the inputs are in as long as you cover all the possible inputs. Um, but in
general, I've shown you how to build a circuit by chaining logic gates together and that you can verify that two
circuits are equal by showing that their truth tables are identical for every pair of input combinations.
So this function, this circuit is a function of A and B and it's going to equal Y. And so is our NANDgate. And it
turns out they're actually they're the same function um because they always equal the same value. And so this is
this is pretty interesting. And uh we could every time we we could avoid using the nan gate just by by drawing this.
But it turns out that we use a nan gate enough where we we want to give it its own gate. um because it it kind of when
we have these big circuits that chain a lot together, it it becomes a little bit more convenient to have uh simplified
ways to represent things we use frequently. And we will see this later on. We'll design some interesting
circuits and then we will just uh demark them as a new type of of of gate or maybe we'll say uh a triangle represents
this circuit underneath the hood. Um but but don't think about that now. That's something that uh is more of a future
concern. Let's try another circuit. So you've seen um a circuit now that has two inputs and
one output. And so for logic gates, I I made a claim. I said a logic gate has uh at least one input
and it always has exactly one output. A circuit has at least one input, but it may have multiple outputs. And
we'll see this later um when we actually try to build more meaningful circuits. Let's try build something uh and let me
draw a circuit and then we will build a truth table for it and and analyze that truth table and see if we can derive
some meaning for that circuit. Okay, this is our circuit. I'm going to take a sip of water while you look at
that. And I would like you to notice that there's three inputs. and one output. And uh we have a
inverter a notgate right here. Then we have two ANDgates over here. And then we have an OR gate over here.
That's just because uh you know it may actually be useful to screenshot this as you follow along or just you can Google
logic circuits and it'll be there. It's also in the website I have linked below. Um but we have a a circuit now and uh
this may seem like a random circuit but it actually does something a little bit interesting. Um so [snorts] I will spoil
the fun and say that this is a selector circuit. So if you put some value of A and B uh put some values in A and B then
C serves as a selector where if C is zero then the output so I'll write this out. If C is zero, then
Y is going to equal to A. And if C is one, then Y will equal B. So C effectively
controls what flows through as the output of this. So I'm going to move this over here. And let's verify this by
building a truth table for our circuit. So this truth table is going to be a little bit longer. There's actually
gonna be eight different values we can pass in. Um, so I'm going to come here. We have eight different values we can
pass in. Our output's still y. And uh, I will write out all the eight values. Uh, it may be interesting to you where I'm
getting these values from. Uh, but you you need not worry about it. Um, I'm just you'll get more comfortable
enumerating all these values out the more you do do it on your own. Um, it's getting a little messy because I
have not the best handwriting. Um, okay. So, uh, believe me, please when I say that these are all the possible
values of A, B, and C. If you do not believe me that I challenge you to pause this and try to generate a a
new uh combination of A, B, and C that's not in this table. And if you're curious where this this comes from, why how do I
know how many are in how many different values there are? Well, if we're looking at A, B, and C stacked next to each
other, um they're all either they're binary values. They're binary digits. So, there's two different values that
can go into each of these digit positions. And so I multiply the different possibilities together to
figure out the total number of combinations. So in that case there's eight. Uh and that's because um for each
of the position we can either choose a zero or one uh 01. And then we multiply that by the number of ways there are to
pick out the remaining digits. Um and we can apply this process over and over. Um,
but I'm not going to get into that cuz I think it's a little mathy and uh that's actually a study of combinotaurics,
which is probably a little too uh technical for what we're getting into today. But let's go ahead and trace
through this. So, I'm going to switch my pen back to red and I'm going to make it a little bit bigger here so it's more
readable. Our first example um I'm going to zoom out here is I'm saying if A and B are both zero and
my claim is that if C is zero which or C is one then our output should be B which is zero in this case. Um but it's a
little hard to distinguish because they're actually both zero. But let's let's verify that it's not one for sure
and that'll have you partially convinced. So, uh, what we do right here, um, is we have C, which is one.
And so, that means that we invert it. It's going to be a zero here, and it's going to be a one here. And then we have
these carried through from our our original logic gates. And so, for the first gate, we know 0 and 0 is zero. And
0 and one for the second logic gate, still zero. And so, we have two zeros flowing into an orgate, which means the
output is zero. Um, so we traced through the circuit and we our claim is verified so far. Uh, C is one and the output was
equal to B. But you should be confused because how do I know that it wasn't equal to A? Um, and or just it just
happened to be zero independently of what B was picked. Um, and uh, that's a great question. And
let's actually toggle whatever. Let's set B to a one and see what changes. So, I'm going to erase this. Um,
and everything I've left on the screen so far is un is unaffected by our changing of B from a zero to a one. But
now, if I set B to a one, then this AND gate over here is going to output a one, which means this or gate's going to
output a one. Uh, and and now we we do have a little more substance to our claim. So, we have the case here of uh A
is zero, B is one, and C is one, which is over here. And we know this output's going to be one. Um, so I'm going to
keep going through these examples. But what should be a little bit apparent by by now uh is
if you look at these and gates, we know that for an ANDgate to output one, both of its inputs need to be one.
And C goes to both of these AND gates, but one of them gets the regular C and one of them gets the opposite of C, the
inverted value of C. So we know that the one that's getting the the uh we know one of these and gates can never fire
because by virtue of C getting C going to one gate and not C going to the other gate then
the gate that gets the zero version of C whether that's the inverted version or not can never output a one because it
it's both of its inputs can't be one. So for example if I if I set C to one here then this is going to be a zero. So this
this gate can never output anything but a zero. So this gate effectively gets silenced and then the value of B can
just flow through this gate and it gets to this orgate where it's just going to go through because the orgate has a zero
here. Um and and it may take a little bit of time for you to rationalize this. So So pause it. I I would recommend you
pause things actually and fill out the rest of the circuit for yourself mentally or pause it as I work through
some of the cases. So now let's look the example um and I I miswrote this here. There should be 0 0 0 where everything
is zero. So uh 0 0 and then we have a zero here which means a one comes here and a one comes and a zero goes there.
So in this case uh both and gates output a zero. So our y is also going to be a zero. Um [snorts] and we kind of have
this rule where we said if if c is zero then y is equal to a. And that that's still the truth because C is zero and
and we have Y equal to A which is still zero. Okay. Uh
now let's look at a more interesting case. So if we have A is zero but B is one then let's look at the first case
where uh C is zero. So if C is zero then this is going to be a zero. So this gets cut off cuz we can't output anything but
with a zero. And then this is a one. Uh but we know one and zero is zero. But this is still a's value flowing through
cuz if a was a one, a one would get through. And a is a zero and a zero goes through. So a's value is is flowing
through here. That's what I'm hoping you can see. And then we have a zero or a zero which is is a zero which A's value
still flows through because if it was a one then that's sufficient for this orgate to activate. So we have C is zero
and then in our case A goes through which is is kind of what we would hope to see. And I am going to uh quickly
work through the remaining cases because I said I would. So let's do uh A is 1 and B is zero. A
is one, B is zero. Uh the first case will start with C is zero. So then a one goes through here, a zero comes through
here, a 0 and 0 is 0, one and one is one, and then a one or one is one. And you can verify this. This meets what we
would expect of the rule. And now if we flip the value of C to get the next row in the table, then we have a C is one,
this is zero, this is one, um, and then we have a zero coming through here, a zero coming through here, which means we
output a zero. And this this rule still holds. And now let's look at the very last case. Uh when both the inputs are
one. So if both the inputs are one and C is a zero. Um start with that case. Then this is a one. This is a zero. This
outputs a zero. This outputs a one. And that means we output a one. I'm going intentionally fast here just because uh
I don't want to waste too much time. Our very last case we switch C to a one. And now we have a zero going to this
gate which is effectively cutting it off. And we have a one going to this gate which is letting B's value flow
through. And we have zero or one which is one. And now we've completed this truth table. And let me erase what I
have. And uh hopefully you can you can verify this
and see that uh this claim I've made actually does hold. Um if C is a zero, then we let the value of A flow through.
And if C is a one, then we let the value of B flow through. So we've built what we called a selector. This is called a
selector. And hopefully my goal in showing you this was not to waste your time, uh, but to show you a circuit that
we are going to revisit in the future, um, but also show you that we can chain these logic gates together to do pretty
interesting things. Um, this is a a somewhat of a trivial task, but it's it's actually not that trivial. It's
it's it's trivial because I gave you the solution, but if I asked you to invent this on your own, it might not have been
apparent as to how we how we do this. Um, so yeah, this is a a a brief overview of
circuits and how we can chain logic dates together and build circuits. Okay, so what we've now accomplished is I
clear my screen here. I showed you what a logic gate is. I showed you what a truth table is and how to derive a truth
table by looking at either a circuit or a logicate. And then I showed you how you could compose logicates together to
build what we call circuits that encapsulate more meaningful behaviors. And I also made this crazy claim that
we're going to build an entire computer out of just logic gates. So before we even did that, we talked
about binary And what I would like to do now, as I promised, I would like to bridge those
two different concepts together. So, we discussed binary and we discussed logic gates. Um,
but I haven't yet told you how they relate to one another. That was a bad arrow.
And what I'm going to do in this part of the video is is bridge this together by showing how we can build a circuit, i.e.
chain some series of logic gates together to build a circuit that can add binary numbers. And this is going to be
cool because we have a way of going from the decimal numbers we know to binary numbers. And
then I'll build I'll we'll end with a circuit that can add two binary numbers together.
And in the real world, these logic gates act incredibly quickly. If you change the inputs on a on an ANDgate, for
example, the output will will settle. It's what we call propagate very quickly. And in this way,
if we uh assume that there's no time for us to convert from decimal to binary, which is obviously not true. But if we
just assume that we have a long list of binary numbers and we wanted to add them all together, then doing it with a
calculator, I guess say we also have a decimal equivalent, then adding those numbers up with a calculator or
manually, should I say, adding those numbers up manually as a human is going to take some time. Whereas we could
build this circuit that that outputs the value once you put the numbers in almost immediately.
And these logic gates operate very quickly and this will be a useful circuit because adding numbers is
something we would like to automate as a computer. At the end of the day, you may have heard some saying that a computer
is a glorified calculator. And the further along you get in this video, the more convinced you may become that that
is the reality. And it is true. Computers operate on numbers and they are able to perform arithmetic on
numbers incredibly quickly. And in in that sense, a computer really is just a glorified glorified calculator. But
let's start with the simplest case. I previously said when we learned two's complement that we had to assume that
every number in binary is going to be the same number of digits. I also then said that our computer is going to be an
8bit computer, meaning that we use eight digits to represent every binary number. And I I should have mentioned this
earlier, but I' i've been I've been trying to avoid it just in hopes of trying to avoid throwing too many words
at you. Uh bit is just a zero or one. It's synony synonymous with a binary digit uh bit. And we talked about how
most modern a lot of modern computers use 32 bits or 64 bits or even 128 or 256 to represent their numbers. We're
going to build an eightbit computer because it's enough for us to represent some numbers uh and build these circuits
out without using incredible number of logic gates. But but at the end of us building the computer, it'll be
incredibly easy for you to understand how we could scale it up to be a 32 or 64bit computer, something perhaps, let's
say, more modern. And the reason I won't do it is because uh I am drawing these logic gates out by hand and and drawing
something 64 times every time we want to have a number. This will make more sense later is painful compared to eight which
is still inherently repetitive but bearable. So let's start by building a circuit that does one bit addition. So
one bit addition means we're adding binary numbers that are just one digit position. And and so this is pretty
easy. If we're just adding two numbers, we know that the the two uh if we're doing the addition tables, we could have
a 0 plus 0, a 0 plus a 1, a 1 + a zero, or a 1 plus a 1. And I'm setting these up in the way we we normally do
addition. And so we know the output in this case is a zero. Uh in this case is a one. In this case is a one. And in
this case, we have a zero here, but we carry over a one. And so the output's 1 zero. And this makes sense. We're we're
what we're saying here is that 0 + 0 is 0. And then here we're saying 0 + 1 is 1. And then here we're saying 1 plus 0
is 1. And here we're saying 1 plus 1 is 2. Because in in binary this is uh 2 the 1st + 2 to the 0, which is just 2 to the
1st, which is just two. So we'd like to build a simple circuit that can do this arithmetic for us. So
if we set a to be one number and b to be the other, we should get the output. [snorts] And what we've noticed is that
we have two inputs here. So let's try do this in reverse. We previously looked at a circuit and then built a truth table
for it. Let's in this case start with a truth table just we have our our consolidation and then try guess what
circuit might match it. So in our case, our two operands, operand meaning the numbers that we're adding together are a
and b. And we'll let out our output y be our sum. And for [snorts] now, we're going to ignore this carry bit. We're
just going to say the the trivial uh onedigit result. And so in that in that case, I'm going to
kind of draw a red circle on this cuz it's something we're going to have to revisit. um because when we do add one
and a one, the output's not going to be zero, but we're just going to design our circuit to only have one output for now.
And we'll come back and and and add a second output that could be used to represent that carry bit. So if our
truth table may look something like this and we know the output in each of this case is a is as follows. And so what
we'd like to do is build a circuit that that encapsulates this truth table. And now what you may be recognizing if you
paid close enough attention is that this truth table over here is is uh very similar to the one I had for the
exclusive orgate. And it's not just that it's very similar, it's actually the case that they are identical.
And this is uh something that works out just conveniently. And this exclusive orgate can be used uh to do our our
little bit of a one bit addition here. Um so as it as it turns out uh if I let A
and B be our inputs then we could feed it into this exclusive orgate
and the output Y uh is is holds. And so this does our our addition for us. You could fill this truth table out again if
you don't believe me, but that's pretty cool. Um I know we made this huge oversimplification. We're looking at
onedigit numbers, but it's cool that this one gate can be used to perform uh this one singledigit position worth of
arithmetic. But what we're missing out here is that we have in the in this last case where
this red is on the screen, we have it's possible that if we're adding a onedigit number, two one-digit numbers, that our
output could be two digits. So if we're adding two one-digit numbers, a one-digit number and a one-digit number,
our output doesn't always have to be one digit. It could be two digits as we saw here. So what we need to do is update
this circuit to accommodate for this. So what we could say in each of these examples that only have
one bit of output. I talked earlier about how we can pad with zeros. So I'm just going to pad each of these
scenarios with a different zero. I'm going to do a little bit of separation here. And now let's say we have a
circuit that takes in two inputs and produces two outputs. The two outputs will be the two bit positions. And what
we're going to do is we're going to call this one the sum bit. Sorry, I wrote that weird. This is the
sum bit and this is the carry bit. And uh I'm just going to happen to call it that accordingly. Um so what I'd like to
do then is update our truth table and and add in these additional values. So our carry bit and our sum bit. And it's
just going to be the exact same values that we had as before. Um
but the interesting thing is now that we're able to represent correctly the the value of what happens when we add 1
plus 1. We can represent two. And that's because as we talked about earlier, we can't represent two with one binary
digit cuz that's just not possible. And you also may be wondering what about this two's complement? Uh let's just
ignore it for now. Ignore's complement because we're we're just going to assume that we're working just with positive
numbers. So ignore that rule where you multiply the leftmost digit by the negative one of its [clears throat]
weight. And then over here, let me just write a note that uh we're going to say S stands for the sum position and C
stands for the carry position. So now what we want to do is build a circuit that has two outputs. One for the carry
bit and one for the sum bit. And [snorts] well, we already knew how to build the
sum bit. We said the sum or the carry the sum bit, sorry. Uh, we already knew how to build this out and I' I've made a
mistake here. There should be a zero. We already knew that the sum bit is going to be equal to the exclusive orgate. So,
let's recreate that. I shouldn't have erased it, but I did. Uh, just cuz I wanted to have us a fresh slate for us.
Um, and let's say that the exclusive orbit right here gets the input from A and it gets the input from B. And this
is going to output. Let's put this over here so we can get it in the left to right. This is going to output the sum
bit. And then now let's look at this carry bit. If we look at the carry bit, it always outputs zero except for when a
and b are both one. And if you uh pause pause for a second here and try to think of what gate this reminds you of. And
the answer is that this is equal to the AND gate. This has the same exact truth table as our AND gate up here, the
[snorts] very first gate we talked about. And this works out really convenient uh because it means that we
can just steal the AND gate and use it to represent this uh C this carry bit position. So if I just
have an AND gate, then the uh output sorry let me write this over here. So we have the carry bit here um and let's
have the sum here. here. So that let's just read it left to right as we we did up here. But let's go ahead and chain
these inputs in. So when A and B are both to one, then we should have this carry. And I apologize because uh I
accidentally inverted this CNSS, which I apologize for confusion. Uh that's the summit bit the one that we
saw we could already use the exclusive or and then we wanted to add this carry functionality which we saw the andgate
lets us do and I uh now you have built a circuit that uh takes two one bit i.e e onedigit
binary values and outputs the sum of them and I'm not going to trace through every single example but uh I will do
one let's say we are adding one and one then we can look at this gate we see a and b are going into both of these gates
so one exclusive or one is zero but then one and one is one um and so we have this this this uh carry so our result we
know is in the order of we read it left to right which is carry sum and so we have a one for carry and a zero for sum
and if we interpret this as a binary value then we have 1 zero which is two. So this is really interesting. We've
we've learned about binary we thought it was useless but uh we actually then learned about logic circuits and built a
logic circuit that can add two one-digit binary numbers together. And this is really interesting
and I will let you suggest that you pause this video and trace this circuit through every case and make sure you
really understand what's happening here because what's really happening is that if we look at this uh there's one way to
justify this truth this this circuit is by the truth table. But what I like to do is I like to have an intuitive
understanding for why these circuits work the way they do. And I think I kind of did a a what I would hope was an
explanatory job with the selector circuit we previously looked at. But for this example, let's try to do a little
bit of rationing. Um there's only going to be a carry if we are adding one and one. That's the only case there's a
carry. So if there in any other case the carry zero. So we can use an andgate kind of to represent the carry logic. So
whenever there's a one and a one then this and get triggered and that's the only scenario in which we carry. And
then for this exclusive or, what that's saying is if we're adding zero and zero, then we don't want anything to be
outputed for the sum position. And if we're adding a one and a one, we also don't want anything to be included for
the sum position because there's going to be a carryover. So we only want something to be added if there's just
one one of the if if one of the two inputs is a one. Exactly one of the two inputs is a one. And that is just the
definition of the exclusive orgate. So this works out perfectly. So, we've built uh this circuit and it
turns out this circuit has a pretty uh it's famous enough where it has its own name and this is called the half adder.
And this is cool because if we're adding two binary numbers 0 1 0 1 0 or 1 1 0 1 1 then this circuit lets us add
each of the corresponding digit positions together. Right? And when we're adding numbers in binary
or decimal for that case we do it as we can think of we actually divide up the work. We look at each digit position at
a time. We start with the rightmost digit position, do its work, and then put the sum below and then do the next
digit position and put the sum below. And this right here, this circuit is effectively doing that for one digit
position. Now, there is a little bit of a shortcoming to this. What we'd like to
do is to be able to have these half adders. If we want to add to in our example, we have two five bit values,
fivedigit values. If we wanted to add them together, we would like to use five half adders and put them next to each
other. And so we could write in all the operands values on uh each of the A lines and then all the other ones on
each of the B lines um and and have a circuit that could just produce the output immediately.
But this uh this unfortunately is missing something. And I I I would actually take
it as a challenge to pause the video and try think about why we can't do what I've just described. Because in theory,
we've built a circuit that can add one digit position. So why can't we just have a bunch of these circuits next to
each other? Why couldn't we place five of these next to each other and let that be used to add the five-digit positions?
And I will spoil it now. The issue is exactly what happens when we carry over. So let's look here.
Let's do our little example. So we have a one and a zero. If we fed this into our into our half adder, then we would
get the output 01. So we'd have a one for the sum and a zero we would carry over. And so that works out fine. And
now if we do the input 1 one if we suppose we had a half adder that did this this computation we know it would
output a zero for the sum bit. So a zero would go down here and then a one would go up top for the carry bit. And now the
issue comes when we run into this digit position here because we have three values we need to add for this digit
position but our half adder only adds two different values. So there's an issue in that we output
the correct number of values because for each digit position we have one we have two outputs. We need to know what goes
down here and if we carry over to the next position. But our fatal shortcoming is that we are
missing the scenario for when there is carryover. We actually have three digits we want to be adding.
And this is something that we are not accounting for in our circuit. So maybe you guessed by the name, but half adder
uh lets us do half the adding logic, but what we're looking for is a circuit called a full adder, which lets us do
the exact logic. So we'd like to do is we'd like to have our A and B, but we'd also like to add a new line for carry
in. And so what we'll do is we're going to have to build a new circuit that can kind of handle this logic. So why don't
we try start with the truth table for this. We have a b these are our two one bit values and there could be a possible
carry in. And I'm not going to use a uh I want to distinguish because our output over here we had a C and now we're going
to have a C as the input. And that's because there's a carry in and a carry out and they're fundamentally different
as you know. So our output now is going to be carry out. I almost wrote in and then the sum. So we still have two
outputs but now we have three inputs. So, let me uh go silent for a moment as I populate this truth table.
[snorts] Okay. So now let's take this truth table and think about what this means. So now
we're adding effectively because of the carry in bit we're adding three three one-digit values.
And again we're still ignoring choose complement. Uh we'll come back to this later, but let's not over complicate
things now. And let's see what happens. So uh I'm going to go ahead and uh it's it's easy
to do these quickly because uh we're working with zeros and ones. So 0 plus 0 plus 0 is these are all binary numbers.
But remember that uh 0 in binary is equal to 0 in base 10 and one in binary is equal to one in base 10. So we can
kind of ignore we don't need to convert between binary because we don't need to convert these binary operants these
binary inputs to our circuit to decimal because for 0 and one they're equivalent which just works out perfectly for us
because we don't have to overthink but it can be tricky to sometimes wrap your head around that. So we're adding 0 plus
0 plus 0. So we know the output's just going to be zero. Um i.e. uh there's there's we're adding
zero. we need to represent that in two digits. The output zero. The uh if we're adding a zero and a zero in a digit
position, but the carry in is one, then we know we output a one cuz that's what we sum. And then there's no carry over
to the next digit position. We know we just we just throw a one down below. So that's fine. Then let's look what
happens if we're adding our A and B, which are zero and one in the next row. And then first off, we have no
carryover. So if we're just adding a zero and a 1, then we sum a one with no carry out. But if we have a carry in,
then we have 1 + 1, which is two. And we know we put the remainder when you divide by two below. So two divides into
two exactly. So there's no remainder. And then we do have a carry out. So in this case, we have a one for the carry
out, but a zero for the sum. And and maybe rework that for yourself if it's unclear what's happening. But you may
need to go back to the earlier part of the video where I showed you how to do addition of binary numbers.
Let's move on to the next case. Uh which is when you have a one and a zero. And the first one there is no carry-in. And
this is kind of what we looked at before. You just have a one carries down and there's no carryover. Uh so this is
a zero and a one. But if you do have a carry-in then uh you have 1 plus 1 which is two and two divides perfectly into
two. So the remainder is zero and then we carry over a one. So we have a one here.
And then the last case is last two cases are when we're adding a one and a one. So if we don't have any carry in then 1
+ 1 we already know is two. So the output is that we carry out over once and then put a zero below. And then the
uh edge case that is is bringing us here uh is this 1 one one. Um and that means 1 plus 1 plus 1 is three. And that means
we put the remainder down below. So three would the remainder 1 divided by two is one. And we have the carryover.
Um and so we have we have three up here. This is actually three in base 10. And you know this makes sense because this
is in binary. We have 11 one two in base 2 which is equal to three in base 10. if you do the conversion.
Cool. So, we have our truth table. Now, let's think about how we can actually build this circuit. [snorts]
And so, what I'd like to do for this is go ahead and I think this is a difficult one to build because we have three
inputs, which is more than two. So, we have eight different cases we need to handle and we have two outputs. But I
would like to take a logical approach to building this. Not just spill out the answer and let you prove it yourself,
but kind of give the answer um build up and explain why it is we're doing what we are doing. And so the way I would
think about this is uh let's look at each of the outputs separately. We could build a separate
out a separate circuit for each output. We could think of building a separate circuit for each output. Um and that's
because uh they are two different outputs. We could build two different circuits. Uh
one circuit that takes a b and c in and outputs c out and another circuit that takes a b and c in and outputs s. But uh
this would be inefficient because uh computers are have to be physically built. This computer that we're going to
build has to add numbers. They have to be physically built and logicates cost money in the real world to buy. uh they
are incredibly small. Your computer has trillions of them, but they do incur some cost. And so we would like to
design a circuit that minimizes the number of logic gates used. When we're building our computer, we would like to
minimize the amount of logic gates used. And uh an easy way to minimize those is to try combine the logic for these
circuits. Don't build a separate circuit for the carry out and the sum. Just just do it all together.
Okay, let's start by thinking about the sum bit. So if we stack this in the uh vertical kind of notation that you're
used to and that we've kind of seen throughout this video multiple times now we're interested we know this is the sum
bit. We know this is the carry carry out bit and the carry in bit comes up top and this is per bitwise operation. We're
just looking at one vertical digit position. So let's figure out each of the definitions in which the sum bit is
going to equal one. So if we ignore this carryin bit then we know the sum is going to equal one as long as either a
or b equals 1 but not both of them because if so if neither of them equal one then we we output nothing. uh in the
case where it's a 10 or a 01 then we just output a one for the sum but if they're both one then we actually output
a zero and then the carry out is going to be what's equal to one. So in the case where carry in equals zero. So
let's let's try make a rule here. So if carry in equals zero then we want exactly one of a or b to be true then
then sum is going to be exactly a exclusive or b and uh recall what exclusive or is from earlier. Exclusive
or says output a one if either a and or b is true but not both of them. So this is exactly what we're going for because
if A and B are both zero, the exclusive or of those two values is going to be zero. If A and B are going to be one,
they're both one, then the exclusive or is going to be zero, which is synonymous with the sum in these two cases. And for
any of the other two cases, we have one of A or B is equal to one and the other is equal to zero. And that's going to
output a one, which is what we'd expect. So we've kind of built this rule out where if the carry in is zero then the
sum bit is equal to a exclusive or b and we have the exclusive or gate. So we can kind of build that out. Uh but what
happens we need to complete the rule then if the carry in is one. So then if if I clean up the screen a
little bit here but if carry in is equal to one then what is the sum going to be? And for this one, we can actually
probably get a little more context out of looking at the truth table. So, uh, in this row, carry is one. In this row,
carry is one. And we have four rows where Krion is equal to one. And we can see that in this case, uh, sum is one
because and a and b are both ones. In this case, sum is zero and only one of a and b is a is a one. Over here we have
sum is zero and it's the same condition. So it looks like when it's just one of a or b i i.e if we have a exclusive or b
then sum's going to be zero and then if we have not that then it looks like the sum's going to be one. So we can
actually draw this rule up where if if the carrying is one then s is going to equal to not so I'll do like negative to
represent a negation ax or b and uh this is probably a good time to
pause this and and and verify this is true that is if you're interested in in knowing where this full adder circuit
that I'm I'm going to design comes from. if you don't really need to understand that technically to move on, but I just
thought your inner curiosity might be satisfied if if you had a more complete answer. So let's let's let's build this
out as a circuit then. So our our inputs um and this is just for uh just for S. So if our inputs are a b and carry n
then well if c in equals zero I'm going to go ahead and spoil the most efficient way to do this. It is probably a good
practice for you to try implement this on your own but uh I will go ahead and uh spoil this. So what you actually end
up doing uh is for the first part we will use an exclusive ore which isn't that surprising because exclusive or was
there and then we're going to actually output this into another exclusive or and the output of that exclusive ore is
going to be equal to y which is you know let's not even call it y let's just call it the sum that's what it is and the
other input of the exclusive ore is going to be this right here this carry- in. And so what this what this says is
if carry-in equals zero. So if carry-in equals zero, then the output of this exclusive or right here is just going to
be whatever flows in. So whatever flows in uh will just flow straight through. And that's because if we put a zero on
the top line, then zero exclusive or zero is zero. And if we put a one up here, then zero exclusive or one is one.
So uh if C in if the carry in is zero then whatever comes in here will be the output. And so what is it that comes in
here? It's a exclusive or b. So that's what we've built out right here. We've shown that when the carry in is zero
then the output is equal to this gate and this gate is a exclusive or b. Now if carryen is one let's see what
happens. Krion is one then the output here will be the opposite of whatever comes in because if if if we have a zero
coming in here then zero exclusive or one is a one but if we have a one in here one exclusive or one well that's
both inputs are being on and that's a zero and so it kind of functions as a as almost a negator in this context and uh
we know that whatever so we we know that's the the negated of whatever is coming in here and we already showed
that whatever coming in here is is A exclusive or B and uh as it turns out this is this is the most efficient way
to implement this in terms of minimizing the number of gates that are being used. So that's how we derive the sum bit.
What we're then interested in is the carry out bit. So I'm going to erase what we have right here.
And again, if you need to pause the video and and convince yourself this is true, then now is a great time to do
that. We have the sum bit. Let's look at this carry out bit. We have this carry out bit which is going off in a few
interesting cases. And so we intuitively know that we're if we're if we're stacking numbers, we can
look at it this this visual approach again where we have our AB and then we're adding them and then
we're also adding a carry-in. So, so then if we look at this right here, if A and B
together sum to anything greater than one, then we're going to have to carry out.
So, if if A and B are both set to one, then we're for sure carrying out. And it doesn't even matter what the carry in is
in this case because we know uh if the carry-in is zero, then we output 1 zero. And if the carry-in is one, then we
output 1. But in both of those cases, we're carrying out. So for for determining carrying out, if if A and B
are both equal to one, if A and B are both equal one, then we uh we know that carry out is
going to equal one. And there's not the only case here. So uh that would cover in our in our truth table that would
cover these two cases right here. A and B are both one and we carry out. But notice that uh we have two other cases
where A and B are not both one but we still carry out. And what happens here? And that's what happens when we only
have one of A and B. So a or b but we also carry in cuz then we have 1 plus 1 effectively which which is a carry out 1
zero. So then if a exclusive or b i i.e if there's just one of a or b is set and
carry in is equal to one then we carry out. So these are our our two conditions for when we actually carry out and uh
let's go ahead now and and see how we can build this up with logic gates. So we we it's obviously we're going to need
some and gates. So uh let's start with this first rule. If A and B both equal to one, then we carry out. So I'm going
to add an AND gate down here. And I'm just going to feed whatever A and B's values are
into this gate. And uh we will start by claiming that this is the carry out. Uh so we know that if a and b are both
equal to one then we do carry out. But there's another case uh it could either be that or if we have a exclusive or b
and a carry in then we also carry out. So there's actually two cases. So let's let's implement the second case. Um and
I'm going to extend this line just cuz it's going to get a little messy here if I don't. Uh the second case is we need
to know if there's a exclusive or b which we have right here. So we can reuse this logic gate and this is what I
was talking about with optimizations. So if we need a x or b so I'll just bring this down. Um and we have a
carry-in. So we can just bring this down too. And this is another and gate. Uh then we're
saying that this should also mean we carry out. But we want to carry out when either of these lines are a one. So
let's join them with an orgate. We have a gate that already handles this. This gate will trigger if either of the
inputs is a one and that's an and not an or. Here is an orgate. And we can call this carry out. And this uh as it turns
out is what we call a full adder. And I've done a better job of drawing this up here. So let me go ahead and
bring it down and let's just look at this drawing which is not as as painful to look at as
the one I I have provided. Uh so I will respectfully uh move this out of the way.
Okay. And I'm also going to do a little cleanup here just because I'd like this to be referenceable later in case I need
to. But this circuit right here is a full adder. And uh this is a another great opportunity. Let me go ahead and
erase this to convince yourself that this is true. Uh if you would like the exercise, go through take this circuit,
I'll zoom out the perfect amount. Uh pause this video and for each of the rows you see in this truth table,
compute what the output of the circuit should be and verify that it's equal to what you see on the right hand side over
here. If it's not, then there's something wrong with your logic or I made a mistake in this video, which I
will hopefully correct in the description or someone will point out in the comments if if that if someone ever
watches it. And what we have is this really cool circuit which if we're doing some sort of binary
addition, then can correctly do the addition for each one of these these vertical
positions. So for each digit position, this full adder can perform the correct addition. And where we're going to go
with this is we're actually going to build a circuit that can add uh 8 bit numbers together because we said our
computer is 8 bits. So let's let's get back to the realm of our computer. And that circuit's going to work by us
effectively having eight full adders together. And we're actually going to chain them together because the carry
out from this add adder right here should become the carry into this adder. And then the carry out from this adder
should become the carry into this adder. And then the carry out from that adder should be the carry into this adder. And
so on and so on. And it might be a little bit vague what I'm trying to say, but hopefully at a high level you
understand that the the carry out from this position right here becomes the carry in over here. And then the carry
out for this sum becomes the carry-in over here. And we've built this generalized circuit which can handle any
of the possible input combinations and correctly give us the the sum bit and the carry out bit. And now I hopefully
convinced you that we've been able to tie together the first fundamental concepts we learned in this video. Uh
that being binary and logic gates. We now see a bridge between these seemingly distinct concepts and we're going to
work on drawing them together. And so we're actually going to work on building our first part of the computer which is
called the ALU, the arithmetic and logic unit. And that's going to be responsible for adding and subtracting numbers. So
[snorts] I will uh go ahead and move on. Now's the last chance to pause this and just make sure you understand everything
that's on the screen. But we'll move on and see how we can build on top of these full adders and combine them together to
build a circuit that can do more non-trivial addition because right now this is trivial. This adds uh this adds
technically it's meant to add one digit position together but it could add three binary numbers three binary digits
should I say and and tell you what the output's going to be. And you should also be able to convince yourself that
uh for any three binary digits, the sum of those is going to be somewhere in the range of 0 0 which is zero or 1 one
which is three in binary uh three in decimal one in binary. And you should be able to see that this fits
within two different uh bit positions. So there should be no doubt in your mind that for whatever values the inputs are,
we are able to correctly represent the output without any loss of memory or information representation.
Okay. So before we actually do the uh expand this out to multiple bit addition, let's go ahead and and give
some notation so it makes sense. So I guess the example we just built out, we did something along the lines of A and B
and we get from that a sum and then we also possibly get a carry out carry out. So there's there's there's two inputs
and two outputs for each digit position. And this happens in regular addition as well as as well as binary edition. So if
I give you two numbers, say 74 and 83, then we could say that for this digit position right here, the sum is seven.
And then there's no carryover. So we'll just say the carry the carryover is zero. And then for this digit position
right here, the sum is five and the carryover is a one, which comes down. And the same thing happens in binary. So
if I gave you one zero and then one one then here when we're adding this digit position we sum a one and then there's
no carryover. So we can implicitly write a zero for the carry. And here we have a sum and a carry which comes down.
So that's just a little bit of recap. So now let's say we want to have two bit numbers we want to add. That's what I
said we would build up to now. So let's give some notation. So A and B are now our two operandines are two bits. So
let's let's give them indexes to make this more apparent what's happening. So we can say we have A2 and A1 and we're
adding it to B2 and B1. And then from this we would get a sum down here. And we're actually going to
get two sum bits. So let's have a way of distinguishing them. And I think it makes sense naturally to give them the
same indexes that we did for the the operands. So we get a sum one and then we get a possible carry over one. And
then for this digit position, we get a sum two and possibly a carryover from two. And if that's the case, then we we
carry it down. And again, carryover is either going to be a zero or one. So it makes sense that if we have the zero and
then some binary digits, it's the same thing as just the binary digits that we saw earlier.
And so what this means, and I'll go ahead and erase this
to stop the screen from getting too crowded, is we are going to effectively use two of these full adders we've
described and chain them together with these operands as so. And so what we're going to see is
that the uh first full adder is going to take this a1 and b1 as its operands. And then we're going to treat the sum output
as someone. And then what we're actually going to do with the carry out this C this carry out uh that we have right
here is we're going to feed it into the carry in of the second h full adder. And the second full adder is going to be
operating on A2 and B2. So we can kind of see how these get chained together.
And so why don't we go ahead and try build that. And luckily because I'm drawing on my iPad, I can just duplicate
this right here. And I'm going to paste it twice below. And we will give the proper notation. So
let's not this I have up there. And I'm going to copy this again and paste
it one more time right here. Okay. So now what I'm saying and I think this will make a little more sense now
is let me move this over here. Okay. So if I have A1 and B1 I want to
use this half adder and let me actually yeah I'm going to use this this full adder.
Sorry, not half adder, full adder to do the A1 plus B1. So what I do is I'm going to feed in A1 and B1. And this is
the the the rightmost digit of the two numbers we're trying to to operate on. And now you
might be wondering, this is a good question. What do we do for carry in? Because for this rightmost digit
position, we're never going to have a carry in. I mean, if if we think about it, that's where we start our addition
at. So, how could there already be something before that to which we could have carried into? And the answer is
there's nothing. There's no way we could ever have a carry in on the rightmost digit position. But we do know is that
what we're effectively doing is a1 + b1 plus the carry in. That if if we're doing this plus this
plus this, and we know that the carry in is zero, then we're really just left with a1 plus b1.
And so what we're going to do is just hardwire this right here to be a zero because there's no way we could have
carried into our first bit of addition. And then that's going to let us use this this this full adder we have to do
exactly what we want. So now let me go ahead and erase what we have marked for these outputs. And let's say that the
output of this is going to be S1. and then the carry out. So this is carry one. We're going to feed that in to the
carry in of this next full adder. And and visually that is the arrow we see right let me erase these. That is
the arrow we see right here. When we add A1 and B1 we get the sum one down below. And then we may have a carry out a
carryover from S1 from that first edition into the second edition. And that's exactly what this arrow
represents. So now for this second full adder, we will feed in not a1 and a b1 but a2 and
b2. So now I'll throw in a2 and b2. And from this we get sum two and then carry two. So now you
can see here that if we have and let me go ahead and erase this cuz I think it's a little complicated. If we had two
operands we wanted to add and both of those operands were two binary numbers, two binary digits, then we could simply
plug them into this circuit right here and we would get the sum as something we could read from S1 and S2 combined with
C2. And I should not have erased that. And this C1, it's not that it's useless, but it doesn't actually show up in our
output. We use it as part of the computation, but we only care about these three for our output because these
are what we actually read and that's what we want to know. We want to know the sum of the value. We don't really
care about where the carryover happen intermediately and internally. We just need to know that we make sure that we
deal with it correctly when it does happen. So let's go ahead and plug in a little
bit of an example here. Uh just to make sure that this works. Suppose I wanted to add one zero. Sorry, I wanted to add
the binary numbers 1 1 and 0 1. And this in binary is going to be equal to 3 + 1. So 3 sub in base 10 + one in base 10.
And we know we should expect four back in base 10. And we can do this arithmetic manually. And we would see
that we know the 1 + 1 is a zero, but we carry over a 1. And then we know that this 1 plus one also a zero but we carry
over again another one and then that one drops down. So we can kind of see that we would expect this to be s1 this is
sub2 and this is the c2. And then we know that this is we know how a1 and a2 are structured based on this diagram
over here. And uh sure enough uh we if we plug this into our circuit we expect to get that.
So let's go ahead and do this. So I'm going to plug in to our circuit. We have a one and a one.
This is the A1 and B1. And then a one and a zero for the A2 and B2. And we have to start with this circuit on the
right because you notice the circuit on the left. One of the inputs comes from this line over here. And
that's actually the output of the circuit on the right. So we'll have to start by doing this first. So we have
one exclusive or one which is a zero. And then we have to this right here we have zero exclusive or zero which we
know is just a zero. So sum one I'm saying a zero. And that that checks up with what we have here. And then I'm
saying let's come down to this andgate over here. I am feeding that a one and a one. So I'm going to output a one from
that andgate. And then over here I have a zero and a zero going into this gate. So this is going to output a zero, but
they feed into an orgate. So we're going to have a one or zero. And that means we're going to output a one. And so that
means that carry one should be one. And this makes sense because we have a one up here. And that's carry one. You know,
this should be carry one and this should be carry two. So then we bring this one all the way
around here. And we can do the same calculation for this full ladder. So we have one exclusive or zero which is a
one. And then we have this one exclusive or one going into S2 which means we're going to output a zero
cuz one exclusive or one is zero cuz both the inputs are set. And that checks out with what we have here. And then we
come down to this and gate over here and we have one and zero which means we output a zero. And then this and gate
down below is one and one which means we output a one and then when we or those we get a one and this is exactly what we
see. So this is really interesting. We have the two full adders chained together and now we're able to do two
bit addition. We're able to add numbers that are two bits in length. But one thing you may be noticing is that this
is pretty ugly to look at. We're having a lot of logic gates and it's not visually appealing to look at. And I
kind of hinted at this earlier, but we can kind of abstract this away. So instead of drawing this ugly circuit
out, let's find a way that we can just represent the full adder as something easier. And let's just give it a square.
Let's just say that if we have a square, sorry, a rectangle with full adder FA written in it, we just know it means a
full adder. And what we really need to show is the inputs and outputs cuz we now understand how the full adder works
internally. But when we use it to build more complicated circuits, we don't necessarily need to see all the logic
gates inside of it. We just need to be able to represent what goes into it and what comes out of it. And so we know
that for a full adder the inputs are an A, a B and the carry in. And then the outputs are a sum bit and a carry out
bit. And all you need to understand is that this full adder I've this this this rectangle which I'm claiming to be a
full adder. If we really wanted to see what was inside this rectangle, it would be this right here, a full adder.
So if we assume this diagram to be true, and this is something we're allowed to do. We're we're building our computer.
So we don't want to have just logicates littered everywhere. What we're going to like to do is build out individual
components of our computer and then abstract them away in these rectangles. So what I'd like to do now is if you
understand or at least can believe that this is going to be used to represent a full adder, let's rebuild the circuit
that I've drawn up here and show how much easier it is. And to do that, all we need is another full ladder,
which I will copy and paste. And all we now need to do is update our inputs and outputs. So, we know that
we're feeding in a zero here because we don't carry in to our first full ladder. And then we know that this is A1 and B1.
And we know that this output is S1. And then we feed this carry over into the carry in of the next full ladder.
And then we're calling this A2 and B2. And then from there we get the sum two and carry two. And look how much easier
this is to visualize compared to the mess of logic gates we had above. So this is pretty interesting. We've now
learned how to do two bit addition. So we started with one bit addition. We've done two bit addition, but now I'd go
ahead and like to show you how our computer's going to do addition. And it turns out it's nothing new. What I said
earlier is that our computer is an 8bit computer. 8bit computer, CPU, central processing unit. And what this means is
we operate we operate on 8bit values. And to elaborate on that, every value we
store in our computer is going to be eight binary digits long. Nothing more and nothing less. And this makes this
makes things easier. As you will eventually see, if every single component of our computer is expecting
values to come in eight bits, exactly eight digits, then we can standardize a lot of components of our computer and it
makes ensures a lot of consistency between the different components. And this eight, as I also mentioned earlier,
is just a random number. Modern computers can use 32 or 64. And we'll we'll we're going to build an 8-bit
computer. And then we'll eventually talk about how we can scale up to 32 or 64. And it turns out it's not that
difficult. But what I'd like to do then is build a circuit that adds 8 bit numbers. So what would this look like?
Every input to our circuit is going to take in A1 through A8 and then A B1 through B8. So now all of our
operands, we can still we're just building a circuit that just adds numbers.
But we're going to go ahead and make sure that those numbers are all eight bits. They all have eight binary
digits. And so if you understand what we did up here, then it turns out the process for eight bits is not any more
complicated. Instead of two full adders for adding two bit numbers, we're just going to use eight full adders for
adding eight bit numbers. And this ends up making things look pretty easy. So I'll go ahead and give
the expanded notation just so we have something to work with. But in general, if we're adding two 8-bit numbers, what
it looks like in the graphical form that you and I are both used to is we'd have 88 and then our A value has eight
different bits in it. And our B value has eight different bits in it.
And if you are a computer person, you may be wondering why through this point in the video I've been doing
one indexing. I've been starting at index one and going onwards. Uh even though you know that in computer science
we start at index zero and move onwards. And the reason I'm doing this is because at the start of this video I said I
wanted it to be understandable for everybody. Even if you didn't have a computer science background and to those
of us who do not have a computer science background, it is my belief that indexing starting it based on one is
more intuitive to understand compared to indexing based at zero. It's one of the things that I mentally had to adjust to
when I started learning computer science. So, I figured it'd be easier if I just didn't make the viewer
additionally bear that burden of having to mentally shift everything over by one as they watch. So, for those of you who
do have a computer science background, I think it's not too painful for you to mentally put the zero indexing away and
go back to one indexing for this video. And for those of you who have no computer science background, you don't
even understand what I'm talking about. And this is intuitively how you would represent things to begin with. [snorts]
Cool. So, these are our two operands. We're just looking at addition. And for now, we are assuming that they are
positive numbers. We will after we build an addition, we will come back and look at subtraction and and negative numbers.
But we would expect then you when we're doing this addition, you'd start with the A1 and B1. You get a sum one and
then you get a carry one. And then you would add those A2 B2 and the carry one together and you get a sum two and a
carry two. And then you'd add A3 V3 and the previous carryover and you would get sum three and carry three. And this
process would repeat and then the one interesting is the very last addition. We take the carry 8 and
if it exists or not we bring it down. So this is the the addition we have and all we need to do is chain eight of
these full adders together. So I will start by drawing that out. And this is not going to be a pretty drawing because
I don't have good handwriting. So I am just going to go silent for a minute as I draw this out because I find that the
more I try talk while drawing the more errors I make. So, we start with a full ladder and
we're going to feed in A1 and B1. And then we actually feed in this hard-coded zero for the carry-in.
And then we will take a another full adder. And I probably drew that a little too
close together. And from the output of this one, we get I'm going to bring this down here just
so it's easier to read. We get the sum one and then the carry one is going to come over here.
And for this one, we will feed in A2 and B2. And this is still a full adder. and we
get sum two coming out and then we're gonna have the out the carryover feed into the next full adder.
So then we have another full adder and we're going to feed into here A3 and B3
and don't forget about the carry out that's going to come in. And then from here we get sum three.
And I'm going to move this over before I run out of room. And then
what we're going to do is we're going to have another full ladder. And that's going to take in A4
and B4 and the carry out from the previous adder.
And then we're going to have the sum for and the carry for. Then we have another full adder.
We feed into this one A5 and B5. And then we also feed in the carry out from the previous full adder. And we now
get sum five in the next carry out. And then we will have another full adder.
I know this is getting repetitive and that's because it it is. We've designed this so that we don't have to build out
this massive circuit that handles every possible value of the two combinations of 8bit inputs. We've built this
something simple circuit, this full adder that lets us reuse everything. And once again, we feed in A6 and B6.
And then we also take the carryover from the last one. And
from here, we output S6. And we are getting so close. We have a full adder.
We get an A7 and B7 and the carryover from this last one and we get S7. And now we have our last
full adder where we take in A8 and B8.
we get this S8. And then we're also going to get this carryover from eight. And the last thing
we need to do is make sure that we chain this carry in. Cool. And I'm not even actually allowed to
zoom out that far on my my iPad without it complaining. So I'm going to hold it here and let's look at what we have.
We've now built a circuit where we can put in any two 8digit values into our input lines and we'd be able to output
their sum. And I'm not going to work through an example because I'd probably take me just as long as the entire video
up till now to look at each of the logic gates for each of these full address. But you should be able to convince
yourself this is true. And this is something that's really novel. We've built something simple and we've chained
it together to build something more complicated. And this is a fundamental principle of
computer science. It's called abstraction. We've built the circuit that's a full adder and we then we've
abstracted away by just drawing it as a rectangle and then we're building something on top of it. And that
abstraction actually lets us lets us add uh 8 bit numbers. So we've we've taken these logic gates which on their own are
pretty trivial and have now forced them in arranged them is probably a better word should I say we've arranged them in
such a way that the logic gates do something useful to us and you may be wondering this is this is cool and all
but how does this actually happen in the real world? Well, I mentioned this earlier, but uh all of these lines I'm
drawing, you can actually think of them as wires. And the fundamental property you need to understand of wires is that
they conduct very fast. So if I have a long wire, assume there's no electricity on it. If
I put electricity on it on this end, then we will say almost instantly this is a big asterisk
the electricity will appear on this side because wires conduct electricity. And we're saying almost because nothing
in this world is immediate. And there actually is a little bit of delay between when we put electricity on a
wire and when it's received on the other end, you know, cuz electrons actually have to move across the wire.
So what you can actually believe in and and kind of use to understand this is that all the lines I've drawn in these
above diagrams and up until now are physical wires. And this is not an exaggeration. This is
this is the truth. And if I I also said that we can build these logic gates physically. You can buy these little
chips that have them built in. And so one such chip has it looks like this. And it has many inputs and many outputs.
But I could then put electricity on this line and this line this line and I by electricity I mean I'd either put
electricity or not put electricity. This is a high level. Again there's a little bit different in the in the world of
physics but this is how we will understand it and how I think it's all you need to understand it as. And if I
put electricity on the wire we say that it's a one. There is a truth truthful value on the wire a one. And if there's
no electricity, we interpret that value to be a zero. So then this chip that that does exist internally does
something. It somehow manipulates the electricity in the real world to function like a logic gate to this
output. And I have this chip. And so if I genuinely put electricity on this wire and didn't put electricity on this wire,
then there would be no electricity on this output wire. And then if I did put electricity on both the input wires,
then there would be electricity on the output wire. And we're not going to study how this works in the real world.
We're not going to study how you can build these logicates. But it is it is really possible. And
it's it turns out it's not too difficult to actually build them. And there's a lot of resources online which uh I'd be
happy to link if if someone requests. But what we do want to understand for the sake of this video is that when I'm
drawing these lines, they're actually wires. I'm I'm drawing something out that could really exist in the real
world. And these lines are wires. And when I say put a one or put a zero on a on on a line or comes in or out of a
logic gate, I'm really saying we either have electricity or don't have electricity. But but thinking about it
at the level of ones and zeros is certainly easier than thinking about it at the level of electricity or no
electricity. Electrons or no electrons. So I'm going to stick to this understanding where I say zeros and
ones. But really you should understand that this is actually something that could exist in the real world and uh
something we we'll talk about more more later on down the line. Okay, so we've now built an 8bit adder.
We built an 8bit adder, but there's an issue here, and I I've mentioned it a few times.
How does subtraction work? And it may seem to you like we will need to
destroy what we have and rebuild the entire computer or the entire adding logic circuit. But it turns out another
really interesting takeaway uh at least what I would call interesting is that we are actually 95% of the way there with
what we have up top to being able to do subtraction. And the reason is is remember what we
said earlier. We said that if we wanted to do a minus b, it is really the equivalent of doing a +
b. And so we know this circuit can do a plus negative b. But the issue is we we have b feed being fed in as all of our
inputs. We don't have negative b. And how then if we have all these B inputs, can we actually make them
negative B? So what I'm going to do here is pause this video and redraw this circuit in a slightly more optimal and
more condensed way and at least in a way that I can I can show everything on my screen at once and then we will I will
uh cut back and we can pick up and answer this question. Okay, so I have drawn this in and let me go ahead
[clears throat] and erase these bars on the side. And let's do a little recap of what I have here. And you've also
noticed that for about the first time in this video, I've decided to use something besides the color white. So
you're welcome. And basically, I wanted to color code things because we have a lot going on here. And
I'd also before I even start to explain this would like to appreciate or hope that you can appreciate how much less
painful this is now that we've abstracted these logic gates out of the way. And it's still pretty messy. So we
we can do more eventually. We'll abstract this entire thing. But um as we have it right now, there's this is this
is fairly reasonable to look at. And hopefully with the color coding and the explanation I'll give in the next few
minutes, we can make sense of this. [snorts] So, what I have up top are our eight A
inputs and our eight B inputs. And I'm drawing these little circles because uh you'll you'll see this notation if you
ever end up watching some other videos where uh this this represents an input almost. And we can either put in a zero
or a one to that input. And it's just a notation. And I personally think it's a reasonable notation. And if we were to
do this in the real world, then this circle would probably just be some source of electricity or not. And you
know, we we'd connect electricity there and go through that wire into the corresponding full ladder. [snorts]
But I have all the A inputs highlighted in red and or orange if if that is orange to you. And I'm actually not sure
what it is to me. I I'll stick with the orange. So I have all the orange connected to all the A's and those go
into the A lines of the full adder. And it doesn't really matter if we we commute A or B for the full ladder. It
doesn't really make a difference because A plus B is the same as B plus A. But more interestingly,
I have or equal equally interesting. I guess there's not really anything more remarkable about it. I have all the B's
in blue going into the B inputs of each of these eight full adders. And then I have in gray here the carry
out from each outer adder going into the carry in of the next adder. And the one exception to that is the very last carry
out from the very last full adder. And that comes down here. And so uh the greens are the sum bits of each of the
full adders. And sorry I don't know if I was just saying half adder. I meant to be saying
full ladder. If I was, I apologize. And the we know that the carry out from the last full ladder is special because it
it is not just carry out, but it's part of the the output of our sum. Whereas all the other previous carry outs, we we
need them to do the computation, but we don't really care what they are. We just care what the result is. And that last
carry out is is technically part of the result. And the only other interesting thing is
this this over here where I have this carry into the very first full ladder. And we said that's just going to be a
zero. So I kind of written this zero in quotations. And uh I I've I've given it some special
attention. That's because we're going to see that it it may not actually always be the case that we want it to be zero.
And I also wrote S9 in quotes down here because it's it's really equal to the carry out of the eighth full adder. So
we could say carry 8, but I wanted to make it apparent now that you know even though this is the carry out when we use
it, it actually does become part of the product, the result of the sum. And so I think it it makes sense to
treat it like the rest of the result bits. But I I'll leave it in quotations from this diagram for now so you can
understand that is it is a little bit different than the rest and where it comes from. But when it comes to
interpreting the output, it's the same. So now you can see we have this circuit where we could put in our A values and
then our B values and then we'd be able to read the output right here. And you could understand all the logic gates if
you really wanted to. You could take this yourself and draw out all the logic gates, but that's that's not necessary
for what we have going on right here. Now, what we really said that we wanted to do was figure out how to do this
subtraction with the our circuit that we have here. And we know that this circuit can do a plus b. And the issue is we
want to do a minus b. And how could we do that? Well, I I invoked the rule that we learned at the start of this video,
which was a minus b is equal to a + negative b. So, what we really want to be able to do
here is take the inputs. We'd like to have the same circuit. We'd like to be able to
use the same circuit for addition and subtraction and have a way of telling it when we're adding and when we're
subtracting. And when we're subtracting, we don't want to feed the B's. We want to put our our B input up here, but we
don't want these wires just to feed directly into these gates cuz we want negative B to feed into each of these
adders. And so, let's think about how we can accomplish that. And I'm going to write
down what we learned earlier. Given B or given A or B or C any variable to get negative B
what do we do? We do we invert every bit in B. So invert the bits [snorts] bit meaning just a
digit position in B. And more formally put, let ones become zeros
and let zeros become one. And once we do that, there's one other thing we have to do. If you remember,
that's what we have to add one to the result. So add one to the result. And that's that's decimal one or binary
one. They're equivalent. whatever one you want to conceptualize. Um, and so if we had
if we had this number one zero one1 and we wanted to negate it, then we do 0 1 0 0. So that's our step one. Then we add
one to that for step two. So we do 0 1 0 0 plus 0 0 1. And we're left with this result right here.
And you might be wondering something very valid and that's I said up until this point we we've been building these
full adders that only work on positive numbers. And that's true but it actually works
out with negative numbers as well. And this is true because I very earlier in the video I showed you how to do
addition and subtraction in binary. And we learned that addition for negative numbers is the same as positive numbers
for two's complement representation. And subtraction also works out as long as we can just negate the number. So this
circuit, we've only been using it with positive numbers, but it turns out we can use it to add negative numbers as
well. And >> so now we're going to learn how we can
now that I've kind of I kind of shaken that up a little bit because I've been ignoring all these negative numbers, but
it is the case that this works with that that it does work with negative numbers. You could actually plug in an example
maybe maybe not on the 8 bit one, but on our our two bit example, our two-bit adder when we just had two full adders
trained together and and see that it actually does work out. And what we're interested in though, back to the topic,
is how can we do subtraction? And so we have B and we want to get negative B. So what we really saw is we could take all
of the bits from B for step one and just flip them. And how do we do that here? And what we can recall is that we
actually have a logicate that does exactly this. And it's our good old inverter. The inverter
looks like this. And if you feed it in a zero, you're going to get a one. And if you feed it in a one, you're going to
get a zero. And oops, let me erase that. This is exactly what we'd like. So to
accomplish the first step, what we can really do is is have our inputs of B and just feed each of them through an
inverter whenever we want to subtract. That's the issue. We we want to be able to distinguish when we're adding and
subtracting. But let's just assume that we're just going to use this for subtracting. So we'd we'd end up
inverting all of these. And so I guess I'll just draw an arrow and I do some uh some sort of demarcation that we'd
invert. And that handles the first step. And then the second step is adding a one to the result. And now let's think about
this because this is this is a little bit weirder. And how could we do this with the paradigm
that we have up here? Uh this is a good good challenge question. And it turns out to add one, we could just feed in a
carry-in of one to the very first addition because we know that when we have uh for some digit position when
we're doing addition, if we have a to the x and then b to the x position, then we also take in a carry in at the x
position and we output a sum at the x position and a carry out at the x position. And this carry in effectively
just adds another value to our step. And what we've been saying so far, if we come back up here, is that when we're
doing these 8bit addition, then we actually are sure that we're never going to have a carry in the first time
because we have nothing else to carry in from. But if we actually just set a carry-in to be one, then we're
effectively adding one to our overall result. And so this actually works out perfectly. And so you might be
wondering, well, this this adds one to our result, but we're trying to just add one to B. But doing those are
synonymous. If I do um if I do a plus a + b, so if I have all these a's and then I have all these b's, but I want to do
neg a minus b, then I first off, I invert all of these. And I'm just using this bar up top. Uh this this this
notation of of this bar on top of B just means the opposite of B. Um and if we just throw a one up here, then
this is the same thing as doing and now this becomes addition. A a a plus [snorts] and then we could have done uh
bb the negated version plus a one. So if we did that sum and then this outer sum,
then it's the same as just just doing it all at once because a plus we're effectively saying here that a + b + 1
is just the same thing as a + b + 1, which could just be however order you want to permute it because addition has
this this commutive property you learned about when you when you were way younger.
And this is a really powerful result. This lets us mean that when we want to do subtraction, we could just set this
carry in which we know is going to be a zero for addition to be a one and it adds one to our result. And so we found
a way effectively using our circuit of full adders that we originally designed for addition to be able to do
subtraction. And when we want to do subtraction, we're going to do instead of a minus b, we're going to do a plus
negative b. And we have b's as the input over here. And to make it negative b, we're going to take the two-step
process. The first of which is inverting all of b's bits from ones to zeros or zeros to one. And the second step is
adding a one to the result. And we can do both of that with the circuit we already have minus a few additions of
inverters. So what we're going to go ahead and do now is there's one final step and that's
that we need to distinguish when we're adding or subtracting because when we're adding we don't want we don't want these
values to be inverted and when we are adding we also want this carry-in to be a zero but when we're
subtracting we want the carry-in to be a one and when we're subtracting we want all of B's bits to be inverted.
So, how can we actually do that? And what we're going to do is add another input to our circuit. And that's just
going to be the addition subtraction flag. So, I'll make this a circle for consistency. And I'll just write this is
a slash s. And what I'll say is that if if this is zero, so if zero, then addition, and if one, then we'll do
subtraction. That's just how we're designing this. And then we'll be sure for the rest of this of this uh to make
sure that we adhere to that consistently. And
then what we want to do is how do we deal with well this this first part's easy for the inverting the adding the
one. It's easy. It's just the value of this because if we are adding then we want no carry into the first full adder.
And if we're subtracting, we want to carry into the first full adder so that we have the effect of one, the effective
addition of one that we needed for the binary conversion. But this this inverting part is a little difficult. If
we have this line and we have a value, let's just look at B1, then how can we use both of those to
get the right B1 that should go into the gate or the right, I guess, uh, modified B1 or not that should go into the gate.
And so what we really have is if as is set so if as equals 1 then let b then we want to invert so we should
invert b1 and if as is zero meaning we want to add then we leave b1 alone
and how can we do this? Well, a a simple simple gate. We can do one gate and that's the exclusive orgate. And let's
see what that looks like. So if we feed in B1 here and then this AS line, then let's look at happens for each of the
input cases. So if as is one, then we want to invert B1. So if B1 is zero, then we would we would hope to get a one
out and one exclusive or zero is one. So that is correct. And similarly, if B1 is is is one, we'd hope to get a zero out.
And one exclusive or one is a zero. So So this works. And then if as is zero, we would want B1
to to come out exactly as it is. So if as is zero, if B1 is zero, then zero exclusive or zero is zero. So that
flowed through. And if it's one, then we know one exclusive or zero is one. And so that is exactly how this behaves. And
if you have a little bit of trouble conceptualizing that, go back earlier to when we looked at the truth table for
the exclusive orgate or you can just Google it now. But this is just a property of the gate and it it works out
very conveniently that we can use it for this this task that we have right here. And then it turns out this is uh we were
looking just at B1, but we can do this for each of the B's. And that's exactly what we'll do. We
will have an exclusive or gate for each of these B's and we will exclusive or the corresponding B with the same AS
line that we have up here. So I for the sake of drawing am going to pause and update this diagram to have that in
there. Okay. So I've updated the diagram now and I was running out of color. So I have this lightish purple and basically
all that changed in the past millisecond is I have exclusive ored each of the B gates with the value from this as line
this addition subtraction line before feeding it into the B input of the full adders. And so what this means now is
that if we want to add, we load in our values to A, load in our values to B, and then we can look and get the result
on the S lines down below. And if we would like to subtract, we do the same thing. We put in the A, we put in the B,
and then we set this AS line to be one. So whenever this as line is zero, it's just going to add this circuit is just
going to add whatever is on the input lines. Even if we don't have any meaningful value, it's it's a circuit.
The logic gates are going to do what they do. And we're going to get some output that we can read on these S
lines. And if we set this S line, this AS line to one, then it's going to subtract whatever values happen to be on
the A line and the B line and come into this S line as the output. And
so this is the extent of difficulty we're going to need for this video. And computers can do uh multiplication and
division, but it's a bit more tedious and involved of a process. And I don't think there's as much not to say there's
not benefit, but I think this suffices for understanding the rest of the fundamental concepts of computing.
So there is no plan for me to show how subtraction or multiplication division work but there is plenty of resources
online that you can feel free to look up if you are curious about it. It is uh interesting because we then have to
without going into too much detail you have to update how you represent numbers. For instance, we can only
represent whole numbers. But if I divided seven by two, the output's 3.5. But we have no way in binary b up till
now what I've taught you of representing this.5. And so you kind of have to go back and and revisit your representation
of numbers. But we're going to stick with this two's complement. And so now we're back and we have this we have this
circuit that can add. But there's a little bit of an issue. And it I think I've kind of made it not super clear
until this point until I was working through, but that is these are logic gates. And so they're always just going
to operate based on whatever the inputs are. So if we don't really have any meaningful values on these A lines or B
lines, there's still going to be some value in the S line. But that what that also means is if we did want to add two
numbers and we put them in the A lines and B lines, then the output's going to correctly come out on these S lines down
below, but as soon as we change any of these inputs, the output may or may not change. And that's that's that's
actually problematic. Uh we'd like to be able to to save the result. And let's say I'd like to to add two
numbers, put them I put them on the A lines and B lines and I get the result. And then let's say I want to take that
result and and add it to something else. Then right now there's not really a clear way of me doing that. And that's
kind of as we're going to get into the more uh different components of computers and how we can actually make
this this this adder and subtractor do meaningful work rather than us having to manually put electricity or at our level
put zeros and ones onto each of these input lines. And what we've built right here is 95. Again, we're not fully done.
We're actually going to revisit this one more time, but it's not going to be to add anything too more complicated. But
what we've built right here is called the ALU. Stands for the arithmetic logic unit. And every computer has one of
these. Uh the computer you're watching this on has one. And it's responsible for doing doing the arithmetic. And I
mentioned this earlier, but computers are effectively glorified calculators. So, this is actually probably the uh
heart of most modern computers. And obviously, your computers are implemented in ways that make these run
incredibly fast and uh can do a lot more than what I've shown uh more than just the addition of subtraction. But this is
just to illustrate the concept. And what we'd like to do, as I' I've kind of hinted now, is be able to take the
result of an addition or subtraction we do on this circuit and save it. But we know with these logic gates that
the moment we change one of their inputs, the output may or may not change depending on what the gate is. But in
general, it's it's safe to assume that once you change the input to a logic gate or a circuit, if you change any of
the inputs, it's not necessarily going to be the case that the output stays the same. So, if I have these A lines set to
some value and I have these B lines set to some value, I would need to in the real world wait a small amount of time
for these logic gates to do their work and settle. And then that's something we'll talk about more later. But then
once we we give it some time to settle and it do all the electricity flows through and everything, we we'd like to
be able to take this output and save it. And this is a what I mentioned earlier in the video at the very start. This is
going to be the second hardest concept we taught in the video. The first most difficult I in my opinion was learning
binary which you made it past and this is the fullest you'll learn binary. But the second concept is how can we save
numbers with logic gates. And so I'm going to scroll down because this is going to be a new segment of the video.
So everything we've looked at so far had this property that I talked about earlier called determinism
determinism. And what this meant is that the output of a circuit dependent strictly on the
input values. And this is not really that complicated or it probably sounds a little bit obvious to you because
obviously these outputs just determine on the inputs because they all chain into each other. But let me give you a
circuit that actually breaks that principle. And it's something we haven't seen before. But uh what happens if I
take this simple orgate? So this is the orgate. And I have some input. Let's just call this X.
And let's take the output of this orgate and actually feed it back in as the other input. And this is really weird.
We haven't seen this before. We've seen the output of logic gates go into the input of other logic gates, but we
haven't seen the output of a logic gate come back into itself. And this is going to break this idea of determinism
because let's assume that when I turn this on, I have a zero on this line. And then let's assume that there's a zero
coming out. So I have zeros all around. So I have if I make this input output, if I make a truth table, actually that's
what we'd like to do. So let's call this this input X and this output Y. Then I've shown right here
that we could have a zero for X and that we have a one for Y. But now let's look at what happens if we set this X to be a
one. So if I set X to be a one, then we have the inputs 0 and one feeding into an O
orgate. And an O orgate turns on as long as either of the inputs is one. So then the output of this orgate becomes a one
and something else happens at the same exact time is this output becomes a one but it's also the input to this orgate
which means it's this input is now effectively a one as well and what this means is that this this orgate is now
forever going to output a one because we have it outputting a one now which means it's inputting a one on this top line
and that means that it's guaranteed to be getting in a one forever. And what I mean by that is you convince yourself
this is true by changing this input x back to zero. And if I change it back to zero, then notice that the two inputs
are a zero and a one, which still ores to one. And this might take a little bit to wrap
your head around because it it is fairly confusing and I I struggle with this when I learn the concept. And it's not
very intuitive to take the output of a gate and feed it back into a gate. But it turns out that when we do that, we
have some really powerful properties that we can achieve. And so if you can kind of understand that what's happening
right now, uh let me prove to you that this is unlike anything we've seen. If that's not already clear, because now we
have a zero uh a sorry, this this is correctly incorrectly written. When we powered
this on, we had a zero going into x and we had a zero coming out. But now we have a zero and then we so we started
with zero going in and zero coming out and then we made it a one going in and we had a one coming out and now we've
set x back to zero and the output is still a one. And you may notice that these [snorts] first and third entries
in this truth table contradict each other. And what I said about determinism earlier is that a circuit is
deterministic if the outputs depend strictly on the inputs. And clearly this is not the case because we have this
output one and it happens sometimes when x is one and sometimes when x is zero. So this
output one isn't depending just on whatever the input x is at the time. There's clearly something else going on.
And the difference here is that the output depends on not just the input but also previous values that were emitted
by the circuit. And so this is what we call a sequential circuit. This is sequential logic. Sequential
logic does not have this property of determinism that made things work out well. And as we've seen, this isn't an
invalid circuit. We've done something valid here. We've just taken the output and feeding it back into one of the
inputs. But a truth table doesn't actually work. And we're going to have these things called timing diagrams that
that that better illustrate this property. So let's try create a timing diagram to explain what we saw here.
So I'm going to erase these again. So this timing diagram is literally going to be a graph with
time is the x axis and then we'll say a uh we we'll have separate lines for the x and y. So oops did not mean to erase
that x and y. And we assume that they both start off as zero. So we'll we'll we'll
let this be uh the the zeros line and then we'll let the one for y be up here and we'll do the same thing for x. And
so x and y are different but they both progress in time together. So we want to see how x and y change as we move
forward in time. So we started off with zero and zero and then I eventually at some point changed
this x to be a one and at the same exact time this y became a one. So I set x to be a one and then y became a one and
then we just left it like that for a little bit. And you may be wondering
uh I said earlier that these logic gates have a little bit of delay in the real world. And so if I set X to be one,
there should be a little bit of delay before Y becomes one. And that's that's the truth. But we we won't worry about
that delay for this part. Uh we don't want to over complicate too much. So let's just assume that this logic gate
works instantaneously. As soon as one of the input changes, the output immediately reflects what the correct
value of that logic gate should be. So then now we have one and one. But the difference is that I noticed that when
we brought this x back down to zero that y stayed as a one and then time continued and they both just stayed
where they were at. And it turns out no matter how many times we bring this up or down this y is just going to stay at
one. And so this is what we call a timing diagram.
This right here shows the state of a sequential circuit as we pass through time. And we saw the issue with the
truth table is that a truth table does not complete the full picture for a non-deterministic circuit. But a tying
diagram does let us see everything that's happening. And so this is something that we will see uh
increasingly in the next segment of the video. But what we'd like to now do is I'd like to show you how you can build a
sequential circuit that actually lets you store a value. So we talked about this this 8 bit adder we have here and
that we wanted to be able to store the outputs that are are coming out onto these output lines and we're going to
dumb this down. We we didn't build this this 8 bit adder all at once. We started with a one bit adder and then scaled up.
So we're gonna do the same thing. We're going to try to build a circuit that is sequential that can save one bit of
data. And we'd like to design this circuit so that we can choose when we we save the value um and update its value
afterwards. And so what we're actually going to do is I'm going to scroll all the way back up to one of the first
things I showed you, and that was this selector gate right here. And I'm going to copy this
down below and this will go right here. And this selector circuit is deterministic.
The output depended strictly on the inputs. And so uh if you happen to not see the earlier part of the video, then
I'll just go ahead and quickly summarize. Our selector circuit takes two inputs A and B
and the output of that circuit will be the A input if C is zero I.e. If C is zero, then we let whatever
A is flow through. And if C is one, then we let whatever B is flow through. And I originally showed you this this circuit
earlier in the video to show you uh a motivation as to how you could build more interesting circuits by chaining
logic gates together. And this was the first example I showed. But it turns out we can uh we can we can kind of build
something very interesting by by making this selector non-deterministic.
And so let's see just how we can do that. And what we saw when we did this orgate earlier, we started with the
ordinary orgate which looks like this. And we made it sequential by doing something like this where we
took the output and wired it back in. And then we had our one input and this was simultaneously this y line was an
output and it is an input over here. So we're going to be able to do something very similar
with this selector. So I'll keep the original selector up there for reference.
And then I will bring this one down. And all I'm gonna do is with this Y, I'm
going to get rid of our input A here and feed it back in. And I don't know why I drew that so far
back. So I'll redraw that. And now let's see what happens. So our inputs are kind of weird. We have this
this C and B. Uh it doesn't really matter what they're called. Well, why don't we just uh make this easier and
rename this A because we no longer have an A input. And so we have a A input and a C input. And I'll keep the C as C
because later it's going to stand for the word clock, but we don't really know what that is yet or why. So I'll I'll
leave that there just so when it is introduced later, it's not as confusing to understand. Uh but for now, let's
just think of it as the control line because that's kind of what we saw in the selector. The C is what controls
which the inputs goes through. And then this Y stays the same. So let's see what happens. Let's suppose we start our
circuit and C is a zero. Then we know that when C is a zero, it lets the value up top flow through. And
let's also assume when we start the circuit, everything is a zero. So this Y is also a zero. So we have a Y coming
out and then we have a Y over here, which means and you know the Y flows through. So the if we uh even try to
change this a to a one let's say then uh the other output the other input to this and gates a zero. So this this output of
this and gate is always going to be z is zero regardless of what a is and then we have the zero ored with the zero and so
we're always going to output a zero. But then let's suppose we have our zero here. So we have our zero on y still.
And let's let's go back to zero A. And now let's suppose we flip C from 0 to one. And now what this means is that
whatever's on A's value should flow through. So [snorts] we have this we have this zero going
through and it's just going to propagate out. And it's also we already had a zero. So it's going to propagate in. And
that's the same thing as before. But things get a little bit interesting when we actually take this a and make it
a one. And that's not the only thing we need to make this interesting cuz we know when we make a a one then this is
going to be a one which means this is automatically going to be a one and this is the same one as above. So all that's
really changed right now is is is nothing. We've seen that when C was started off as zero, then we actually
weren't able to change the output at all because it was just the the input the output was flowing through in a circular
fashion. But now in C's one, we let this a flow through and it kind of controls this entire circuit because it flows
through and it's flowing back around here. But let's look at what happens the
moment we go from a uh from c being one to zero. And this is where things get a little bit tricky to rationalize.
But what we see here is that we start off we have a one coming out here which is a one up here. And I'm just actually
going to erase that cuz it's the same one. It's the same wire. And we have C being one. And the moment we go from C
being one to zero, then what happens is that this becomes a zero and this becomes a one. And we know
that the output of 0 and one is zero here and the output of one and one is one here. And so we have this one come
through and then the output of that orgate is still going to be a one. And this line doesn't change at all. And
that's uh a little it may may take a little bit to understand that but nothing crazy has happened. But if we
change a to zero then nothing is going to change either because we already know that this and
gate is always going to output a zero. And we know that when c is equal to zero the uh whatever value is on this line is
going to flow through. So what this pretty much means is we have two observations is when C is zero
output doesn't change output doesn't change when C is one.
The output follows A. And I'm using follows as synonymous with equals. The output is
just going to be whatever A is. And the interesting thing that we saw is that when C goes from 1 to zero,
C goes from 1 to zero, then we actually freeze whatever the output is at that exact time. We kind of and the output at
that exact time was whatever on was on the value of A. So when C goes from one to zero, we lock in the value of A. we
lock in the value of a and it's not going to change the entire whenever c is zero as we
already dictated because we set it up here we have a rule that the output doesn't change when c is zero. So when C
is one, we have this output following whatever A is. And then the really interesting thing is the moment we go
from C being one to zero, we kind of latch in A's value. And this is very interesting because now
we can freely change A and the output doesn't change because of our rule up here. So in an interesting way, this is
actually storing data because what we have happening is if I wanted to store a value on this line Y, then what you
could use, what you could infer from these three rules is that you would need to set the value of C to one to let the
output equal whatever A is. Set A to whatever you want it to be. And so then the output currently equals A, which is
what you wanted it wanted the value to save. And then you set the C from one to zero and that locks in the value. And so
the output is still the exact same thing that you had as a but now you can change A freely and the output is still
captured. And this is really useful. And what we've done now is we've we've effectively stored data. And I think
this this diagram gets a little bit confusing. So the same way we represented a full adder with a
rectangle, let's go ahead and represent the generic selector as so. So we're going to call this selector notation.
And we have a b and then uh we can call it c for your your your choice or s for your
selector. I'll just call it call it c. And then you have your output y. And all this is saying is this is our original
selector before we had the output going back to the input. So that right here represents this. These are the same A,
B, and C and Y as before. And so what we've done now, and I think a more intuitive way to represent this is we've
taken I don't know why I made that so vertical.
We've taken this A output and gotten rid of it. So we just have this B and this clock, this carry, this choice, and our
output Y is still going to be an output, but it's also going to serve as an input. And you
know the rule selector is that we've seen that we kind of have this above. So when C is zero, the
output doesn't change. When C is one, the output's going to follow, and I'm just going to replace this with A. It's
going to follow whatever A is. And then the interesting case is when we go from C to one, I've shown you that we
actually lock in a value. And we've come to a point in the video, I've been drawing on this screen for about 3 hours
that my my regressive handwriting skills are are getting a little too complicated to follow for you. We have these ones
and zeros diagrammed all over these these these logic gates, and it's it's not very intuitive what's happening
anymore. And we're actually, as we saw earlier with sequential logic, we have to rely a
lot more on timing diagrams. So, it is time for me to actually switch over to some computer software and make
this a lot easier for you to understand. So, let's look at this exact same example before, but let's let's migrate
it over to computer software. So, I'm going to switch screens here now on this new software. And as you can see, our
logic gates are actually drawn somewhat visibly coherent. And I've built the original selector
that we've seen. And I'd like to show you that the selector works as we expect. So what we have right here is
I'm going to set our our C is zero. So I guess I should first distinguish distinguish that this this dark green
represents a zero and this this lighter green represents a one in the software I'm using. The software I'm using is
actually called digital. The website that I link below that I wrote with one of my great friends is all of our
graphics are built in digital and you can actually follow along. There's instructions there. I won't explain
exactly how to do everything I do in digital, but you will see me build out in digital.
And for now, all you should need to know, and I'll walk through everything as we
go, is that this this light green is a one and this dark green is a zero. So in our first case, we can see that C is a
zero. And the rule we said earlier for a regular selector is that if c is equal to zero then y should follow follow
whatever a is. And we see this happening. You can kind of see and I think it's easier to visualize now the
this this this a just flowing through. And you can see b's value just gets cut off by this and gate because its value
is zero. And this is far more intuitive to see on the software once you can recognize the the lighter green being a
one and this dark green being a zero. And then we can see that if I if I toggle C, then the output should follow
B. And that's exactly what happens. So the output follows B no matter what A is. And we've we've built a a our our
selector circuit. And another cool thing the software does is it will let me generate a truth table automatically. So
we don't have to do everything by hand. So you can see for this this circuit we have right here
that the truth table that I I have drawn out previously in the video is equal to the same truth table that's being shown
right here. And I won't switch back to my drawing because it's going to be a pain, but you can go back in the video
and see yourself that's true. And then the last thing I showed you before I before I switched to this view was that
there's a a notation the to draw a selector. So, and we said it was represented with this right here. So,
let's go ahead and see what that looks like. So, I'm going to copy this over
and we will see what the output looks like. So I have our A and let me call this A2
so we don't get it confused. Then we have B. I'll call it B2. And then we can also have our C2. And these are the
same. This is the same circuit before. And and we're going to have our Y2. And another thing about the circuit
is we represent our inputs with these squares. And you can see you saw me previously clicking them. And our
outputs are going to be these circles. So now if I if I play this, let's convince ourselves this is true. So C2
is zero. So the whatever we set A, it should flow through and that should happen regardless of what regardless of
what B is and we see that happening. And then if I toggle B, then nothing should happen. And that's the case. And then if
I flip to C, flip C to one, then we see B flows through. And toggling B lets that value flow through. and toggling A
has no effect. And hopefully you that is enough to convince you that these are are are equivalent circuits. And I can
actually delete this right here and do the same truth table generation. And if you go back uh less time this time, just
a minute, you'll see that these true these two truth tables are the same. And so now what we want to do is do what
I showed earlier where we took the output and chain that to the input. So I'm going to rename this. I'm going to
rename everything accordingly. And this A goes away. But let's just call this this B and let's call this C. And let's
actually just we said A when I was drawing on the screen. So let's be consistent here.
And let's have this output feed into the input. So now if I do this then the point I was trying to make earlier which
I think may have been a little hard to visualize without seeing it in this manner is that whenever C is zero the
output is not going to change because whenever C is zero we let this top input flow through the output. And then
whenever C is one, the output's just going to be whatever the input is, whatever this value A is because a
selector behaves such that when the C is equal to one, we let the bottom input flow to the output. And
the interesting thing was then when we toggled C from one to zero and what we said is going to happen is that at the
moment we toggle C from one to zero, it's going to go from letting this bottom input flow through, it's going to
go from letting the bottom input flowing through to letting the top input flow through. And the top input is actually
equal to the bottom input whenever C is one because it's all the same circuit as you've been able to see. So what's going
to happen is I'm going to toggle this to zero and this value is going to start coming through and this value is a one
and it's going to trap this one in this loop right here. We have a little bit of a feedback loop happening and I know
this is a little difficult to understand but uh it should be it should be this is what I explained is the other one of two
hardest concepts to understand in the video is what I'm showing you right now. But let's see what happens. So we
toggled this C from one to zero. And this is what you would expect because the input at that time we toggled C from
1 to zero was a one up here. And so when we actually toggle C back to zero, we just let this one flow through. And now
it doesn't matter what we set A to because it's we have this rule that whenever C is zero, we just let this top
input flow through. And so we've actually trapped the value in here. And if we wanted to trap a uh a one a zero
in here, then what we need to do is have C go from one back to zero. So in order to do that, we have to set C back to
one. And then we would set whatever value we want on A. And then to trap it, we'd set C from one back to zero. And
the the remarkable thing that's happening here is that we trap the value on A at the exact moment that A C goes
from one to zero. And let me show you exactly what I mean. So I am going to add another cool thing this software has
is the timing diagram we saw from before and I'm going to bring this back in case I need to use a lot of space. But we
start off with C. These are all zeros. Uh we see the two inputs are zeros and the outputs are zero. If I toggle one,
if I toggle this A, nothing happens because C is zero. And then when C is one, you can see on this diagram right
here that the value of Y is just equal to whatever the value of A is. And then suppose we wanted to lock in a one as
the output. I need to have C go from the one it is right now to the zero. And you can see that C goes from one to zero.
And now it's at this my claim is that it's at this exact moment that we lock in the value because now whenever I
change a c is going to stay at that original value that it was at. And now suppose I wanted to lock a zero in. We
said it only happens we can only lock a value in when this C goes from one to zero. So what I need to do is bring C
back up to one. And as soon as we do that we can see that the output starts updating. It follows our input A. But to
lock it in, I need to bring the C back down to a zero. And now you can see the C, we have this falling clock edge. We
have a rising clock edge here and a falling clock edge here. And the falling clock edge is what actually latches in
that value. So now I toggle A and nothing changes. So
the good news is that if you follow me, you're halfway through the complexity. We're going to turn this up one more
notch and then we're going to be done. So the good news is you're halfway. And the bad news is that it gets a little
bit more complicated than this. So if you do find yourself confused by this, this is not something that you should
let stop you from proceeding. You should check out the website that's linked in the description that I wrote. It's it's
meant to illustrate this example a lot more detail. And you should just draw through this
and play with it by hand until you're able to rationalize what's happening. Personally, when I learned this, I found
this incredibly difficult and was not able to wrap my mind around it for days. It took me multiple sessions of opening
up my computer and drawing things through before I finally had this aha moment and everything snapped. So, don't
be discouraged if it takes you some time. And once you do feel like you have an
understanding of everything I've spoken about this circuit that we have on screen, then you can continue is that
this is a something called a positive latch. And what that means is that the output's only ever going to change when
C is one. When C is positive, the output's going to change. Interestingly enough, we could actually
just negate C and we'd get something called a negative latch. And that's the same behavior is that the output of the
Y is only going to change when C is zero. So on the positive latch that we just saw, you lock in a value when the
the C line goes from a one to a zero. And on the negative, it's the opposite. You lock in the value when the C goes
from a zero to a one. So, let's actually look at what that is. And I have inverter here. And I probably shouldn't
have erased this. Let me do this. And now let's look at our timing
diagram. So C is zero now. And previously we said that when C is zero, the output's going
to follow this in this this top input which is equal to the output. So it's never going to change. But now we see
we're toggling A and it is changing. And that's because when you invert this C line to a selector, also known as a
multipplexer, it just inverts the behavior. So the original selector let when C is zero, it lets this top input
flow through. And when C is one, it lets this bottom input flow through. But now that we've inverted it, it's the
opposite. When C is zero, we let this bottom input flow through. And then when it's one, we let this top input flow
through. And so if I want to set a value, I need to get it so that we we have the value coming through. And then
I want to pick the output to be this one. And we do that by setting C to be one. So to set a value, I have my A.
Let's say suppose I want to store a one. Then I need to put this this this C to one. And now we're selecting this top
line. And now as much as I toggle A, nothing happens. And then if I set C again, then notice now that the
output follows A. And then at the moment where I set C back to one, we start picking this top input again. And so now
the output's never going to change. And so this is called a negative latch because it latches onto the value
whenever we go from zero to one. So it's it's latching on whenever we have this this input C being
one. And it turns out that there is a shortcoming with what I've described here. So this is a very interesting
combination of circuits and not that many logic gates that actually enabled us to to persist information. But the
issue is that if I go back to this this original uh positive latch instead of the one we were just looking at, the
output is changing whenever C is one. And it works out that if we're trying to
store a value, it's fine cuz we know that whenever C is is falling from one back to zero, it it it stores the value
in and then it's not going to change. But we'd like that to be true until the next time C falls from one to zero. But
that's not the case because as you can see right here, the outputs uh going from 0 to one. And
now we have we have this uh we have this this output changing before there's another falling edge. And so it's hard
to give full motivation as to why we need this behavior. But it turns out as something that will
become more clear as we design more parts of this computer is that if we can have information
only change at one specific time, then it gives us more time to actually look at this value. So we know that on our in
our current circuit that whenever C falls from one to zero, it's safe to assume that the output is going to be
the same. until C goes from 0 to one. And this is this may seem fine. You're saying, "Hey, look, this is a lot of
time, but what we'd actually like to do is make this as long as possible." Because I mentioned that these logic
gates take time to settle. So, we'd like our output to be able to be used in other parts of our computer for as long
as possible before we lose that stored in value. So what we'd like to do and what we're going to do next is update
this circuit such that it only the output Y only updates every time C goes from one to zero.
And this is going to be very useful because then it means until the next time C falls from 1 to zero that value Y
is going to be the last latched on value. And you can see that's not the case here because in between this C
going from 1 to zero and the next time C goes from 1 to zero, we have our output Y changing.
And so this circuit that we have right here is called a latch. as I've mentioned and we're going to build
something called a flipflop which has this desirable property that the output's only going to change when C
goes from one to zero. Build this. I'm going to spoil the solution because it is something that would be incredibly
difficult to come up with on our own. And once I spoil the solution, we'll go ahead and convince ourselves that it is
true. So, the solution is that we're going to take a positive
latch, which we have right here, and feed it into a negative latch. And let's go ahead and see what I mean by that.
So, I'm going to copy this positive latch we have here, and I'm going bring this over here. And what I'm saying is
that I'm going to take the output from this first latch and make that be the out the input into this other latch.
do. So I take the output from this latch and I'm going to make it be the input into
this latch. And then we we still want the same property where we have one A, one C, and the outputs the Y. So we we
have two C's here. So I'm going to get rid of this. And I said we take a positive latch, which we have here, and
feed it into a negative latch. And the way just a few minutes ago that I took a positive latch and made it into a
negative latch was was by inverting the C line. Because we saw that inverting the C line has this nice consequence
that it it flips the behavior of the latch making it a positive to a negative or vice versa.
Um, in our in our default case, the software I'm using, it's a positive latch by default, meaning that the input
the output follows the input whenever C's value is positive. And if we invert it, then we have the opposite behavior
where the output only follows C's input when C is negative, zero. Cool. So let's go ahead and
use our timing diagram that we have automatically generated to verify that this property holds
that the output Y is only ever going to update at the exact moment C goes from 1 to zero. And the value that it'll latch
in at that moment that C goes from 1 to zero is whatever value is on A. So let's say we want to latch in a one here. So C
is zero. So I'm going to show the output's not changing even when we toggle A. That's correct. We have our
our input toggling, but there's no falling edge. There's no C from one to zero. So we don't change. Now,
um I'm going to set C to one. And we see that C the output Y still doesn't change. And this this is different than
what we saw before. But we saw before the output y would have changed here. But now as I keep toggling a nothing is
going to change because we are waiting for this falling edge of c to happen. So let's actually do that. The moment I
toggle c from one to zero we would expect this y to latch in the value finally.
And that's exactly what we see. And now I toggle a and nothing happens. And to to do the
cycle one more time, let's we've shown ourselves once that this this y value only updates when C falls. It has a
falling edge. Let's do that one more time. Let's get back to that point and say I want to replace it with a zero
now. So I bring C back up and I can show you that toggling A has no effect. And then I set A to what I want it to be.
And now the moment that C goes from one to zero, we should be able to hope that this Y value locks in the zero. And
that's exactly what happens. And so this is the exact circuit that we call a
flip-flop. So let's try give a more reasonable and intuitive explanation as to why this
behavior happens. And this is something that you could derive your on your own just by pausing
this video and playing around and trying to offer up and justify the behavior of the logicates that you just saw. But
I'll go ahead and spoil that out for those of you who don't want to rationalize on your own like myself
would have not wanted to. So what we have here when we start this is C is zero.
So whenever C is zero, the output here is not going to change because we're locking in this top value. So in order
to for us to get a value from A to Y, we know that we have to get A onto this line at some point or else it can't even
make it over here. So the only way to get A onto the line is to toggle this C positively. And now we can have A
getting onto this line over here. But then the issue is that we also have to we want to get A over here. And
because this is a negated clock, the only way we can get a to come over here is if we we get a
zero onto this line because then a one would come onto this line and let this value
through. And the only way we can then do that is we now have the value on this intermediate wire that connects the two.
And it's the exact moment that I toggle C that this value gets trapped in this wire right here. Because when I toggle
C, then this original latch that we're so comfortable with now, the output of it is just going to be this top line,
which is a one. So C goes down and then this one is going to be locked on here even if I toggle A. So if I do that, I
toggle C down. Then notice how me toggling A has no effect on this middle line. This value is is locked into this
intermediate wire. And the last thing is that at that same time that we had C
going from this one letting this value through to a zero, we also had something interesting happening over here because
we we trap the one on this line. But then having this clock become zero means that this line becomes one and that we
actually let this bottom input through. So now that in simultaneously what happens is we trapped the value of a on
this intermediate wire and we now let our gate output what that intermediate wire is.
And I know that probably sounds fuzzy. So, so take the time to justify this on your own, but that's the high level of
what's happening here. And I'm again not going to spend too much time dwelling on this because the article that I wrote,
which is linked in the description, goes through a lot more. So, I will say that you are 95% of the way done with
learning the second hardest concept in this video. There's one final thing we need to do. And so recall that I taught
you what a latch is and it was a positive latch. And then I showed you that inverting the clock makes it a
negative latch. So what we've actually built here is not a flip-flop, but it's a negative
flip-flop because the value is only ever locked in on the falling edge. When C becomes negative, i.e. it goes from one
to zero uh a more negative value then the value is latched in. And so this is incredibly useful. But what if we wanted
to have the opposite behavior where we wanted the value to get latched in when C goes from 0 to one? It turns out that
we just negate our order of operations. So right now we have a positive latch being fed
into a negative latch. If we just have a negative latch being fed into a positive latch, then the output is that our our
our intuition remains the same except the output y only ever changes on a rising edge when C is becoming positive.
So let me go ahead and update this to show that. So I get rid of this negator. So now we have regular C coming into
this selector. So, this is a positive latch, but we need to make this one negative. And so, I'm going to do that
by bringing this down here. And I'm going to add a negator. Let me rotate this
like so. And now you can see that this this first this first latch is inverted meaning
meaning it's a negative latch and then this one's the positive latch. So now what happens is that the value of
C only ever changes on the rising clock edge. This is because when C is zero we have this value on the intermediate
wire. We have this A coming to the intermediate wire. And the exact moment that we toggle C from a zero to a one,
then this becomes a zero over here. So we trap in this current value of A in this feedback loop up top. So we're
trapped in the value of a onto this line. And at the same time, this becomes a one. So instead of selecting the
feedback loop to output, we select the value of this intermediate line to output, which is the value of a. And so
we'd expect that when I toggle this from zero to one that the y becomes a one. That's exactly what happens. And now
when I toggle this, the output is not going to change because we've locked in the original value of a onto this wire.
And it's not going to change because of this this latch right here. Cool. So this is something very
powerful. And I'll prove it one more time that the output only changes when C hits a rising
edge. And this right here is incredibly powerful. A latch did let us store values, but this flip-flop is going to
let us store values in in a in a way that modern computers actually do do things. And it's very important for us
that we only update the value that we're storing on a specific clock edge, either the rising one or falling one.
because that maximizes the amount of time that the output is stable that this this value is going to be just whatever
we intend it to be versus we saw in a latch that half the time whenever depending on which type of latch you're
looking at but for either when C is zero or C is one depending on the type of latch the output is just going to follow
whatever the input is and that's not as useful to us because we would we would like to persist the value Y for as long
as possible. So, now that we've we've seen how to build this flip-flop, let's actually put
it to use. So, in the previous section of this video, we built an alou. And our motivation for even doing any of what I
was talking about is that we'd like to be able to save the value of the output of our of our adder. So, if I if I add
two values, I'd like to be able to save them. And what we have right here is a circuit that lets us save one value. But
what happens if we want a circuit that lets us save two values? And it's pretty easy. All we have to do is just have two
flip-flops. And what that means is that if we have a value A1 and A2 we want to persist, then
we could feed A1 into the first flip-flop, A2 into the second flip-flop, and then feed the same C control line
into both of those. And then at the exact moment we go from a r a rising clock edge, then we latch in both of
those values. And the two outputs are Y1 and Y2 effectively save the value of the ALU. And before I want to move on, I
wanted to add this in and that is a little bit of notation. So we saw when we built our full adders that it was
pretty difficult to conceptualize what was happening. We had so many of them. So we abstracted away as a little
rectangle. And then not too long ago, I showed you how to abstract away the selector that we built with this
trapezoid shape. And it turns out because this flip-flop we've built is so common, it's called a D flip-flop.
Actually, there's there's different variants of flip-flops, but the one we'll use for for our computer, we only
need D flip-flops. And we can abstract it away with this rectangle.
And what we can see is that takes in a D, which stands for data because that's the data that we're going to be saving.
takes in a C, which is this clock signal. And we've we've I I've kind of beat around the bush here, but we'll see
as soon as we finish the next section why what this clock exactly is and what it's going to do. And then we get two
outputs on the right side. Uh it's called Q and not Q. So, traditionally, if you do decide to research into
flip-flops more or even latches, you'll find that Q is the common symbol to denote the uh stored value. And and in
this software I'm using, we get Q and not Q because in some certain cases it could be useful to have the inverted
value. But what I really want to show is that these two are equivalent. So we know that the value should only update
when C goes from 0 to one. So if I want to lock in a one, what I would need to do is is put in a one on my input line.
And then the exact moment that C goes from 0 to one, we should see the one co the the value of a get locked in over
here. And that's what we see with this circuit. We already we already saw that. And to prove that these are similar or
actually identical, we'll do the same. And we see the same behavior here. And then similarly, we know that if we
toggle a while uh at any other time, the data on the output line's not going to change. So if I wanted to write a zero,
I would need to let the clock go back down to zero and then the clock put the clock from 0 to one. So I do that and
then this has the same exact behavior. So, this this right here will be used to represent a flip-flop and it'll make our
our analysis easier and it'll save me time when I'm building these circuits having to draw these selectors out every
single time. So, you can kind of see this already makes it easier than drawing this and these are already
easier than drawing all the logic gates. So, it's really nice to have these layers of abstraction.
So now what I'd like to do is we talked about we wanted to save the value that our adder can have. And the issue is I I
drew my adder on on my uh iPad, but we don't have an adder built here. So I'm going to stop this and we're actually
going to build an adder. Uh we're going to build exactly what we had before and then proceed from there. And if I come
down to this arithmetic section, you can see that there is a circuit called an adder. And this should look very
familiar. You see there's an A and a B. And then the CI is the carry in we're very familiar with. And then the output
we get is a S for the sum bit and a CO for the carry out bit. And what I'm going to go ahead and do is I'm going to
pause the video and then build our full arithmetic logic unit and then we will we will come back and and go from there.
Okay, so I've cut back and I know this looks like a lot, but this is exactly what we had earlier. We have our series
of A inputs and they get wired into each of the A inputs of the full adders. We have eight A's, eight B's, eight full
adders and eight outputs. We have all of the B's and we have them exclusive or with our add subtract flag. And then we
also have that add subtract flag going into the first carry-in of the rightmost full ladder.
And before we proceed to add um our flip-flops to this, let's let's make sure this works. So this is two and this
is two. And we expect that 2 + 2 is four and this is four in binary. And if we just have 0 over here plus two, we just
get two. That makes sense. or 0 + 1. And if we had something like 3 + 1, we would expect 1 2 4. And we can verify that
subtraction still works. Let's suppose we had four and we wanted to get rid of one. So, we toggle our subtraction. And
then this is 1 + 2, which is three. I know I went fast with the binary conversions there. I've done it enough
where it's almost second nature. But if you wanted to pause and go through the sections I showed slower, you would be
able to verify that this is all true. And now that we've done that, we have the issue as we can see and we talked
about where changing the output changing the input immediately changes the output. And
this is problematic because we would like to capture the output of our adder subtractor, our ALU, arithmetic logic
unit, which I'll call the ALU here onwards. as if I haven't already done that in the video earlier, but we just
learned that we have this flip-flop that we can use that can let us accomplish exactly that. So, I'm going to stop this
simulation and going to zoom out a little bit here and let's use the flip-flop that was
already provided to us. And I'm not going to skip the video this time because I think there is some true
benefit to to seeing this. And I don't want to just keep confusing everyone by by skipping back and forth.
Wow, this is not my nicest performance. Oh, maybe this is exactly what I did want.
Yes, this is what I want. Okay, so what we going to have is we know that each of our flip-flops can store one bit. So,
just as we had each of our full adders could add one bit, we're just going to have and our solution to add eight bits
was to have eight full adders. If we have a flip-flop that can store one bit, our solution to storing eight bits is to
have eight of these. And we're all going to feed them the same uh clock input line because we have some
value on our output from our ALOU and we want to trap each of those bits at the same exact time because collectively
each of those bits is just a zero and a one. But if we look at all of them eight in a row, we know we can actually
interpret a human readable number from this. So I'm going to create a new input that is going to be our clock.
And that's going to get fed into every single one of these. I'm actually going to bring this down.
And we're going to have eight of these. So I will
have eight. And let me bring this up a little bit. I think we may need to bring this down
later. And then we're going to get rid of our outputs here because they're actually no longer outputs. We will be
needing them in feeding in each of these logic gates. So I'm going to delete this. And let's go ahead and wire in
each of the data the D lines for our adder. So this is easy based on the
construction I have. We can just bring these all the way down. And I admittedly did not need to bring
it so far down. I thought it'd be a little more complicated. Um, but now we can feed this clock in to each of these.
And on this software, you'll notice that when you have two wires overlapping each other, it doesn't mean they're the same
wire. They can be distinct. They are the same wire if you have this dot. So you can see right here this wire is
physically connected to this wire but there's no dot at this intersection. So they're actually two different wires.
Okay. And then we have that. So let me then come down here and let's take our outputs of these and use them
accordingly. So let's see if it's 90. Nope. 270. And I will have eight of these in a row.
>> Okay. And then we should be able to And we want to look at the Q, not the inverted Q.
Because as I mentioned earlier, we actually don't care about the inverted queue for
the computer we're building. Okay. So now I'm going to run this circuit and let's let's see how we can
actually use this. So let's say I wanted to add the numbers 1 + 1 and we would expect two. So I'm going to put in a one
and a one. And our input our add subtract line is zero. So we're going to be adding and you can kind of see by
looking at these lines up here that we have we have a one or a two as our result. Sorry. But we want to capture
that result because the moment I start changing this input, you can see the output starts changing and that's not
good. We want to be able to save what we have. So we know then from here that the whatever the input lines are, they only
update to the output lines at the exact moment this clock goes from 0 to one. So if I set this from 0 to one, we should
expect to see this bit be set cuz that represents two. And that's exactly what we see happen. And now if I keep
changing this input, you can see that the outputs of this of the ALU are changing, but they're not going to get
trapped as the stored value. The stored value is not going to update until the next time the clock goes from zero to
one. So I'll keep changing this. Nothing happens. Set the clock to low. I can see even changing things has no input. And
then now suppose I wanted to latch a new value in. Suppose I wanted to do uh 4 minus one. And I would get the output of
three. And if we do that and propagate this
through, then we Oh, it looks like I Oh, I messed up something. I did four and one, but I had the add line. So, that's
fine. I will fix this. Let me set the subtract line. And then the moment the clock goes from low to high, now we can
see this this three gets latched in here. And changing the inputs has no effect. And so this is very very useful
because we're now actually able to store values. And that's that's something that we'd like to do in our computer. Our
computer actually needs to store values. We have this effectively calculator up here, but if we can't save anything, it
it's not very useful. And what we've actually built here is not just eight flip-flops. This entire thing is what we
call a register. So a register is a abstraction over a flip-flop. It's multiple flip-flops together, but we
don't we don't perceive them as individual flip-flops. We looked at them we look at them in aggregate. And each
of these flip-flops get in their own inputs, but they all have the same clock line and then their outputs are all read
together. And if you've done a little bit of computer programming before, specifically in the language C, you may
or may not be familiar with the keyword register. And if you're defining a a variable in a programming language,
which if you have no idea what I'm talking about, don't worry about it. If you define a variable in a programming
language C and you mark it with the keyword register, then it instructs the compiler to try store that value in a
register instead of in your computer's RAM, which is what we'll come up to next. And this is because these
registers are very very fast to read and write from. You can see they they operate as soon as the clock goes from 0
to one and then their values are immediately persistable. What we're actually going to do next in this video
is build up RAM for our computer so we can save values that we add into different spots in memory. And you'll
see when we actually go to use it that it takes more time to interact with memory from RAM. And that's because you
would have to figure out where in RAM you want to go and then pull that value back out and then put it into a
register. [snorts] So there's a few more steps involved. But before we build RAM, I'd like to show what would we could do
by taking the output of our register and then chaining it as the input to one of the arithmetic logic unit sides. So I'm
going to stop our program here and I'm going to copy this cuz I I'd like to keep it. And what I'm going to do is
destroy these and destroy I just want to destroy. That's not possible. Okay. But I'm going to take
our outputs, our register value, and make it the input to the ALU. And what that's going to do is that the it means
we're always going to be adding or subtracting the current value that's stored in the register with whatever our
input is. So now we only have one input value and we can add or subtract whatever value in the register is. We'd
pulse the clock and then it update the register with the result and we could do another addition on that. So let's build
this and then we'll see what I actually mean here. Okay. So now something interesting
happens if I if I start this circuit. And it's it's weird. We only have we have one we have one one input here. And
I've I've done a poor job of designing this. But you can see that our our register is also outputting a zero right
now. And what I can do though is let's say I add one here. Then we're we have since a
register is storing zero that means our our input a side is zero and then we're adding a one and so we can see here the
result is a one and if I want to pulse that in clock then we can trap that value in the register so I do that and
now you can see that the register is outputting a one and let me actually pause this one more
time because I think it could benefit from some actually labeled outputs.
Okay, this is a better representation. But so we started off with we have a zero on our output which is also our
input A. So now we add we want to add one to that and I'll pulse the clock from low to high. And now we get a one
as our output. And notice if I keep one on this circuit, then now we have our one here and we're we're keeping it as a
one here. So then the next time this clock goes from low to high, I should expect to see 1 plus 1, which is two
being on the output of the register. And we see that happen. And now I have two here and two is our first input and one
is our second input. So I should see three, which means this and this should be set the next time the clock goes from
low to high. That's exactly what happens. And if I keep pulsing this clock, we have three. and then I'll go
to four and then five and then six and then seven and then eight and it would keep on going.
But what we have is a circuit that lets us count. And this is going to be useful.
It's a sake of illustration for now, but later in the computer we're actually going to revisit this. We we are going
to have a part of our computer which just incrementally counts and it's counting up and up by one at a time to
know what instruction i.e. what work it needs to do next. But that's not something we need to worry about for
now. Uh I just wanted to put this in to maybe give a little bit of context about our our alou. And we can also count back
down from zero. So we have eight. If I toggle the subtract line, now we have eight. And every time I pulse the clock,
we're going to be subtracting one from the value. So I go to seven and then six and then five 4 3 2
1 and then we're back at zero. And interestingly enough, if I subtract one again, then
0 - 1 should be negative 1. And this is actually the binary representation using 2's complement of negative 1. So this is
very interesting. So now what we'd like to do is operate on multiple values. So we would
like ideally to be able to perform large and more complex expressions. And what I mean by that is right now we can either
do a + b or a minus b. And I'm not suggesting that we're going to do multiplication or division because I
said that's probably out of the scope for our video because of YouTube's upload limit. But what we would like to
do is a + b minus c plus d. Something that involves more variables than just two. And the only way we can do this
with our alou is if we had a certain way to store memory beyond one register because we're always going to only have
two inputs to our alou. And what we'd like to do is just be able to control what those inputs are based on looking
up values in memory. And to bridge this gap, we're going to build what we call RAM. And you are probably familiar with
RAM independent of how well you know computers. And RAM stands for random access memory.
And so the way RAM works is that we will be able to give it an address and get back a value. And we'll either be able
to give an address and get back a value. And that's when we want to read from our RAM or we give it an address and a new
value. and the the RAM unit will update whatever is in memory at that address. And what I mean by address is we
effectively have a table of values. So what I'm going to do is switch back to my drawing screen for the next segment
of this video so we can make some sense of this and then we'll come back here and see if we can build it out in
hardware. Okay, so we're back to the iPad. Let's recap where we were. So, we had
um this ALU, arithmetic logic unit, and you'll often see it denoted in this V-shaped form, and it kind of means
input one, input two, and then your output. And I know we're ignoring some stuff like the add subtract flag, but
that's just for sake of demonstration. And we had a our output of our ALOU coming
back around into a register and then that would actually feed into one of the outputs or back into one of
the inputs and then this other input was controlled by us. And what this let us do and there's also this like clock
signal or latch signal for the register that I'm also ignoring just for the sake of visualizing. What this let us do is
keep operating on one value that was persistently being stored in this register. So the output of the alou we'd
freeze it and then we could do something um that we call that add any term our result and then we could say if I want
to add one I could throw in a one here or if I wanted to subtract two I could throw in a negative -2 here and then I'd
let that propagate through and capture the output in the new register. And the issue with this that we're seeing is
well there's no issue with it but it's kind of simple and that we'd like to be able to do more. So instead of having
one value um that we can save, what if we had like a whole ton of values over here and we had like several values like
value one and two and three and keeps going and then at any point we could say hey I want to take uh whatever is at
like this value three and feed it into our AOU do some work and then maybe take the output and then uh have that output
go back and we write it into say like address one or something. Um, and this is kind of abstract. So, what we're
going to do is figure out like what this actually means. So, for now, what I'm actually gonna say is let's throw away
all of our thinking of the AOU and just focus on this thing over here because what we want is a way to be able to
store data. And I talked about this just a few minutes ago, but um, yeah, there's going to be some replication here, but
let's try to figure out what this this part actually means here. And the first thing you'll see is we're
talking about storing values. And your mind's probably jumping, hopefully it's jumping straight to registers. Like a
like if we want to store a bunch of values, let's just have a bunch of registers. And that would be the correct
intuition. So let's say um we want to have several registers storing values. But before we even get there, let's just
figure out like what this might look like on a table. So we we have several values that are all unique and so we
want like a table to represent like what the state of this memory unit looks like at any given time. And so we can have
this like this list of entries. So like maybe the first entry has like value 0 0 0 and the second entry has value 0 1 1 0
and the third entry has values 1 0 1 0 and the fourth value maybe it's uh 1 0 0 0 and so on. These are just random
values and it actually turns out not to get ahead of myself when you plug in your computer for the first time and
start it up the RAM takes on any number of values. the actual values specified in memory are kind of just subjective.
Um, we usually reason about them starting off as all zeros when you first plug in a computer. And I don't want to
get too lost in specifics, but that is an interesting thing. Um, if you like plug in a RAM unit and then try inspect
the values right away, you'll see like sometimes they're all over the place. Um, and so let's say in this
hypothetical we have three, five different values here. Um, we need a way to assign each of these values some
unique identifier. Um, when I'm when I want to read like this value right here, that's a bad arrow. It's kind of Let's
say I wanted to read this value right here. I need a way to tell the RAM unit, hey, I want that that second value in
that list. And I either want to read read what that value is or write something new into that position. Um,
and so what what is that like? We how do we actually represent that? And that is what we call an address which is pretty
straightforward if we like how we can reason about the addresses is just what position we are in our list of numbers.
So whereas this this right hand side has the value at a particular address the left hand side specifies what the
address actually is. And we'll start counting from address zero. And so we can say this first value has address
zero. This second value has address one and so on. And this can keep going. Um, and so now it's easy for us to ask the
RAM, hey, give me uh RAM, give me what's at this address 2, and it would correctly return one 0 1 0. Or I could
say, hey, write some value into address one. And that value is 0000 0. And uh the RAM after that operation would write
that correct value into this address. So um that's how we represent things. RAM is a
series of addresses and values. And you can kind of notice that there's there's two things that vary here. Um the first
and kind of obvious one is is hinted by here and I say a dot dot and how many values do we actually store in RAM. So I
guess our our tunables to say our parameters when we're designing our RAM system is number one
uh how many addresses and the second thing is for any given address and this might not be apparent
but it might be more subtle. Uh right now I just said each address has four bits like that's how at each address the
value that we store there is four bits. um that's arbitrary. I just did that because that's easy to draw out. But uh
what is the actual like width of the values at the address? So um bits of values. So uh for a given address,
how many bits are we storing there? So for modern computers, this ends up being uh larger numbers um millions and
millions into billions of addresses and each value is is a 64-bit address or even some computers it can be even
higher 128 bits. Um I'm actually going to get to the point where we we build up a fully functioning RAM in the same
software I just came from. So I'm going to keep the value small here because you can learn the concept without getting
lost in the uh specifics of trying to make it one to one with the computer we're going to build. So I'm going to
build a simple version of RAM that says uh let's say it's going to put the number we'll say four different
addresses and then for also each address will store a four bit value. Um these two numbers do not have to be the same
and I'm just doing that because it'll make our construction simpler here. So if I relook at this table now given this
um we have addresses 0 1 2 and three and then in terms of the values let's just make up some random
values. Uh let's just say this is the state of our RAM at any time. Um,
and now let's get into the construction. So, we kind of suspected we were going to have registers to hold these actual
values, and that would be correct. That's what we're going to get to. Um, so let's say we have a register for the
first value, and then register for the second value and a register for the third value,
and then finally a register for the fourth value. kind of made my spacing wheel too big
here. But um let's now talk about before we actually get into construction. I got a little
bit of my a little bit ahead of myself there. Um the two operations we have to RAM. So this this table diagrams the
internal state of RAM and how we think about it. And we know that like under the hood once we build this out it's all
going to be a mess of logic gates. But this is the the the values that are underlying underlying held in those
logic gates. Um so RAM has two operations and they're surprisingly
straightforward. Um and you could probably also deduce it by now. So the first one um operations.
The first one is read and it takes in an address and then your output is going to be a
value. So in our example, if I called this read function and said I want to read what's at address 2, then the RAM,
if you told it that, it would give you, if it's built correctly, it should give you um 0000.
So that's the first half of RAM, being able to read the values that exist. And then if we could only read values, that
would be cool. But uh part of the thing we wanted to motivate in our previous example was being able to take the
output from our arithmetic logic and possibly write it back into one of these values. And so you could probably guess
the second thing we want to do, the second operation we'd want to expose is called write.
Um and write takes in a value that you want to write into RAM. and then the address
that you want to write that that value into. And there's there's really no output here. Um it just kind of it
happens. You don't get anything from RAM and return really. Um so in our example if I called this write function and I
said hey write the value uh one one one into address let's say address two then what we'd expect to happen is that in
this address we'd end up with one one one and then if I came back and called the same read operation we did earlier I
should expect not to get 0000 0 I'd expect to get 11 one1 because that's the new value we just persisted in
So these are the two basic operations of RAM. But it turns out that's also the complete operations of RAM. There is
truly nothing more to it than the ability to read from a certain address or write a particular value into a
particular address. And I'm going to clear this out. We are going to start with the read
because the read and the right. Well, the write's a little more complicated than the read, but you kind of if you
understand how reading works, writing is not too much more complicated. Um, so we're going to start with the read. Uh,
and now with that proper context, I'll jump back to these four registers here. So, let's say this is our RAM.
I'm just drawing around here. Um let's say for the read operation we need to specify an input which is our address
and that would go into this RAM and out of RAM we'd expect to get some 4bit value back. Um and now here's where
things get a little tricky. How do we actually do this? So we understand how these registers work and we understand
that they hold values. So let me do a little bit of erasing. move these over.
And let me also show the values that are in each of these registers. These are all just fictitious values.
Okay. So the first thing is how do we uh capture this input. So these addresses 0 1 and 2 and three are just they're
they're base 10 values. Um so I can denote that here to keep it clear. Base 10 recall is just how we reason about
numbers as humans. But we also learned uh several times now that computers don't really understand these values.
computers especially the one we're building think in base two. So our addresses are actually have to come in
as base two values. So let's let's for the sake of uh understanding here um let's go ahead and make our add a new
column to our table which just shows the address in binary. So address and then in base 2 and then this address that
we've been reasoning about is in base 10. And so we have four different values and uh I'll just spill out the answer
because it's not too complicated. um we're just counting in binary here. So these are the binary values and you
can see to encode these four different values we need two bits. So when we're thinking about address into our RAM unit
we're looking for two binary inputs. So that will actually be the representation of our input here. So we'd expect uh on
our input um some value that is two bits and
I'm going to bring that up here and you'll see why shortly. And um this will be human for now. Eventually we'll
automate this entire thing when we finish the construction of our computer. These will be values that we can
specify. So I can write 01 and uh I' I'd hope to get uh this second register. So this one right here.
Um I'd want that output to flow through. So um
that's how we represent our input. And then we know our output. Since we're storing four bit values, the output,
we'd expect to just be four bits. And so if the user specifies 01 as their input, um we'd want the values in this
register to flow into this into our output representation. And then if I switch that input, let's say to 0 0,
we'd immediately want the values that are in this register to flow out to here. So let's figure out this first
arrow that I've been drawing over here based on whatever the input is. How do we collectly correctly select uh the
right register that we want? And so we're going to learn uh a new type of circuit. It's not too complicated, but
it has a a word to it. Um it's called a decoder. So a decoder says for a given input we will decode that in spec like
pick exactly one output. Um and so what does that look like? Let's start by giving each of these registers their
addresses up top. So we start with 0 0. Here's 0 1. Here's one zero. And here's one one. And that'll just help us for
referencing. And let's also extend these wires that capture the input all the way down here. This will
make sense shortly. Um, and recall that, you know, if I if the user puts a one here, then no matter where you fork
from, you're getting a one on this value because it's a wire. So, it just carries that value uh as far as it can. So,
um, let's start with this register 0 0. I'm going to spill out the answer to the first one and then we're going to figure
out why it works and then go from there. So, what I will say here is that we want some we want this register to be
selected when both of our inputs are zeros. And so, the way I'm going to do that is I'm going to invert both of the
signals. Remember, this is the not gate. And then I'm going to feed it
into an andgate. And now we have something interesting. So I I you're probably confused how I
got here. Let me prove to you that this works first. So if both of our inputs are zero, we're going to get a zero here
and a zero here. When we invert it, we get ones because the inversion of a zero is a one. And then for an ANDgate, it
only spits out a one if both the inputs are ones, which happens in this case. And we get a one here. And you can kind
of see that for any other combination of values, the output of this this on this line at the end here is going to be a
zero. Um, so for example, if I threw in the input, if the user had specified the input one zero, then what would happen
here is this top inverter would spit out a one, this bottom inverter would spit out a zero. And then one and zero. Since
neither since not both of the inputs are ones, we'd spit out a zero here. And I'll let you uh work backwards. And if
you want to trace through every possible uh case of the four input combinations, you'll see that the only time that this
line right here gets a one is if both of the inputs are zero. Um there's another way to think about this too is work
backwards. So we if we want a one on this output line, then we can we can work from right to left. So we'd say if
this and AND gate outputs a one it means that both of the inputs must have been one by the definition of the AND gate.
And then for this inverter up here if the output is one then the input must have been the opposite of one which is
zero. And the same would follow for our our gate down here. And then you can kind of see that the only time that we
could possibly end with a one over here is if both of the inputs are zero, which I think is a pretty neat way of thinking
about this. So that would correctly let us pick out this first input. And then let's try go
do the same for this next input. So this next input, we'd only want we'd have some line here. We only want this line
to have a one if the user has zero one on their inputs here. So what I'll do is we'd want something very similar to what
we have above, but maybe we drop some inversions. So, we'd want this to be a zero and this to be a one. So, what I'll
do is I'll invert this zero and then this one would just feed through and we'll just funnel it into an
AND gate. And we have this wire out here. And so now you can kind of see again that the only way this can be a
one is if both the inputs of this ANDgate are one. And that means that for this inverter up here, if the output of
the inverter is one, that it means the input must have been zero. And for this line right here, it just it just pulls
in from this uh this input from the user. The only way that could be one is if the user had specified one there. And
you can kind of see that's exactly what we have going on here, which is pretty interesting. And
we can then do the same for the next case. So, uh, now we want to do one zero. So
we only want this output to be a one if our input looks like this. So one zero means that this should be a one and this
right here should be a zero. So we can invert this one and then feed both of these into the end gate like we've been
doing above. And you'll see that it's kind of just the uh the this top one and this bottom
one are just reflections of each other. And that kind of follows from the fact that the 01 flipped is just one zero.
And last but not least, uh our very our fourth and final register. We'd only want that line to spit out a one if both
the inputs are one. And this one's straightforward. So if if we if we want both the inputs to be one, we'll just
and them directly. So the only way this can be a one is if both the inputs are one, which should
imply that user had selected one one. So this is pretty neat. What this means is that for every value we put in here,
we will get only exactly and only one of these four output lines will be turned on and that is really neat. So if I
specify 0 0 then this will turn on and the rest of them will turn off. If I specify 01, you'll see that
this one turns on and the rest of them turn off. And that keeps going. For one, zero, we'd see off, off, on, off. And
last but not least, if we had one, one, it'd be off, off, off, on. And so we've kind of built this selectoresque
circuit that says for no matter what the input is for the input we will correctly turn on only one of these output gates.
And that'll pretty much be our cue for the register to then say hey if for each register it can say at any point in time
if its corresponding selection line is on then it wants to pass its value to the output over here.
And this is pretty interesting, but what does it actually mean uh to now pass the output? That's where things get tricky.
So, we've dealt with the input side. For the input side, uh we have this, it's not connected to our registers yet, but
we have this circuit that lets us say, uh lets each register kind of know if it should be pushing its value to this
output line. Now, we need to figure out how to look at that signal um i.e. look at that line and if it is set, i.e. it
is one. Then how do we actually push the the value in the register at that current time to the output lines?
And so let's start with our bottom register. It turns out
that this is pretty straightforward to do. Um my drawing is going to get messy here, but we're quickly going to move on
and build this in our our digital logic simulator. So, uh it shouldn't be too much of a concern here. So, recall that
our register um has output lines and how I choose to visualize this is up to me, but um it has its output lines
and pretty much uh what I'm going to do is I'm going to take this signal and and it with either each of the output lines.
So, what I mean by that, let me draw it out. Now what does this mean? So what do the
outputs here look like? If our selection line is a zero then we can guarantee that all of the output lines will be
zero because the these are and gates and we know that the nature of an andgate says that if any of the inputs are zero
then the output for sure will be zero. So this is interesting because it means for this we can verify that if our if
our current register is not selected i.e. this line is a zero then the output of these four andgates down here will be
zero. Now, let's see what happens if we actually were to flip this to a one. Then we'd have ones. And I've kind of
alternated uh putting these on the top and the bottom, which is my fault here. At least each of these inputs the
ANDgate there would be one of the inputs would be a one. And then the other input is just the value from that
corresponding bit position in the register. And so when that happens we know that the output of the AND gate is
just going to be whatever the out the the uh other input is. So for here this zero will flow through here this one
will flow through here this zero will flow through and here this one will flow through. And so now we can see that when
the the selection line is a one then the output of these registers the output of these and gates is just going to be
whatever is in the register itself. And when that uh selection line is a zero, the outputs will be all zeros. So this
is pretty interesting um because it then means we can now we know that uh if we extend this to all
four registers um we we can kind of get the uh the same behavior where we have these these four
gates that spit out values. I'm not going to reconstruct the same circuit. I don't have enough space here,
but uh I can kind of box these in together. How about that? Oops. That should not
Okay, so this is this is this the first register because I kind of have them
close together. This is the second register, third register. And then we already have
our our fourth register down here. uh that's just for distinction. So if each register builds this property
up then we know that for at any point in time um three of the four registers their output will all be zero and then
that last register whatever one it is will have the correct values uh being pushed through i.e. whatever is stored
in that specific register and so then we kind of want to join these all together to the actual output. So let's look at
the uh the rightmost bit only for each register. So we'd want
that rightmost bit from here to flow to this one. Um and the same for the rest of them.
And recall what I just said a second ago where for any particular address that the user specifies on that input line,
three of those four lines, we don't know which one. It depends on whatever the selected register is. Three of those
four lines are guaranteed to be zeros. Um, and that's because only uh only one of the selection lines over here will be
true at any time. And so that means for the remaining three that their gates are just going to get blocked as we just
learned a few minutes ago. And so then it means that the last one it either outputs a zero or one. And that's the
one that we want to flow through. So uh how we simplify that we can't just have four of these lines going into
um into the output line is we can ore them together because when we or these if zero or zero or zero the ore of the
three that are off is always going to be zero and then if we or that with the correct one that we do want to pass
through the value will just flow through because zero or x is just going to equal x. Um, so let's kind of show what that
looks like. So for this specific one right here, um, we could take a
orgate and feed in the rightmost bit from each of our registers. And that will correctly let us capture
prop push through the uh correct rightmost bit position of all the registers to the output one. And that
that handles the rightmost bit. Uh and my drawing as I said is getting bad. And we just do the same for all the other
bit positions. So this second from right would just we'd ore all of them together from all of our input registers.
And that kind of captures our output. So, this it turns out is the fundamentals of RAM and how we build
reading RAM. Um, I've kind of gone a little bit fast here and I've like to keep it everything correct and thorough.
And so, we're going to jump back and actually build this in our logic software.
Okay, so I built the uh very first half which was the selector. So there's none of the register logic, no registers or
output flowing through. But um this was the very first part where we said uh based on whatever the input is, we'd
expect only one of these four outputs to be true. Um and we'd expect it to match the input. So we can kind of see that
when we have 0 0 the very first one turns on and uh the next one would be 01. So if I keep this as a zero and turn
this to one we'd expect only this one to be on and the remaining three to be off. We can see that that happens. And then
the next input was 1 zero. So that we'd expect if I flipped this on and this off, we expect this one to turn on and
the remaining three to turn off. So that's true. And last but not least was 1 one. So if both of our inputs are on,
we'd expect just this one to be on. And that seems to be true. So it looks like this first part is all good. So I'll go
ahead and continue with the building there. reconstructing what we had diagrammed on
my iPad uh in this software. In some respects, it is cleaner than what I was drawing on my screen and in some
respects it's a lot uglier. Namely, uh over here this gets messy. Um so, let's actually walk through and see what's
happening here. But before I start the simulation, one thing I'd like to call out is that uh instead of using actual
registers, i.e. making a bunch of flip-flops next to each other, um I'm just using inputs. And that way
we can easily change the value that's in RAM just by clicking ourselves before we actually go and um implement the uh
writing the writing part of our RAM, the second half. So I'm going to go ahead and start the simulation.
And this is our address selecting line. So right now we can see it's 0 0 and the coloring here is pretty interesting. Um
you know we can see some things light up and it kind of corresponds to this this selector part here. So first off we
correctly see that only uh this first line gets selected and we can see these and gates are all fed uh ones versus the
others all of theirs are fed zero which means they're all outputting zero independent of the value that's stored
you know in in each of these RAM uh in each of these registers. But this first one's different um correctly since it's
the address we're selecting is giving the data in these registers the ability to flow through. And so basically this
is our output line. And what I'd expect to happen if I turn on this rightmost bit position is that the output should
show that rightmost bit position. And uh that's exactly what happens. So um how do we make sense of the uh the mess
that's going on over here? So, we know that uh these are our registers, our four registers. They're kind of just uh
inputs right now, but they will become registers not too long from now. And then we also had our decoder over here.
Uh this decodes the input address selection and we'll select uh one at a time of the correct quote unquote
registers. And then we then join the value that's in each of these registers with the value of the decoding signal to
determine which value should flow through each of our four output lines. And this happens at the per register
level. And then what we have to do is we have um an output line for each bit position of each register is we actually
need to go join them uh to get the output for our for the RAM unit as a whole. So if I look at this this uh
let's say this rightmost bit position we're seeing an orgate whose input is two more the whose input is the output
of two other orgates. So this is just kind of a four-way orgate. Um if any of these four inputs are true uh the value
one will flow through and the output overall will be a truth. The software I'm using, as far as I know, does not
have the easy ability to do um or gates that accept multiple inputs. So, this is kind of how I've gone the show around,
but that's fine. It doesn't make our logic too much worse here. Um and so, if this output corresponds to the rightmost
bit position, what I'd expect is that the rightmost bit position from each of our four registers should be the four
inputs to this. And that's kind of what we see. So if I turn this on, we can kind of see, hey, this green over here
flows to this one. And then this line right here flows to that top register, the top output of the second register,
which hooks into its rightmost bit position. Similarly, if I look at our third register, here's the rightmost bit
position. Here's that output. If I trace it up, it comes into that third ore. And last but not least is our uh fourth
register. Here's the rightmost bit position output. If I follow that line, which gets a little tricky to do, I can
see it follows into the fourth bit position. And so because of this the the decoding properties combined with our
andgate uh little trick there, we know that at any time um three of the four inputs to this quad orgate is guaranteed
to be off. Um which means the fourth value will effectively flow through. So if it's a zero, the entire circuit will
output a zero. And if it's a one coming in here, that value will just flow through. Uh and this is pretty neat. So
now let's suppose I put in the value um this value into the second register. So that's what is that? That's 2 + 4.
That's the value six here. And if I then switch the address selection part of the RAM to read what's in this second
register, then I'd expect that value to get reflected over here. So the address for this register is 0 0. The address
for this one is 01. So to switch it, I just turn the second one on. And now we correctly see that the uh the output of
our RAM um corresponds with the register denoted by our input address selection, which is pretty cool. And if I then turn
that back off and go and select uh address zero, then I get that original value um correctly outputed here.
Similarly, if I were to select uh the [snorts] third register, the register whose the address in RAM uh
corresponding to one zero, this third one down here, we can see its output is all zeros because the value of that
register is all zeros. If I flipped it one bit at a time to turn all of them on, we then go ahead and you can see
that the output turns on. And uh just for sake of completion, let's go with this for our last our last register. And
this is address 11 one in RAM. So if I select one one on our input select our input address selector I get the uh
exact value outputed here. So this is pretty cool. And we notice that even as I select uh different addresses
that the values in RAM are held but we're just only letting one of them flow through at a time. So this is pretty
cool. We have four different 4-bit values. They just exist as is. And based on how we specify an address in our
control input, we get to read the value of RAM for that given address at any time on this output line.
It's pretty powerful. Um, the next thing we'd like to do was we can now read from RAM, but we'd actually like to be able
to write in values into RAM. So, what I've been doing for this demonstration is just uh toggling the values here and
they're just kind of input units. But we really know that these uh this shouldn't this shouldn't be a you know four input
units. It should just be a four bit register which boils down to four flip-flops. So, what I'm going to go
ahead and do is uh we what we want to do next is actually replace each of the inputs that I've mainly spent with uh
flip-flops. So, I'll actually show the first one. Um, let me get rid of these. And if I go over here in the tool, this
is less relevant. Uh, we want to D flip-flop because that's what we learned about previously. Go ahead and rotate
it. Oh, wrong way. And that's one bit position. We know
just a register is just uh a four-bit register is just four flip-flops next to each other. And then
I'll hook up the wiring. So recall that uh in our digital software, we get the output of the register is denoted Q and
we also get not Q which is the inverted value. We only care about the value as is. So we'll just stick with Q. And
cool. So now we've replaced um instead of selectors, we've used actual flip-flops.
So I'll go ahead and do that for the rest of them. I'm ahead and replace each of our
selectors with actual flip-flops. And recall that when we chain flip-flops together, just stack them next to each
other, we get um registers. So now the next thing is how do we actually go ahead and make it such that we can write
values in. So we have the reading of of our RAM working. How can we actually write a value into one of these
registers? So let's start just by saying that uh these over here can be the four bits. So if I were to want to uh you
know oops it's not going to let me run this. If I were to want to uh write in a new value to a particular address, then
what I want to do is be able to put punch in that address here and then punch in that value here and then have
that um be the signal that tells our RAM unit to actually go ahead and hey like do this right operation. So there's two
things to note here. The first one is that if these are our if this is the input for um reading a value in to RAM,
then we can hook this up to each of our registers pretty much like the only way to replace a value in a register is to
specify it on this on these four lines just by our construction here. So uh the same way we can loop this this right bit
into the the input line uh for each of our right bits of our four registers and I won't do that right now for the
sake of uh time. I do that for each bit position. So that's observation number one. So each of these registers whenever
it is going to update its value it's just going to look at whatever has been set by the user and right now that's
just going to be set by the humans. Eventually we'll get to the point that the computer sets the values itself
because you know the computer is fully autonomous in that sense but that's uh we're still a little bit of ways away
from that. And that's the first observation. So I can go ahead and do that wiring. The second observation is
uh I know we've talked about registers a little bit and we're a little bit of back but and we're going pretty quickly
here and it's that even if we hook in the input here so like if I had you know each of these four bit positions lined
up here um the only way to actually get uh the value the register to accept the new value is by that clock signal.
Um, so recall there has to be like a a clock edge, a transition from low to high or high to low depending on what
flip-flop you're using. Um, and so we're going to add one more input here. And that's going to be
uh kind of this I guess we'll call this like this this trigger operation. Um, so if if I wanted to write a new value into
a particular address, I'd punch in the address here would punch in the value here and then because RAM needs a uh
because registers need a clock edge rising or falling to be triggered, I'd have to then punch this. And at the
exact moment I punch this and it triggers a transition from high to low or low to high, the correct register uh
would hold in that value. So we said that for reading in the data input for each of these registers it's going to
come from these lines. So we can hook these input lines to each of the registers. But we can't hook the clock
line um into each of the registers. And the reason is is if we did that then the moment we punch this clock line the
values here would get written into every single register. But when we punch the clock line, we only actually want to
write the write into the register that's uh being referenced by this address setting line right here. So recall that
we built the decoder to actually solve this problem already. So we can kind of reuse that. We know that at the exit the
output of each of these andgates that we have on this vertical um it'll only light up if the address selected by the
user here is quote unquote picking this register. And that was for reading, but we're going to use the same logic for
writing. So what we'll actually do is take advantage of that same principle that we have with the each of these and
gates. And that will and the decoder output with this this uh clock line and feed that as the clock line into each of
these registers. And what that'll allow us to accomplish is that when we punch this clock line, it won't actually
replace the value in all of these registers. it'll only replace the value in the register whose ANDgate uh here
i.e. the output from the decoder corresponds to the input set on these lines right here. So uh I think
demonstrations will make this clear. Let me go ahead and wire this all up. gone ahead and finishing up the wiring.
And uh a few things are going to look different now. So I've kind of moved some of the stuff around from when I cut
the last video to make it fit. But um you'll see that this is our value input line. So when we want to write a new
value, we set the value here. This is the same. when we want to read or write to a register in RAM a specific address,
we punch in that address on these two bits. And uh last but not least, you'll see
that this is the clock signal. So uh in our diagrams, we're using positive D flip-flops. So that means that this
register will latch in a value uh on a rising edge of that clock signal. So whenever the C line goes from zero to
one, uh that's the exact moment that the register will actually look at what's on its input lines, these D lines, and hold
that and record that as the new value that it persists. And so if I zoom out a little bit, we can see that this is our
input value. So when we want to write a new value, we place it here. And you can kind of see that uh it is simply fed in
uh left to right the same way on all four of our registers. So since these four lines are the only way to specify a
new value, we can just hook each bit position of the register, i.e. each bit, each latch um or sorry each flip-flop
to the correct uh position on the line here. And then we said we could not do the same for the clock signal because if
we did that same thing for the clock signal, then the moment we punched it and went from a low edge to a high edge,
uh, all of these registers would read in the new value that's on these four lines, which would write to every single
address on our RAM when we really only want to write to the address denoted by the user selection here. So that's
enough talking. Let me go ahead and run this circuit and turn it on and let's see what happens. So the first thing to
note is that if I look at the output of each of the registers, then you can see they're all all zeros.
So that's just the way that uh registers uh initialize in this software, which means that if all the values in our RAM
are zero at every address position, then no matter what address position I put in here, uh the output's always going to be
zero. But that's not very interesting. What we want to do is if we know everything's at uh zero, let's go ahead
and write a value to a specific register. And uh we'd expect that when we select and selected that register,
the uh output would be captured here or the value of that register after we perform the write instruction. So let's
write this second register. This register is the first one in our our RAM. So that's address zero. This one is
address one in our RAM. So I'm going to punch in address one. And then let's say we want to write all ones to it. Uh 11
one1 in binary. Um which I believe is 15. 8 + 4 is 12 + 2 + 1. Yep, that's 15. So we have 15. We want to write 15, the
value 15 into address one. So we've specified those two things. And what we now need is a rising clock edge. So I'm
going to punch this. And the exact moment I punch this, two interesting things are going to happen. First the
value 1111 should be captured in this register. So you'd expect to see these four lines light up. And then since uh
we are selecting this address still and not changing that this output line is all is still going to be reading what's
in this register. So we'd expect 111 to now be the new value in the register. So if our implementation is correct, we'd
expect 1111 to appear on the output line. So let's go ahead and trigger a rising clock edge to see that. And it
everything looks correct to me. We have 1111 as the value in the register and all ones
um on our output line. So let's do two things. Let's clear this just for visibility sake. It doesn't really
matter. We don't have to because we know that the next time uh the values on this line are going to be useful is the next
time our clock signal goes from low to high. And it's at high right now. So I'm also going to push it back to low. And
that's also not going to do anything because of the discussion we had on flip-flops earlier. But now if I go back
to address zero, it correctly has zero. That's where we started. And in fact, all the other addresses are still going
to spit out zero. What would be interesting is if I specify address one, I'd expect to see 1111 on the output. So
let's do that. And we can now see that we are correctly selecting the right output. So this looks good. Um let's go
ahead and punch in values to uh the other registers just to make sure. So we'll start with this one uh
register zero uh the first address spot. So 0 0. We already have 0 0 punched in here. And let's say we just want to
write the value one to it. Then as soon as I push this, we'd see a rising clock edge 0 to one. And we'd expect that
value to get latched in here. And then since we're still selecting one, um since we're still selecting the address
zero, we'd expect to see that on the output side as well. So, let's go ahead and do that. I see one and then I see
one. So, that looks good to me. Um, let's now I'll undo this and unset that. Let's write a value into the third
register. Oh, so we can actually say I need to check that we haven't broken anything. So, we know we wrote one here
and one one here. So, if I switch to this address, I still see 1111. And uh let's go ahead and uh write to
our last our third right of storage to make sure. Um, so that's address 1 zero. Let's say I want to spit in uh 1 one.
Then I'll trigger that rising clock edge. We see that as soon as that happens, 1 one gets written into the
register. And we also see that being reflected on the output line. And we can go ahead and do this for the final
register um by let's say we want to write this value into it. Uh we specify address 11 one and
that is interesting. So I wasn't expecting it uh to actually read in any value. Um
so let me see what's happening here. My mistake here. So I I misspoke. What I originally said is that uh when the only
way we can actually write this value into a new register is when this goes from zero to one. Uh I actually have a
clarification to make that uh that's actually not the case. Um ideally after we write a value into a register, we
should put that this uh read signal back down. The reason is is let's say I wanted to write a new value into address
111. Um, let's say I wanted to write the value 1111 into this final register down
here. So, if I switch to this address and this is already high, it's going to cause a right to happen. And I'll prove
that to you. Let me switch to address one one. And we can see that the right happens because that's the value being
reflected here. But, uh, that's actually not what we want to happen. And at first this appears to be a violation of what I
said that uh latches or sorry flip-flops only write hold in a new value um when you go from a low to high clock signal
but that's actually uh we have not violated that that is still true. So if I punch this back down let's say we want
to write this value into that register. Um and when I
uh what's interesting is when I even if this line is set to high when I go and push the right address of 1 one in what
happens is that that triggers this and gate here. Um so this uh this the selector will then get the right value
to see hey one is set and one is set. So it comes through and uh originally this AND gate was outputting a zero but then
it outputs a one and it goes from a low to a high and that output of that AND gate is what we're feeding into the
clock signal not actually just the clock signal itself. So that's why that happens. Um so we should be careful that
uh once we write a value in we should push this signal back to low. Um, and that way when I swap to address one one,
this time I see that the value on the input line isn't uh getting written to immediately. In fact, it's not until I
click this and trigger the rising edge that that happens. And the the nuance is then that now that I've done my right, I
should push this back down to avoid uh the issue I just described happening over the ALOU arithmetic logic unit that
we built uh probably about an hour ago. And I made a few modifications. So first it was adding on 8 bit numbers and
therefore putting out 8 bit results. Since our RAM only stores 4bit values, I've go ahead and cut this ALU down to
four bit values only for both inputs and outputs. And then the second thing I've done is I've made both the inputs to
alus be registers and just let the output be asis. And uh basically the reason I've done this is because what
we'd like to do is connect the ALOU and the ALU and the RAM together. And so what that might look like is I might
load one value from a particular RAM address into this register. Then put on a different address in the selector,
load that value into this part of RAM into this register. And then I let the arithmetic happen. And then maybe I want
to take the output and then write that back into a particular address in RAM. And so um I' I've made each of these
inputs be registers so we can read from different parts of RAM uh without worrying about like arithmetic
happening. Um and then we'll deal with the output of this and how we can like that together. So uh here's the output
of our RAM. If we want to be able to read from RAM into uh positions in this ALU, then what I could do is I could
wire each of these output positions into the input positions of the registers that represent the input to the ALU. So
we'll call this the A register and the the B register. Um and so if I did that, you know, I have a full connection of
these four to these four inputs and then these four to these four inputs. And if I wanted to load a value, what I could
do is I could, you know, punch in the address here. The value would come up. It'd read into both these registers, but
then because of the clock command, it wouldn't actually write into those registers until we'd actually pulse one
of those. Um, so that would work and that would be fine. But the issue is that as we design this computer, there's
going to be more components. And what we're doing is a onetoone mapping of outputs to inputs that we know are going
to exist. But let's say the RAM like there's other things that might want to read from RAM. Then this would require
us to also extend not only wires from here to here to here. Uh but also from here to you know maybe somewhere else
conceptually on this on this figure board. Um and both of you both these uh like this gets do the same result. Like
there's nothing wrong with this in terms of functional correctness. This will behave as expected. But you can already
see here you know simple things require a lot of wires and uh it's it's it's pushing the design limits with a lot of
connections happening. Um similarly you know like the output of this uh alou if we want to write that to
a specific position in RAM the easy solution is to take these outputs and you know kind of wire them into the
inputs here. But then you know what if something else in the computer wants to write write into RAM it also would then
have to you know wire something to these same lines um that gets risky you know you don't want to be writing uh
different values on different lines at the same time by accident um and so this generally becomes a like design
bottleneck here. So what we do is introduce the concept of a bus. And what a bus is in our example here,
it'd be pretty much it's fairly straightforward. It just be four control lines.
And then for every component that wants to interact with another component of the computer, instead of wiring those
two components to touch each other directly, we just wire those two components to touch the bus. And then um
if I want to read a value into this register, I would have the output uh of the RAM write some value onto the bus
and then this register would just read whatever value is on the bus. Similarly, this register could connect to the bus
too. And that way instead of having a onetoone pairing um between everything that wants to connect to each other,
each component just needs to have one connection to the bus and that's all. So it's you know it's it becomes like a one
uh connection per component rather than something that in the worst case could be exponential if every component wanted
to connect to every other component. And this may seem trivial in the context of connecting RAM and the ALOU, but uh you
know now that we've once we finished these two components, we're going to go ahead and add a few more simpler ones to
the computer. Simpler in terms of understanding them, but still critical for correctness and actually making this
computer run independently without us needing to punch everything. Um so as long as you can agree or understand
what's happening here, then um that makes sense. So then similarly you know like if we wanted the output of RAM to
be input uh possibly to each of our operands for the ALOU we just write this output of RAM onto the bus. We connect
RAM to the output of RAM to the bus and then we connect each of these input registers to the bus as well. Um and
that works uh you know we could literally hardcode these wires as is. Uh and then similarly you would want to say
if we want to then write the output of possibly an arithmetic computation into RAM we just connect our output here to
the bus and then we could connect our our our input over here to the bus as well. And that way you know if there was
we had some result here and we wanted to write it. We'd you know let this value write to the bus. We we'd tell hey RAM
RAM needs to stop writing onto the bus. Um this value can write it it's what it wants onto the bus. And then uh this
value over here can can read what's on the bus and then you know we punch in the right address and you
know pulse this clock and that'd be fine. So the only thing to be careful about here um anything can read from the
bus. Reading from the bus is free. So literally to connect these registers to the bus I just wired them in as is. But
the issue is we have to be careful to ensure that only at most one thing is writing to the bus at a given time. It
is totally fine if nothing is writing to the bus. But we'd uh we cannot have two things writing to the bus. The reason
this cannot happen is because you could short your entire circuit. So what happens if this wants to write a one to
this wire of the bus and this is still just connected to the bus without any disconnecting capability. Uh and this
writes a zero to the bus. So what happens is if you're writing a one to the one to a wire and a zero to a wire
well the wire as we learned earlier can only have a zero or a one but you are simultaneously trying to write a one and
a zero to the wire at the same time. Um, this this is bad. This can't happen. It's going to actually, you know, it
would it would break your computer. So, I actually don't know the the physics behind this too well about electricity
itself. So, I won't try to speak to this too much. Um, I believe this is just called a short circuit. You know,
connecting high to low directly with electricity uh will cause like a surge of electricity to flow through uh and
it'll just like blow your computer. Um, I'm not going to say anything more than that because honestly I don't
understand. But, um, that's what we need to be careful about. Uh, the issue that I just described only applies when two
things try to write onto the bus at the same time. I said that they only apply when you try to write onto the bus uh,
and not when you read from the bus. And that's because when you're reading from the bus, you're not like these
components aren't actually putting out a zero or a one. It's just reading what's on the wire connecting to it. So uh
reading from the bus is fine because you're not actually putting a value out there. The issue is when you put
possibly contradicting information onto a wire. So how do we get around this is something called a in this case um we
will what we'd like to do is simply uh if we know something is about to write onto the bus uh that and we want we
would actually want to you know establish that connection with the outputs onto the wires. Um, but if
something is that something is in a state where it could write to the bus in the future, but it doesn't want to right
now, we need to disconnect it physically. So, uh, one way is, you know, simply destroy the wires and then
reinstrument them when you do actually want to ride on the bus. That's pretty painful to do. So, instead, what we have
is, uh, turns out this is a common enough thing that we can just use something called a
switch. um switch. Oh, there's a whole section for them. And what the switch will let us do
is it'll literally, you can kind of see this in this diagram. It might be a little small. Um uh if I click it, uh
it'll, you know, connect the two ends of the wire. So, here's your switch. You'd have something like this and then this.
So, in this current state, these two wires are not connected. But then if I was writing the simulation, I clicked
it, it drop this down and it connected two wires. And so that's pretty much what we're going to use here. When we
don't want this or this to be writing onto the bus, uh, then we disconnect it. And when we do want to write its value
onto the bus, we'd click it and it would drop down and establish a full connection. So let me go ahead and wire
this up and then we'll come back and see what that actually looks like in practice.
Okay, so I finished wiring things up. A few changes I've made. So, first off, we had a lot of extra space here. Uh, I I
condensed things in. No functional changes there, but just wanted to make things so I had to do less navigation
and zooming around. Second thing you'll see is I've implemented the bus right here. It's
just four ordinary wires. The A and B registers to our ALU read in into each of their corresponding data bits from
this bus. Similarly, the output writes onto the bus, but it's gated behind these
switches we've talked about. Therefore, we don't risk two different components running onto the bus at the same time,
breaking our circuit and leading to an invalid state. And I've also updated RAM. So, the output of RAM also writes
onto bus. And similarly, it's gated behind these switches for the affformentioned reasons. And on top of
that, for when we want to write read values uh from the bus into a specific part of RAM, I've now connected our
input lines which we previously had as toggles up here uh onto the bus. And then other thing I've done, I've added
outputs to our bus so we can easily see the value on the bus. It's already apparent by like the dark green versus
light green. I just think sometimes having this explicit signal is easier to uh see, especially if if you're watching
this on a phone or something. And then um the last changes I've made are up here. So the first one is for being able
to uh write stuff onto the bus myself manually. I have these input lines and they're also connected by switches
because that's writing onto the bus which has to be gated against. And uh this is because when we turn on our
computer, all of the registers, flip-flops in this software will initialize to zero. So everything in RAM
will be zero. Everything in the ALOU will be zero. So if we wanted to move values from RAM to the ALOU, do some
math and then send them back to different address in RAM. It would be uh inconsequential
um because every value is zero. So when we turn on the computer or start the simulation, I'll use this these manual
toggles to write some values into specific addresses of RAM. And that way we can actually move them onto the bus
and then into a register of the ALOU op one of the ALOU operand registers. And then we'll actually have some sensical
data about rather than dealing with a bunch of zero plus zeros. And the last thing I've done is uh you probably
noticed that we have a decent number of signals now. Uh signals, flags, however you want to call it, but um basically we
have several registers. So you know we need a a flag to write into the register A, a flag to write into register B. For
our ALOU, we have an add subtract flag. Then we also have a flag to update a specific address in RAM. Then we have
two inputs um for when we want to select a specific address in RAM. So I grouped all of these up here. And I also wanted
to call out that uh or highlight doubly so that these switches which control whether or not we write onto RAM are
right now something that we'll have to click and toggle. Uh if we use a different unit, we could you know kind
of add that functionality uh as something we toggle here. Uh maybe I'll do that afterwards. But what I wanted to
highlight is that um these these two areas or three areas we write onto RAM plus these are all that we have to
control. And once we get this demonstration working, uh, a natural next question is like, well, okay, these
things are all punched by humans. So like, how do we actually get the computer to know like a how is it, how
could it be possible the computer could even know to punch certain things? Like what is the computer meant to be doing
when it's turned on? And b, if the computer had some instruction uh about what it should be doing, then how could
we actually rearchitect this to make it such that the computer itself can be the one pushing these buttons? Um, and I I
say that lightly because it doesn't actually look at that in practice, but uh the computer is not physically going
out and pushing these. It just it's wired into control. That's the output of some other decision-making part of our
computer that we'll get to. Um, but I digress there. So now what I'm going to do is go ahead and power this on. Uh, as
I kind of mentioned, you know, everything is is all zeroed out. So if I, you know, if I pulse pulse any
address in RAM, you can see that the output here is zero. And then the other thing that's actually new here is we've
seen in this software that you know zero uh is this dark green and this brighter green is denotes a one and we see that
on all of our wires and stuff but now for the first time we're seeing gray here. Uh and what is gray here? Gray
would be the absence of zero or one. So now that we uh have things that connect to the bus conditionally if nothing is
writing to the bus at any time then there is simply just no values on the bus which is fine. It's just a different
state that we uh we think about. You know, we're used to our our wires and all of our uh logic gates taking in
strictly zeros or ones. And that is true and that still has to hold true. Um but you know, it's not actually you can have
a wire. You can imagine this. It's not that difficult a concept. If you just had a physical wire that you held in
your hand that was not connected to anything on either end, that would have neither a zero or one uh no amount of
electricity on it. So that's what we're seeing here. But uh if I actually want to put some values on the bus manually
specified then uh you can see I can toggle these switches which connects it'll connect you know here's my input
line to the this line of the bus and then you can see that you know since we've now written values onto the bus
anything that connects to the bus and the bus itself now has zeros or ones on it. So let's say we want to uh write
some values into RAM. Now let's say I just want to do something simple like in address zero of our RAM I want to write
the value one. In address two of the RAM I want to write the value two. And then I want to add that one in that two. I'd
expect a three. And then I want to write that back to a different address in RAM. Um how can we do this? So first let's
use this manual hack I have. Let's write one onto the bus. And let's actually write uh that that value one. Let's read
that from the bus into the RAM address zero. So we already have address zero selected. So that's not that
interesting. And then all I need to do now is pulse this line for writing into RAM. And recall when we go from a low to
a high, RAM will catch that value. You can kind of see that happening internally. You can see, you know, uh
we're not letting RAM write onto the bus, but if we did, it's trying to write the value one. So that's cool. That
seems to work. Now we want to write the value uh three two into actually let's let's write an interesting value. Let's
write the value three um into the next address in RAM. So I I put the value three onto the bus and let's write that
into address 01. And um I messed up uh it already actually captured the value as you can see. Recall what we talked
about 20 minutes ago. I should have let this value fall back to zero after I wrote into the RAM, but I didn't. Um so
I fixed that now. put the the values in RAM. And now let me go ahead and disconnect our manual input from the bus
and connect the output of the RAM unit onto the bus. And so now I can see if I select address zero on the RAM selector,
we have the value one in address zero. And if I select address one, we have the value three. So we can see that we've
correctly written these values into RAM. And now that we've written values into RAM, let's get those into the ALOU. So
let's put the value one into this register and then the value two into this register and we'd hope to see a
three. So I have the value one written on the bus because I have address zero selected. And now what I need to do is
toggle the line to write into register A. So I'll do that. And now register A has the value one. I'll put that low.
Now I want to write uh the register I want to take what's in address two of RAM and write it into this register. So
let's put address two of RAM onto this bus. At address two, we have the value three. And I'll now post the line for
writing into register B. And when I do that, you can see there's now this three there. And so, um, now what I'm going to
do, I've done writing stuff from RAM into these registers. So, I'm going to disconnect our RAM output from the bus.
And now what we can see happening is that our ALU, uh, well, this was happening before I disconnected the bus.
I just didn't want to forget to do that. Our ALOU has the output of three + one. And we can see that it's trying to write
four onto the bus. We disconnected it though. So let's connect the output of the ALU onto the bus. And we see that
the value four is out there. And let's write that back into a register. Let's let's say let's write it into the
original address uh zero address zero in RAM. Um I'll do that by setting the correct address uh field. And then I
will pulse the flag to write into RAM. And that should be done. So if I now disconnect our ALOU from writing onto
the bus and let RAM write its value back onto the bus. I can now see that with address zero selected, the value four is
what's stored in there. Uh and if I pulse back to the other address we set, we'd expect a three, which we see 1 + 2.
Um and so yeah, this looks functionally complete. Uh and this is pretty interesting because now we're doing
complex arithmetic. you know we have four different values we can be operating with. So we can do a plus b
minus c plus d whatever we want to do by you know simply moving values from RAM into these corresponding registers
setting the add or subtract flag if we want to do subtraction letting the ALU do its computation writing that value
back onto the bus and then reading it from the bus back into RAM. And you can kind of see we we did something very
sequential here. you know, this is very reproducible. Um, you know, to to write a value onto the uh to read a value from
the bus into RAM, you have to, you know, specify the address plus the flag. Then, if you wanted to read a value, write a
value from RAM onto the bus, you need to connect the the uh RAM output to the bus, select the right address, and then
it'll be on the bus. And then, we wanted to read the value from the bus into one of these registers, we just do the same
thing. We just post that flag. Uh, it's pretty interesting. So the last thing I want to show here is I said you can't
write zero and one uh to the bus at the same time else you cause a short. So we have the value uh let's look at this
rightmost the rightmost bus position has a one on it. Let's what see what happens if I put a zero on it and then try
connect this zero to the bus. And when that happens what you're going to see is that we're connecting a zero and the one
and I guarantee you the software is not going to like it. Perfect. Yeah. So more than one output is active on a wire
causing a short circuit. Cool. So I was right that when you try to write a concurrent zero and a one onto a
specific wire, it's called a short circuit. Um cool. So that uh now concludes our ALU and our
RAM sections. Uh this is probably the trickiest part. Uh it is a tricky part to reason about holistically. you know,
you get into a a lot of logic gates, but um the rest of the stuff we the next few things we'll do are far simpler and uh
are just going to iterate on uh be things actually reductions of what we've already seen. So, the next little bit of
the video should be a bit chiller. So, one of the things that you might have noticed that can be kind of
annoying during this process was uh trying to go from binary values to figure out what the actual decimal value
was uh for a particular thing. So, you know, if we were feeding an address to our RAM, uh, we kind of had to like peek
at these four lines and see which vendor were set to then know what the actual value was. And that's kind of how we
were sanity checking. Similarly, if we wanted to know what was stored in the registers for the ALOU, the A and B
registers, we kind of have to look at these four lines or these four lines and and figure out what's happening uh what
the actual value is. And you know this is 4bit values so it's not too much effort to do it but it can be kind of
annoying to go to have to do that so much um especially when you're you know kind of early onto this and still bring
your intuition. So what if we had a way to actually just visualize the numbers? You know, we know computers
operate in base 2 binary, but you know, us humans when we're debugging these machines or or trying to inspect the
internal state at any particular time. Be nice to be able to see like, hey, like what values on the bus, uh, and I
could see that, you know, just as digits without having to, you know, scroll in to the bus and look at each of the wires
and, you know, start counting powers of two and adding them together to figure this out. And um for this we have
something called a seven segment display. So I'm going to hop over here. Um and this is something that you can
see takes in a binary input up here i.e. machine readable values and it'll it'll draw out the actual uh decimal value
that we can see that we can easily reason about. And some of you may have seen this some of you may have claimed
to have never seen this before but I guarantee that you actually have seen this. If I come over here, uh this is a
scoreboard, a picture of a scoreboard probably that you maybe have seen in your high school. And you can see that
actually everything is a seven seg seven segment display. So why do we call it a seven segment display? If you look at
each of these numbers, let's just pick this one here arbitrarily. You can see that there's um seven different lines
that make up the display. So there's four up here and then three in the bottom. 4 + 3 7. And
um by turning on or off certain combinations of these individual lights, we can create the illusion of drawing
the number. And I wouldn't even say it's an illusion. We actually just are drawing the number. And uh this is a
pretty simple way, you know, without having to get a whole LCD display, which we'll talk about later. Um, it's an easy
way to draw numbers in a way that this is significantly easier to see that this is a two rather than trying to like do
some binary to decimal conversions and reason about that yourself. And there are things like there's seven segment
display, there's higher number segment displays um, and those can do more complicated things and maybe show
symbols like the division symbol. Um, for the sake of illustration here, we're going to keep it to seven segment
displays. And we're also going to keep it down to just one single digit. So, uh, we know that our bus here is the one
we've built is actually four bits. So, there's two to the four different val uh, different numbers. Sorry, there's 2
to the^ of four different values that we could write on the bus. 2 to the^ of four is 16, which means we can represent
with these four bits numbers 0 through 15. So, it turns out digits 10 through 15 require two of these displays if we
wanted to. Um, the logic is the exact same. It just requires more effort. And so, we're going to keep it simple and
say that like our display for now will only show the numbers 0 through 9. And uh you'll understand that if we wanted
to have uh we could put two of these next to each other if we wanted to handle two digits, but it just requires
a lot more work uh and doesn't teach you anything that you wouldn't learn uh by doing the one seg the one individual
digit display. And so this is what this chip looks like in real life. You can kind of see there's uh several pins on
the bottom here. And turns out if you write a one or put power these are synonymous things uh to one of these
specific pins then it'll light up a corresponding part of the display. And so if I look at a diagram uh that I
found online for what one of these these physical units that you can buy what that pin structure looks like, you kind
of see that uh you know we have these these individual lights labeled A, B, C, D, E, F, and G. It kind of wraps
clockwise with the G being the last one. And they each correspond to a specific uh input pin. So if I put one here and
zeros for the rest of these, we'd see just this one light up. And you're also probably wondering, uh, well, we have
inputs A through G. That makes sense. What is this DP and ground? Um, in this specific unit, DP will let you
hit that decimal point. And then ground is just effectively you need to give it power. Um, so it can complete the
circuit internally. We don't really have to worry about that for the sake of what we're building here. Um, but let's
actually see like what each number looks like visually. So you can see we can represent numbers zero through nine. And
to turn to represent the number uh zero, we turn on A, B, C, D, E, and F. And we'd leave G off. And for the number
one, uh we just turn on B and C and leave everything else off. And you can see that we can kind of just draw out
every number and pretty easily see what we need to turn on to get each digit position ready. So, what I've done is
gone ahead and made that into a table. Um, we have our 10 different numbers that we're going to be capable of
drawing with our singledigit display. And, uh, you can see decimal numbers zero and then I have that in binary 2.
And you can see that, you know, for zero, uh, you'd turn everything on except G. And if I tab back here, you
can kind of see that's the same source of truth we have here. We can check one more as well. uh let's say the digit
seven, you'd only turn on A, B, and C. So if I look here at digit seven, we can see that only A, B, and C are on, and
the rest are off. And so this probably looks familiar. This is the truth table that we looked at way earlier in this
video. Um, and what I'd like to do is actually go back to our software and and build something out that'll let you
specify your 4bit binary value and it'll correctly light up the correct segments of the display. Um, so if I tab back
here, I can see that my software that I'm using actually lets me have this. Uh, so I've wired in our display and it
has uh, you know, pins for each of the values. And so I can kind of see here's that A, here's B, C, D,
E, F, G, and then the bottom right here is the decimal point. We're not really going to use it, so we don't really have
to worry about it for now. But uh what I'd like to do is go from this truth table to logic gates that actually
correctly light up the display. So let's see if I look at uh this position A here. Um position A, then I can see that
what numbers on the input line should turn position A on. You can see that's numbers one, two, or sorry, that's
number zero, two, three, five, six, 7, 8, and 9, which is kind of what we see in this table here. If I look at the
column for a, when should a be turned on? It looks like it's for every number except one and four. So then what I'd
want to do is if I had, you know, the four uh binary inputs coming down from the bus, if that's what we wanted to
display at any time, then I need to do some logic where like if the value of of the four bits represents any of those
numbers, then this input line should turn on. And we do that for each of the each of the different segment uh pills
that we have on our display here. And it turns out we've already done something pretty similar to this. We built this
decoder earlier for RAM where we have something uh it's easier because there's only two bits of addressing space for
our RAM. But basically we know that whatever value the user puts on RAM uh we can correctly select the right index
uh in our RAM. So you know uh it's 0 0 right now and we see this first position lights up. When I put in one, I see that
second position lights up. And if I put in the maximum index, that's 2 + 1 3. And I can see 0 1 2 3. Uh index number
three lights up. So we've already built out this decoder logic earlier. So we're literally just going to do the same
thing except this time we have to extend it uh to be four bits, which is a little more complicated. So, what I'm going to
do is bring our bus further down and build this off to the side here.
So, I'm going to actually pause the video or just speed through this part um and and we'll we'll talk through the
output. Okay, so I've gone ahead and built this decoder and uh basically what I have I
detach it from the bus so it's its own unit. Right now I have a 4bit input and for each of those bits um I actually
carry that input bit down and then also I carry a negated version of it all the way down. Um, and the reason I do that
is because if I jump over to our RAM, basically what we had to do for our RAM whenever we wanted to negate a line was
to manually add an inverter. And uh this means we end up using a lot several inverters when really if we just
used one inverter up here to create a forked version of or two one per line, two different inverters to create a
forked variant that inverts each of the two input bits, then we could have just had our inputs pulled in from these
hypothetical lines directly. So, not only does it use fewer logic gates to do it the way we've done here, I'd also
argue it actually makes things easier to read because we kind of know this means, you know, uh, leftmost bit and then the
inverse of the leftmost bit, and then bit position number two, the inverse of that, bit position number three, the
inverse of that, the rightmost bit position, the inverse of that. And so, you can kind of see then, you know, uh,
this would correspond to zero, then one, two, and so on. So for zero, what I'm doing is I'm looking for 0 0 0 which is
the inverted of all of the bits. So 0 0 and zero. And you can kind of see that you know uh if I were to turn this
circuit on and all the inputs were zero then you can see all the inverted lines turn on and then that triggers this this
and gate where it's looking for the the this bit positions off this bit positions off this bit positions off and
this bit position is off and that's why we can see exactly one output lights up and we've decoded this input. Um, and so
similarly, if I turn this off just for visualization, let's jump to the next one, which we'd expect to line up with
the 00001. So, we can see there's the input in inverted of the first bit, the inverted of the second bit, the inverted
of the third bit, and then the regular of the fourth bit. And that would give us that's saying 0 0 1. All four of
those have to be true. So now if I powered this on and turn this to one, we'd see the same thing. And you kind of
continue that logic all the way down. And we'll verify this is correct by counting through. So we tested zero and
one. Now if I plug in two, we'd expect this third output to turn on. That's case. If I go to three, we'd expect this
to come on. Cool. If I go to four, I'd expect the next one to come on. Then five. Cool. Then six.
then seven and then eight and the last one is nine. Cool. So
it looks like zero through nine work correctly. And so now that I have an output line for each of the numbers, um
I can feed this into our seven segment display. So this simple one right here, this bit turns on. This is the A in our
diagram. So if I come here uh I now want to pull that to be I want to ore all I would like to logically ore together all
the numbers for which this a should be on which is 0 to so 0 2 3 5 6 7 8 9. So we will draw lines from all of those
numbers that I just listed out. We will or them all together using several orgates and then feed it into this line
right here. So, I'll go ahead and do that and then we'll uh come back. Okay. So, I've gone ahead and built out
everything for all the logic for deciding if we turn on a this top line up here. And then for all the other
inputs to seven segment display, I've just wired it to this thing right here, which is called ground. It just means
zero. It's a logical zero. So, it means all the other segments will be off. And if I jump back to our table that we
started with over here, I can see that we'd expect this A button to be on for everything except numbers one and four.
So, let's go ahead and pulse through each of the input combinations and verify that this works. So, we start off
with zero. And this is on. That's good. Now, I'm going to put one, and we'd expect it to turn off. That looks good.
Two should be on. Three should be on. Four should be off. That looks good. Five should be on. Six should be on.
Seven should be on. Eight should be on. And nine should be on. So this looks totally good. And
we've done this together just by oring together everything except the two values for which this segment should be
off. So that means if if one or sorry if zero or two or three or five or six or seven or eight or nine is selected by
the user by the input then we should turn on that one. So this is kind of the logic uh and we'll go ahead and do this
for the remaining ones. Okay, so we have built this out. You can see there's a lot of logic gates. Uh I
probably could have done this more elegantly in hindsight, but uh should be working. Um, so you can kind of see for
each of these uh what we did for this this A kind of repeated for B and C, D, E, F, and G oring a bunch of stuff
together based on the input lines and then feeding them around to this uh seven segment display. And also that
control bit for the dot we just hardcode to zero because we're not going to use it here. So let's go ahead and
make sure that this works. So, I'll go one and then I'll count to two. That looks good. The next is three. That
looks good. Then four. That looks good. Then five. That looks good. Then six. That looks good. Seven looks good.
Eight looks good. And last but not least is nine. So we can see that 0 through nine work as expected. And then uh our
logic gets set up so they work for 0 through 9 correctly. We said we're not going to consider multi-digit
uh numbers for now, but just out of curiosity, what happens if I tried to put in 10? And you can kind of see
uh just by chance, you're this one lit up nothing. Um 11 12
Okay. Yeah. So, um I was expecting gibberish output, but that actually works. So, yeah, we can
see this this works as expected. you didn't have to bear the pain of what I went through to make these logic gates,
but you probably saw in the sped up version. And you might be wondering yourself, this seems kind of
inefficient. Um, you know, for everyone who ever wants to use one of these displays, they'd have to rebuild the
same exact circuit themselves, which kind of sounds interesting and uh redundant. And you can imagine there's
other times where we'd want to replicate similar behavior because what we've effectively built here is a lookup
table. So we we clearly specified here for every possible input here is the exact output we would want. Um and it
turns out this is a very common thing and we'll actually come to it again not too long from now on our computer uh
that there is a chip for this. So I'm going to tab over here um and there is something called ROM read
only memory. So let's read the definition from Wikipedia. Readonly memory is a type of nonvolatile memory
used in computers and other electronic devices. Data stored in ROM cannot be electronically modified after the
manufacturer of the memory device. Readon memory is useful for storing software that is rarely changed during
the life of the system. Software applications such as video games for programmable devices can be distributed
as plug-in cartridges containing ROMs. So uh let's not get this confused with RAM. RAM stands for random access
memory. I don't actually think I explained why it's called that. Uh RAM is called random access memory because
in our implementation we can look at any specific address in RAM just by punching in a value. So it supports random
lookups. Uh this might seem intuitive but earlier versions of of memory were actually sequentially accessed. So, if I
as if I read address one, I would have to read address two next, and it'd have to uh circle all the way back around the
address space for me to be able to read address zero if I'm at address one already. So, that's where RAM comes
from. ROM, read only memory, is pretty different. Um, the second thing that we should break down in this definition is
what is nonvolatile memory. So RAM is considered volatile because when you unplug and restart your computer,
RAM starts off being zeroed out or has some random values. So the the values we put in these registers are not lock are
not persisted. You can see that if I punch in a value and then stop the computer and start the simulation again,
that value is gone. Um, and I guess the uh analogy to your computer is if you're typing on a word document, if you
command S or control S if you're on Windows to save it, that writes it to your hard drive and that's that's
stored. Until you do that, the text that you've you have pending in your editor is just sitting in RAM of your computer.
And if you were to unplug it and plug it back in, you lose that data. So, you've probably seen this and dealt with the
frustration before of computer shuts down or loses power, someone trips over the cord, and you lose all of the state
of your computer. And that's because all those things are stored in RAM, which is significantly faster to access than from
disk. Uh, but has this trade-off of, you know, not being persistent. But I digress. Um, so ROM is effectively a big
lookup table. And something interesting about ROM is that it says it cannot be modified after the manufacturer of the
memory device. So if I actually wanted um to build a readonly memory for what we' built right here, I'd need to find a
manufacturer for ROM and effectively send them this table and they would manufacture a physical chip um that
would look like this. and you would plug in your whenever you plug in your inputs on some of these pins, it's guaranteed
to to light up the correct pins on the output. Uh, which is pretty cool, but you know, unfortunately,
uh, that's kind of limited. You know, what if we accidentally made a mistake in this table, got that ROM chip, and,
uh, realized that there was an issue, we'd be out of luck. You can't do anything about it. You'd have to go to
the manufacturer and and get a new one. So, this is kind of inefficient. So what came after ROMs was called PROMs which
stands for programmable ROM. And let's read the definition from Wikipedia. It says a PROM is a form of digital memory
where the contents can be changed once after manufacturer of the device. So basically what this means is uh I mean
it's exactly what it says. you now get the ROM chip that we kind of have here. But then there's also this burner and it
lets you specify the values as you want them. So rather than having to spend that data sheet to the manufacturer, the
manufacturer will just give you the ROM and you can burn in the values yourself. But if we look back to the definition,
uh it can say it says can be changed once after manufacturer. So this kind of still has the same mistake. If you
accidentally punch in the wrong value at a specific for a specific input combination, your chip's useless. Uh,
and you'd have to throw it away and get a new one. So, that's also not optimal. Um, so what came after that was called
EPROM. Stands for erasable programmable readonly memory. And so, we're not going to read this long definition, but what
it is is I'll show you a picture of it. Uh, it looks very similar to the readonly memory we saw earlier, but it
has this exposed element here. And it turns out that if you uh shine UV light onto this exposed element for enough
time, usually it's like 15 or 20 minutes, it'll wipe the entire contents of the ROM uh out. It'll replace
everything with zeros, which means you could then take a a programmer and do the same kind of burning that we had for
uh programmable ROMs. So, this is pretty cool. Um it now means that if we accidentally plug in a wrong value, we
don't have to throw away our entire trip. we can actually uh just redo it ourselves. We can clear out the entire
contents of the chip and then redo it ourselves and then it's still ROM at the end of the day. So once it's in my
computer and I have it programmed, if I keep powering the computer on and off, the values in this lookup table will
still exist. So that's cool, but um it turns out that's still suboptimal. As I just said, it takes 15 to 20
minutes, I think, uh to clear out these erros. It also requires you to pull it out of your computer, which is not
ideal. So the the latest thing we have um is called an EROM. Stands for electronically erasable programmable
readonly memory. So let's build up from what we have. We know what a ROM is. That's just a basic lookup table.
Programmable means that we can program it ourselves using this programmer tool. Erasable means that we are now able to
clear the values of it um if we accidentally put in a different an incorrect value or just want to change
it for our own reasons. Perhaps you want to use it in a different computer or something. And the last thing is
electronically erasable. And that means that instead of having to pull it out of your machine and expose it to UV light
for 20 minutes, there's simply uh a line on this that if you write uh if you put one on, it'll clear out the entire
contents of the ROM. Um so this is pretty useful. And it turns out um we're going to need this
in our computer again. So if I come back here then all this is to say these logic gates are functionally correct and work.
But we could also just have used a um a some form of one of these variants of ROM ideally probably just like a EROM
and uh program this table in uh using by burning the the physical values. So I literally burn in for this specific
input 000000. These would be the outputs that the ROM should output. Now, I do that for all of the uh 10 different
values. And that way, our wiring would be a lot easier. Instead of having all this right here, decoders uh feeding
into all this mess of logic gates, we'd have the outputs here feeding into a ROM chip, and then the outputs of the ROM
chip would just feed directly into the seven segment display. So, that's pretty cool. Um, and that's
all we'll talk about for ROMs for now, but hold on to what we just learned because it turns out ROMs are going to
be the the glue that irons this computer together. and lets us go from the human having to punch specific control lines
to the computer knowing what it should do when based on some predetermined human instruction that exists somewhere
in the computer's uh computer's mind. Um so the last thing I'm going to do here is right now this is connected to inputs
that we're just toggling manually. Um I'm going to connect this to the bus so we can actually just see what values
on the bus at any times uh for values that are under or equal to 9. But uh I will do one other thing where I'm
actually uh going to hook that into a register. So the issue with just displaying the values of the bus at any
time is we saw that when we were moving values in between uh RAM and the ALOU uh the state of the bus is changing pretty
constantly. But if we just wanted to load something from RAM into the ALOU, do an operation, and then see what the
result of it is on our display, uh it'd be easier if we uh could only display the value that's on the bus at a
particular time. So the way we get around that is we just have our bus uh read in. We build a new register like
we've done for the A registers here and the B register here. We just have another register and um it has its own
control line. So right into the like we could say like the display register and whenever we pulse that line uh the
register will latch whatever is on the bus and then that value will be locked into the display until the next time we
change it. So that way even if the bus starts changing immediately and we just want to keep the value there. Um we
don't have to you know have the user be seeing a bunch of intermediate values that are effectively nonsensical to
them. And this would be pretty cool because uh you know one of the first programs we're going to write for our
computer is just counting by twos. So we'd like to see like 0 2 4 6 8. And turns out if we didn't have a way to
selectively decide when we'd want to display the value on the bus to the user uh I guess to the human um it'd be a lot
of gibberish and you would see a lot more numbers being displayed than the the counting you'd expect even though
your computer's working. but you just wouldn't really know um what to what to interpret versus what's a scratch value
being used temporarily. So, let me go ahead and wire that up. Okay, so I've pulled the bus down a
little further, added new a new register, 4bit register, and uh the clock signal for that register traces
this wire all the way here, which is now w right into the out register. So whenever we pulse that from low to high
uh whatever value is on the bus will get latched into this register and will be shown onto this display. And I've also
prepared this by loading uh two values into RAM. So at address 0 we have the value we have the binary value 000 which
is two and then at address one uh we have the value 00001 which is just one. Um so we can do we have two and one. uh
let's load those from RAM into the ALOU and then do the arithmetic and then capture the result onto the uh output
register and expect it to be displayed. So what I'll do is I will connect the RAM to the bus. Uh let's start with
address zero. So we can see we have uh the value two written onto the bus. Let's write that into register A. Now
let's switch to the second address in RAM and let's write that into register B.
And now we can see that we have two in this register and one in this register. And
um that all looks good. And
let's go ahead and disconnect this from the bus. and connect these two connect the output
of the alou. So now we can see the output of the uh RAM which was doing 2 + 1 is now three. That's binary 0011.
And what I'll do oops uh doesn't really matter what address we're selecting on RAM. What I'll do is I'll pulse this uh
right to our register. So I'll set the clock input on this register from zero to one which is triggers a rising edge
which means the register will latch in the input value which is 0011 corresponding to binary 3 and we'd hope
to see that on our display. So let's go ahead and uh set this high. Congrats. We see three. I'll put this down low. So um
now let's just take this a little bit further. So we have the output of three onto the bus. Let's let's do uh 3 + 1.
So we have value three on the bus. Let's read that back into the reg the a register which has two
and I can now see four is written on the bus. That's 0 1 0 0 which is binary four. So if I pulse this flag
I can now see four is written on the bus. If I do the same thing I can keep just adding by one. So the steps we take
now that we've gotten our computer into this state is to read the output of the ALOU which is on the bus into register
A. Step one, read into register A. Step two is simply just update the out the out register.
And now we're at five. And so I can just keep doing these two things. And you'll see that the uh the register
just keeps counting up on the on the display. And if I then disconnect the output of the ALOU from the bus, you
know, it doesn't affect our display. Um it still just holds on to that that last value. So our display is is is kind of
stateful in a sense. It is a register which is then connected to a readonly memory that uh forks into a seven
segment display. Cool. Um that's all we will do for now and let's
jump back to my iPad. So we're back here and let's diagram where we left off uh at at a more
abstract level rather than showing specific logic gs. Um so we can just reason at a higher level. So we have
this bus and in the demonstrations we just built we had a 4-bit bus. Our computer as I
said earlier is going to be an 8bit computer. The reason I was doing four bits in the demo is you could imagine
that the complexity would just increase exponentially every time you add a more a new bit. So you know for each register
there'd be a lot more control lines um and RAM would be a lot more complicated. So the the issue is for the sake of time
it made more sense for me to work with four bit buses and four-bit values but the concepts are still the same for 8
bits. You just imagine we literally extend everything. So all of our registers are 8 bits and um
we will also increase our memory addresses for RAM to four bits. But let's just uh start with the bus. So
six 7 8. Okay, so we have eight lines and uh
this is going to be our bus. And so every component will connect to the bus from here on out. And the next
thing we had was the alou. So I'll draw that. And the inputs to the alou were actually
two registers. So we'll call this register A
and its output fed into the first operand of our ALU. and then register B was our second operand and it both these
always just feed straight into the ALOU but then they can also read values in from the bus. So these are both
connected to the bus and and then we had uh an add subtract line and then the output of the ALO wasn't
directly connected to the bus because recall only one thing can be written to the bus at a time. Um so it's actually
going to be backed by something we call a tri-state buffer. So a tri-state buffer um pretty much in the examples we
were showing we were using a switch. So I'd manually have to click and it would either physically disconnect or connect
uh each bit position of a particular component. In our example, that was either the ALOU output or the RAM output
to the bus. Um, rather than us having to click, uh, it'd be nice if we could just have an input signal and that would
determine if something was connected or not. So, uh, let's see here. So, the tri-state buffer is kind of
denoted, let me move over here to some crash base. It's kind of denoted like this. It's denoted like this. And I know
this gets confusing because our inverter gate is denoted like this. Uh but that that circle kind of makes a world of
difference. And we have some input A and then we have this control line B and then the output of that is C. So pretty
much what we can say is if B is one and there's obviously inverted
versions of this. Let's just say if B is high then then C will equal A. I.e. the value of A
will just flow on through. Then if B is zero it does not mean that C is zero.
It does not mean that C is one. It means that C is just disconnected from the from the bus.
So it's not an output of 01. It literally means no output, the absence of an output. And so that'll physically
disconnect us in the same way that we saw in the software um that uh clicking that switch would
physically d disconnect the wire. So you can imagine like it's kind of the same thing we saw um
here. This is what the software looked like. And so pretty much whenever we have B is being one um then when B is
one the thing will fall and it'll let the it'll let A connect to C and then whenever B is zero it'll pick this thing
up and it'll be physically disconnected and that's how we'll guarantee that uh only one thing is writing to the bus at
a time. And this is nice because it now adds a new toggle for us. You know we've talked about the toggles we have for
writing values into registers or putting something out on the bus. And here's a new one we have uh for physically
connecting or disconnecting us from the bus. So, let me go ahead and clean this up
and let's add this in here. And so that means we actually have a new control line now. And we'll call this
ALU out. And pretty much when this is set, the output of the ALU will be written onto
the bus. And when it's not set, i.e. it's zero, it'll be physically disconnected. So I'm also going to do
one other thing here is I'm going to start highlighting all of the control lines we have because we kind of have a
good number of them now. So let's just uh keep track of this. Uh the add subtract one is one we have.
And then we also have control lines for um register A in
register B in. So let me highlight those as well. Cool. So that's our ALOU. And then after
we had the ALU, we went and built RAM. So we have pretty much a register that reads uh an address from the bus. So our
bus is 8 bits. Uh we're going to make our addressing space four bits. So even though the bus is an 8- bit bus,
we will actually say that our memory addresses in our computer are all four bits. And this is a design choice. We
are designing this ourselves. We actually could make them any number of bits. More bits means more address
spaces, which means that your computer can have more working memory. Um, but we'll see that when we need to do an
operation, we need to specify an address. And so we are going to get to a point where we need to say something
like load the address from load the value from some address in RAM onto the bus and we need to encode that signal
onto the bus itself. So if our address was 8 bits then there's no way that we could fit in a operation that says to
read from RAM whatever signal that may be however we would like to encode that and the actual address. So we decide
that uh this is just by decision that we'll say four bits are going to be for addressing and then maybe four bits
later down the road will be used for saying what we want to do with that address. Um
so yeah so we have something we'll build a register uh in this register we previously in our software
representation just called it the RAM address selector. We're going to give it a bit more uh a slightly changed name,
but the effect is still the same. We're now going to call this the memory address register. Uh it's short for uh M
is the acronym. And we can just know that it stands for memory
address register. And so it is a 4bit register.
So let me actually show that a little bit better. So I will say a four up here. And all of
our other registers that we've worked with so far are 8 bit registers. So let me make that clear too.
And you know this is an eight bit value. The output of the ALU is an 8bit value. So it'll write to all four uh all eight
sorry all eight lines of the bus. The memory address register will just read in the memory the bits from the
rightmost four address lines. So it won't actually connect to all four of the address all eight of the bits uh on
the bus because it's our memory addresses are only four bits. So then there's kind of a choice of like which
four bits are we going to actually use on the bus whenever some other component wants to put out a memory address for
RAM to operate on. And just by design, again, we could have picked any four bits, but uh just by design, we're going
to go with the rightmost four bits. And these are called the least significant bits. And the leftmost four bits would
be the the most significant bits or the highest significant bits. So we have our memory address register hooked in to the
least four significant bits of the bus. And this is always just reading in. So, let me actually uh update that
representation slightly so it's not ambiguous in the future because we're going to be adding several more
components here. Uh versus these two registers are both reading the full eight bits.
And let me clean up this right here. Cool. And then there's also another uh control signal here. You know, the um
the we we're turning this into a register. It was just a selector in our original
thing, but we're now making this encode the value in register. And we know that for all registers, uh they need some
control line to determine when they should read the value that's on the bus into their uh bit positions. So, there's
a new signal called um we'll just call it M in. And let me highlight that so we can keep
track of all of our signals, our control lines. And then this memory address RAM is always looking into
what value is here because recall that RAM has two operations read and write and the only thing uh the for both of
those operations it needs to work with a specific memory address. So you're either reading a specific memory address
or if you're writing a value in you need to know which address you're writing that into. So RAM has will look at this
memory address uh register to determine what to do and then um it'll also need to uh read in from
the bus directly too. So and so RAM when it wants to write a value onto the bus we also know that
happens. um it has uh so we're use the same sort of uh tri-state buffer that we talked about previously. And so the
output of RAM can be written onto the bus conditionally
based on whether or not this output line is set. And so we have an output line here and we will call this one RAM out.
So if it's set, RAM will write its value onto the bus. And if it's not set, then RAM will not write anything onto the
bus. It won't write zeros onto the bus. literally would be physically disconnected from the bus. And so this
will be enough for reading in values. If you wanted to read in a value, uh if you wanted to read a value from RAM, you'd
put that address onto the least four significant bits. Then you'd punch this uh MAR in and so it would latch it into
the register. Then RAM's always looking at whatever that memory address line is and it's trying to write the values
here. And then we'd punch this RAM out and the value would come out onto the bus. Uh, and we need to make sure that
in between us putting the address on the bus and us punching this RAM out, uh, whatever is writing the address onto the
bus needs to stop writing it onto the bus. Or else we'd have a, uh, short circuit because two things will be
writing onto the bus. The RAM would be trying to write its output onto the bus and whatever is writing the memory
address to read is writing onto the bus. So that covers reading from RAM. Then the last thing is uh, writing into RAM.
So if we wanted to load a value into RAM or write a value into the RAM, we need to specify the address. So we'd put the
address onto the bus, we latch it into the memory address register, then we'd stop putting the address onto the bus,
but then we'd put the value that we want to write in at that particular address onto the bus. And so RAM needs to always
be listening to what's on the bus. And it's RAM operates in our uh in our actual computer. It's going to operate
on eight bit values. So I'll denote this with an eight. And so it reads all eight bits from the bus. And uh the only thing
we then need to add was the punch button we had that would perform the right operation that we saw in our in our
circuitry. So I'll add one more signal called RAM in and that is used to write some value
into RAM. Cool. So we uh started with our ALU then we built RAM and the last thing we just did was build this uh
display register. So in our our contrived example we had in the digital software it looked at four bit values
and only outputed one number uh one digit position. In reality, we're going to uh let uh this this uh display
display any number of values. You understand the concept. We're not going to go and reprogram that ROM, but you
can, you know, we uh have a register that looks at the entire bus and
that's going to connect to a an 8 bit register. So a register and then I'll denote it with an eight
and we're going to call this the uh display register um or the output register. So
uh that means that we have a corresponding signal here a new line. Um we'll call this display
in. So display register in. So whenever that hits a rising edge from 0 to one, we will latch in whatever values on the
bus. And we'll just need to be careful to make sure that there there is a bus. And then we know from what we just built
that there would be a ROM that looks at this 8bit signal and then outputs some number of control lines. And those
control lines will get consumed by several uh of these seven segment displays next
to each other. Pardon my bad drawings here. And tada. This is kind of what we've
built uh on the software uh just conceptually laid out here. And so let's actually now talk about um some of the
operations our computer is capable of doing. So the first one is let's talk about how you would load a value um how
you would read a value from RAM. So to read a value from RAM we have a few steps we want to take. So
uh read from RAM. The first thing we want to do is
put an address onto the bus. So in order to read a particular value um onto from RAM, we'd actually need to
address it. And so we need to get that value into the memory address register. So the first thing you do, I don't know
why I denoted this with a one. Um we'll just say read from RAM. First thing you do is uh put the address on the bus.
Then the second thing you do is once you put the address onto the bus um you'd put uh you'd go MAR in uh you'd toggle
it. So you you know you'd let it go from low to high. And then the third thing you do is at that point uh the RAM is
always looking at this memory address register. So it' immediately look up the value that's in that address and try put
it right here. But it's blocked on this RAM out that's disconnected. So before we can put that value of RAM out onto
the bus, we need to uh stop writing the address onto the bus. So stop writing
address onto bus. And then finally, we could then put RAM out
uh from low to high. And at that point we would uh have successfully
loaded a value from RAM. And now let's talk about how we'd write to RAM. So if you want to write to RAM,
the first thing you do is you put the address that you want to write into onto the bus. So put target
address on bus and then mar in toggle. So you want to
read that into this memory address register over here. Then now that we've loaded the address,
we want to stop writing the address onto the bus. It's looking pretty similar to the read so far.
And now we need to put the actual value that we want to write into the address that's now captured here onto the bus.
So since we've captured the address into the memory address register, it's not imperative if it's not imperative that
we keep that value on the bus, which is why how we actually free up the bus to then hold the 8 bit value that is the
value that we'd like to write into that particular address stored in the M. So now we can say put value on bus
and then once we've done that the only thing we need to do is pulse this RAM in signal and that'll latch the value into
the corresponding register. So RAM in would need to go from low to high and just like that we've uh written to RAM.
So cool. And so what I'd like to do now is actually delete everything we just drew
over the past 10 minutes. And let's actually start getting back to the concrete part of this
implementation. And what we're going to do for the remainder part of this segment of finishing off the CPU is
tying things together. And it may not seem like we're done yet. And it's because we're not. we're kind of in this
weird state where we have memory and an alou, but like no way to really tie it together or understanding of like why
we'd want to tie it together. Um, so up until this point, like we started off with with binary and like all of that
logic clearly stacked on top of each other on top of each other until we got to the point of a register. Then when we
got past the register, we kind of had to split where we like went and built this ALOU, then we went and built this RAM,
and then now we kind of recently tied those concepts back together. And unfortunately to bring this all together
as a whole requires us to go into some ambiguity. It can't clearly stack. there kind of has to be some amount of uh take
this for what it's worth and then when I can tie components back together it'll make sense in the end but I I think
there's a way that we can do this uh that minimizes the number of head scratching that happens along the way so
um what we we described some possible operations let's take one of those and try to build it out a little bit so what
we're going to say now is uh we're going to build a lot of operations ations um commands we can call them for our
computer and each of these commands is going to do something very small. Maybe load a value from RAM into a register uh
can be one and then maybe one can be uh doing an addition and writing that value back into RAM can be another instruction
or maybe one instructions taking what's in a register and writing it onto our seven segment display. Um and the idea
is if we have these different instructions defined and the computer knows how to implement each of those
instructions i.e it knows actually what to do which of these lines to punch at which time and in what order then us as
humans can create programs by stacking these instructions together which is pretty cool. So, what I'm going to do is
excuse me there. Um, we're going to create another register. Yes, one more. And we're going to call this the
instruction register. And for now, don't worry about uh how a value gets into this register. But let's
just say that this register uh tells us what we what the what instruction our computer should do next.
And then let's suppose and I'm going to do this actually in a different color because this is is is conceptual. Um
there's some part of the computer that actually looks at what's in this instruction register and it's the brains
uh we can think of it as the brains that will actually go and punch each of these other lines that go and do something at
different times. Um and we're going to finally make sense of how that looks like really shortly.
I.e. now. So uh our instruction register is going to be an 8 bit register and here's the format of the instruction
register. So let's uh let's come over here. The instruction register it's an 8 bit
register and we're going to split it into two components. So it'll be one, two, three,
four, five, six, seven, eight. We're gonna split it down the middle. So the the first four bits, the left four bits
are going to denote the uh instruction. And then for some instructions, we might have an argument that we need to pass
in. And so we'll use the last four bits for denoting the argument. So the first instruction we're going to
build, so this is our instruction register. our in we're gonna build a table of instructions
and um basically what I'm saying is we have four bits to note what
instruction we're going to execute which means we have two to the four different values can fit into that instruction uh
instruction part of the instruction register which means we have 16 different instructions.
That's how many instructions we'll build up to. That's as many as our computer supports. You'd imagine if this
instruction register was more than 8 bits, we could have a lot more instructions. For example, if it was a 9
bit register and we put an extra bit towards that instruction component, we could have 32 uh 32 different
instructions. For this case, we'll start with uh 16 different instructions. and we will talk a little bit about how this
computer scales up to like more modern computer. Um, and we're going to build out up to 16 different instructions. It
turns out we don't even actually need to use all of these to to get a functioning computer. Um, and then argument. So,
what actually do we mean by argument? So, let's actually figure out what this means just by building our first
instruction. So, I'm going to define what our first instruction is. Our first instruction
is going to be called load a oops lda. Um let's actually put this into
table format. So I will say short name, full name, op code,
which I'll explain in a second, and then we'll give it a description. So basically um instruction is
synonymous with op code and then for any instruction that has an argument we call it the operand
or it can be also just called arg for short. You'll hear these used interchangeably. Um so let's let's
define our our first instruction that we're going to implement from scratch is called load A. Short name is just LDA.
Its full name is just load A. These are invented by us. This is the point where we are just inventing these
instructions. Different computers have different instructions and that's something we'll also talk about and is
really interesting. Our simple computer we're building right here has just enough instructions to show off the
fundamentals of a computer and write non or I guess you could say non-trivial programs uh that the computer can
operate. Our op code, let's just give it the number one for now. 00001. And what load A does is it is going to
load into the register A of our computer. Recall register A is this uh left hand side of the operand
and what it's going to load into A is RAM at the address specified by the ARG.
So if you've programmed before, you know those uh these these brackets uh are kind of indexing into a structure. I'm
making this video for people who have not programmed before. So what the brackets just mean is it means go to
whatever's left of the brackets. So that's RAM and then this position in RAM. So it's saying pretty much whatever
the argument is, if the argument is two, whatever the second value is at RAM, whatever RAM at address value two is,
take that, load that from RAM and spit it into register A. So why do we care about this instruction? Well, in order
for us to addition, we're going to want a value in register A and B. So the very first thing we need to do is get a value
in register A. Um, and we're going to say that that value is probably going to live somewhere in RAM. So what this
instruction will do is take some value in RAM, throw it on the bus, and then register A will need to at the right
time read in that value from the bus. And so if I throw in O1,
then the four bits that we have remaining are the operand, the argument, and that's going to tell us which
address from RAM we actually want to load into register A. And so I have four bits to do that. But what's really
interesting is that when we designed RAM, I also said that the memory address register is going to be four bits, which
means those four bits, even though the rest of our computer is centrally designed to be eight bits, those four
bits are enough to fully index every spot in RAM. And so I know when I earlier I designed RAM and I said, "Hey,
it's kind of arbitrary. We're making uh the memory addresses four bits in RAM." It actually works out perfectly because
it means for all of our instructions that we build here on out that need to reference a specific address in RAM,
they can do so using those four bits. So our instructions can be fully complete. Um, which is also really nice. And it
we'll see for the most part that uh our instructions usually if they have operands, arguments, they're going to be
addresses into memory. But that's not always the case. But uh it's designed around this. So there is something it's
not just coincidence that we're designing our uh instructions to use four bits for
arguments and then the RAM has four-bit addresses. This is not coincidence. This is very much a a choice made with high
agency. And this is again something that we designed for our own computer. So you could imagine, you know, maybe if we had
a a 20 bit uh bus, maybe we'd have like 10 bits uh could be used for the memory address register, which means our our
instructions might be 10 bit operands and then maybe four bit instructions. Um so there that there is something
pretty cool to be said about this. So uh basically if we throw in this value, let's let's try consider an actual
value. If we throw into the instruction register the value 01
010 um a few hours ago this would have looked like nonsense to you. Uh maybe
one hour ago you would be able to derive the number out of this and now we're actually saying hey this this isn't just
a number in binary that we want to convert to decimal. There's more to it than this. This is actually first off
it's it's two different numbers and the two numbers are not just one and two. Uh there there's actually it's an
instruction. We're saying execute some instruction whose ID is one and that instruction takes an argument and the
value to that argument is two. So if I come back over here uh to our scratch base um I have 00001 0 0 1 0 was what I
talked about. So this means load a we're saying load a value at this address in RAM into register A and this is address
two um 0 1 2 uh however you want to denote that
third index position or just two um if you want to convert that from a binary number back to a decimal number. So,
let's go ahead and see the exact specific steps we need to make this instruction happen. So, I'm
going to go a little bit slow here. You might be able to figure this out faster on your own, but
the first thing we're going to want to do is
get this 0010 and look up whatever value there is in RAM.
So this instruction register has the values that we are interested in. And so in order for us to get it to the memory
address register, this instruction register actually has to be able to write its value onto RAM. So it can do
that. And I will denote that here. There's something interesting. Um, it only needs
to write those right for bits out. It only it only needs to write its argument onto
the bus. It does not need to write all eight bits onto the bus. Because the uh the left four bits are
the operation code, the op code of the instruction that we're trying to to uh in uh that we're hoping the computer
will execute. And the control is the only part of this computer that makes sense of the op code, the instruction
code. The only the control part of our computer, which for now is just us as a human brain. It's our we are the control
of the computer. um we care about that op code because it tells us what we should be doing. No
other part of the computer cares about it. So uh when the instruction register writes its value onto the bus, it only
would ever need to write the address onto the bus. And you can kind of see that motivated in the case here where if
we have this uh 0000 one 0 one 0 uh we only need these to end up in the memory address register. So
uh that's why we will have that and that'll actually be a new signal um and we will call that uh
instruction register out. So assuming uh let's say our steps to implement this let's let's talk this out
very clearly. So our our step zero and slash our assumption
um is that we are we are assuming the instruction register has the right
command in it. So we aren't going to worry about how this 0000 one 0 0 1 0 got into the
instruction register yet. We'll get there in a bit. Um let's just assume for now that we some human came and punched
that value into the instruction register. So that's our assumption. So now let's actually walk through the
specific steps. So the first thing we need to do is put the instruction register argument onto the bus at 00 1
0. So we'd set instruction register out must equal one. And then after that after that instruction
register out is set to one, the value of the argument 00 1 0 will be on the bus and we need to let these this m in line
go from 0 to one. So it's everything we'll assume is zero. We want that M inline to go from zero to one. And what
that'll do is uh latch in the uh the value um from the bus into RAM. So we'd say
m a r in goes to one. So it's it's interesting because there's a strict ordering
requirement here. um the instruction register has to put its value onto the bus before the memory
address register can read it in. And that might seem like a trivial thing, but this is actually really important
and we're going to have to make sure that we're very careful about that. In general, uh we've talked about this
already. It seems trivial, but I'll restate it again because I think it can't be understated. uh if something
wants to read a value from the bus then the component that it's reading the value from has to have that have to have
has to have its value already written onto the bus uh before the another component can read
it in. Um so we we kind of view this ideally this is atomic in reality has to be sequential. So
ideally we'd say, hey, at the same time that we write the instruction registers on the on its value onto the bus, the
memory address registers to read it in. Um, and that's how we'd like to think about this. That's how we'd like to
think of putting a value from one component to another. Uh, in reality, we know we'll have to do it sequentially.
And so we'll also come back. I'm going to leave this written on the screen. Um, and I'll also highlight it so we don't
forget to come back to it in a timely manner. So once that happens the value from RAM
is in. So then what I can do is we can say okay well now the value uh from the instruction register well the
instruction register outline and the mar inline are both not necessary anymore. So they can go back
to zero. We can reset those components to back to what they were. And so this means nothing's writing onto the bus.
Um, and it means we've s we've loaded our value. Uh, the 0010 has made its way into the memory address register. And
what that means is that RAM has looked up that value because RAM is always looking at what's in the contents of the
memory address register and it has the output right here. So then what we'd like to do is write that value onto the
bus. And then to get it into register A, we can use our register A in line.
So we'd like to transport the value from RAM onto the bus into register A. So the two control lines we use to do that are
RAM out. RAM out will put the value onto the bus and then after the value is written onto the bus, register A can
read it in. So I will say RAM out
and then register register A in. So RAM out equals 1 and register A equals 1. And
then we kind of the same thing where ideally these are atomic. Um but it's important because the moment we set this
RAM outline it connects it to bus to the bus and its values will be on the bus and then only
based on what we know about registers only when we set a in from low to high only at the exact moment that rising
edge happens are we actually going to latch in a value to register a. So
once that's done, the value uh that we wanted to get into register A is done. And so we've kind of fulfilled this
contract right here. Register A now equals whatever was in RAM at the address specified by the argument to the
instruction, which in our hypothetical example is uh 00 1 0. And so once that's done, I can now say uh we can reset both
those lines as well. Okay. So, uh this is pretty interesting. Um that doesn't seem that complicated,
but let's look at it at a slightly different view. Uh where instead of listing it out in a series of steps,
let's um look at it in terms of clock edges. So what I want to do is we said
um we can have all the signals we used here. So IR out,
M A R in, then RAM out, and register A in.
And each of these starts off at low. And then to execute the instructions, they said the first thing that needs to
happen is instruction register needs to write its value out. So it needs to go from zero to one and then at some point
after uh it goes from one uh this memory address register can also go from zero to one. And because this is after this
we know that the value on the bus will be correct. And then we said okay now that that's done uh both of these values
can can kind of at the same time we can reset them to zero and that continues. And then the whole time this register
this RAM out and a in lines are off. And then now that the value uh is the address has been loaded into MAR, we we
can we know the value is looked up in RAM. So we wanted to get the value from RAM's output into register A. So what we
did was we pulsed on this RAM out kept it up and then at some point shortly after that we can uh punch register A in
and that'll latch in the value and then uh once that was done our operation was done so we just reset both these lines
to zero and I can carry these because these top two lines nothing changes so you can kind of see like there's there's
uh two timing signals of interest here um And in order for a like this represents a transaction of information
from one component to another via the bus and this represents another transaction of information from one
component to another via the bus. And what we'll find I'll spill this out is as we build more of these instructions a
lot of the implementation is going to just be moving data from one component to another using the bus. So,
what actually turns out happens is we're going to introduce a another component to all of this called the clock. And I'm
going to draw the clock up here. And basically what the clock is doing, it's a physical uh component to the
computer. And it's uh you don't need to worry about how it build how it's built, but at a fixed rate, it'll just keep
going from 0 to one back to zero. And this is pretty cool. I'm not drawing
this, so it's fixed rate, but uh ideally this is a fixed rate. And the same way every component's kind of hooked in uh
to the bus, uh the clock is also pretty central to the computer. even though I've draw I've drawn it a little bit a
little far out here. Um, but basically what we saw here is uh is is something similar to this. We had
components you know we had a a a zero to a one here and then one to zero here and then a zero to one of interest here and
a zero to one of interest here. So, uh, let's see how we can actually use the clock to, um, aid in some of what we
just did manually. So, it turns out there's something slightly interesting
about what we did here, and that when you're writing a value onto the bus, you can do it whenever because we're kind of
making this guarantee that when we start, nothing is writing onto the bus. And then we're also making guarantee
that like when we are done riding onto the bus, we stop writing on before we start putting the next thing on. There's
clearly this nonzero gap here. So to write a value onto the bus is safe to read a value in from the bus is when
things get tricky because we want to make sure the value is written out onto the bus before. So pretty much this
clock here. And then the other thing is when we do need to read in, we can only read in on these rising clock edges. So
uh what happens is you know when the clock is low we can put in we can put the value out on the bus any time from
here to here. Let's say like from here to like right before anytime here it's fair game to read out to the bus. And
then at the exact moment we go from 0 to one, we want to feed in. But um we can take this a step further
and actually say well it'd be nice to just throw the in signal and the out signal at the same time. Uh because
that's kind of what we want to do. We know we want to write from component A out to onto the bus and then have some
component B latch that value in. So uh but you know the issue is we can't do that at the exact same time because of
the issue we described. you know there's uh it's unclear if the value would be on the bus at the exact time that the
register latches it in. But if we actually uh let's take the example of this M in uh if we actually and this
signal with the clock and let that be the signal that goes into the register then it turns out we
can throw both these values. So we can throw both um the out and the in to both equal one here. And so the out still
goes onto the bus. The value goes onto the bus totally fine. In our in our case that was um the instruction register its
last for this 000 goes out to the bus just fine. And the in because it's ended with uh this this clock signal stays as
a zero. And that stays as a zero until the exact moment we hit this rising edge. And at that point this input uh
both inputs of this endgate become a one. And so we latch in at this exact moment the uh the
rising um signal. We latch in the exact value that's on the bus. So we've accomplished what we want. So then now
when we're up here uh we can just you know be sure that we uh unset these signals. So we can, you know, make sure
that we put both them back to zero. And then when we get down here, we can do the same thing for uh RAM out and
register A in. And we could set both of these to one. So out and in, RAM out and register A in can both equal one. And as
long as we have register A's in signal anded with the clock, then we can take advantage of the same
trick. So let me go ahead and for all of our in signals, let me go ahead and and them with the clock so that we can make
sure that's apparent for the rest of this. Okay, so I've gone ahead and for each of
our in signals, I've ended them with the clock. So for our memory address register reading into RAM, reading into
the A register, reading into the B register or reading into the display register
and all of these are now ended with the clock. So what this means is our clock
signal uh we can also pay attention to that in our control brains and us as humans as we're punching these lines we
can kind of do is react to the fact that the clock is going up and down and being sure that we set lines appropriately. So
if I jump back to our original example here of how we constructed this load a operation. Let's say we have our clock
and it's just always going from low to high. Then basically what needs to happen is
while the clock is low, we need to set IR out um equals to one. Let me
and m a r in equal to one. And then we aren't changing the clock. the clock just exists and change its value on its
own. But at some point, the clock will go from zero to one. And that'll have the effect of actually latching in the
value into the memory address register because the me the the line for the memory
address register in was one. But because we're adding it with the clock, the actual input that doesn't that goes to
the register doesn't actually hit that rising edge until the clock hits a rising edge, which is pretty neat.
And then when the clock is high, we can pretty much set these things back to zero.
And [snorts] then when the clock falls low again, we can kind of reset and prepare our next signals, which was RAM
out. equals 1 and a in equals 1. And then the next moment we have this rising clock
edge, it'll latch the value from RAM that's written onto the bus into the A register. And so then when that we're at
the rising clock signal and I guess for just indefinitely, we can reset all of our signals back to zero. So instruction
register out or sorry RAM out equals Z and A in equals Z. So this is cool because now what it kind of means
is our control here is just going to be listening to this clock signal and it's going to need to for every instruction
set various control lines right before or right after a rising or falling clock edge.
And this is convenient because it means the uh speed of the computer is now bound to
the clock. So you can kind of see for this load a instruction we needed two clock cycles. Clock cycle meaning we
needed to go from low to high back to low to high. So we hit this like low high cycle uh alternation two times and
that's enough to actually implement the entire instruction. So that's pretty cool.
Um before we go ahead and extend this to our second instruction, let's actually take this one step further. And uh one
thing I've left ambiguous until now is how we get this instruction into the instruction register.
And there's something interesting to be said about the fact that our instructions are
all eight bits because that goes handinhand with everything else. you know all of our operants the ALU are 8
bits the output register takes an 8bit value the bus itself is 8 bits and uh interestingly each value in RAM is 8
bits so what we actually do um is store the instruction itself in RAM
and then read that from RAM into the instruction register and basically the way our computer will execute is it'll
look when we power it on, it'll look at whatever is in address zero of RAM and treat that not as a number or a value,
but rather as an instruction and it'll load that value from RAM into this
instruction register and then the computer, the control branch that we still have left ambiguous
will go ahead and actually execute that con that instruction. And once that instruction is fully done,
we'll then look at the next index in RAM. So we start with the first item in RAM, treat that 8 bit value as an
instruction, execute it, and then look at the second item in RAM, treat that as an 8bit instruction, and execute it. So
in order to keep track of where we are in our execution process, we're going to have a new instruction, a new register
called the program counter. So I I've extended the bus a little bit and recall that our addresses to RAM are all four
bits. So uh that means we can only index using the least four significant bits here. And we're going to have a new
register called the program counter and that is a 4bit register.
And there's also going to be uh so I guess it can put its value onto the bus
and we will call that signal um PC out. So this program counter is going to
start off as zero and it's going to point us to the first item in RAM. And then once we execute
that instruction, we'd like to increment this program counter to become one. And once we execute, once we increment to to
be one, then uh we'll look up whatever is in that address at one in RAM, treat that as an instruction, get it into our
instruction register, and then execute it. And once we do that, we've executed the uh instructions at address zero and
at address one. So we'll increment this program counter again to two. And we'll do the same thing. And so basically,
this program counter um is going to be incrementing. it's when it should be counting up from zero.
And you might be wondering how we do that. You know, this is just a register on itself. But uh we actually talked
about this earlier when we constructed our ALOU. We showed how you could uh if you loaded some value into the left
operand and then a fixed value of one into the second operand and the output will always be the the operand plus one.
And so you can kind of feed that output and read that back into register A and it'll keep incrementing register A every
time you latch in a new value because we fix the value of register B to be one. The output of the ALU will always be A +
one. So that's one way you can uh implement this counter. It turns out in an actual computer it's not really uh
pragmatic. There's a faster way uh faster meaning fewer logic gates to uh build out a a counter. We're not going
to deal with that for the sake of this video because I don't think the complexity justifies the divergence
here. But let's just say you already know how to build this counter. So we're going to suppose that this counter
mechanism already exists. And I'm going to denote that uh with a better circle
and we're going to call this like a plus one. You can imagine that could be a whole alou that takes the whatever
whatever values in the program counter uh adds one to it as a fixed second operand and then feeds that back in. And
we're going to have this same um kind of program counter in signal that'll kind of read in. And we can also
go ahead and end it with the clock because we've done that for all of our other signals. So I'll say
so now we have two new signals. But now what happens is
we need to think about we know what happens uh for this load a instruction we've designed. We know what happens uh
in terms of what the control logic needs to do once that operation command is in the instruction register.
But now we need to take a few steps back and actually figure out how we get this instruction from RAM into the
instruction register. So we know that there's a value in the program counter at any given time. And
so what we'd like to do is look up treat that as an address in RAM. Look up that value from RAM and get it into the
instruction register. And since everything's hooked into the bus, you might be able to guess already what we
do here. The first thing we'd like to do is program counter out. So read that onto the bus. Push that onto the
[snorts] bus. And then also pulse uh or set memory address register in. And then at the exact moment we have a rising
clock edge because we've added the M in signal with the clock uh the value on the bus which will be the program
counter address will get locked into memory address register. So that's our first clock cycle. Then
our second clock cycle, we'd like to take the value that's in that address at RAM, which is here because RAM RAM is
always looking up the value that's in the memory address register, put that onto the bus. So we'd set RAM out, and
then to read it into the instruction register, we'd actually need a new line, uh, a new mechanism that can read from
the bus into this instruction register. So, we're going to hook in the instruction register to the bus
and we are going to uh kind of have that be denoted uh accordingly.
And that means we have a new line that we care about which is uh instruction register in. And we're obviously going
to end that with the clock because we've done that for all of our input signals so far.
Cool. So basically uh we just need to add two more clock cycles here. One is to get the value from the program
counter out and read it into the memory address register. That takes one clock cycle. And then the second clock cycle
is putting the value from RAM out onto the bus and reading it into the instruction register. So we can come
back to our original diagram here. And this we know is what happens once we have a value in the instruction
register. But we're interested in uh the full steps of equations of how we get the value into the instruction register.
So what we do is we set the program counter out and m in to both be one. So PC out equals 1. M in equals
1. Then we'll do that you know at some point here. Hold that value. Hold those two. And then on the rising clock edge,
uh, that'll have the effect of moving the value in the program counter into the memory address register. And so then
we're looking up the the RAM is always looking at the MAR. And so we have the value at that address ready. So we'll
put that value onto the bus with RAM out and read it in with this instruction register in.
And then as soon as there's that rising clock edge, we will then have the value uh latched in the instruction register.
And then we can actually start the execution of the load a instruction that we've designed here. So we can literally
just connect these two. And so this is the full timing sequence of how we implemented that instruction.
And so you can kind of see that this takes one, two, three, four, four clock cycles.
Four clock cycles to implement this instruction. And
it turns out um that is also something that we will design. It'll be a design choice. We are going to give all of our
instructions five clock cycles to execute. And that is because we will see that the longest of the instructions
that we design, the longest instruction takes five clock cycles and so we need to kid to that longest instruction.
There are some computers that could have variable length clock cycles per instruction. For us, it becomes easier
to understand and reason about if we just say every five clock cycles is synonymous with the computer having
executed one instruction. So for this uh command, we've used four clock cycles. This last clock cycle
would just be wasted. Like it'd be a no operation, no wasted. And that's fine. It's trivial. Um, but it also turns out,
we'll talk about this later, but more modern computers can actually execute uh entire instructions in a single clock
cycle. And it turns out uh that you also, you would think, hey, if we just uh turn up the clock frequency. So right
now we have this if we just made the clock go from high to low faster that we could uh actually you know we
could just crank that the width of those intervals and have them approach zero and that would let our computer execute
instructions faster and faster and you know that give the the illusion of it being just as fast as if every
instruction had used one clock cycle. But it turns out you can't actually push these to zero because
uh there's a delay here. there's a delay between everything you you the line you set and then it actually being received.
So every time I say like this instruction like program counter out uh there's actually a slight delay in the
logic gates like uh in the logic gates like physically connecting the program counter to the bus has a a delay it's
very small delay uh but it turns out you have to wait for the longest delay of all components before you can pulse that
clock again. So something like the ALU where if we put a value in register A and B, there's a lot of logic gates in
the ALU. You kind of have to wait for if each logic gate takes like uh a millisecond, we have 50 in the slowest
route, um then you have to wait 50 milliseconds for the output of the ALU to be considered valid, which means your
clock cycle, if you have something that depends on the result of the ALU output, has to be at least 50 milliseconds. So
we can crank these pretty far and it appears that the computer runs fast but there is a true limitation and it also
turns out modern computers have that clock running really really quickly. So we built the first uh implementation
of load a but we've actually forgot one subtle thing and that once this load a operation is done we will correctly have
the value uh denoted in the argument part of the instruction in register A. But our program counter
is still going to be pulsed at zero which means if we repeat the same process we're going to execute the same
instruction. we'd like to do is actually increment the program counter so that when we repeat these same set of
instructions to get the value from the program counter uh stored at RAM into the instruction register we have the
next instruction not the same one. So there's one subtle thing and that there's this program counter incin flag
uh that'll effectively increment it and once we use the uh we only use the program counter value
at this very first step. So actually at any other step along here between here to the next instruction, we can just
pulse that flag on any of the rising edges and it'll have the effect of incrementing the program counter so that
when we get to the next instruction, it's already ready. So I could just pick any uh any of these rising clock edges
to do this and say you know program counter inc uh equals 1. And uh I'll also call this I will enable
this program counter increment uh because it turns out we'll eventually want to be able to read actually a value
into the program counter directly and that'll be called program counter in. So I don't want to get this confused with
uh being able to increment the program counter. So what we're going to go ahead and do
now is actually define all of the remaining instructions from this computer, discuss them at a high level,
and then after we do that, we'll finish out by tying together what this actual control brains is. And once we have that
done, we've uh completed the computer, which is a crazy thing to say. So, we're not quite there yet. Let's keep going.
Um, so our second instruction is going to be called add. And, uh, the full name is also just kind
of addition. Uh, this is just for our our readability here. The op code will be two. the next number
and what add will do it's slightly different uh you know we've we've done addition in the ALOU and you kind of
know how that works and how you kind of punch a value onto the bus hook it into some registers and
uh kind of latch that value in. But what AD does is it says register A will equal whatever is in register A
plus RAM at the arg specified. So there is an argument to load a. So if
I uh call load a with uh address 3 as the argument, it'll take
it'll go to RAM. It'll say, "Hey, whatever is in address 3, look it up, put it in register B, then take the
output of that and feed it back into register A. And that will have the effect of adding whatever is in RAM at
the specified argument into uh back into register A. So it'll be A equals A plus uh whatever we put in B and what we put
in B is that that address and RAM. Okay. So that's how we do addition. Then you'd
imagine uh the logical equivalent to that is subtraction. So sub
and you could probably guess its op code is whatever comes next which is three. And you could also probably guess that
the effect is register A equals register A. So we take we're always operating on whatever is in register A and we'll
subtract whatever is in RAM at argu. So, it's the exact same thing we've done before, except uh when we actually get
the result of the LU, we'll punch this add subtract line. When we set it to a one, uh as we learned earlier in this
video, it'll have the effect of doing subtraction. Our next instruction
is called STA and that stands for store A.
And this has the op code of 0 1 0 0. That's four in in decimal. And what store a does is it lets us take the
value that uh is in a and write it into RAM. So we we talked a lot about reading from
RAM, but uh writing into RAM is also important. And the register A is kind of the design of our computer here. We
actually don't have any uh commands in the default instruction set that let you specifically do something with register
B. Everything that is done with register B is usually implicitly defined as part of one of these operations that works
from register A. That's just by design. We could totally have these equivalent arguments for we could have you know for
this AD we could have a add B which is the same same operation but for register B. So it' be like register B equals
register B plus some argument into RAM. We just don't have that. uh and you know we actually could easily build it and
that's something uh you could leave as an exercise to you afterwards but I digress. Store a uh it'll say RAM
at whatever argument you specify it will treat that argument as an address uh equals whatever is in register A. So
to implement this one, what would happen is once we get the instruction in the instruction register, we put the least
four significant bits on the bus that op uh that memory address and we could hook that into the memory
address register. And then what we need to do is write register A uh have a way for writing register A's value out onto
the bus and then we can read that value by pulsing uh RAM in. And since we've already loaded the address that we want
to write into into the memory address register, uh the moment we pulse that RAM in and have a rising clock edge,
it'll lock the value from register A in to the correct uh component on the bus. And so you might be noticing, hey, we
don't have a way for to actually write register A or B onto the bus. So, uh we'll go ahead and and do that. So, we
could do two. And so we can call that uh A out. And
then we can do the same thing if we needed to for B. And this is the the power, you know,
this is the power of the bus. we can just decide to connect things and it's just as easy as connecting them versus
for if we had to connect it to every other component, you know, we'd have to connect it to the the RAM in signal and
then or it with a bunch of other things that already connected to the RAM in signal. So the bus really makes things a
lot easier here. Okay, so that's story. The next is called LDI.
That stands for load immediate and its op code is you could probably guess 4 + 1 is five. And [snorts]
this one's a little bit different. So up until now, every argument here has every instruction has had an
argument which is interesting in itself. Uh we will see that that's not always the case.
But uh whenever there has been an argument, it's been an address into RAM. This time
the argument will actually be treated as a literal number. And what it'll do is it'll load whatever the argument is into
register A. So this contrasts well with our very first command, our very first
instruction load A. And you can see the difference here is load A will take the argument and interpret it as an address
in memory and go look up whatever is in that address of memory in RAM and feed that into register A versus load
immediate will just take whatever the argument is, interpret it as a number itself and load that into RAM. So if I
had load immediate with uh 011, this will load the immediate 011 into register A, which means register A will
have the value of 1 + 2 + 4, which is 7. And it does mean that load A the you can only put in a value that's representable
in four bits. So the the biggest value you could put in is 1 1 1, which would be 1 plus 2 + 4 + 8. 8 + 4 is 12 + 2 is
14 + 1's 15. So any number 0 through 15 you can load in immediately uh to register A. And so why would we
want to do this is because let's say I want to load the value five into register A. If we just had load A, we'd
have to waste two instructions. We have to waste two of our slots in RAM. We'd have to waste one of them to h to house
the load a instruction and then the argument to the load a instruction would point to a different spot in RAM which
would house a literal value five. So this uses two spots in RAM to load a value uh to load a value into register
A. But with a load immediate we could just do it with one. And RAM is a constrained resource here. So we only
have uh four addresses. So we have two to the four um like 16 different spots in RAM which is not a lot to work with.
So we want to use it as efficiently as possible. So that's why we have this. So that's a little bit different. And so
now we've seen uh four examples where the ARG is a data
address in RAM. Now we've seen one where arg is a literal.
There's a literal number and well let's see what comes next. The next instruction is called
JC. That means jump if carry.
And here's our op code for it. Oh, sorry. I jumped ahead of myself here.
Next instruction is called JMP for jump. It just stands for jump. And
this is an interesting one. What this says is all it'll do is set the program counter
equal to the argument. So this is super interesting and it might not seem that crazy at first, but recall
our program counter starts at zero and then goes to one and goes to two. And pretty much it tells us
uh what instruction what position in RAM we should look at to figure out what the next instruction our computer should
execute is. And this has the effect of just walking down RAM top to bottom. But what jump lets you do is it lets you
jump back to a a start. So let's say my RAM had some value. Uh let's say like my RAM was organized. So at address zero we
had a load immediate three and then we had a add um
add uh we could address six and then at address six and RAM we had the the literal value
like one. Then what would happen is our computer would uh load an immediate three into
register A and then add the value one to it so it have the value four. And if we wanted to keep doing this over and over,
we'd have to keep adding uh the same add command over and over in RAM. Uh, and what we'd
find is that we could only do it uh oh so many times before we run out of space and RAM. But what's kind of neat here
is if I have a jump command back to address one, then when when our RAM finishes this first ad instruction,
it'll then the program counter will increment from one to two. We'll get here. We'll see that the value in RAM
address 2, its interpretable value says jump back to address one. And so it'll reset the program counter back to one.
And so our PC will now point back to one. And then we execute the next instruction and we'll load the value
address one into the instruction register. And that value is the operation to add uh whatever's at
address six back to the register. And so this would just increment the A register by one again. And then we do that, come
back here, and we kind of keep bouncing in this loop. Um, and so our program would kind of never finish. And the
value in the A register would just keep increasing by one, and it it just be counting up, which is pretty
interesting. So jump lets you change the fundamental control execution scheme of your
computer. Okay, we can take jump a little further. Uh, I'm actually going to
I'm going to go ahead and erase this and I will clean this up here. And we have four more instructions left
to build out. The first we have two more jump related instructions.
JC, which stands for jump if carry and the op code is as you could guess 011.
And what this says is if the last result, if the last operation that happened in the ALU uh carried i.e
there's that overflow then we can jump to a specific address in memory otherwise you go on. So
if carry lag equals 1, PC equals arg
no and When we talked about our ALOU a long
time ago, we built this by chaining a bunch of uh full adders together and the carry out
of one adder became the carry in of another adder. But then recall for the leftmost adder, there was a carry out
flag and if that flag was a one it meant we overflowed, meaning we couldn't fit the
value into eight bits. And so we've kind of ignored that in our alou here. But what we can actually do
is just uh build a build a new register that uh looks at this carry flag. Um so it can take the uh it can be called the
flags register and it'll whenever we do an alou operation we'll look and say hey did this last carry did the last the
leftmost full adder carry out and if it did then we know that the last arithmetic operation that happened in
the alou overflowed assuming we're working with positive numbers we'll just keep it simple for now and if it did we
should latch that value into this flags register So,
what I'll do over here is I'll have a flags register and it's going to be two bits.
And what we'll need to do is also add a new control line for flags in. So this will
read uh it'll interpret whatever values the state of the alou and and and latch that value in. And of course
I need to end that with the clock. So why is this two bits if uh we encode if the carry out is one bit? Well, we'll
see when we come back to the next instruction. But uh the other thing that's interesting to know is if the uh
output of the ALU is zero. So we'll have one flag for measuring if the ALU carried uh if there was overflow carry
out I guess. and then one flag for measuring if the last ALU operation was a zero. And you
can detect if the last ALU operation was a zero by looking at all the output registers and uh saying that they are
all zero. So you would invert each one and then add them all together. So the each bit position like bit position one
not and bit position two not and bit position three not and so on and so on. So you'd assert that they're all zero.
Uh so you could invert all of those and add them together and that would pretty much be your your zero flag. I won't
show that logic here because I'm trying to avoid draw drawing individual logic gates. But let's see why this jump carry
command might be useful. So in our original example we have here um we started with the the number three.
I guess if we looked at the state of the aa register, it'd be three and then four and five and six so on. And then
eventually because our register is eight bits, we'd uh we'd get to the maximum value that we can store in 8 bits. So 2
to the 8, let me use my calculator right here. 2 to the 8 is 256 which means we can store values 0
through 255 assuming we're just looking at positive numbers only but let's say we are for
now. So uh your computer were to get to 250 and then 251 2523
254 255. And if we tried to add the number one to
it again, we have the binary. This is what the 255 looks like in binary. If we tried to add one to it,
what actually happens is pretty interesting where you can kind of see that it'll roll all the way back to zero
and then we'll have carried out. So, uh, if we let this keep running this program, what happens is we'd start at
three, count to 255, and then go back to zero and keep counting from zero, back up to 1 to 2 to 3 to 4, all the way back
up to 255. So, pretty much what happen is we just keep counting 255 and then resetting to zero. Maybe you once you
hit 255, you don't actually want to uh count any further. So, uh, we could restructure this program and say,
um, what I'll do here is we could say if once you every time you do an
addition, if you carried uh, then maybe you could jump to like address 10 or somewhere further away in memory. Um
otherwise you would jump back to address one and keep doing another arithmetic. Um and so that way you know every time
you do an addition you don't try to do another one right away by jumping back to that same ad operation. You check hey
did the last result overflow. If it did we can stop counting we're at 255 and end the program by jumping somewhere
else in memory. So that's just a little bit of motivation for that.
And I said we uh added two flags to our register because we wanted to know if you could carry out. We also wanted to
know if the last ALOU result was a zero. So we'll go ahead and build that out too. And that's going to be called JZ.
That means jump if zero. So, uh, that's going to have the op code of 1 0 0. And it pretty much says if the
zero flag equals one, then the program counter can equal the argument, i.e. we jump to a
new address. Otherwise, we don't do anything. And uh well, you kind of already know
how that one works. It's the same thing except uh you know we need to look at the value in the uh the flags
uh the flags register. So you know the first bit position is for carry and you could say the second bit position is for
zero. So we just need to make sure we're looking at the right one. Okay. Our next instruction
you might have noticed is we've now inter we have instructions to interface with every part of this computer except
for one and that's the display that we talked about just uh just fairly recently.
So let's have an instruction to actually write uh whatever value is in the A register since that's so critical to our
computer um onto the bus and then latch that into the output register. So, we're going to
call this command out. It's just be display. Uh, they're synonymous here.
It's op code. Uh, we'll just jump around here. We can give it whatever. Let's just, uh, throw it 11 1 arbitrarily. We
can kind of choose what our operation codes are. We've been sequential up to this point, but there's no harm in
using the address space however we'd like or sorry, using that four bit space however we'd like to define our
instructions. And so if we say formally what the uh out command does, it's going to set
our output register equal to register A, which will then have the effect of displaying
whatever is in register A onto this seven segment display because the way we designed that readonly memory was to
just look at whatever is in this uh display register. And so you can imagine to implement this command is pretty
straightforward. You just put the value of register A onto the bus with a out. And at the same time, you'd have display
in. And since display ins and with the clock, as is every in signal, once you have a rising clock edge, it'll hook the
value into that register. And then ROM is just a constant lookup table. So, it's always just going to be looking up
whatever is it's always going to be reacting to what's in the register. It'll look up. It'll say, "Hey, this
value is on the register. Like, let's just display it on the screen." And last but not least is called halt.
We'll give it op code 1111. And what halt will do is it'll physically stop the computer.
So if the computer sees a halt signal, if the if we uh have some address in our program counter, we look up whatever is
in that address in RAM, put that in the instruction register, and our control brain sees that it's a halt, it'll stop.
It literally will just break like it it the computer will not execute anything further.
Um, and that'll be the end of execution here. So these are all the instructions our toy computer has.
And what's uh a few things to of of note here. So we saw that for the first few
arguments we for the first few instructions we built out there were all arguments. The arguments were all data
addresses and RAM. Then we saw an instruction who had an argument but the argument was a literal number. And then
for the three jump instructions, the argument was an instruction address,
but the instruction address also lived in RAM. And so there's something interesting to
be said that RAM has both our data and our instructions. And the only like the computer doesn't
really care which is which. It's strictly just going to do what you tell it to do. So you could actually write uh
an instruction in one position. Uh let's say if I wrote an instruction uh at address 4 in RAM
and then if I called load a on address 4, then it would still work. It would interpret whatever the value of the
address is as a literal for the load A. But then when we're our program counter equals
that address, it treats this instruction. So it's pretty cool that uh it might be weird to wrap your head
around but conceptually values in RAM can be both literals data literals or instructions. It just
depends on the context of how it's how that value is being accessed. Whenever that value is being accessed
through the program counter or one of these yeah through program counter it's definitely going to be a
treat it as an instruction. But then in a lot of these other contexts, if it's like an argument to an instruction, it's
going to be treated as a a literal number, which is pretty cool. And then we saw down here that there is
no argument for our final two instructions. So those write four bits are actually kind of wasteless. You
could set them whatever you want. Another thing to note is we have four bits of our op code. So that should be
16 possible different instructions. But if I count out what I have here, I have 1 2 3 4 5 6 7 8 I have 10 different
instructions. So we are not using our instruction space fully. So we could define more instructions. It turns out
this set of instructions is nice because it lets us uh kind of define simple enough pro like complete enough programs
on its own. But you know we talked about very earlier like hey like our load a add and subtract all simply operate on
register A. Maybe I could use some of those extra op codes to build load B or add B or subtract B instructions.
Similarly, our out command will only put whatever is in register A onto the seven segment display. Maybe I want a way of
having uh an an out for B. Uh and you could imagine extending this computer in ways on your own. You could have a
second output display and that second output display could have its own command where you could write in the
value of register B to that uh display based on you know whatever the argument is. Uh so there
there's different ways you can extend this. So now we've defined our instructions.
Um we walk through load A in great detail and we'll do that for some of the others. But the last thing we need to do
here is tie it together with this control brains. And it turns out we know everything we
need to tie this computer together and explain how this control logic works. The very last thing I'll do that I will
spill out to make it a little bit easier. Well, obviously because I'm going to explain. But [clears throat] um
we discussed this instruction. The very last one we discussed was halt. And what halt should do is it should stop the
computer. And so you can pretty much see that when we designed our the only one we walked through in good detail was
this load a we looked at the timing diagram and the in the rising and falling edges of the clock. Um pretty
much you could see that we were making every decision based on and making every setup i.e. setting different lines you
know saying this component read its value in from the bus and this other while this other component is putting
its value onto the bus. All that was based around the clock. And so it turns out if a very simple way to actually
halt the computer and prevent it. Uh there's two things you could think of. Um my mind when I went to this was
actually just stopping any instruction from getting in the program counter. So if the program counter if the program
counter could never update, then no instruction, you know, could be executed. But then there's actually like
something incorrect about that and that there's still a value in the program counter. So that value cannot can be
interpreted as an instruction and it can't execute. And so that wouldn't work. Nor would blocking the program
counter from writing out onto the bus work because uh basically we we always say that if a component is reading in
from the bus, there has to be something on the bus. You get into a weird state if you have a component that tries to
read on the bus and there is physically nothing on it. I.e. every other component that could write onto it.
Namely, the program counter here is actually disconnected. So you might want to think let's stop the program counter
from being able to write its value onto the bus and therefore when we try you know looked up that address and ram and
treat as instruction it it won't work but the behavior is somewhat undefined here um what's easier it turns out and I
think the only solution for the sake of our simplistic computer is just blocking the clock but we discussed earlier that
the clock is always just going up and down like it's a physical circuit and that's not something we can necessarily
control. So the way we circumvent this is by actually all these components that depend on the clock, they don't quite
depend on just the clock. We'll actually and it with some signal. Um and basically whenever we set that signal,
it'll if it is set, it'll prevent the uh the clock from actually making any progress. So it'll force it to either
always stay low or always stay high. It doesn't really matter which we let it at. Um,
but what we can do here and how we can build that circuit is so our instruction is called halt. So I'll just call it
halt. But it'll just be quite literally the input to a logicate. We have this halt sign and this clock sign. And we
also probably want uh or let's and them together. Um, what I was going to say is we
probably want it such that if our halt line is zero, everything is allowed to run as normal. But then the when we set
our halt line to one is when we block the clock. So uh what I'll actually do is instead of hooking in the halt line
directly, I'll invert it and then feed it in here. And then this output is the clock that goes to the rest of the
computer. And so it turns out that this then is another circuit for us to use. And I'll kind of move this diagram up
here and we can shift it all down a little bit.
Cool. So what this has the effect of doing is what normally in normal execution this hot line is zero which
means we the inverted value is one and so the output of this and gate is just whatever the value of the clock is which
is this circuit that's always just pulsing between zero and one so it's frequently changing it's volatile. Now,
if we were to flip the halt sign to one, the inverse would be zero. And hopefully you know by now that anything ended with
zero is just going to give zero itself, which blocks the clock from making any progress. And so that when we actually
get to it and figure out how to implement the halt instruction, all that instruction has to do is flip this halt
line to true and it'll stop. It'll actually just break anything from making further progress. the computer gets into
a quite literally rec uh unreoverable state and nothing can make progress. So now we can tie this all together and
figure out how our control works. And I can guarantee that you could reason about this yourself
based on everything we've learned in the video so far. There is nothing I'm going to introduce here that isn't already
known to you and that's pretty powerful. Um, that being said, I remember when I was learning this, this was insanely
bizarre to wrap my head around and I had to have it spilled out to me. The entire time I was building the computer, I
could understand how each component works. And it was like, oh, that's how the ALOU works. Oh, that's how a
register works. That's how a counter works. That's how RAM work. All makes sense. And then when it gets to tying it
together, you're kind of like, okay, well, I understand how each of these components work individually, but I
understand how you can punch lines to kind of have them communicate with each other via this bus, but how can the
computer do it all itself? And, you know, how do we remove the human element of this from the loop, assuming that we
have some uh program specified in RAM? Um, so let's tie it together right now. And I'd say this is probably the crux of
the past eight hours of this video. Um, it's crazy for it to be the crux because we're not going to build anything you
already have not seen, but it is very powerful to see how something you have already seen can be reused again in a
totally different manner to bring so much more complexity to the system. So, let's think about so far we've been
serving as the control. Let's been thinking about um what we've been taking in as input to the control. So us input
um and basically there's a few things. So
number one is the op code from the instruction register. This you know this this line right here this arrow. Um
whatever the op code is in that instruction register we use that to denote what instruction we're executing
in. So our inputs are you know this op code and that's four bits.
And then we also said that we were designing this computer such that oops some weird coloring happened here. Um
such that each instruction that we defined in our table took five clock cycles to add. And when we walked
through the load a instruction, you know, we went through the little timing diagram and showed, hey, we'd set this
here and you know, clear it here and then set these next thing here and clear it here. Um and so we were internally
keeping track of our clock cycles. Um, turns out we're going to want to have a counter. And so we can we want to count,
you know, just from uh zero to to one um or sorry 0 through five, I guess, 0 through four here. So
what we do is um we have to represent five values. So we'll build a simple counter on the side
here. And the counter we build is going to be very similar to the program counter up here. Except the difference
is is it's always just going to be counting one to five. 0 to four to be correct here. It's going to count in
cycles of five and then reset on its own based on the clock at any given time. So instead of it needing to be explicitly
set by anything, we already know that each instruction is going to use five clock cycles. when we get to clock cycle
number five and do our work, we should reset to zero because now we're counting for the the start of the next
instruction. So, we kind of have this counter circuit um counter and it has this same plus one
except it's input signal is just always going to be the clock. So I'll highlight that too for sake of
consistency. So the output for our counter our counter is actually going to be three
bits. The reason is um we need to count each instruction takes five steps. We need to count 1 2 3 4 5. If we do that
in binary, you know, it starts with 0 0 0 1 0 0 1 0 0 1 1 and then 1 0 0. These
are the uh five different values we use to denote whatever step we're currently executing of the current instruction
we're on. So not different instructions. Instruction is an entire one of these commands right here. But to implement
each of these instructions, we use five steps. and five steps would be five clock cycles here because we're counting
up one every clock cycle. And so this is three bits. So, uh that was a little bit of a side tangent, but that is to say
that uh this counter goes into our um you know, it's part of our control brains. You know, we'll just call it
like step number represents the step number. What step are we on of the current instruction? So each time we're
setting lines, it matters how far along we are in our current instruction. So that's also something we've been
interpreting as an input to our control. And we haven't explicitly designed this as a counter. But you know, when we've
been doing it, we've been looking at the timing sequences, looking at, hey, the clock's low and it's about to go high or
vice versa and then setting control lines appropriately. as we shift the uh discretion away from us mainly punching
lines uh into the computer, the computer needs a way of knowing what step number is on. Is it on one two three four five?
And so the easiest way to do that is just to have a register that is a counter and then feed that as input to
our control. And our control then can say rather than us as a human on step five, we could just look at what the
output is on that step number line to figure out where we are in terms of execution of the current instruction. So
that's the second step or sorry second input step number and that's three bits
and then the last thing that might you might think that's all but recall the uh two jump the two conditional jump
statements um those perform a jump operation based on the last output of the alou
which is stored in this flags register. So that flags register we also need to know what's in it for us to make our
decisions. Um so that's also something we've been considering as input when we've been implementing the control
ourselves. And so you know we have the zero flag which is one bit and then the carry flag which is one bit.
And so these are all the inputs we've needed to then look at a timing diagram and figure out what inputs uh or what
control things need to be set at any given time. So what are the outputs now? So any step we've been looking at, hey,
this is the instruction in the instruction register. Here's what step number we're on. And for two of our
instructions, our conditional jump uh instructions, we're also looking at this flag register. we for the most part we
aren't really looking at it but we you know we are for two instructions. So it turns out for the bulk of them we ignore
them but you know we can't just turn things on and off for what our control looks at when it's the brains. Uh so
we'll have to just have the input be fixed all the time. You know you couldn't pick up a wire and throw it
away. That defeat the purpose because then you'd have a human be intervening. um the control itself will just know
like if you're doing a load a instruction or a subtract instruction or something of the sort and it doesn't
matter what's in those flag uh in that in that flag register it has no effect on the output of uh how you implement
that instruction. So then that's what we've been looking at. I'm calling that as our input to
make each of the decisions as to what outputs we set. And so outputs are pretty much uh if I've drawn this
correctly and completely should just be everything that's yellow here. So you know we've been serving as the human but
ideally the control would look at the state of the machine based on these inputs and then just know what outputs
to set. So let's actually go ahead and enumerate over all of our outputs. Um and then we can you know tie it together
in a more concrete place. So we just discussed the halt line that'll stop the clock from executing. So the moment the
computer puts that out, it's actually doomed. Uh the moment it puts that out and um means the clock cannot make any
further progress, which means the state of the machine just freezes pretty much. Um then uh looking below it, we have the
memory address register in I'll just call that, you know, mar in. I don't want to write the full
thing out. After that uh we had RAM in which reads the value uh on the bus
into the whatever address is stored in the memory address register. Then we have RAM out. And to recall what RAM out
does is whatever address is stored in the memory address register, it'll always be looked up in RAM and that
value will always be on this side of the line. When we toggle RAM out, it'll just let that value flow to the bus. So in
short, RAM outputs whatever value is in RAM at the address denoted by the memory address register onto the bus.
Then we have uh this instruction register instruction register out because sometimes we want to jump to a
specific address. So we need to get that argument of the address or anytime we just need to put the argument uh for an
instruction that has an argument onto the bus you know we write instruction register out
and then we also have instruction register in because we obviously need to get whatever instruction we're executing
from RAM into that register. So IR in and after that um we can jump to the top of
our computer. Uh for the ALOU we have register A in and then register A out. Register A in
will just latch whatever is on the bus into register A and A out will write whatever is on the in register A to the
bus. So we have A in and I'll denote that A out.
Then we have the uh alou out. Whatever the output is, whatever the current output of the alou
is, write that onto the bus. AL out. Then we have uh this add subtract line.
If we're doing it for any instruction that's not an add or a subtract, it doesn't matter what it is because we
don't care what's what the output of the ALU is. We're not going to interpret it. Um, for add and subtract is the only
time the value on this line matters. When we're adding, we just leave it as a zero. When we're subtracting, uh, is the
only time we'd ever need to set it to a one. So, we can just call that sub uh, sub
because it's always going to be zero. The only time we need to turn it on is for doing subtraction. So, let's just
call it sub subtraction. Um then we have uh B register in and B register out. It turns out for the instructions we've
defined here and you'll see this as we I'll go through and we'll implement each of these instructions
B does not ever actually need to write onto the bus. If we were to expand the computer then it would. So I'll actually
for the sake of keeping this diagram as minimal as possible erase that. But you can imagine we could add more
instructions here. We talked about this like not too long ago. You know the out instruction for example writes whatever
is in the register A to the seven segment display. We could have a similar command for outb just takes whatever's
in the register B and writes that to the seven segment display. So uh it's not like it's useless but for the
instruction set we've defined here it's actually not necessary. So I'll just keep it out for uh simplicity. But we do
have BN. I'll denote that be I and then we're just talking about the output register.
So we should uh talk about that. Uh the output register in it's called display in here. I'll just call it oi. Output
register in. Then um I I should have started up here. We have the program counter. So we we want program counter
in if we're going to read a value uh or increment the program counter. Sorry. and then uh program counter out
for writing that program counter uh value onto the bus. So we have counter
increment and then counter out. And as I'm writing this, I realize we
have a little mistake here where uh we know for our jump instructions. What the jump instruction does is it tells the
computer, hey, like if the program counter says the next instruction executes at address five,
ignore it and jump to actually this other address. So right now we actually can't support that because our PC inc
will actually only increment the program counter. But we also want um a way to read a value from the bus
into the program counter. So you can imagine we could just update our little circuit here because it's just a little
alou. um and one line is doing the increment. Uh another line could actually just read a value into the
underlying register that's the operand of the alou. And so we can call that um J. We'll call that jump because that's
the only time we ever need to to write a direct value into the program counter. So sorry for the confusion there. We
actually weren't fully complete. Jump. And then the very last output we might ever set uh is back here. Whenever we do
an add or subtract, if we do an alou operation, we need to update the flags register because if there's an add or
subtract and then there's either a jump carry or a jump zero afterwards, uh that means we would need to have known what
the output of the last ALOU operation was. And so basically whenever we do an ALOU operation we will terminate it or
just along the process look at the output and conditionally set these jump and carry flags which we talked about
how to build those sep those um circuits. They're relatively simple. And so um these are all of our inputs
and all of our outputs and I've kind of listed them out and we've talked about these uh so I'll stop explaining them
because I think most of these should be clear by now. But the uh the crux of all of this is I claimed uh several times
now that you already know how to build this control. And the reason is is because the input
i.e. the combination of whatever instruction you're executing, what step of that instruction you're on, and then
maybe the ALU flags will tell you exactly everything you need to know to determine
which output lines to set. So basically we know that output is clearly some function of the input
and we also know that each input combination has a well- definfined output combination because we've defined
every instruction and we know that the program counter uh we also know that each instruction our
counter over here is only going to go up to five up to one 0 0 uh binary Okay. And then we know that the zero flag and
carry flag can either be zeros or ones. And so basically every time there's an input, the output will be the same. So
output is always the same for the same input. So f of input equals output will always be true. Which means we basically
have a deterministic input output sequencing here. The output can entirely be computed strictly looking at these
input flags. And for a given combination of input flags, the output will never change. It'll always be the same. And so
how we can encode this is the same way we built out the seven segment display. The readon memory that took in that we
built earlier took in a value on a register denoted by eight bits and figured out which of the output lines
for these four dis for I mean we built a one segment display or one unit of a seven segment display figured out which
of the seven lines need to be set and so we had like a eight input um followed by I think that's four five six seven
dimension output um and we said hey you So depending on what the input is for each of the input values we know oh
sorry this also not uh this was whatever we could fit in in two digits I think it was four bits
sorry but points taken here that um I'll say four or eight I don't remember off the top of my head but the point is
taken that for this readonly memory it's core service was if you have a effectively would like to build a lookup
table so for any for every combination of inputs has a clear output and then for any input combination the output
should always be the same. Those two properties are what we discussed that made a readonly memory appropriate here.
And so it turns out our control is none other than
a read only memory. It is a ROM. and it takes in 4 + 3 + 2, that's nine inputs.
And then it has however many outputs we have right here. And this is pretty powerful because it means
if we set up this control ROM correctly, it means that the computer can be fully autonomous. And for every instruction,
it'll start, it'll take the value in the program counter, look it up in RAM, get it in the
instruction register itself, and then the control knows what values in the instru what instruction we're executing
on. Uh, it also has this counter that's telling it what step of the instruction we're on. And then it can keep setting
the correct outputs to execute the current command. And then since we have a line that increments the program
counter when the current instruction is finally done, we can just repeat the same process because now our program
counter points to the next address in memory. And this is the crux of of tying the computer together. You have now gone
from individual components to actually a full computer that's uh fully independent and can execute instructions
in comma uh sequentially and has the ability to do complex logic where it's not just instru uh not just executing
instructions top down but it has the ability to jump to other addresses in memory i.e interrupt its own control
flow. And not only can it jump anywhere in memory, it can also do conditional jumping, which means your programs that
you can publish into this computer can have some some amount of logic that requires uh a forking control. So if do
some operation, if the output of that operation was this, do something else or continue on the current path. Um, so
this is pretty cool, but you're probably scratching your head a little bit where uh we walk through the load a command.
And so we we looked at a timing diagram, but we we didn't actually see what the inputs and outputs would look like for
this control ROM. And we also have a whole bunch of other instructions we haven't talked at at all. So to tie this
together, what I'm actually going to do is we're going to go build out this ROM um and look at what the output should be
for every possible input combination. And you've probably seen enough of me drawing to know that my handwriting is
bad. So I'm going to switch to my computer and we're just going to do it on a Google Sheets and keep it simple.
um in neat there. But um once we do that, we'll jump back to this one more time and we can reiterate how this all
sums together. But this is pretty powerful. Um we are now like I think just like shy north of eight hours of
this video and if you did some handwaving or if you somehow understand how you could set up the control
entirely by yourself, you know how a computer works. Now this is actually it. This is the true fundamentals.
Obviously, this is a toy computer, but this is a fully functioning independent computer. Uh, and that's pretty
powerful, but we're not quite done yet because I haven't explained how this control ROM works. So, what we're going
to do next, uh, I said we'll swap to my computer. We'll build out what the inputs and outputs for this ROM should
look like, and then, um, we'll come back here, tie it all together, and then we'll talk about how we go from like
this to an actual more modern computer. Okay. So what I've gone ahead and done is constructed a table that shows what
our hypothetical RAM would look like. And so let me walk through the structure here. So first um our inputs are going
to be uh this column through this column and then our outputs are going to be this column these columns. So remember
our input we had the first op code which was the leftmost four bits of the um instruction register that tells us which
argument we're currently executing or should be executing. Then we have the step number. We had that special counter
that was uh specifically for the control that we just added in last minute that counts zero and then up to uh it counts
up to four in binary or realistically five um sorry yeah five different operations
zero through four and then that's here. Then we also had uh the zero flag and the carry flag for our two conditional
jump instructions where we need to see that. So these are our inputs. Recall this is still binary. It's a ROM. So all
of our inputs are going to be zeros and ones as will all of our outputs. Then our outputs were all the uh control
lines that we had highlighted yellow in our original diagram. And uh we'll walk through this again real quick. So I can
make this a little bigger for now. Um, I've uh added like acronyms here just because it'll be easier to say rather
than having to specify, you know, memory address register in, you know, MI is probably easier to say once we once we
work through these because these will get a bit repetitive. But HLT is halt. Remember, this stops the clock. MI
stands for latch a value from the bus into the memory address register. That's a four-bit register. recall cuz all of
our instructions and arguments are or so all of our RAM addresses and arguments all of our addresses are just four bits.
RA stands for RAM in reads whatever value is on the bus into RAM at whatever value address is denoted in the memory
address register. RAM out, it'll say whatever the uh memory address register is, that's an address. Look up that
value in RAM and write it out onto the bus. Instruction register out IO. Take the value that is in the instruction
register and put it onto the bus. I I is instruction register in. So take whatever value is on the bus and read it
into the instruction register. 8 bit register. By the way, our arguments, our op codes are four bits and our arguments
are four bits. But the register itself is eight bits because an instruction has an op code and may be an argument. So 4
plus 4 means we need at least eight bits to represent a maximally utilized instruction.
AI stands for read a 8 bit value from the bus into the A register. AO says A out. It say put whatever 8 bit value is
in the A register out onto the bus. EO um stands for ALU out. I actually am not sure why it is EO. Um, I think, you
know, we already have AO used, so uh I think they they could have found a better acronym. This was the way I was
taught, so I'll stick with it just because my mind is so wired to say EO that if I tried to change that letter,
I'd probably accidentally still say EO, and that would confuse you. Um, SU is subtract. This is the line on the ALU
that dictates if the output is either going to be register A plus register B or if it's register A reg minus register
B. Um BI stands for B in read whatever 8 bit value is onto the B on the bus into the B register.
Oi stands for output register in. So whatever value is in the output register or sorry whatever value is on the bus
read it into the output register. Then CE this stands for counter enable which is synonymous to incrementing the
program counter. So it'll add one to whatever values in the program counter and because of overflow we talked about
this earlier the program counter is a 4-bit register. So the highest value we get to is 1111. And because of what we
discussed earlier, when you call program counter enable at that maximal value, it doesn't crash the computer or anything
because of overflow, it'll actually reset it to 000000. Which means if somehow you have a a program that spans
your entire memory, when you get to the end of it, then it'll actually effectively restart it by going back to
address zero and starting again from the top. Um, which you know, if you don't have a halt instruction, that is what
will happen. You should probably end for correctness your programs with a halt instruction. But, you know, I'm not here
to tell you what to do and we haven't made any programs yet. We'll we'll talk about that a little bit too. CO stands
for program counter out. So, this takes whatever uh four bit value in the program counter and uh writes it onto
the bus. J stands for jump. that takes uh whatever uh four-bit value, the
rightmost four-bit value uh four-bit values plural, sorry, uh reads those in and that's useful for jump instructions.
As I'm actually looking at the diagram I drew, I realized my diagram makes it appear as though it's reading eight bits
from the bus. That's a uh incorrect statement on my part. I apologize. That should be four bits. And last but not
least is FI which is the flags register that we discussed that looks at continually [snorts]
uh looks at the value of the output of the ALOU and and determines if the output is either carried overflowed or
if that value is equal to zero and then whenever we do an ALU operation we need to pulse that to latch the value in.
That was a mouthful and that's very similar to what we discussed. Um, and so basically now what we're going to do is,
uh, implement all of our instructions and we're going to start with load A. So from the table we originally had, we
said load A's op code was 00001. And so if I look at the inputs, 001
um, and you might be wondering, well, hey, hey, what's up with, you know, all the outputs up until there? So basically
what's interesting here is we just know that those are never going to be hit because assuming the user puts in a
correct program there's no instruction we define that has the op code 000000. So conceptually that should never end up
in the instruction register itself. But uh we discussed a property of the ROM and that it's for every input
combination there must be a deterministic output. Uh and it turns out we can still guarantee that. It just
doesn't matter what it is because we know that like the input will never be this because this is a malformed op
code. Uh the output never really matters because it it conceptually should not be reachable. You know, it'd be user error
if the user put in an instruction into RAM uh and had the op code 000000. That'd be a bad instruction. But if that
happens, it doesn't really matter what happens because we don't support that use case. So pretty much the output for
all of these lines does not matter. Um, and what we'll default to is that if a line is not set in this diagram, uh,
there's going to be a lot more zeros than ones at times, most things will not be set. So, I'm just going to say if I
leave something blank, it means zero for these first uh, entries. Actually, you could actually totally get by
and uh, press in uh, ones if you wanted to as well. The only thing you just want to be a little careful of is if you you
don't want to trigger this halt line because we saw earlier that if you did pulse that then the program would stop.
Um it wouldn't matter cuz again we don't support the user putting in this op code but you know if we wanted to be a little
bit defensive. This is not the uh the worst thing to do here. So um I'm not going to like read this out. Uh we just
kind of know that like hey these like these addresses like this is not reachable. Um so I you know I could
denote that here but most a lot of things you'll see are unreachable. Um are you know there's we don't define all
of our op codes as we discussed earlier. You know we have two to the four I think that's 16 possible op codes represented
in four bits and we just do not have 16 instructions and that's totally fine. So a good amount of our space here is
actually going to be unreachable um with a technical asterisk that you could reach it if you just put in a incorrect
program or incorrect instruction into RAM. But um we start with uh this part is reachable. Sorry, that's
not true. We start with uh op code 00001 which was this load a. And uh we'll also assume that our our flags uh we're just
going to ignore those for now because we know it doesn't matter for this instruction. So it turns out as we
discussed uh the very first thing that happened for load A is we actually don't even know we're executing the load A
yet. So, um, what we actually need to do is get, uh, because it's the first step of the
instruction, that's how we start, our step counter starts at zero, and that's how we know we're executing a new
instruction. And so, we don't even know what we're executing yet. So, basically, we need to take the instruction
um, we need to figure out what the instruction is and get it into the instruction register. And the way we do
that is we start with the program counter because the program counter has the address of the instruction that
we're about to execute. So what we actually do to start our instruction is put the program counter value onto the
bus and then at the same time read that into the memory address register. And so then this is what happens on our
first clock cycle. The clock will go from low to high. Uh we set these values um you know this is our output. So our
steps our step counter our little incrementer circuit that connects just to the control will start at zero. And
so we know that's the start of an instruction. So our a our uh control should just output these two lines
because this is what we need to do to get the value from the program counter into the memory address register so we
can look it up in RAM. And then the clock is low this entire time. The clock will then go from 0 to one which will
latch in the value to the memory address register. And then because our our little incrementer is connected to the
clock, it'll have the side effect of automat automatically incrementing it, which means we're we end up at the row
below here because our op code's not changing and we're ignoring these flags for now. They're not changing. Um, and
so that's pretty neat. And so then we're looking, okay, when this we know the first step was getting the address from
the program counter into their memory address register. Now that we've done that, uh, we want to take the output
from RAM because that's the actual instruction we want to execute and then put it into the instruction register. So
what I do is RAM out and instruction register in. And um, we're going to do one other
thing here. We're just going to choose. We talked about this earlier. At some step in each instruction, we need to
increment our program counter so that we get to the next instruction. We're going to do that right here.
Um, we're going to choose the second step. It could have happened at any of the next steps after this before the
counter over here resets to zero. I'd rather just get it done early so we don't forget about it. Um, and then so
what what effectively happens is the clock will pulse. it'll go from low to high and that'll take the value that's
on on the bus which is the instruction we actually want to execute and it's going to latch it into the instruction
register and then also as a byproduct of what we defined uh we've incremented the program counter but uh that that's a
different thing than this this is this is a step counter used to used as a counter just for control the program
counter is actually keeping track of what instruction the computer should execute next um but that has no side
effect because we actually don't look at the program counter until we get to the next instruction. It's useless for us
for now, for this instruction too specifically. That's not true for some of the jump instructions uh that
actually need to modify the program counter, but we will get there. So now that we've loaded the value into load A,
we've loaded load A into the instruction register. Sorry. Now, uh the control unit would get this as its input. Um
this would be the op code. And so it it finally know after these first two clock cycles, hey, this is the the op code
line is actually saying um what instruction we're going to be executing. And that's pretty cool. Um it
turns out as we did kind of hinted at, it's pretty explicit now though that for the first two steps of every
instruction, the op code is actually not going to be matter. It's not going to matter. We know that the op code is
because it's looking at the instruction register. we haven't put the right instruction into the instruction
register. So the op code is nonsense. So it actually it can ignore it and it will for the first uh every time you know our
step number is 0000 or 001. We know that for every instruction we need to do the lookup of the program counter value in
RAM and then get that into the instruction register. But once that happens, we get to step 010. That's
binary 2. Um and now we have the op code here. And so now the control knows we're executing a load A. And so what did load
A need to do? It needed to take the argument of the instruction um and look that up in RAM and then
write that into register A. So we discussed this earlier. So I'm going to go you know a little quickly here. But
you know we need to take our our instruction uh is eight bits. The left four bits are this 00001 that denote the
load A. The right four bits are actually the address that we of RAM that we want to lo whose value we want to load into
register A. So what we do is we want to put the instruction register out onto the bus.
And then we also want to read that value into the memory address register.
And so then we the the clock will pulse from low to high. The clock's low here goes to high. Um and it'll latch that
value from the a uh from the the rightmost 4 bit to the instruction register into the memory address
register which is great. Um because uh we've you know kind of looked up that value now. Um oops sorry I have
a typo here. This should be instruction register out. We take whatever values in the instruction register, the right four
bits, put them out and read that into the memory address register. And then the very last thing we need to do is
take whatever value we get from RAM. So put RAM's value back onto the bus and then read it into the A register. Um,
and once we've done this, the clock pulses, we then looked at that value from RAM and stored it in the A
register. And we've actually completed the load A. So this is the original timing diagram. we drew by hand and we
were originally just punching those values by hand saying hey you know you need to put like a in to high at the
same time you put RAM out to high and then wait for a rising clock edge so the value gets latched in because of the way
we've defined this in a in the uh ROM readon memory we don't have to do that anymore the computer will do it for us
because we have now programmed you know hey when you know you're executing a load a and you're on the third step then
this is what you need to do and then when you're on the fourth step this is what you need to do. Um, and it turns
out for load A, we got it done in four steps and we said all of our instructions take five steps. So for the
very last step, uh, one Z on the program counter, um, on the step counter, uh, we don't do anything. We actually just zero
zero it out. Um, and then we also know uh that so that concludes our load a we've executed our
first instruction here and we we specified the microode. You can see two things I want to talk about here. Um the
first is we say first of four copies. You might be wondering well why is that true? you know we we discussed uh you
know for this step for these two it doesn't matter for these these first instructions the opaque doesn't matter
but we know hey for the first two steps the instructions will always be the same and then afterwards we actually resolve
the instruction which is a load a and so we've done the the two the two clock cycles worth of work to go look up some
value in RAM and get that into the A register uh so why do we need to do this four times and it turns out uh I told
you to ignore the zero and carry flags uh and I part of me wanted to ignore them for the entire video, but I didn't
think it'd be fair because it wouldn't explain how conditional logic worked. But
um this logic, this load a instruction should be the same. We would want to
take these same steps no matter what the value is in our flags register. But we actually don't know what the value in
the flags register is. Maybe some operation happened uh some add or subtract happened before this load a and
the carry flag is zero and the load flag is one. Um well those technically are input to the control logic. And so that
actually means when when the let's say the zero flag is set to one, we aren't over here and we aren't here in our
lookup address. We're somewhere way further down. um but it still would resolve to a load a um and similarly if
if the carry flag was one you know it wouldn't change what we do here cuz we don't care about it but because ROM
doesn't really know that ROM just like a naive lookup table it doesn't we have to program that four times effectively
because hey there's two there's two one bit flags so your combinations of of of the two uh zero flag and carry flag is
either 001 one 1 zero or 1 one. So for each of these four combinations
of the flags inputs to the ROM, we'd want the same out output because the flags just don't matter for load A. So
it turns out we have four four copies of load A. Um and so if I if I jumped to let me first off replace these back with
zero. If uh and if anyone ever watches this, I'm happy to
uh include this sheet. I'm fairly confident no one will ever watch this, but especially not this far in, but who
knows? Hopefully, I'll be wrong. Um if I actually go where were to jump uh to the row in this table, uh it it
you can see that it exists. way further down here would be if the zero flag is zero and the carry flag is one then this
would be our same instruction obviously the op code doesn't matter for the first two clock cycles but then these three
would represent the execution of load a uh if that if that flag was set so it turns out we actually need to specify
that this microode four times here um so you know what I've defined here will need to get defined here. And you
can literally just paste it. And then here's the the third time where zero flag is one and carry flag is zero.
And then here's the very last one where both the zero flag and the carry flag are one. So
that's why uh yeah, that's where this this first of four copies come from. And then over here we'll just take some
notes and then we'll recap we'll recap what we did here. So the very first thing we did is we said the program
counter has the instru the address of the instruction we're trying to execute. So we need to go look up that value in
RAM. So the value in the program counter is not the instruction itself. It's the address of the instruction. So what we
want to do is we want to get the program counter to the memory address register in the first step. Then what we do is we
have that value. It's looked up in RAM. we take the um the RAM and put it into the instruction register. Then we also
incremented the program counter. So program counter equals program counter + one. Then the third thing we did was now
that we know that we're executing a load A, we put the uh address component of the um of the instruction onto RAM
onto uh onto the bus and read it into the memory address register and then that looks up the actual uh argument in
memory. And so the last thing we do is we say RAM the output of RAM goes into uh register A and then we said we only
so that after that fourth clock cycle we execute our instruction. Uh so we do nothing um no op it's a wasted cycle for
the fifth one. Uh we just say you know every clock cycle takes five instructions. So that's the first thing
of discourse I want to talk about. The second thing is our next op code is 00 1 0
but there's actually because our input uh just counts sequentially. This is just counting as if it as though it was
binary. Um 0 0 1 0 doesn't start to here. So we have three rows that uh are in between this
the load a instruction and then our add instruction which comes next. Um, and it turns out those are also just
unreachable. And here's why. We discussed our step counter that counted from zero up until four and then reset.
So, we know that based on our construction of the step counter, when it goes from 1 0 0, which is binary 4,
and we increment it, uh, it'll it'll carry back over to zero, it'll reset to 0 0. If you were counting in regular
binary, what you get next is not zero. you'd get 101 which is five because that's what comes after four. But we
know that because of the way the counter is constructed uh that will never be possible. So these three addresses right
here are not reachable. And so the same way we said that these over here are not reachable. So it doesn't matter what the
output is of the ROM. The same goes for these addresses right here. And we can actually take uh this a little further
and say that these are definitively not reachable because we know that um it's not possible for our step
counter to reach five. As soon as it reaches four and it tries to increment, it'll roll back to zero based on our
construction the circuit. And that's different than what we had up here. This is technically reachable if uh someone
tries to interpret someone tries to execute an instruction with op code 000000 because we can't stop what they
put in the RAM. It just wouldn't do anything. So that's the load a instruction. So the next instruction is
add. Recall that add um takes has an argument. The argument is an address in memory. And what it does is it'll say
register A equals register A plus whatever is in RAM at the uh address specified by the argument. So um the
first thing we need to do though we don't know it's our first clock cycle. We don't know that we're executing an ad
yet or the computer doesn't know that. So it actually needs to take whatever's in the program counter look that up as
the instruction and get that into the instruction register and then the input to the uh the readonly memory would
actually you know or the readon memory that is control would be able to see the op code over here and derive semantic
interpretation of the instruction that the user i.e. uh I guess the machine uh wants to execute. So the the first two
steps are actually identical uh between what we did in load A and what we're going to do in add. So the first thing
we need to do is get the address that is stored in the program counter out onto the bus and read that into the memory
address register. So we'll read into the memory address register and then we will also put the uh program counter out.
[snorts] And then now that we have the uh address of the instruction we're trying to
execute in the memory address register, that value is looked up in RAM. Um because that's RAM's always just
observing what's in that register and putting it out. So we actually want to put that value onto the bus RAM out and
then get it into the instruction register. Instruction register in. And now uh what we've done is we've
effectively taken the uh we've effectively taken the found instruction we're trying to execute and gotten it
into the instruction register. So now the actual op code that our um control sees is 00 1 0 because that is the
operation code. Uh that's how we denoted add in the table we constructed earlier. Again that was our decision here. Um and
so now that we've fetched the instruction, we actually need to execute it. So recall that add um I can add this
over here. You know, add is saying register a equals register a plus whatever is in
RAM at uh the argument. So what we need to do is take the argument that's in the instruction
register uh and look it up in RAM and then we can put that into the ALOU. So what we need to do is write the
instruction register out onto the bus and feed that back into the memory address register. Uh recall that when we
put the instruction register out onto the bus, it only puts out the rightmost four bits, the argument. Uh and then
RAM, the memory address register is four bits. So there's a perfect match there. Now we've have the uh RAM that's looked
up the value. So let's put that out onto the bus for our next in our next clock cycle. And uh because our operation says
register A equals register A plus RAM arg uh our two operants of the ALU are register A and register B. So we kind of
just want to put the value in register B because we're adding something to register A. So why would we change
what's in register A? um you know that's our first operand is already correctly wired in to the ALOU. So uh we will
write that value from that we're putting out from RAM uh into the B register and then uh now that we've actually
moved the argument into the B register we have our correct arguments to the ALU in register A and B. So the output is of
the alou after through this clock cycle is equal to this uh register a plus ram of arg this right hand side of this
expression. So what we need to do is take the output of the alou and write it back into register a and so we can do
that with um alou out this eo signal. Uh take the output of the alou um and then we want to read it into the a register.
So it will read a in. And there's something else that we uh need to do here. Um not to execute this
instruction. This technically would correctly execute this instruction, but um we also need to update our flag
register because we need to know our our guarantee for our jump zero and jump carry instructions is it's looking uh
it's making a decision based on the last ALU operation we performed. So now we're performing an operation. We don't know
when a jump a conditional jump expression if it ever exists will happen next in the instruction. We don't know
you know which of the following instructions will actually look at this flag but we play it safe just by always
updating the flags. And we discussed the circuits for checking those flags. And so those are always looking at the
output of the ALOU and always feeding in as the input to this flags register. So all we actually need to do is pulse
flags in um to get that there. So uh that implements our add instruction. But we've actually forgotten one thing and
that after we execute this instruction uh the way it's it's executed right here the control logic uh the program sorry
the computer itself would actually execute the same instruction again afterwards and it keep getting stuck on
this instruction because we have not incremented our program counter. And so we could do that at any of these clock
cycles. Uh any of them work. uh we always are just going to do it early so we don't forget like I I just did right
here. Um so we can walk through and type out what we did. So the very first thing we did was we took the value in the
program counter and got it the memory address register. Then we took whatever was in RAM at that address and put it in
the instruction register and then we also incremented the program counter. Afterwards we took the uh address of the
instruction register to the memory address um register. So we wanted to look up the argument in RAM
and then we put RAM to register B and then we actually performed um you know ALU to register A
and then also update flags. And so that uh completes our our ad instruction. There's a few things I want
to talk about before we move on. So first is this ad instruction actually used all five clock cycles meaningly.
Our load a recall the last one we were able to do in four clock cycles. So that fifth clock cycle went wasted. Here we
were actually able to get it done in five clock cycles. Um and it turns out every instruction we created in our uh
instruction set earlier um can be executed in five or fewer
instructions. This is the lengthiest of them. So we just forced like hey because you know all these instructions are the
longest instruction uses five clock cycles. We just designed this such that uh every instruction will take five
clock cycles. And you could imagine that's not super efficient. We don't want to waste clock cycles uh especially
as we're trying to speed up our computer. So we talked about making the computer run faster by making our clock
just move faster. But then there is some limitation there because we have to you know wait for logic gates to settle
whenever we change inputs to them. Um so the other natural thing to do is okay maybe like make instructions use fewer
clock cycles. There's there's two things to make the computer faster. You make the clock run faster or you make the
instructions more efficient in themselves. Um so we could have designed this computer uh to you know as soon as
that load a instruction was done to move on to the next instruction. We just for sake of simplicity uh aren't
going to do that. Um, it's actually not that trivial. All you need to have the instruction do is just move on to the
next instruction address, like reset that uh the the controls uh internal step counter, and you know, that kind of
have the effect. But yeah, we're not going to worry about that for now. Um, the second thing I want to talk about
before we move on to the next instruction is the fact that these two instructions uh that we've done, load A
and AD, look pretty similar. In particular, uh we can actually see that the first two steps are identical. Um
and that's because we have to figure out what instruction we're executing before we can actually make the you know
personalized or conditional logic of executing that instruction. Um it turns out this will be true for every
instruction. the first two clock cycles will always be the same um to figure out what the instruction
actually is before we can go about executing it. And this is called a fetch decode execute cycle. So we kind of see
that like the first two clock cycles are used to fetch the instruction and then the decoding would be actually deriving
the uh like you know we have the instruction what is it actually trying to do and then executing it would
actually just be executing it. We don't really have a decode process here because the way we've set up our control
logic it's it's just kind of deterministically responding to the input. So decoding it is effectively
just looking it up in the ROM, but you don't really see that on the timing sequence because it happens so quickly.
Um, so you kind of see like the fetching is clear and then the executing is clear and then the decoding is kind of
inherently implicit given to us for free because we're using a readonly memory. The decoding is actually taking whatever
uh instruction or inputs we have to the control logic and looking at the corresponding outputs which is just a uh
readon memory read operation. Yeah. So our decoding our fetching involves actually moving data between registers.
Our decoding is just a lookup in ROM and then our execution is actually the interesting part where we perform the
operations. Um and the third interesting thing uh before we move on to the next instruction is um I've also said for our
ad instruction this is the first of four copies and that is because uh our zero and carry flags
don't change what we do for our ad operation but we do need a handle that those values might be different. So
especially after this uh like let's suppose we do this add operation um and then there's a load a afterwards
because we set the flags register in this ad operation uh it doesn't affect a load a operation the load a operation
doesn't care what's in those flags but it does mean that the input to the ROM can change uh and so that's why we had
the we talked about copying this load a four times uh we actually have to do the same thing for add um and we'll see that
that also is true for most other instructions. So yeah, that's uh that's our add instruction. Let's move on to
the subtract instruction which is very similar to the add instruction with the difference being that you know instead
of register A equals instead of register A equaling register A
plus RAM of arg ram at the address specified by the argument. Uh it's the exact opposite. We just subtract. And so
you can kind of imagine before we even spill this out that the uh the instructions, the microode that we're
putting out for of the ROM are going to be very similar to this ad with the only difference being that we're going to
have to just uh set the ad subtract line in our ALOU. So I'm actually just going to copy over everything we've done.
And all we have to do is make one very trivial choice change, sorry. Um, which is we want to set the subtract
line right here. So basically everything stays the same
and we are uh just telling the ALU because the ALU is always looking at the it's
always just taking whatever the inputs are which are these values in the register and it's always just doing an
add or subtract on them and that's always available on the output line. So when we put the output onto the bus and
necessarily read it back into register A, uh, we can pretty much just get the output we want by flipping the add
subtract line before we reinterpret the value of the ALOU. So because the ALOU is just always responding to the add or
subtract line, we can just flip it. And there's no issue with timing here that the line is not really a register. So,
it's safe for us to just uh flip the line and then read that value pretty quickly afterwards because it's always
going to be, you know, it's not dependent on a clock cycle to like latch in that line. Like, we don't need to
tell the ALU before we do an add or subtract, we're going to do an add or subtract. We can just tell it as it uh
does the computation to consider whatever is on that line. Um, and yeah, everything else is exactly the
same. Uh and then similar to what we described before, this is replicated four times in memory or sorry four times
in this readon memory. Okay, let's hop on to the next instruction
store a. So um this will take an argument and it'll basically say RAM at the argument should
equal whatever is in register A. So basically we need to take whatever values in register A, get it on the bus
and throw it into RAM. Um it lets us move data. We've talked about moving data from RAM into the ALOU. We are now
going to talk about moving data from the ALOU into RAM. And because everything is connected to this this central bus unit,
uh we have all the control signals uh available to implement that uh instruction with what we have defined
here. So uh let's start. First, as usual, we don't know what we're executing. So, we need to look up the
instruction uh based on the value that's in the program counter. So, we put the uh the address, we put the value from
the program counter out onto the bus and read it into the memory address register.
And then we take the uh output of RAM, read that into the instruction register. And then we also increment our program
counter so that uh we will move on to the next instruction uh by the time that the our internal control uh step counter
goes from four to zero or from five to back to zero. And now that we've uh that's our fetch
process again that's the same for all three instructions we've implemented so far. Uh what we now need to do is take
the argument of our instruction that's telling us what address we'd like to write the value into and get that into
the memory address register. So I'll I'll read into the memory address register and what I'm reading is I'm
putting the value uh of the instruction registers argument out onto the bus the right the right uh rightmost four bits
sorry. And then this one's pretty simple because now that we've loaded the uh address into the memory address
register, what we'll do is we put the a register a value onto the bus, read it into RAM,
and that's quite literally all we have to do. And then we have a wasted clock cycle afterwards because we've we've uh
we've already finished our instruction. So I'll copy over uh our explanations here. And then what we did here was
address of IR to the memory instruction register. And then all we do here is uh register A's
value reg a into RAM. And after that uh we finished our we finished implementing
what we said the store a instruction would do. That's pretty straightforward. Um let's move on to our next instruction
which is this load immediate. So recall uh that load immediate was the first instruction we saw where the argument
was not an address in RAM an address in memory sorry uh so not a miss but instead the argument was an intended to
be interpreted as an immediate binary value. So uh what the argument what the instruction does is it takes register A
and it writes uh into register A whatever the argument to the instruction is. So this one's actually easier for us
to implement internally because we get to skip having to go through RAM. Uh we get to skip that indirection and instead
just take whatever value the argument is, treat it as a binary number, not as an address, and uh throw that into the A
register. So let me go ahead and copy. We already know the first two instructions are the same. I don't want
to keep repeating that, so I won't anymore. Um now all we have to do for this one to
implement it once we know it once we know we're uh executing a load immediate um all we need to do is take the
argument that's stored in the instruction register put it onto the bus and then also just read it into the A
register um and then we're done. So then IR to register A is what we're doing here. And then our last two clock cycles
are wasted. there no ops and I can denote that for the ones above as well.
Um, and this one's interesting because it's so simple to execute. Uh, we actually
have two wasted clock cycles. So only three of the allocated five clock cycles here are being used meaningfully and the
others are are are useless. So you can imagine that's it's pretty inefficient. You know, we have like a 60% utilization
as measured by clock cycles for actually doing work that we need to. Um, and yeah, that's a that's a bad that's a bad
one. And the reason we have two instead of one like the previous instructions that had a wasted one is because we get
a skip uh skip a RAM lookout a RAM lookup sorry because rather than the argument being a address that we needed
to go decode in RAM which would take an extra clock cycle to do we can just we got to circumvent that for free just by
being able to use the argument literally without having any indirection through memory. So you can kind of see that uh
because of that looking up a value in RAM is slow. So operating with these registers is really fast but then
operating with RAM is a is a is meaningfully slower. Uh and so it turns out uh modern computers will have many
registers not just two that feed into the ALOU but you know a set of registers for you to use for scratch work and
there will be dedicated instructions for reading and writing values into each of those registers. And that makes it
easier for the it makes it faster for the computer to implement instructions and do work because it can skip every
time you're operating with uh a register value instead of a RAM value. It's faster to access that value so the
computer can execute the instruction faster which overall improves the uh performance of whatever program you're
running. If you've written code before, then certain programming languages, maybe
you've seen it, have a keyword. I'm thinking about C specifically here, uh, called register. And so you can kind of
tell the computer like, hey, this value is going to be worked with a lot. Um, so put it in a register. Uh, and you know
the there's something called a compiler which will take that English code you've written and translate it into these
machine code instructions that we bu we're implementing right now. Uh, and maybe that's a hint to that program
saying, "Hey, don't put this value in RAM. Put this value in a register." But, uh, I get ahead of myself here.
Um, let's talk about jump next. So, jump's a little interesting because the argument to jump and I guess recall
whatever all jump does is rather than the program counter executing sequentially, it just keeps counting uh,
from address one, treat that as an instruction, execute it. then it'll say look at address two treat that as an
instruction and execute it. Then look at address three treat it as an instruction and execute it. Uh instead
jump lets us tell the pro lets us tell the program counter to like not break it lets us break that sequential ordering
and say hey if maybe I'm on instruction five right now don't go to instruction six uh jump to maybe instruction 10 or
zero or any of them. So all it's doing to implement that is just setting the PC equal to the argument. The argument is
uh an address, but it's not an address we need to go look up. It's an address we just need to change control execution
to after this current instruction. So uh our first two clock cycles for the decode part are the exact same.
And then uh this one's also pretty straightforward where all we need to do is take the instruction registers
argument, the instructions argument which is in the instruction register, put it out onto the bus and then read it
in to the program counter which we have this J line for. Um and that's all. And then our last two clock
cycles are wasted. So all this does is say program counter equal uh sorry instruction register to
program counter. Um and there's something interesting here and that this instruction touches the program counter
twice because first we actually increment it because that's what we've been doing for every other instruction
and then we update the value that's in here. So it turns out because it's a jump instruction and
um we know that the program incrementing the program counter is useless because we're changing control flow to go
somewhere else immediately afterwards. Uh it it's possibly a little redundant to be incrementing the program counter
here, but um we could so we're conceptually we could avoid incrementing the program counter here. Um but uh the
thing is we know we don't know until after this clock cycle because this is the fetch that we're executing a jump.
So it's we don't we can't know that we're executing a jump. So we can't decide to skip incrementing the program
counter here. So if we had designed our implementations for all these instructions to increment the program
counter at the very end of your instructions implementation then we could safely omit it here like
we did on the fifth clock cycle. then we could emit it here. Um, because we're choosing to do it at the
start, we can't make that mission, but it also doesn't break anything because it's safe to increment the program
counter because it happens before we update the value uh that we're jumping to. So, what would be bad is if the user
was saying if the user had jumped to address 5 and we wrote address 5 into the program counter and then afterwards
we incremented it. That'd be bad because it'd actually end up jumping them to six because that's what the value in the
program counter would be when the CPU goes to execute the next instruction. Uh we don't have to worry about that
because you can do whatever you want to the value in the program counter if you're going to eventually write in the
intended value afterwards. So uh just wanted to have a little bit of discussion there.
Um, this instruction as is the case for load immediate and store a also has to be repeated four times and it's
identical for each of the combinations of the zero flag and the carry flag. Up would be jump carry. So recall that jump
carry is the same thing as jump. We only perform the uh updating the program counter. I we only do anything if the
carry flag is one. So this in in interpretation means if the last ALOU output had a carry out on the most
significant bit of the ALOU then we will perform this jump otherwise we will do nothing. So our fetch and decode are the
exact same and also copy over this for just completeness.
Now, if we know we're going to jump, it's pretty straightforward. All we know we have to do is uh put the instruction
register out and read it into the program counter. And that way you know that uh when you get to the next
instruction, i.e. when this our internal counter for the step number uh internal counter meaning internal to the control
logic resets back to zero it'll go and fetch the PC value which is now the address
that we intended to jump to. So uh it just sets us up for executing the next instruction correctly. And
for jump carry though, uh we want to know if the carry flag was set from our ALU's flags register. And that is why we
need this context to know what control lines to set, which is why we have these uh this carry flag here included as one
of the inputs to the control logic ROM. And that's because we as we were doing this as humans, we know that we're
making a conditional decision. Hey, based on the value in the carry register, we will make a decision about
whether or not to actually jump. When we're automating this and letting the computer make the decisions itself, we
need to feed this as input to the read only memory. So for this specific
uh instruction, it actually does matter what the zero sorry what the carry flag is. And uh it doesn't matter what the
zero flag is, but similar to the way we had to repeat all of our previous instructions four times based on the
combination of the zero flag and carry flags. We're also going to have to repeat this for four times because
there's four combinations of the zero flag and carry flag. But it turns out that the value of the carry flag uh
actually influences what we do here. So it's not going to be a straight copy four times across the ROM to account for
all four possible permutations. Um it will appear for all four permutations, but the microode will actually be
different based on the value of the the carry flag here. So in this case, the carry flag is zero. So we actually don't
do anything. Um and so we just do nothing for the we waste the the remaining three clock cycles of this
instruction. Now if I jumped to uh the spot in the RAM address in the ROM address table
where the carry flag was one. If this was a one here then we would do the jump. So we'd have the instruction
register write its value into the program counter. But in this case it's not actually. Uh
so we'll undo that. I would say let's go hunt for the correct address that would match up to this for the other
permutations. But the the sheet I'm making with is non-exhaustive and and it doesn't have that. Um so instead let's
actually uh look to jump zero implement that and then we actually will look at all the permutations. So jump zero just
as a reminder what the instruction does is the same thing as jump carry but our condition to make the jump is if the
last alou operation equal zero. Um, and recall we implemented this by looking at the output of the ALOU and oring
together the inverse of all of the output bits. So if all of the um output bits
are are all zeros, sorry, we and them all together. So we we if all the output bits are zero, then the inverse of all
of them would be ones across the board. And then if we and those together, the only way for that output to be one is if
all of those inputs are one, which pre-inverter means all of the inputs are zero. Um,
so the fetch and decode process is the exact same. But, uh, now we have to look at the zero
flag. And the zero flag uh, the interpretation is could be a little misleading here. the zero flag uh if
it's zero it means the last alou output was not equal to zero. It's only if it's set to one does it mean that the last
ALU output was actually zero. In this case uh the zero flag is zero which means the last ALU operation was not a
zero. So we don't actually do the jump. So we also similar to the jump carry we just saw we end up wasting all the
remaining clock cycles. Um but let's go see a more interesting case. So this is how jump zero behaves
if the zero flag is zero. Uh we're kind of interested in the what the output looks like if the zero flag is one.
And so I would copy this. Here's another jump zero. So our first two instructions will be the same. Our
zero flag is still zero. So we actually do nothing. So we still waste the remaining three clock cycles.
Not as interesting. Let's keep going. Here's Oh, here's another jump zero. And we know it's jump zero because the op
code is one 0 0. That's how we defined in the table. So I'll copy over the first two clock cycles of the fetch
process. Oh, those are gone. Um, so these two are always the same. This time though, the zero flag is one. So we
perform the jump. And so what that means is we actually take the argument stored in the instruction register, throw it
out onto the bus, and then read it into the program counter, and then we waste the last two clock cycles. So this says
PC PC uh instruction register to PC. Uh put the argument instruction register out
into the program counter. Um and there's something interesting because it may not be very apparent but it is also probably
worth calling out. Uh this right here is the same micro code for the regular jump instruction that unconditionally makes
the jump. So uh every time we are making a jump carry or jump zero and the correct flag is one the microode
uh i.e how we uh implement the instruction is called microode uh setting each of these lines output
lines. Um the microode the control output is always the same and so it's based on uh the inputs that actually
determine which of the uh which of these control lines will be set for the output. Um, so it means we do replicate
this this instruction four times like every other instruction is replicated to handle for every permutation of the zero
and carry flags. But unlike every other instruction we've seen so far that ignores it, this one actually has to
look at those flags to make a uh well-informed decision to know what it should or should not do. So that's
pretty interesting because this is kind of how we make decisions in our in our control. um you know everything else
everything else in terms of instructions we discussed so far uh it's easy to tell the control what to do strictly based on
you know for hey this op code represents this instruction the steps are always the same but then for the the jump carry
and jump zero instructions uh they aren't necessarily the same uh and we'd actually need to look at it so
as a human we said uh I'm going to look at what's in the flags register to determine if I make this jump uh and we
how we bridge that gap of us making a decision conditionally uh was feeding it as input to the readonly memory and
generating different outputs based on the different input combinations. And so because a readonly memory has to be we
have to fully map out every input and output combination we can pretty much say hey for each of these input
combinations this is what you should do. So we are we give the readonly memory a very verbose yet complete and necessary
uh set of [snorts] input output combinations to handle for this. Um so for every other instruction if we wanted
to add more conditional instructions to this computer uh you kind of have to find a way to incorporate that logic uh
into the readonly memory by adding in an extra input um that represents the decision state for what you're making
that instructionally do. And then it'd also have to be replicated however many times. Um, cool. So that's interesting.
Uh, let's see if I So the remaining jump zero one 0 0. Um, the jump flag is set. So we also perform
the the jump operation. So I can actually literally just copy the implementation for jump because it's the
same. Cool. Okay. So, that's how we implement our conditional logic onto our final two
instructions. Uh, I know this has probably gotten a little bit repetitive, but I wanted to be as complete as
possible because I part of the video is I do not want to leave out otherwise meaningful information as I explain
things from scratch. Uh, so I want to minimize the amount of handwaving I do until we have the barebone CPU working.
And then as we talk about how this translates to a more modern CPU, more modern computer, uh then I'll do a
little more handwaving. But um our second to last instruction was out and all it does is put the value in
register A into the output displays register. And that has the effect of updating the seven segment display to
show whatever's in the A register. Even uh in future instructions change the value of the A register. um it won't
update the display explicitly. So the display is not always just hardcoded to the A register. Remember we built a new
register uh and the display is hardcoded to that register. Um so our fetch decode execute cycle starts
with fetch. We do the same thing that we've done for every instruction. Oh well that's interesting.
Okay. Um, and for this one, it's also pretty trivial. All we need to do is get the value in the A register into the
displays register. So, I'm going to put the A register out onto the bus and then I'm going to read it into the uh display
register. And uh once we do that, uh I'll mark that as register a into display reg. um
our remaining two clock cycles can be wasted here. So that's a pretty simple instruction. And then this brings us
um to the halt instruction which is the last one of them all but worthy of instruction worthy of discussion. Uh and
for both the output and the display instructions obviously these are repeated four times uh based on whatever
the inputs uh for the zero flag and carry flag are don't affect what these operations do. But we still need to
define them because that is input to the ROM. Um, I display should do the same thing uh if if the zero flag is one and
the carry flag is zero as as it should as if both of those flags are zero. But we actually need to go specify that in
the ROM um for completeness. So our halt instruction is pretty interesting where all we have to do is set the halt line.
And recall that the moment we do this because the halt line is is connected to
the clock, the halt line is inverted and then connected to the clock. So the moment we set halt one, the inverted
value of halt is zero. And since the clock is anded with this, it means the clock output that we connect to the rest
of the computer is just going to get stuck at zero because zero and anything anded with zero is zero. So even if the
clock itself is going high to low, high to low, uh when we end it with this inverted halt signal, it'll block that
from actually carrying through out of the endgate. Um and so as soon as we get here, the computer cannot make any
progress. So it actually doesn't matter what we specify for the remaining two clock cycles here because not none of
it's conceptually possible for it to get executed. The only way to get the computer out of this state would be to
physically turn it off, turn it back on, reprogram uh your memory and then things will be good to go again. Um, cool. So
that concludes us implementing each of the instructions. What I'd like to do next is uh define a program with the
instructions we've created. So I'll switch over to this other tab here. And what I've copied over is all
of our instructions and their op codes. We know what they do. So I don't carry over a description. And then uh a
conceptual layout of RAM um where here's our 16 different addresses starting from zero uh in
decimal. And then this is the binary representation i.e. if we wanted to you know lo access that value either read or
writing into it. Uh this is what we need to put in the memory address register. So it's just the the binary equivalence.
And what I'd like to do is let's write a simple program that just starts with 15 uh and keeps counting
down by one and displaying that on the uh our seven segment display and then once we hit zero we stop. Uh it's a
trivial program and you'll probably see why I pick 15 very very shortly. Um but if we think about what we need to do to
get this program in RAM, there's a few things. First, we need to you know for our our primary loop, it's going to be
doing subtraction by one each time. Assuming we get the value 15 into the a register and we get the value one uh
somewhere else in memory then we can keep calling the subtract instruction with wherever the one resides in memory
and then we know every time after we do the subtract instruction we want to write the display to write to the output
display and then we check if we're at zero or not. Uh if we're not at zero we could jump back to our same uh
subtraction instruction. uh otherwise if we are at zero we need to jump and and have this um halt command. So let's kind
of think about the very first thing we need to do. Um the first thing we'd want to do is get this the value one
somewhere in memory so that we can always use that as our thing that we're subtracting from. And if I if we were to
count down from two, we would need the value two somewhere in memory. And that's because the subtract instruction
uh the operand is not the value you want to subtract from whatever's in the A register. It's the address to something
in memory and that value at that address is what you want to subtract from the A register. So uh the first thing I want
to do is actually get um a load immediate of a one into the A register. So
the reason I'm doing this uh is that in the only way we can write something to RAM is with our store a instruction. So
if we want to get the value one somewhere into RAM, we'd actually first need to get that value into the A
register and then we can write it from the A register into RAM. And so the load immediate is what does exactly that. It
treats the argument as a literal 4bit integer and then it'll put that into the A register. So uh we can comment this
you know register a equals 1 and then uh the actual RAM value the actual value we put in RAM to do this is the load
immediate argument. So 0 1 01 and then the argument here I'll do a space to separate the the op code the operation
code the instruction code and then the argument to that instruction just for our readability purpose. Um, and the
argument we want to load into the val into the A register is just one. So now that we've loaded the A register with
the value one, we want to store that somewhere in memory. And here's where we get interesting. So like our computer
will start at address zero in terms of execution when we power it up. The program counter will always start at
zero just by assumption. Um, we kind of you get to use RAM however we want. So it's kind of us up to us to figure out
where we want to put the uh the the constant one in memory and we designed the program knowing that okay if we
store the constant one in memory at this address then every time we do our subtraction instruction this is the
address we need to feed it. What I like to do and what you'll probably see if you poke into this further um since RAM
can contain both instructions and data people generally like to separate the two. Uh but it's not actually a strict
requirement. It's totally up to you. And I think this is where some amount of like creative expression comes into
play. Uh cuz you'll also see that there's probably a different way to implement the same exact program. You
might actually have a different thought of how you can get this countdown program. Um and I'd encourage you to try
it out. And we'll we'll show you how to try it out shortly, but I'm going to go down the route I have. So I've loaded
the register uh a with the value one. And I'm going to say that uh address 15 is where we're going to store that into.
So, what I'd like to do then is store uh the value in the A register into address um 15. And so, what that does is it says
mem 15= 1. And I guess I'll just use RAM at address 15als 1 uh for the comment here. And
then the actual binary value that we need to load into RAM at address one to signal that instruction to the computer
is the store a command which is 010 and then our argument is 1111. Okay. So basically when we power up the
computer if we had if we ran uh ran the the instructions uh this in this value followed by this value the output is
that uh the value in RAM at this address would look like this. Um if we let that program run but that's not what we want
to do. That's just getting started. So now that we have uh this this value address that we're going to say so like
this will just be like a hard-coded one is kind of how we will the the value I mean we don't specify that our program
will put that in there. Um there's an alternative where we could have skipped these two steps if we had just punched
the value the uh this value one into that RAM value to begin with. Uh but I'm just
going to work with just what we can express with instructions for now. Um, so now that we've uh gotten our one,
which might still a little bit be might still be a little bit ambiguous as to why we've done these two instructions,
but you'll see very shortly. Um, we simply want to start with the value 15 and count down from it. So, basically,
uh, what we need to do is load the value 15 into the A register. So, load immediate 1111. Uh, ooh, this
is actually, um, Oh, that's fine. That's fine. So it what's interesting here is 1111 in this uh instruction is an
argument that actually points to this address in memory because that's how we interpret the argument for the store
instruction. But as we discussed earlier the 1111 in this load immediate argument is actually referring to the literal
binary value um 15 if you were to translate that from binary back to decimal. So uh the effect of this
instruction is that register a will now equal 15. And so the actual RAM value that we need to put in to encode that
instruction is load immediate 0 1 01 and then 11 one1. Okay. So now we have 15. Um we need to
think about what the user expected behavior of this program is. And what that is is that they should see the
number 15 and then they should see the number 14 and then 13 and it should go all the way down until they see the
number three then the number two then the number one and then the number zero and then it stops. Let's not think about
the end conditioner yet. But uh let us think that the fact that we have 15 in the A register, let's go ahead and show
that to display. So all we'll do is an out for our next instruction. Um and recall that the argument is useless. So
to encode that in binary um we will say uh
the operation code for the out instruction is 1 1 0 and then the argument does not matter. There's no
argument for this instruction. And as we discussed earlier when we looked at how we implemented the ROM over here since
we handle um like yeah it doesn't actually matter what the uh argument is here. This could
be anything. We just actually don't even use it um for for our our implementation of the microode. So it doesn't even
really matter. Uh I'll just keep it zeros for for clarity. Um okay. So now we're going to say display 15. Okay. So
now we've displayed 15. we got to get to 14. So all we need to do since we have 15 in the A register is subtract one.
And recall that by the time we get to this instruction right here, by the time our program counter equals the value
four, we were guaranteed to have a hard-coded one at RAM address 15. So all we need to do is subtract one from the A
register. So we could just do sub and then recall the argument is the address uh which contains the value you want to
subtract. So we're going to do 15 here, which is binary 1111. Um, and so this has the effect of of a equals a minus
one. And so the binary value that we need to put into RAM to encode that instruction. Uh, the operation code for
sub is 0011. And our argument is address 15. So 1 2 3 4. Cool. Um, so now that we've subtracted one, we are at 14. So
let's go ahead and show that to the user. So it's the same thing we've done up here. Um so we will say uh display a
um and then uh the operation code is 11 1 0 0. So now that we've subtracted a value and
shown it to the user, we need to make the decision. Do we go subtract another value and repeat this process again and
show that value to the user? um or are we at zero and should we end the program? So naively what you might think
is uh let's just ignore the termination condition of the program is all we once we have gotten to the state all we'd
like to keep doing is subtracting the val subtracting one from whatever's in the A register and then showing that to
the user and subtracting one from the A register and then showing that to the user. Um, and so what we what you might
be tempted to do here is just jump uh to this address 4, which is 0 1 0 0. And that would work. Uh, but this wouldn't
stop correctly. When we got to zero, uh, this would jump right back to this instruction. And then you get a weird
result in the ALOU because, you know, you're subtracting uh, one from zero, so you'd get negative
one. Um, but we said we wanted to stop the program at zero. So what we'd actually need to do is check uh if we
have a zero from our last result, which is the exact purpose of this jump carry instruction. And if we hit if we do, we
should if we jump carry to zero, uh we can jump to an address that will end the program uh by just having the halt
instruction at that address. So what I mean by that is let's put a halt instruction just somewhere random in
memory. Um and there's no argument to the instruction and so this is just halt. Uh
and then basically what we can then do here is like if we uh jump if our if the result of the last ALU operation i.e.
the subtract at address 4 was a zero to this address 10 which is binary 1 0 1 0. Um and so if we think about where we
start here, uh we display 15, subtract one. So we just and then we have 14 and then we display 14. This jump carry 14
is not equal to zero. So it just continues uh execution. And so then we know that if we make it to this address
7 that it means we actually are not done with our program. So we should jump back up to here to do the subtraction uh
again. So I can just jump to address 0 1 0 0. Um so basically uh check if done and then otherwise
um loop. We're pretty much doing a loop here. You know we loop back to the top where our loop is a pretty
straightforward just uh subtract by one. So the last thing I need to do is go ahead and specify what the RAM values
for both of these instructions are. So this jump carry is 0 1 1 and then our argument is 1 0 1 0 and then regular
jump has the operation code 0 1 1 0 and then our argument is 0100. So um if you believe what I'm saying
then let's walk through our program. We start we load the value one into register A and then we store that at
address 15 in RAM. Then we load the value 15 into register A. Show that to the user. So the user then sees 15. We
subtract one from the register and then display that user. So the user sees 14. We check if the result was zero. 14 is
not zero. So we move on to this instruction. This jump just says go back to right here. And so then we have 14
and then we subtract one from it. So we get 13. We show 13 to the user. 13 does not equal to zero. So we continue. And
then we jump back up here. And now we have 13. We subtract one to get 12 in the A register. Show 12 to the user. 12
does not equal zero. And so we jump back up here. And the process will continue until we eventually get to uh having one
in the A register. Then we subtract one from it to get zero. We show the zero to the user. Then we check if the last
result was zero. It was this time. So we jump to this address in memory. And this address in memory just has to halt the
program. So nothing continues afterwards. So, I've invented this hypothetical program. Um, but like how
can we actually verify that it's correct? Uh, it turns out I've built the simulator for the computer we've
designed. So, we can go go ahead and punch this program, this this value into RAM, um, and let it run and see if the
behavior is exactly what we expect. So, let me go ahead and tab out and let's do that. Uh, a few years ago when I first
built this computer, I guess 6 years ago now, uh, I also built a simulator afterwards. This is publicly available
if you want to play around with it. Um there's instructions on how to set it up and how to use it here. Um I won't focus
too much on that. We'll just show how how do you actually play around with it to show what we the program we just
made. Um so what I've gone through and done is uh let's look at the simulator itself. So at any point you can see the
contents of RAM. Uh this is the addressing and you can just click on any value to change it and it'll show you
every time. There's like kind of a a log here of every every time something about the computer changes, it is shown here.
So if I just set this value to zero, you can see memory address 15 changed to zero. Uh or if I punch it to one, memory
address 15 changed to one. Um and so this is RAM. And then you can see for you can see the value that's on the bus
at any time. You can see this value that's in the A register or the B register. And you can see the output of
the ALU at any time. And then you can also see what's in the instruction register, what's in the program counter,
what's in the output displays register, what's in the memory address register, and then recall that each instruction on
our uh computer takes five clock cycles to execute. So you can see what uh each of the steps are. And then it'll also
show you for each of the steps um what control lines like what the output of the control lines uh are. and
then also what the value of our carry flag and zero flag are. Um there's a few other things. We don't need to touch on
all of it. Uh and you can also see here's the value of the seven segment display. Um the fictitious one we
constructed with just one of these units, but I explained you could probably reason through how the same
logic can be expanded for chaining several seven segment displays together to show uh multi-digit numbers rather
than the zero through nine that we were fairly constrained to. And then what I've done is I've gone ahead and punched
in our entire program. There is one thing I caught. Um I used jump carry here. Uh but our end condition is we
don't want to end when our result carries because it doesn't make sense because we're counting down. We want to
end the program when we hit a zero as the last output of our our ALU operation. So this should not be a jump
carry. This should be a jump zero. Apologies for the confusion. And then the jump zero uh operation code is 10.
So, uh, I fixed that. And then you can see I've kind of, um, incorporated that, uh, in the program here. There's one
other cool thing, uh, that this computer simulator has, and that is, uh, there's this button I built called analyze
program. And you can kind of see that the contents of RAM are nonsensical. Um, but if I click this analyze program,
it'll actually go through and show you what each of the instructions are, um, or what the decimal value of that
address in RAM is. It has to do both because the computer is naive. Uh, it can't know if you're using a certain
address to denote an instruction or to hold a hold a value. Um, and it turns out any particular address can actually
be both. Um, but that's why I do that. So you can see that our instructions here we have load a a store a 15 a load
immediate 15 an out subtract 15 and out a jump zero to address 10 a jump to address 4 two empty instructions a halt
and then um at the very end we have nothing down here and our address 15 is zero because we have not run the program
yet. the first two instructions would get the value one into RAM. Um, so this is identical to what we uh created over
here. So that looks good. Let's go ahead and toggle the clock and uh we can walk through the very first instruction super
closely. So uh the very first thing we have is this load immediate one. Let me jump back to our other sheet and we can
see what our our uh instructions our micro code should be for this load immediate. So um the first
thing that every instruction should do is get look up the value of the program counter in RAM and get that uh get that
value. So we want to put the value of the program counter onto the bus and then read that into the memory address
register. If I tab over here, I can see since we're on the first step here, uh, those exact things are set. The program
counter is going out onto the bus. The memory address register is reading that in. Our clock is low right now. So, if I
pulse the clock from high, from low to high, um, we should see that happen. Uh, it's kind of uninteresting because all
the values are zero. The program counter is zero. So, we're kind of putting zero on the bus here. And, um, the memory
address register is already zero, so when we read it in, uh, nothing really changes. Um,
so, uh, let me go ahead and pulse the clock from low to high. Nothing really happens. But then when I let the clock
go back down, um, we are, we should end up on step two. And we should see that the next set of operations is getting
that output of RAM onto the bus and reading it into the instruction register, looking up the instruction
effectively, and then also incrementing our program counter. So uh let me go ahead and put the clock down and you can
see exactly those three lines are set. Uh we increment our program counter and then we are look taking whatever value
we got from RAM which should be this 01 and putting it into the instruction register. And so there's this other
thing about this uh simulator where this button right here will let us highlight uh whatever the address in the memory
address register is on the RAM. So memory address register has zero. So we can see zero is highlighted uh as we
trace through the program. It's interesting. You'll see that uh move a lot. Um and so that now we we have done
the second step here. So what I need to do is get the clock to be low again. So I'll post uh back down. And the next
thing the very last thing we need to do uh for load immediate is take the argument because now we've decoded the
instruction at this point. We know it's load immediate. So we know the argument just needs to go out onto the bus, which
is what the instruction out does, and read it into the A register. So um let's go ahead and do that. And you
can see that on that rising clock edge, the value gets latched in. So the program counter is now one. And then our
instruction register has the uh value and address zero, which is the instruction we're trying to execute. And
that looks great. And so if I toggle the clock, uh, we fall back low. And we can see that our next set of operations that
we're going to do is exactly that. Move the argument from the instruction register, which is this 00001,
uh, into the A register. So when I pulse the clock from low to high, this O1, uh, should go into register A. Uh, and we
can see that's on the bus already, too. So more interesting non-zero values to work with. We can make some sense of it
now. And cool. So the clock went from low to high. And um you can see that the bus
now has the bus still has the same value but register A has that immediate one in. And then uh interestingly enough the
ALU is just always adding or subtracting what's in the two registers A and B. So we don't care about the ALU output for
this instruction but it is a correct sanity check that like 0 + 1 is one. um we know that and then we know from here
that after three clock cycles we've implemented we've done all we need to do for the load immediate instruction. So
the last two clock cycles are kind of wasted. Um and there's also some detail here. This
uh log will pretty much show you everything that we just said. So like we just had the rising clock edge where we
read the a registers value in from the bus. Um, so that that happens as well. And that's just a nice English
interpretation of what the computer is doing with each clock edge. So if I toggle the clock to low, um, you can see
it's a falling clock edge. Step counter updated the four. Since it's a load immediate, we've done our
implementation, so nothing happens. And, um, oops, I accidentally reset our SAP. Uh,
I clicked the reset button. So we've we've gone back to to reset the state of our computer. If I just toggle it to get
back to where we were. Um, there's there's nothing crazy to it. So, we're at step count five. We hit the
rise and clock edge and when I fall when I fall, we're done with our instruction. So, now we go our internal counter used
for the control resets its step count to one. And now we know we're on the next instruction. And uh that next
instruction, if I analyze our program, was the storing that value into address 15. So, I'm not going to walk through
each of the five clock cycles here. What I will do is I'll pulse the clock five times until we get back to step counter
one. And what you'd expect to see if everything's correct is that afterwards the value in RAM at this address 1111
should equal um the value in the A register which is just one. So, let's go ahead. I'll pulse the clock five times.
And you can see that that's exactly what's happened. the value in the uh address 15 of our RAM is now that one.
Um so that looks good. Then so I'm gonna say this looks good. This looks good. Um now we need to load the
value 15 into register A. Let me pulse the clock five more times to do that. And so now we can see register A has
this value 1111. And then that looks good. Then we display it to the user. So let's pulse the clock five times. And
we'd expect the value 15 to be visible here. Uh, cool. What I should have done when I
built the simulator is add an option uh not to just toggle the clock, but maybe to toggle the clock five times or
through five clock cycles so I don't have to, you know, keep punching it. But uh, it's fine. It gets the job done, I
guess. Um, so that looks good. Now we get into the the loop part here where we subtract one display it and
then after that we either repeat that that those two instructions or we end the program. So uh basically what we're
going to do is we're going to do the sub. So this should end with uh a being one less than 15. So uh 1111 is 15. We'd
expect 11 1 0 to represent 14. And then since we're doing 15 minus one, we'd also expect that the value one should
probably get loaded into V register uh to do the subtraction. So let me pulse the clock five times.
And you can see both these things are true. So we have 11 1 in the A register which is 14. Uh we accomplish that by
loading one into the B register and subtracting it. And uh that looks good. And then
now we check this display a. So we should expect 14 to just end up on the display register because we've done our
subtraction. Uh we've done our subtraction. So we should show this to the user. So let's pulse the clock
through this next instruction. And you can see that's the exact case. We have 14. And now uh let's do a little bit of
checking. Um now we should expect uh this jump zero to not to be a noop. uh we should not end up jumping to this
address right here. So if I pulse the clock five times uh what the what should happen afterwards is the program counter
should equal 0 uh 1111. And that 011 is going to tell us to just jump back up
and repeat the loop. So let me pulse the clock five times. And we can see our program counter is
011 as we expect. So we uh did nothing with this jump zero instruction. And then this this 011 0 or sorry this 0111
011. Yes. Sorry. I was like did I say one too many ones? Uh is just a jump back up to
uh our our subtract loop. So I'd expect the program counter to go to um
address 4 afterwards of this. Let's pulse it and see ourselves. And we can see exactly that our program,
our program counter is address 4. And so then we repeat this uh this loop cycle. So I also built an auto play because I
don't want to trace through this entire program. I built an auto play that'll just keep pulsing the clock like really
fast, low, and high. Uh and you can control the speed here. Um and basically I'm just going to crank
the speed all the way up. And we should see that we just count down all the way to zero.
one and we get to zero and we end up executing this halt instruction which remember it just blocks the program from
doing anything further. So you can kind of see if I scroll to the bottom of the logs here. Um, we set this halt line and
then there's a rising edge of the clock, but because the clock is connected to the value of the halt line, it actually
blocks the the clock the true clock value from propagating out to the rest of the computer. And so that's how we
block execution. Okay, so this is pretty cool. We wrote a simple program uh and it actually does
something. uh it it it we chain together these very simple operations and uh use that to build a program that
does something. And this is cool because not only like we started from scratch and that we implemented these micro
instructions ourselves and each one of those required like very granular uh changes to the state of our system. But
then when you cycle change uh bundle a few of those changes into what we called an instruction, the instruction can do
something a little bit powerful. Not really that meaningful on its own, but something just a little bit like one
action of like move this value from here to here or you know show this value on the display or or maybe jump to this
other instruction instead of executing the one that's below me. Um, and so on on their own, each of those instructions
are kind of trivial, but then when we chain them together, we were actually able to write a program
um that actually does something interesting and and has a termination condition. And what's also pretty cool
about it is like that program is encoded with literally just zeros and ones. And a few hours ago, this would have been
gibberish to you. Uh this this these combinations of zeros and ones would have had no intrinsic value. But it
turns out for this specific computer we've designed, this exact ordering of zeros and ones produces a program that
does something very interesting to us. We've constructed is trivial, but it paints a very interesting uh portrait of
the capabilities of our computer. And what I'd like to do is you can probably imagine the full set of what we can
really do with these eight or so instructions is fairly limited, especially because we only have 16
different spots in RAM. So the programs you write are inherently constrained um by the the limitations of how we've
constructed our computer. More modern computers have a lot more RAM and a lot more instructions, and we'll talk about
that shortly. But, uh, I'd like to try build out something that does something a little more complicated than counting
down from 15. Let's see what the most complicated thing we can do with this instruction set that we have is. So, um,
let's go over here to Wikipedia. So, a sequence, just as a quick definition, uh, a sequence in mathematics, a
sequence is a sequence of integers. Uh, that's kind of a circular definition, but it's literally just a list of
numbers. And um there's one sequence in in particular that's fairly interesting and it's called the Fibonacci sequence.
So let's read the definition. In mathematics, the Fibonacci sequence is a sequence in which each element is the
sum of the two elements that precede it. So basically we start with the number zero and one and then every element
after that is just the sum of the previous two elements. So here we have 0 and one. The sum of that is one. So
that's our next number. And then if we have 1 + 1 as the last two elements, the sum of those is two. And then the last
two elements becomes one and two. The sum of which is three. And then the last two elements are now two and three. The
sum of which becomes five. And you can see that it keeps going. We have 3 + 5 is 8. 8 + 5 is 13. 13 + 8 is 21. 21 + 13
is 34. And uh it keeps going. It grows unbounded. And what I'd like to do is build a program on our computer that can
actually count out the Fibonacci sequence for us. And it turns out the instruction set that we have with our
computer is totally capable of doing exactly this. Um and let's also tab out and think about uh something more
abstract in mathematics and computer science. Uh like what is an algorithm? I remember when I was learning
computation, kept on hearing talks about algorithms. There's classes you take with the computer science degree.
There's one that most people take called data structures and algorithms. Um, and I remember like we we kind of talk about
like you know like what are social media algorithms? Like like what's what's the logic behind that? Um, and an algorithm
has a surprisingly simple definition. It is a process or set of rules to be followed in calculations or other
problems solving operations, especially by a computer. I like the Wikipedia definition a little bit better. So, I'm
going to go over here. Um, Wikipedia says, "In mathematics and computer science, an algorithm is a finite
sequence of mathematically rigorous instructions typically used to solve a class of specific problems or to perform
a computation. Algorithms are used as specifications for performing calculations and data
processing. More advanced algorithms can use conditionals to divert the code execution through various routes and
deduce valid inferences. So this is an interesting definition. Uh I think it's a more interesting one for a few
reasons. Um first off we saw that uh we just performed a computation of what we decided earlier. the counting down was
just a series of computations. And then uh that was a form of data processing, but like that's not really what the
definition is poking at here. But what's also interesting is uh it says advanced algorithms can divert execution through
various routes. And um this is kind of the branching we discovered with our our jump zero and
our jump carry instructions that allow us to introduce some like decision making into our code. So what I want to
do is write an algorithm for implementing this Fibonacci sequence and then go ahead and actually implement it.
Um I think a good starting point is coming back to this very simple program that we had earlier and and writing an
algorithm for it. So uh if we if we wrote an algorithm for this program uh what would it look like about exactly
what we did to implement our our instructions here? um we have to be very concrete and formal with what we said
and uh specify everything very clearly. We have to tell the computer because the computer uh especially the one we built
is kind of quote unquote dumb and it'll only tell you it'll only do exactly what you tell it to do. So what we kind of
did is we said um we had some variable called the counter. We'll just say like let counter equals 15. And in the
algorithm, the algorithm is kind of independent of the hardware of the computer itself. When we went to
implement this algorithm in machine code, uh our counter was kind of the A register. That was kind of the register
that held the primary state of the program at any given time. And we pretty much said while the counter was greater
than or equal to um sorry while the counter is greater than zero what we did is we subtracted one from
the counter. So we said counter equals counter minus one and then display counter
and basically this this syntax I'm using here is an indentation. So like every time this condition is true, everything
that's executed after it or everything that's indented after it will be executed and then it'll check the
condition again. It'll come back up here. Um that's similar to the the jumping we have in our code. And then
once we get out of this while loop, so well after the only way we can get out of this while loop is if the counter is
not greater than zero. And since we're counting down by one, that means it necessarily is equal to zero. And so
that means once we get past here, we just end and we're done. So this is a very formal specification
um of our counter program. Let's try doing for the Fibonacci sequence. So let me clear it out here.
So in general we said the Fibonacci sequence like for any number it is the sum of the previous two numbers. So like
if we let uh let's define like f of i to be the i uh number in the Fibonacci sequence.
So we can pretty much say that uh let's say it's zero. We've been learning everything starting with zero. So f of
zero is zero. The first number in the sequence is zero and the second number in the sequence is one. And then for
every other number, we pretty much say that f of n is equal to the sum the nth fibonacci number is equal to the sum of
the number behind it and the number that's two behind it. And so this is kind of what we call a
recursive definition where the value of the Fibonacci sequence, the value of f of n depends on recursive calls to f of
n. Um, so that makes sense for like us to rebound English, you know, this is just saying start with the numbers 0 and
one and then after that for any other arbitrary position in the Fibonacci sequence, go figure out what the two
Fibonacci numbers before are and then add those together and that tells you what the current Fibonacci number is.
Assuming Ed is the nth number in the sequence that you're interested about. Um, our computer we've built here
obviously can't deduce that. uh we kind of have to spill that out in a slightly different way. So what we're going to do
um is let's go figure out how we can actually build this. So we know that we have these instructions to work with and
we also have uh spots in data memory. So I'm going to go ahead and clear out our old program here.
And basically I'm going to define I'm going to reserve two spots in memory. Let's just do the
last two spots in RAM to represent um like the f of n minus one and the f of n
minus2 here. So like the last two values in RAM will always represent uh the last two numbers in the Fibonacci sequence.
And then if we can get those correctly, uh if we can always guarantee that to be true, then this means to find the next
Fibonacci sequence. Well, to start, we need to load the values like zero and one into RAM here. And you can imagine
that's pretty straightforward to do. But then afterwards to get the next Fibonacci number, all we need to do is
add the values at these two addresses together. That gives you the next Fibonacci number. You can display that.
And then once you have the next Fibonacci number, the n minus one's number becomes the n minus2 number. So
you can replace this number right here with what's there because we no longer care about this one for the next
Fibonacci sequence uh number. So we'd like pretty much say that this becomes a one and then this value here becomes
what the result of the alou was which also happens to be one because that is what 0 plus one is. And so let's do one
more practice of that. Um to get if this is our n minus2 and n minus one numbers then the current the f of n i i.e. the
next Fibonacci sequence number would be 1 + one. So we could add these two values together. Um we get the value
two. We can store that somewhere else in RAM temporarily. And then this number would need to we want to throw this away
and shift this one up. And then now write our two into this spot right here. And so now we have one two as our last
two numbers in the Fibonacci sequence. And we obviously want to display that to the user in there as well. Um
and so now if we do that one more time just for sake of tying it together because we worked with zeros and one so
maybe the shifting is not clear. But we would to get uh if we have these as our last two numbers in the Fibonacci
sequence for wherever our program's at, we add these two values in RAM together using the add instruction. Um and then
we'd get three as the result. We can store that somewhere arbitrary in memory. And then we need to wipe this
one away and shift this two up and then bring this three down because that's now our last Fibonacci number. Um, so if we
kind of think about how we want to specify this as an as an algorithm, um, let's just call this value like a and
this value b. And a and b are always the last two numbers in the Fibonacci sequence. So what we'd want to do is
pretty much start by getting the values zero and one into these spots and rams. Um because this is uh the the first two
numbers in the Fibonacci sequence, we kind of know that RAM is all zeroed out. So the value here will already be
zero because of like how our program instantiates. Like we're just going to say that RAM is all zeros.
But we need to get a one into here. So that's pretty straightforward. Um like pretty much we can say a is already
zero. Then we can say B becomes one. And then what we'd like to do is uh add at any step the two values together.
So we make a new value called C and C is equal to A plus B. And then we can display C to the user
because that's the next Fibonacci number. And then we need to say that uh A becomes B and B becomes C. And that's
how we kind of shift up one here. And um after that we can pretty much jump back to uh jump back to uh the C= A plus B
instruction. Um and so what we do here is we we get our first two Fibonacci values and then we begin a cycle of
calculating uh the next Fibonacci number. So our every time we loop through and jump back up to this, we're
trying to find the next Fibonacci sequence, assuming A and B are the last two Fibonacci numbers we worked with. So
by definition of the Fibonacci sequence, we can trivially compute the next value just by adding A and B together. And
then that's the next Fibonacci number. We display that to the user. And then now that we have three Fibonacci
numbers, we throw away the oldest. The oldest is A. And so then B becomes the older one. Now, so uh what was
previously in the Bcomes assigned to A and then what was in B is now our newest Fibonacci number which is the one we
just calculated up here. So we can set that equal to the value of C. Um and so this is pretty straightforward. Uh let's
actually go ahead and this is like kind of our formal algorithm. Um so I guess like if we wouldn't make this fully
algorithm uh we could say like uh wall like one is equal to one. So just this is wall one is always equal to one. So
this will always be true and this is kind of what the uh the the code
would look like. So this would keep counting Fibonacci sequence numbers forever. We start with uh like you can
say like let a start with zero, let b start with one and then wall one equals one is just kind of a little hack around
like the algorithm specification to say like do this over and over. And what we do over and over is add our previous two
Fibonacci numbers together. Show that to the user and then update what our last two Fibonacci numbers are.
So throw away that now that we have three numbers A, B, and C. uh shift it so that what was previous like uh what's
currently B and C becomes into A and B. So we set A to B and then B to C. So let's think about how we can implement
this using our instruction set that we have available to us on this computer. So we already know by assumption just
that the value in uh in a uh is zero. So what we need to do is get the value one here. So I'm going to load an immediate
one um into the A register. So this just says let me not bold here. Register a
equals 1. And then uh afterwards we can go ahead and get
straight into the um branching logic. So what that would look like is we need to uh sorry we actually need to take the
value we have and store it into register uh store it into this address value. So we take the one that we have and store
that into address 15. So this would just say like b equals 1. And then now what we can do is go ahead
and start the sequence of adding these two numbers together. So to add two values in RAM, we need to load one of
the values into the A register and then load one of the other and then do an add operation with uh the second value. So
what we can do is load a with 14 and then so it just says like register a equals a. Uh there's a differentiation
here. Uh register A, you already know what register A is. The A down here is just a variable that we are calling. uh
variable meaning uh we will say whatever is in this uh we're going to call a and that value can change it can vary so
it's called a variable um apologies I probably use that several times without necessarily clarifying what a variable
is but uh forgive me for that and then now we can add whatever is in 15 so then it pretty much says that
register A is equal to A plus B so that's exactly what we want and And that that's a plus b is the c value we've
described here. So let's denote let's let c equal this equal um address 13 in RAM. So let's go ahead and just uh we
want to take that value uh the output here and store it in C. But before we do that we want to show this to the user.
So we can do an out here and then we can store the value C into the correct memory address that we want which is
1101. And um the ordering between these two
operations actually does not matter. It's kind of up to us that's how we want it. Uh so you know you could have done
you could have stored it and then written the out. Uh and the reason we can do that is because uh store a and
out neither of those change what's in the A register. They just do some action based on what's in the A register. So
this add instruction over here actually mutates the value in the A register. The following two have no effect to the A
register. They're independent operations we'd like to do based on whatever was the outcome of this ad result here. So
the ordering is trivial. It's it's not perceivable to the human and does not affect the runtime correctness of our
program. Um assuming everything else is right here. Um so now we have our like last three uh Fibonacci numbers like A
is the oldest, B is the second like in the middle and then C is the newest. Um and so what we need to do then is kind
of throw away this A, move the value in B into A and then move the value from C into B. So let's start by uh taking the
value here and then getting it into here. So all we can need to do to get the value from B into A is do a load A
uh of 15. So get the value that's in B into the A register and then a store A where we store it into this address. Uh
so that's how we move the value that's how we effectively move the value which overwrites the A with B. Um so what we
do to do that is a load A5. So we say like register a equals b and then uh we can store a into 14 and that
just says a equals what was like a is now what b uh is and then the last thing we need to do is get the value from c
into the into the b address bot. So I can load into register a this address 13. So it says register A equals C. And
then I can use the same trick that we did to overwrite A uh to overwrite B. So I can store that into 15.
Um and then the last thing we need to do to continue this loop is to we've so this is calculated given Fibonacci
numbers A and B. Um everything we've done here uh
calculates the next Fibonacci number. So now that we've calculated one Fibonacci number,
let's just jump back up here and jump to instruction two and execute the next uh the same set of operations to figure out
the next Fibonacci number. So I can just jump to two uh one zero
and then this says continue. Um so if I kind of map that out, these are our instructions.
Um we can kind of see that we let a we get this first one for free. This a is equal to zero. We get that for free just
because we already know that RAM defaults to zero values. So then the next one that we have to do ourselves is
getting the value letting B equal one. And that's exactly what we're doing um in these two instructions right here.
We're getting the value one into register A and then writing that into B. That's how we do this. We have to do two
instructions because of how restrictive our instructions that we have defined are. But they exist like there's enough
instructions to do what we want to do. And then you can kind of see that uh this is our loop part. But so C equals A
plus B is implemented right here because we load A into one of the ALU operands and then B into the other ALOU operands
and we add those together and we get the value C and then we store that uh into the value C. Sorry. So this is these
three instructions implement C equals A plus B. Then the display C is trivially implemented with the out instruction.
And then these four instructions implement the shifting um of replacing
um A and B uh with B and C. And this um program if we let it run will count Fibonacci numbers for us. So,
what I've already gone ahead and done is kind of loaded some of these values into RAM. Let me go ahead and clear
uh this because that's not correct. And if I reset everything here, let's go ahead and run the analyze program and
see what we have. And so, uh, if I'm matching up what is over here with over here, I see a load immediate one, a
store a 15, a load a 14, an ad with 15, a store with 13, a display, a load a with 15, a store a with 14, a load a of
13, and a store a with 15, and then a jump back up to two. So, this is the correct program. I didn't specify the
RAM values here, uh, but they are punched in here. Um, so let's go ahead and just let it rip.
Uh, I'll autoplay this, but uh, the Fibonacci sequence, we can I'll type out some of the numbers so we know what to
expect. Um, we know that we start with zero and one. So, we'd expect to see like one and then, uh, like a a two and
then three, five, five, 8, 13, 21, 34, 55. 89. Uh, and so on and so on. Um, so
let's just let it rip and see what happens. Uh, one, that looks good. Two, that looks good. Three looks good.
Five looks good. Eight looks good. 13 looks good.
21. 34 looks good. 55 looks good. 89 looks good. And um what we're kind of doing is
is verifying the program's correctness just by letting it run and see. Uh that's not the most sophisticated
method. Uh but you know, for the sake of not boring and I can already verify that it does look correct. Uh that's fine. Um
so what's interesting here is I don't have any end condition. I intentionally did
not add a halt here uh just for fun because I want to see like kind of what happens with our computer. So we know
that we operate on 8bit values and the biggest value uh we can represent in 8 bits um is the value 1111 1111 and
that's equal to 1 + 2 + 4 + 8 + 16 + 32 + 64 + 128. So 255 if every bit position is enabled that
gives us the binary string of all ones and that is the maximum value we can represent here and that's 255. So what's
interesting is our program will work up to 233. If I continue it uh there should be no issue but then the
next value after 233 is 144 + 233 which gets you 377. But we actually can't represent that in
eight bits. Um, and so it turns out what happens is there's like overflow. But the computer we saw when we built the
ALOU, uh, if you have an ALOU operation that can't be stored in eight bits, it'll only capture the rightmost eight
bits there. So, um, what this means is that our program will not break. the computer will not
somehow explode or anything crazy like that, but we won't see 233. Uh we will go from 144 to 233 to something that is
less than 377. Um and it's unclear just based on whatever the binary string is, the rightmost 8 bits of 377. Um what
that is. So if I let this rip a little bit longer, uh let's go ahead and see exactly that.
89 144 233 and this is where we should fail. 121 is what we just happen to oops happen to get back to. Um and the the
computer won't break. It's just a matter of fact that we say like for our ALU the way it is designed is such that it
assumes that the result is always storeable in uh an 8bit value but that's not guaranteeable. Um and we have the
carry flag for detecting this entirely. So when we did go from um 233 to 377, if I had paused the
program, we would have seen that the carry flag was set. So what we could have done is recognize that hey, this is
the largest Fibonacci value our computer can handle. And so we could have done uh similar to how we used the jump zero to
end the program in the previous uh program uh in the counting sequence. What we could have done is used a jump
carry to end the program here when we know we get to the biggest value that is represented uh representable. I just
chose not to do that because RAM was running out of space. Um but you know, you can imagine if this
was something more defensible, uh you would probably want to do that. Um but I think this is probably one of the more
complicated programs our simple computer can make. Um, but I'd also like to remark on the fact that
uh this is pretty powerful. Like we saw a combination of zeros and ones for a counting program. And it turns out when
you interpret those as instructions for this computer, they do something interesting. And now we're seeing
something pretty interesting here where this combination of zeros and ones does something totally unrelated that's also,
you know, a little more powerful and very interesting. So this is really cool. um like something that was just a
few hours ago nonsense to you is now something that you can you know you can look at each of these positions in RAM
and figure out what the what the interpretation is and if RAM was loaded to that value on some hypothetical
computer uh that's what the computer would execute. Um, so that technically concludes the
explanation of how we built this computer and everything should be clear from a like ground up like and we'll
recap everything we've done eventually, but um it it wouldn't be right for me just to end here. Like I think there's a
lot of questioning around like we've invented this computer but like why do we design the instructions the way they
are and like why do we architect it the way we architected it and like how did I know to build this computer? Um, these
are all interesting questions and I actually built this computer physically. So, uh, the simulator I have here is
cool and all, but I was very interested when I learned this in school. And I found the series online of someone who
has a full instructional set, which I'll link because it's an awesome series that I highly recommend watching. And in this
series, uh, Ben Eer, his name is, uh, builds this computer, this conceptual computer, uh, from scratch. So, uh, I
actually built one and I can show it here. This is what mine looks like in person. Um, it's a bit messy and there's
a lot going on. So, let's let me give a few, uh, few bits few minutes of context to explain what's happening here. So, if
you look just on the display, uh, you can see this program is just doing counting down. Um, my video, I guess I
just happen to start it at the number 71. And then by the time it loops around, uh, looks like it's down to like
something like what was that? 59. Um, but there's a lot of components here. So, let's break it down.
The first is this clock up here. Um, and I guess as you as I walk through this, you'll see that the way this is
physically built, i.e. the layout of all the components is virtually identical to what I drew on my iPad uh throughout the
video. And we'll see that like that also came from somewhere. Um, I guess the ordering of the components physically
doesn't matter, but I I kept the continuity there to help uh make the understanding easier. So, this is the
clock module. Uh, you can kind of see this is the value of the clock and it's just constantly pulsing. This thing up
here is a little dial and if I were to twist it, I could increase or decrease the uh speed of the clock. And then this
clock is just always pulsing up and down. um kind of like the uh you know the autoplay mode I had in our
simulator, but you don't always want to auto play. Sometimes you want to step through specific high low clock cycles.
So there's a switch over here and if you switch that it turns off the automatic clock and lets you just pulse the clock
using this button right here. Um so every time you push the button it'll go from low to high and vice versa. So
that's pretty cool. That's the clock. Here's our program counter right here. You can kind of see it's counting up. uh
if you repeat those binary numbers and then it jumps uh back down. So it's probably like the subtracting loop is a
loop. We saw that in our subtracting program. And then these are the flags registers. So uh the wiring gets tricky
to trace here, but uh you can kind of see that there's this these wires here. Uh this is a register chip and that's
the it seems to be the output of the register chip and the input on top comes from this wire right here. Um and this
is the ALU. So let's talk about that next. This is the mass of ALOU. We have uh the A register right here. And these
lights show the value of the A register. And then we have the um B register should be right here. And
then this light right here. Uh I can't quite recall if it's showing whatever's on the B register or the output of the
ALU. And then you can kind of see that this thing in the middle is the bus. So the same way, here's our connection of
the ALU to the bus, the B register to the bus. Here's the ALU to the bus. Here's the A register to the bus. And
here's the program counter to the bus. And then these lights up here show you the value in binary that's on the bus at
any given time. Below the ALOU, we have our output display. So we have four seven segment
displays chained together. This is the readonly memory and this is the register. Um so the value in the
register feeds into this read only memory which decodes which lines it should set on each of the displays to
show the value that we want. Um and similarly that also connects uh into the bus.
So then let's jump over here. This is our memory address register the 4bit memory address register and that reads
in from the bus. And um this is our entire RAM unit right here. And then this would be the uh output of the RAM
unit. And then there's something interesting where in our simulator we were kind of just literally clicking
values to punch in updates to the memory. Uh in real world, we actually have these these manual switches up
here. And I can swap. Uh so if I if I if this light is green, it means the computer's in execution mode. If I flip
the switch, uh, it'll actually instead of looking at the value in the memory address register, um, or the value on
the bus, I can use these these blue switches over here to punch in a specific address and then punch in a
specific value. And then if I pulse this button over here, it'll latch that value into a specific address in RAM. Um,
these are really small and honestly a very big pain to use. Um, but that is how I loaded the program into memory. I
kind of have to flip the switch so it's on human program mode and then pulse through cycle through every address that
was used uh for my program, load the value and then punch it in and then swap the address and rinse and repeat. And
then once that's I could flip the switch back uh and then also flip the clock to automatic mode and just let it rip. Um
this will show you the output. Uh so for whatever address is on the memory address register, the ALU is always
looking it up. So this is just always showing the output. You can kind of see like every time the value changes on the
input side, the value on the output side changes as well. Unsurprisingly, um here is our instruction register. So
the instruction register is eight bits, but the left four bits are the argument and the right four bits, sorry, the left
four bits are the operation code, i.e. which instruction we're executing at any moment in time. And the right four bits
are the uh argument to that instruction. So I used separate color lights to denote the two. Um but you can see it's
just backed by register and that also connects to the bus. And then here is the meat uh of our computer, the brains
that tie it all together. And that's the control logic. So this is the control logic. These are readon memory chips
that I literally have to take apart out of the computer and program and then punch them back in here. If we look at
what the inputs are here, um you can see that um we have the uh four bits of the instruction register. So the operation
code and then we also have this five-step uh counter that represents which step of the current instruction
we're on. The way it's implemented in person is a little bit different than the way I explained it. Uh but the
concept is roughly the same or it's it's it's fundamentally the same. So it doesn't matter. I didn't want to
introduce this technically uses a different type of counter. And the way it's visualized is whatever step you're
on, uh, that light will be off. The clock's going so fast that you can't really tell. It just kind of looks like
they're flicker. But if I were to tune down the clock speed, you'd watch a left to right pattern and then it' reset uh
in terms of which one is the quote unquote off. And then the uh the value from the flags register is also hooked
in here. Um, and then we can see that these yellow wires are just going everywhere because that's the the
control output like when we say like RAM out or memory address in or program counter increment like that has to go to
each of the components. So you can see like some of these yellow wires going to the program counter, some going to the
memory address register control, uh some to the ALU operands, uh some to the output display, and then for debugging
purposes, I also duplicated it so I could actually see at any step what lines were being set by the control
logic. So that's right here. Um can't really tell, but there's a label. So like this leftmost light is on if maybe
like the program counter increment line is on. um in the simulator we built that roughly controls to being able to
visualize this right here. So this is the computer. Um I built it over the pandemic. And the reason I even
came across this is I I learned about it in school for a class I took for my computer science degree.
And uh the class uh we didn't build this computer as part of the class. But we did learn how this computer architecture
worked uh as a significant portion of the class. But the professor of that class is also not the one who invented
this computer. Um this computer has a name. I've probably let it slip a few times and maybe you could have seen it
already over here in the naming. This computer is called SAP 1 SAP1. Um, let's go ahead and Google it.
Go straight to Wikipedia because we've been using that. And this says, "The simplest possible computer is a
simplified computer architecture designed for educational purposes and described in the book digital computer
electronics by Albert Paul Molino. Um the SAP architecture serves an as uh for building and analyzing complex
logical systems with digital electronics. So this is an old book. Um, and then, uh, interestingly enough, this
Wikipedia also talks about, uh, Ben Eer, the individual whose video I watched to learn how to build my computer here. Um,
but let's actually, uh, go ahead. If you, Turns out, if you Google the book name, um, it's old enough that it's just
online, uh, on the, uh, archive. Um, and I mean, you can see this is kind of an old book
here. Uh it seems as though this came from 1993 or at least this version of the book. Um so this is old here but uh
what's really cool is this book kind of does a lot of what we did in the video from scratch which is taking no
understanding of how computation works and building a fully functioning computer that demystifies a lot of the
blackbox of like what is happening on your phone or on your computer under the screen or like in the internals. So if I
skim through the chapters here before he even gets to talking about the computer architecture um you see it it starts
with number systems and that's where this video started. We started with you know binary how do we convert from
binary to decimal um and you can kind of see that a lot of that is here like decimal to binary and vice versa. And
then after that we talked about logic gates you know we we know about the inverter gate the orgate andgate um the
some of these other logic gates we also covered like we know about NAND or the exclusive orgate because we needed them
to all build this computer. Then uh we talked about some of these without naming them. Um but like
multiplexers we came across for our computer and same for here uh like some of our implementation our intuition for
implementing the ROM was using actually some of products I just didn't formally say it cuz I didn't want to bog down
with terms um and once we had an understanding of logic gates we went ahead and built the ALU uh and that was
able to support bi binary addition subtraction and we learned about we started with half adders and then
chained those together um or we got half adders and we chain those with full adders to build our ALU.
Um, and then once we had the ALU working, we moved into sequential logic. Everything we had been looking up until
that point was deterministic. Uh, sequential logic was flip-flops and latches and how we actually hold data.
And you can kind of see that's like a fundamental necessity to implement our computer because that's just like the
crux of RAM and register is the need to be able to persist data. um that was possibly the output of some other
deterministic circuit. After that uh you know these are some of the other registers. We didn't really go into too
much there but then we talked about memories. We talked about uh RAM for sure. We talked about readonly memories
and then we also talked about the history of some of the readonly memory types. We talked about programmable
readonly memories and electronically programmable readonly memories and then EROMs
and that was like the foundation of all and then SAP one is the computer we actually built and that's where our
context cuts off. The book keeps going and there's a lot more here. Um I've never read this book but it's probably
pretty great. Um, it's very impressive. But the reason it's called SAP one instead of just SAP, SAP standing for
simplest possible, uh, is that the author finishes the computer we constructed and then builds two more
variants that are just more complicated and get you closer to more modern computers. And we'll talk about some of
those changes at a high level. Um but if we actually jump to the chapter uh starting on page 140 for SAP one
um here is what we see uh and you know skimming this I didn't read this too
closely either but uh the architecture is is pretty identical like we have the program counter we have memory address
register we have a RAM um and you can see the diagram is even identical to the one we designed when constructing our
computer like you have two operants to the ALU we had a program counter we had a memory address register hooking into
RAM and then we had an instruction register down here uh with the readonly memory to decode uh whatever the input
is to it and then an output register with a display. Um and one thing that I do find interesting is if we hop down to
the instruction set of the computer uh the author here only implemented few instructions. So there's quite literally
only a load a an add subtract an out and then a halt. We have implemented uh far more instructions
than what is presented here and some of them are very interesting. Specifically, the jump instructions and the
conditional jumping instructions are what allowed us to build out uh both the the counting down program and the
Fibonacci sequence. But what's cool is that we were able to implement those instructions without making any real
changes to the hardware. Like we didn't add new components that aren't in this architecture specified up here. you
know, we were just able to manipulate with incremental changes to what we had already built uh to get the computer uh
the instructions that we wanted. So, this is super interesting. Um I don't think it's probably worth reading the
entire book here, especially since we've covered most of what in my opinion is the fundamentals. Um, but I felt like
credit was due because I did not invent this computer and uh it's very impressive that this computer exists and
I'd say it's very effective um for its goal of building like educational purposes and explaining how computers
work from scratch. The computer we designed is great for educational purposes that we as we just talked
about, but it's still pretty abstract and there's a pretty big gap if I ended the video right here of like how does
this actually scale to a modern computer. So, what I want to use the rest of the video for is showing like
what a lot of this looks like in actuality and how we take this hypothetical hypothetical computer we
invented and scale it up to something more modern. Um, I want to start by taking a look at the very first iPhone.
Uh, I remember when the original iPhone came out and the first time I played around with it, I was amazed. And I
think iPhones are interesting to look at because they have a lot of computing constraints, uh, but are still pack an
impressive amount of technology into something that fits in the size of your hand. um you know they're striking
things like battery life and then limited storage but then also pack a lot of rich features like very high quality
displays and they appear to be very responsive and you can do several things at the same time. You can take a call
while you're browsing the internet and uh for that I think iPhones are pretty interesting. So, I'm going to hop over
to the uh the Wikipedia for the first generation iPhone, and I'm going to make a claim
that this iPhone and every computer iPhone out there has a processor that is not too fundamentally different from
what we've designed. You can imagine that these phones are obviously capable of a lot more. uh the only output our
toy computer has is a simple seven segment display versus you know iPhones have an entire screen they can draw to.
So I want to compare the instruction set of the computer we built with that of the iPhone um and see like what about
that instruction set is different that lets like lets us scale up from a toy computer to something that's that's
actually tangible. Um so looking at this Wikipedia page there's a few things that stand out. The first is um this iPhone
has the original iPhone has 128 megabytes of RAM. So what is a megabyte? Let's start off with a bite. We know
what a bite is. A bite is just eight bits. And um our our RAM that we designed had two to the four uh
different slots. We had four bit addressing. So we had 16 bytes of RAM in our computer. Now a kilobyte is a
thousand bytes. the hence the kilo prefix. A megabyte is then a thousand kilobytes which equals a million bytes.
So we have roughly I'm saying roughly here 128 million bytes of address base in our RAM compared to the 16 that our
toy computer had. So two things come to mind. Um in one dimension this is a lot of memory. You know, we saw that the
programs we built used almost the entirety of the memory of the SAP computer, but that's not going to be as
much of a concern if we have 128 million uh slots of RAM. The second thing uh is that the same amount of RAM, we could
argue that it's a lot relative to the computer we've constructed, but it's also trivial when you compare it to to
more modern phones. um or the fact that if I take a picture on my iPhone and I took one right before this just for sake
of illustration the size of that picture is 2.5 megabytes which is a lot you know this on one hand we want to say this is
a lot of memory but given how much memory is scaled up in modern phones this is no longer a lot and your phone
has uh several gigabytes of RAM which is an order higher uh on on the dimension scale a gigabyte would be uh thousand
megabytes Um, scrolling down though, that's just one thing I wanted to call out. Scrolling down, there's a lot of
um, a lot more technology packed into this phone. So, for example, we obviously know there's a touchcreen, but
then there's also a microphone, which you need to make calls. Then there's Wi-Fi and Bluetooth. Wi-Fi for accessing
the internet, Bluetooth for connecting to other devices like headsets. Um, and our computer that we designed doesn't
have any of these. So, if I'm making the claim that the processor of the iPhone is not too fundamentally different than
the processor we described, uh, one of the questions I would eventually like to answer is how do we bridge the gap and
get these things like how do we get internet connectivity? How can we even draw to a screen? How do we know when a
user touches on the screen? Um, and these are questions that I want to get to in the remainder of this video. But
let's keep poking a little bit further and let's actually look at the processor that's on this phone. Um, so the second
sentence here says it features a Samsung S5L8900 SOC. Um, and SOC just stands for system
on chip and it generally means that you have a lot of functionality baked into one chip rather than having uh several
isolated components. So one chip could have the CPU, i.e. everything we've built thus far in the video and maybe uh
the networking chip, the chip needed to like make internet requests versus more traditional computers would have these
things be totally separate and they just be connected via wires. Um so let's read into the the system on chip that this
early iPhone used. And um we can a few things stand out here. Um obviously these Wikipedias are very
informationrich. The first thing I wanted to call out is that let's read from here. The default clock rate of the
CPU is normally 666 MHz but has been lowered by Apple to about 12 412 MHz. So what is a megahertz? Let's start with
what a hertz is. A hertz is just a unit of measurement for frequency. And roughly speaking or actually precisely
speaking uh a hertz just means a cycle per second. So in the computer we built you know the the clock we can crank it
up or down to make the computer run faster. And I think roughly in its uh in the maximal video I showed demonstration
it was probably running at like two or three hertz. Uh I think we we're just going through a few few clock cycles
every second. Um a megahertz actually is equ one megahertz is equivalent to a million hertz. So, what this is saying
is that um the chip itself that's used in the original iPhone can can hit roughly 666 million clock cycles per
second. Um and Apple has toned that down to about 412 uh million clock cycles per second. A logical question might be why
would they ever lower this? You would assume that by making the clock run faster, it can execute more instructions
in the same amount of time. So there is an incentive to make the phone feel as fast as possible by cranking up the the
clock frequency. But it turns out uh there's nothing free, no free lunch. And that applies to computation as well. And
that phones are constrained and one of the obvious constraints is battery life. Yes, cranking up the clock frequency
will let you do more instructions per unit time, but uh it also means that the computer is going to be doing overall
more work per unit time, which means it has to consume more battery to do so per unit time. These logic gates at the end
of the day that we're challen you can crank the clock frequency up, but it's going to eat at battery. So,
there's a happy medium where you want to push it lower low enough where it's not uh too slow for the user. Um,
at the point where we can try and maximize battery life for the phones. Um, but that's just something I wanted
to call out. The other thing which isn't labeled explicitly here is our SAP used five clock cycles per instruction.
more modern processors, including likely this one right here, even though it's no longer considered modern by any
standard. Modern in the scale of computing. How about that? Um, they actually beat this. They can do one
instruction per clock cycle. And uh I don't want to make things too confusing because this might not even be
uh easy to wrap your head around, but a lot of modern processors can actually do multiple instructions per clock cycle.
Um, so that's pretty interesting. But let's continue and in and looking at the CPU itself. So
um the first thing is that uh let's just read the start of the sentence. And there's a lot of tech like technical
terms going on here. So I'm just going to read these first uh five or six words. The SL S5 L8900 contains a 32bit
ARM V6 compatible CPU core. What does this mean? So, let's start with the 32-bit. Um, our computer that we built
was an 8-bit computer, which means fundamentally every value we reason about is 8 bits. So, all of our
instructions were 8 bits. The bus itself was 8 bits and every value in RAM was 8 bits. But we saw how this could be
pretty limiting because it meant you had four bits to represent all your instructions, which is not that many
different instructions. That's 16 different instructions. And then every uh instruction that took an argument
used four bits to represent the argument which meant you were pretty limited in how much you could express. 32 bits uh
is a lot more. So if I uh 2 to the 4 our our 8bit computer um had 256 different values it could represent. Uh it's if we
raise this to the 30 32nd power this is incomprehensibly large. Um that would be 4 bill294,967,296
uh you know different values we can represent in in RAM alone specifically. Um, so a lot bigger. But then again,
this might feel big relative to the computer we've constructed, which is 8 bits, but a lot of modern computers
actually use more than this 64-bit architectures or 128-bit architectures. But as we saw with the case with um
clocks, you might think like, hey, why would we not just try to scale this up and use as much as possible? But phones
aren't just constrained by battery, they're also constrained by memory. So, um, if a a computer that uses 64-bit
architecture versus a computer that uses 32-bit architecture, well, in a 64-bit
architecture, each instruction is 64 bits. But in a 32-bit architecture, each instruction is 32bits. So, with the same
amount of space, you can represent twice as many instructions on a 32-bit architecture compared to a 64-bit
architecture. So there's there's also this trade-off of like more higher bit architectures mean you can be more
expressive and you have more data available or more memory available to work with but it also comes at the cost
at like an increased cost of complexity. Uh which pairs poorly with the fact that just phones are inherently constrained.
Um but what is ARMv6? Uh and that's an interesting question. Um let's Google it.
So ARM Let's read this together. ARM is a family of risk instruction set
architectures for computer processors. Okay. Well, so ARM is not a single instruction set architecture. It's a
family of instruction set architectures. But what does this risk mean? Risk stands for reduced instruction set
computer. And if we read the definition here, it's an architecture designed to simplify the individual instructions
given to the computer to accomplish tasks. Um and what this means is that a reduced
instruction set computer might require more machine code to accomplish a task because the individual instructions
perform simpler operations. So risk generally is something that you see a lot of uh phones or just resource
constrained devices such as like embedded systems use uh because they're they're generally more compact uh but
that also means they're less expressive and less powerful compared to to non-risk architectures. Um, but
due to their low cost, let's jump back to the ARM Wikipedia. Due to their low cost, low power consumption, and low
heat generation, ARM processors are useful for light portable batterypowered devices including smartphones, laptops,
and tablet computers as well as embedded systems. So, ARM is very intentional. the architectures, the instruction set
architectures that they introduced are specifically meant for resource constraint environments and they
explicitly call out smartphones as being one such relevant use cases. Um so if ARM is a family of instruction set
architectures, let's see how many there actually are. If I scroll down here, you can see uh
things obviously or things maybe you would have guessed started with ARM v1 and then we went to ARMv2, V3, V4 and it
keeps going and these these keep coming out. I guess the uh I guess the latest is ARM ARMv9. Um but these
architectures, these instruction set architectures are all technically different uh but are somewhat related to
each other and they're usually compatible. And the original iPhone uses an ARM v6 architecture. And this is the
actual instruction set uh that the iPhone uses. So, this original iPhone, I guess just any of them, um, if you're
running any application or doing anything, the code for, let's say, like if you were running Twitter on your
original iPhone first generation, um, all the code that builds up the Twitter app is just a combination of ARM v6
instructions and as is the case for everything that your phone does. So all the code that
represents the ability for you to send texts or make phone calls or uh browse the web all boils down to ARM v6 uh
instructions. So you might think like hey like this must mean that like ARM v6 must have a ton of instructions and you
know maybe there's an instruction to uh send an internet request out or an instruction to make a phone call. Uh but
it turns out when we actually look at the architecture uh we don't see any of that. So what I'd like to do is actually
take a stab and go ahead and look at the architecture. And I also want to keep things uh not too abstract. I guess
everything we've done so far has been pretty abstract. I've showed you the computer we built, but we weren't able
to verify that in the video itself because I don't have access to that old computer. Um, so what I do have is
something called a Raspberry Pi Pico. And it is a small little microprocessor. It's like this. It's about the size of
my pinky. Um, and something interesting about it is that its CPU also runs uh the same instruction set as the original
iPhone. So this this also runs ARM v6. So, what I'd like to do is let's go ahead and take a look at the uh ARMv6
instruction set. It gets very complicated. Let's just take a poke at a high level and then let's actually write
some code for ARMv6 and run it on this processor and and see if we can actually get some code to execute that does
something that we can see with our own eyes. gone ahead and found the technical reference manual, which is pretty much
just a guide for the processor that this Raspberry Pi Pico has. And um just you can see how old it is. This was
published in 2009, had two modifications, and hasn't really been updated since because we're on to much
more modern processors, but this is still a great example. So, we're not going to go too deep into this, but I do
want to poke at the instruction set. Um let's start with the introduction about the processor.
The Cortex M0 processor is a very low gate count highly energyefficient processor that is intended for
microcontroller and deeply embedded applications that require an area optimized processor. So very low gate
count. Gate count is literally referring to the logic gates that we use to build our computers. Generally speaking, the
more logic gates a computer has, the more complexity and more battery consumption uh you can expect it to
have. So having a low gate count is especially enticing for um these like constrained environments and that goes
hand inhand with being highly energy efficient. Um, it is designed to use as little as little electricity as
possible, which makes perfect sense for a phone. Uh, where, you know, especially back in 2008 when this first generation
iPhone was released, uh, 2007, sorry. Um, discontinued in 2008. Uh, you know, you could imagine batteries, they're not
even great today. Um, so they're quite quite worse back then. But, um, let's jump back to the docs. Uh so let's look
at this and see the instruction set summary. So
let's read this. This processor implements the ARMv6 thumb instruction set. And ARMv6 is the architecture we
saw earlier. And the thumb means that uh it's actually a even more compact. ARMv6 is already designed to be pretty
minimal. Thumb makes it even more minimal by reducing a number of instructions that are always 30 32-bit
on general ARMv6 architectures down into 16. So our computer had straight 8-bit instructions. This computer has a
combination of 16-bit instructions and 32-bit instructions. And this is pretty interesting because our computer was
designed to assume that everything we worked with was always 8 bits. But that's not strictly the case. Uh this
computer is a great counter example here. Instructions aren't always the same number of bits. They can actually
vary. You could imagine some instructions that don't have any arguments or have minimal arguments need
not use 32 full bits. Um and so that's there's some use cases where we can get away with 16 bits or just fewer than 32
bits. Um and if we scroll down, we can actually see
the instructions. So, I'll just scroll all the way to the bottom. And you can see there's actually not a ridiculous
number of instructions. I want to say there's like something around like 50 if I'm guessing here. And let's kind of
comb through and see. So, ARM in general and I guess a lot of more just instruction sets in general operate
with many more registers than our computer. Our computer had an A register and a B register, but really we only had
instructions to operate on the A register. And a lot of the instructions that touch the B register were just to
satisfy A register operations or things we'd like to do with the A register. Registers are really fast to access. As
you saw, they literally just existed. you know, registers, you could literally put a value on the bus immediately or
read a value in from the bus immediately versus RAM requires you to kind of put an address in the memory address
register and decode that, which is just slower. So, RAM is still fast, don't get me wrong, but registers are even faster.
So, for that reason, we generally like to work with registers just because they're really quick and easy to read
and write from and don't require that much like clock that many clock cycles to do to interact with. So ARM has a lot
more registers and they're general purpose registers meaning you can do whatever you want with them. Uh but it
just gives you more room for scratch work. So when we built our Fibonacci program we had a lot of intermediate
values like we like to store the previous Fibonacci number and the the second to last Fibonacci number. And it
would have been nice if we could have held those in registers, but instead we kind of had to keep on reserving spots
in memory and writing those values to those addresses in memory when we wanted to update them and then reading them
back into the A register when we needed to operate with them. Um, you can imagine if we just had more registers,
we could just denote, hey, like this register will just for our program will always just hold the the n minus one
number and this second other register will hold the n minus n minus2 number. Um so a lot of what the arguments you'll
see are actually operating on registers. So um if I look at this add instruction uh
it's saying you can pretty much add the values of two registers these two registers into
the and it'll store the result in this third register. Um or you can kind of see that this move instruction. It'll
move the value from one reg from this register into this register. And
that's kind of a bit of a different mental model. In our computer, we kind of only saw for the most part
instructions being addresses and RAM. But here we're seeing that an argument can actually be just another register
itself, like the the name of another register. Um, and then you can also see we still have some things that are
similar. So like we had a load immediate that would load a immediate value into our A register where the value was
encoded in the instruction itself. We still have that something pretty similar here. So you can see like this IMM just
means an immediate and you can see that there's a register that you could want to load that immediate value into. Um,
same same with some of our ads. So you could say, you know, add the value that's in a register, take that value,
add it to some add some immediate value, constant value to it, and then store it in a destination register. Um, so you
know, we can kind of see like there's move operations to move values from registers to other registers. You can
see there's uh this one's interesting. Move any to PC. So the pro PC is per program counter as we saw earlier. That
still exists here. We still have this mental model of the program counter tells us an address in memory and the
computer will interpret that address and the value at that address in memory is an instruction and try to execute it. Um
so we have operations for moving data between registers. We have operations for doing addition. Similarly we have
operations for doing subtraction. So you can kind of see like uh you can subtract um take the value in one reg in one
register subtract it from another register and store store the result back into a destination register or you could
have a immediate variant where you take the value in a register subtracted some constant value from it and write the
result back into a different register or maybe the same register. Um you can see there's like a you can
negate values. There's also a multiplication. We never built multiplication in our computer, but the
way we built the ALOU uh for handling addition and subtraction, you can imagine there's another component on
more modern computers that can also do multiplication. What's interesting is this architecture does not actually have
any support for division. So if you wanted to divide numbers, you'd actually need to write a algorithm using a
combination of these instructions uh to to to get to to have division. you have to implement division yourself. Um you
also have instructions to compare the values uh in two registers or compare the value in a register to an immediate
and this CPU has the same concept of flags. So the same way we had this jump zero and jump carry instruction on our
computer that looked at the flags register that was determined based on the last ALU operation, we have
something similar where you know there's flags and certain instructions can update the flags.
We also have something called logical instructions which are like the logic gates that we designed earlier but
they're actually instructions. Um and we didn't have this in our computer. So let's take a look at this ands andds
instruction. What this does is it'll take two registers as arguments and those are 32-bit values in each register
because this is a 32-bit computer and you'll get a final value that it'll write into a different register. And
what the final value is is also a 32-bit number where each bit position is the bitwise and of the two bits in the same
positions of the operand argu uh registers. So uh the rightmost bit position in the output will be the
rightmost the rightmost bit position of this register bitwise and with the rightmost bit position of this register.
um and that's like the andgate and we have analogous versions for the exclusive or the bitwise or um or even
negation. Then we also have a family of instructions for shifting. Um
we haven't really talked about shifting so I'll kind of leave it alone for now but we'll actually see a program where
it's kind of useful. Um but pretty much you could imagine I can explain that like shifting uh if we had like some
binary value uh 0 1 1 0 if we left shifted it once it would actually just shift all the bits over one to the left
and then add a new zero. So we'd end up with 1 1 0 0 that'd be a left shift by one. Similarly if we started with a 0 1
1 0 and right shifted we'd end up with just 011 which is the equivalent of 0 011.
So that's interesting. Um, and then we also have these load instructions which will let us load values from RAM into
registers. And uh, what's interesting, we'll see this again shortly. The arguments could be literal addresses in
memory or you could actually have that a value um, stored in a different register is meant to be an argument. And so you
should take that argument uh take that register who is the argument go look up its value and treat that as an address
in RAM. Look that up and then write that back into some other register. Um so these are kind of our loads and you'll
also see there's like there's some syntax differences here. So we have three arguments for this one. And uh
pretty much what this is saying is it's saying take the at treat the value that's in register RN as an address and
then add some immediate value to it. So maybe you want to like look two addresses after that. So you could add
two and then it'll go look up that value at Rn plus two in RAM. Uh read that and write that into this register. Um then
we also have very similar variants for storing storing values in registers to RAM. Um we have a push and a pop which
we'll ignore for now. Uh and then we also have branching. So unconditional just means you will always branch. So
that's the equivalent of our just jump. You will no matter what the state of the computer is, you will jump to this other
address. Then we also have conditional ones where you know you can imagine like there's like branch if two values are
equal or branch if value is zero and that's kind of how we implemented our those are the equivalents of our jump
carry and jump zero instructions. Um, and then we have a few more down here. And these are kind of a little bit weird
like what is a supervisor call? What is an interrupt? Uh, what is an event? What is the concept of sending an event and
waiting for an event? What is the concept of yielding? We have no op, which we already saw previously, which
just does nothing. Um, and yeah, we're actually going to ignore these for now. Uh there's no way to not
look at this processor and see a ton of new stuff that we haven't already seen. But that's um this is this is more
advanced and we're not going to probably get into it uh for the scope of what we're doing right now. But it is
possible that a separate video will actually cover this specifically. So this is kind of an overview of the
instruction set. You might be scratching your head where these instructions, yeah, I guess semantically they're a
little bit different than what we built for our computer, but they're not that different. Uh so like where is the
instruction to you know write something to the display of the phone or something like that? Uh and it turns out that
there's just not an instruction for that. So you're probably even more confused now wondering well how does my
the processor in my iPhone actually draw to the screen of the iPhone? And we're going to turn out uh we're going to do
some keep poking this a little further. Turns out these instructions are actually capable of doing everything
that we actually need to draw on the display or know when the user touches the screen at a particular location. And
I'm going to jump back to my iPad and we're going to explain two concepts and then we'll jump back to the computer and
those two concepts will actually start bridging the gap of how our processor can do things that aren't just mappy.
Like everything we see right here is just kind of some variation of math or moving data. So we can implement very
pure algorithms, but we're not seeing how we translate that to very practical things that we'd want a phone to do,
like drawing a shape or, you know, showing some notification in a visual manner. Um, so let's go over the iPad.
Uh, it's a little I don't mean to be very jumpy, but there are two different concepts that I like to explain. Um, and
then we'll be able to jump back and actually write some code for our Raspberry Pi Pico.
Okay, so I'm back on my iPad and I said there's two concepts I want to teach you about. Number one is called Hex. So, uh,
the instruction set architecture where we've been working with that we designed, uh, right up here was all 8
bits. You know, our bus was 8bit, our RAM was all 8 bits, all of our registers were 8 bits. Uh, but now we're looking
at a 32-bit system. And binary totally works for 8 bits for saying, "Hey, our value is 1 0 1 1 0 0
just fictitious number." This works for 8 bits. It's fine. It's a little tedious to write out all eight bits, but oh
well, we have to do it. When it's 32 bits, uh, that's a big pain to represent 32 digits for every number. And we'll
see that not only is this pretty ver verbose, it's just not as readable. um the more ones and zeros you have chained
together the the less the interpretate the interpret interpretability becomes. So instead of writing everything out in
binary, what we're going to see as we look into this further and look at actual uh code for this architecture is
something called hex. Um hex is base 16. And recall from earlier uh we speak in base 10.
And what this means is that there's 10 different values that can go into every digit position. And those are zero, one,
two, three, four, five, six, seven, eight, and nine. Then we learned about base 2, which is binary. And two
different values can go into this slot, zero and one. So I feel like if I told you about base 3, base 4, or anything up
to base 9, you'd be able to reason about how to count and it wouldn't be that complicated. But when I'm saying base
16, this has some implication that there's 16 different values that could go in any
digit position, but there's only 10 different digits that we know. Like every number we know is made up of of
the the digits 0 through nine. So how can we represent 11 different digits, let alone 16 [snorts]
uh if we don't have that many digits? Um, and it turns out we start using numbers. So, let's count in base 16 and
see what it turns out, sorry, that we start using letters. So, let's count in base 16 and see what this looks like.
So, we have 0 1 2 3 4 5 and everything up through nine is going to be the same. We'd like to write the number 10 here,
but that is not onedigit position. That's twodigit positions. But we have to we're looking at single digit
positions. So if we run out of every number, the next logical thing that we can use is other symbols. And letters
are an easy symbol. We could start drawing shapes, but let's just stick with letters. So we're going to say A.
And we sort of implicitly know that A maps to 10. And then B maps to 11, C maps to 12,
D can map to 13, E can map to 14, and F can map to 15. And if we say that
these are this is the set of digits in B 16, that is 16 different digits. Um, and it might be weird to reason about
numbers and letters mixed interchangeably, but it pretty much means let's just look at an example of a
a hex number. Let's say we have um a e this hex number and I'll also say for binary for for base 10 we just write
out our numbers kind of we say like 762 the example number for binary uh we kind of prefix it sometimes with this 0B and
then we say all of our binary digits. You'll see that for hex we prefix with 0x.
So if you see zero x it means you have a hex number. So let's say we have ae. Well, uh we just need to figure out what
the contribution of each digit position is to our overall sum. So if we were looking in base 10, we had the number
56. Recall that this is 6 * 10 ^ of 0 and then 5 * 10 ^ of 1. Um so our analogous equivalent here is this is a *
16 to the^ of 1 plus e * 16 ^ of 0. And we know a is 10. So that's 10 * 16. And then we know e is 14. And 16 to the 0 is
just one. So we have 160 and 14. when we add those together, it's just 174.
So, this is not fundamentally too new from anything we've already seen, but I thought it warranted an example to show.
Um, and yeah, pretty much that means that for example, if I just say 0x 9, that's still 9. But if I say 0xd, that's
equivalent to the value of 13 in base 10. Base 10 being what we reason about as humans. So we can see that with a uh
each digit in base 16 holds one of 16 different values. Which means if we think about this um we know
that 2 to the 4 is equal to the^ of 16. So one hex digit equals four bits.
you need four bits to represent discrete values for each of these uh digits in the vocab. Um
but this makes you makes things better because it means we have a 1 to4 mapping. So when our computer works with
32 bits, it's actually just eight hex digits. So every address in our computer is
eight hex digits, which plays pretty well because that's the same number of digits that we had to to use to reason
about our um our SAP computer. So that's concept number one. Concept number two is called MMIO.
And I would actually say this is the crux of how the computer interacts with components that are not itself. So let's
say that our computer um and this this Raspberry Pi Pico that I'm holding in my hand, which you can't see cuz I'm screen
sharing has um an LED on it. So there's this CPU and we know everything about the CPU and then there's this LED. LED
just stands for light emmitting diode. It's literally just a light. Um, and the question is, how could we
possibly have our processor with the instructions we know turn that LED on and off? And so, let's break down what
mmio stands for. The first M stands for memory, then mapped,
input, output. So, mm IO stands for memory mapped input output. What does this possibly mean?
That doesn't put us any closer. Um, well, input output is kind of this arrow here. In our case, we're just concerned
about output. You know, we just like we'd like our CPU to be able to uh turn the LED on and off, but who knows? Maybe
this is maybe we'd also like to be able to read if the LED is on or not. Uh, so there could be input. Um, and so I guess
the crux of this is the memory mouth part. And with the instructions we saw in the computer we built and in the ARM
V6 manual that we just combed through, there's no way to possibly turn on this LED or something that's just totally
external to the processor itself. Um, and it turns out we use something called a memory mapped IO which is abstracts
parts of memory to interact with third party components. So, we're just going to pick an a random address.
Let's just say 0x. And I'm just inventing some 32-bit hex address. And pretty much what happens
is, let's say you write a one to this value in memory, then it'll turn on the light.
And if you write a zero to this value in memory, it'll turn off the light. And
how could that be possible? Uh it turns out the CPU is just designed these these addresses are just determined
arbitrarily. There's usually some consistency across like processors in the world. But um the CPU will interpret
any attempt to write. So any storing of register val at uh any storing of a value to that address um instead of
actually storing it to that address in RAM it'll have a comp connection to this external component and it'll use that as
the signal to go and turn on that component like give it power or not. So this is really powerful and I think very
interesting um because it does mean that the the implementation of the CPU has to be updated but you can kind of guess how
this would be done. You saw how instructions were decoded. So you can pretty much imagine that any storing
instruction has some extra complexity now where it'll check uh you know if you do some sort of store
then what happens is it goes one of two ways. you have to check the address and it'll either write to RAM
or it'll you know uh connect to some peripheral and we'll say operate on a peripheral
and a peripheral here just refers to any component of the computer that is external but still part of the computer
as a whole external to the CPU um something that can be interacted with via the CPU. So
in this hypothetical example, we'd say that if you write to this FFF AAA address, uh a one will turn will cause
nothing to actually be written into that address in RAM. Uh but it'll actually cause the light to be turned on. Um or a
zero will cause it to be turned off. And then we can just pick a different address. Uh let's just say like fff FF
uh O. And we'll say that if you try read any read
will give one if light on zero if light off.
So if we wanted in the CPU in our program code to know if the light was on, we would literally just load we
perform a load of this specific address in memory to a register, any register of our choosing. And when the computer sees
that we are trying to access this specific address, it won't actually go look it up in RAM, it'll talk with the
light component, figure out if it's on or not, and then put the corresponding value into that register. So this is
super powerful because it gives the illusion that we are just working with memory um but we're actually now using
memory to interact with things that are not just RAM. We've kind of violated this assumption that we had previously
where anytime we write or write uh a value to or from RAM, it would actually do some operation on memory. But this is
not the case. In actuality, uh only subsets of addresses uh will truly update RAM or read from RAM and then
plenty of others will be used to interface with external parts of the computer. So if we explain um how this
might look so we know you know this is just one example of one specific address maybe let's say uh a display
a display on a phone um can maybe be broken up into a grid of pixels. I'm I'm just ignore my dimensions here.
Let's also just for simplicity assume that um each pixel it's a black and white display display. So each pixel
is either on or off. Then what we might do is say uh start with a specific address in memory.
Let's say just hex um just this is maybe our starting address and then
our code would say if you want to turn on this pixel write on to this address and then the next address uh in memory
would correspond to the next pixel. So that'd be o o a1 and then this would be a2 and a3 and
a4. So there'd be a like whole continuous chunk if there's like let's say there's 50 different pixels here.
There could be 50 regions in memory um consecutive starting at this base address of hex a z. and your code could
draw an image or draw a line or anything by figuring out which of these pixels to turn on. So if it uh were to to write
ones into the first five addresses, you'd see like a line appear at the top of your screen. And so that's how our
our program code can actually turn on the display. Uh so this is kind of how your phone display would work. And then
conversely there could be there's several different implementation ways to actually figure out when a user touches
on the screen. Um but perhaps one simple way is just the computer can decide some address. Let's say ff
a621 just making up random addresses. and uh whenever your program code reads this
address, it'll have uh maybe encoded in there the coordinates of the last place the user touched the screen. So you
could constantly be reading from this address and see whenever it changes to detect if the user just touched the
screen somewhere. Um so this is what memory mapped IO does. IO stands for input output. It means pretty much
communicating with the external world or other components that are part of this computer but not physically part of the
CPU. And this mm memory mapped is means that it it appears to the computer to all be under the guise of memory read
and writes. Uh but in actuality your processor is redirecting a subset of those around uh to accomplish this
pretty neat behavior. So this is super cool. Um and unlocks the door to do a lot of things. You could imagine like um
the CPU, you know, can now interact with not only a screen, but it might also have a a some sort of
uh separate processor um that has internet access and then you know the CPU can now make internet requests. it
could write uh the address of a a URL in some register and then this it would get routed to the internet chip and then
maybe the internet chip could return some data about like what's at that internet address. So it's pretty
powerful. Um let's actually write a program on the Raspberry Pi I have that will turn on an LED using memory mapped
IO. So here's the microprocessor I've been talking about for the past 20 minutes. And you'll have to bear with me
because my computer webcam quality is not that great. But you can see this is the actual CPU. So it's analogous to
what we just spent the past several hours building. And one of the other things to note is all these pins coming
off the bottom. And uh a natural first question is like what are these? So again, the the image quality is just not
great here. If I put this close to the camera, you'll see that they're all labeled. And many of them have the
prefix GP and then some number following it. And what this actually is, these are GPIO pins, which stands for general
purpose input output. So each of these pins programmatically via memory mapped IO um can either be used to read in data
from that pin either a zero or a one or put out a value onto that pin a zero or one. And this is using real electricity.
So what I could do is take a specific pin, figure out what address in memory corresponds to controlling that pin, and
then write a one to it. And then if I have like a physical wire right here, I could literally connect that pin
and then plug this into a separate device. So maybe if I had like a light, I could connect it to to this other the
other end of this wire right here and it would turn it on. Um, and there's more interesting things that would be if you
wanted to use a pin for outputting. Maybe if we had a temperature sensor, um, or some sort of other device, uh,
like a button or something that when pressed, we could read in that signal. Um, it all works with memory mapped IO.
So, in your code, even though you know that, hey, this pin's connected to a light or a button or a temperature
sensor, uh, it appears as though you're just reading an address in memory. And one of the more interesting things, um,
that I just happen to have with me as well is an actual display that, uh, has a bunch of holes for
various GPIO pins to connect to. And there's some protocol which we won't get into here, but it lets you uh via memory
address manipulation in the Raspberry Pi write contents to a screen. So I have some text drawn on the screen here. Um
I'm building a subway tracker, but that's an independent project. And the code all exists on this Raspberry Pi and
it'll go to the internet, figure out when the subways are coming from my station. Um, and then by writing certain
contents to various uh addresses in memory, it can actually uh cause uh text to appear on the display of the screen.
So that's pretty neat. Um, so we have all these GPIO general purpose input output pins. And if I actually flip this
around, there's one other thing that's pretty interesting and it's this this copper like thing up here. And above it,
it says LED, which stands for light emmitting diode. And this is just a light that we can use to turn on and off
in our code however we want. Um, it's nice because we could, you know, write to a wire and then have that wire
connect to a light. I just don't have one. So, this computer, this microprocessor, sorry, conveniently has
a light built in. So, what we're going to do now is take a peek at figuring out how we can get code to run on this um
that would turn on the light. And then the last thing we need to bridge is that there's this USB port right here. So
that's how I can connect it to my computer and actually get code that I have on my computer, machine code
binary, uh, onto the device. It's a pretty intricate process in itself, and we're not going to concern ourselves too
much with it here. Um, but it is worth calling out like if I'm typing code on my computer, how do I actually get it
onto this microprocessor itself? So, let me go ahead and tab back to my computer. So, back on my computer and I'm actually
going to start with the solution. And I will claim that if you run this set of instructions on your computer on this
processor that I'm holding in my hand, it will boot up the computer and turn on the light. Um, so let's take a look at
what this is. These are various addresses. Recall that the computer's 32-bit uh
addresses. And so these are actually hex addresses because they're more compact but equally as representative. So two,
three, four, five, six, seven, eight. Eight hex characters in each address where you know each hex character
represents four bits of data means 8 * 4 is 32. So these are equivalent to 32-bit addresses. So that's consistent with our
expectations. And then what we have right here is the literal value that is stored at each of these addresses.
And it's also again in hex. Um but this is a little uninterpretable because it's we know the binary uh is not necessarily
intended to be interpreted as a number but rather as some instruction that encodes an operation with some possible
arguments. Um so there's semantic meaning outside of the digit uh numerical interpretation and that's
exactly what we have over here on the the right column. We have uh a bunch of uh instructions. So we haven't seen any
of these instructions because we only skimmed through the uh instruction set architecture but we'll give some
motivation as we walk through these. So you can see like this is an instruction, LDR is an instruction, bic's
instruction. You can see R1 uh and R2 and R3 and so on are all different registers that we have accessible to us.
Some things look familiar like PC we know is the program counter. Um and then these are all instructions.
We can kind of see down here uh where it says this word. We'll see this shortly, but these are actually used to store
values. Um so the same way in our Fibonacci program we stored values in certain addresses in RAM, we do the same
thing here because sometimes an instruction can't be uh with as many bits as we have can't represent a full
op code and a 32-bit argument uh in the constraint of a 32-bit instruction itself. So we'll see similar tricks
where we store uh addresses in certain addresses in certain part uh positions in RAM or arguments that we can't encode
instructions themselves. Um but if I ran this on the computer, it wouldn't be that satisfying because we haven't
really explained what we've done. So there's two questions. Um one is what is this code doing? And two is if we
understand what the code is doing, how did I know to write this code out? And to answer those questions, I actually
turned to the uh documentation for this processor. And at a brief glance, if I look at the table of contents
and scroll all the way to the bottom, we can see this is 640 pages. So this um pretty small processor
somehow has a lot of complexity going on uh to justify that much documentation. And it turns out this is a pretty
featurerich chip. And we're only going to touch this scratch the surface here. Um, but this documentation has explains
everything we need to do to actually turn on the LED we're interested in. So, I'm not going to read everything in too
much granularity here, but what I will do is jump around the various sections. So, the very first thing we need to do,
um, if you read this is come over to this chapter and let's read this. The reset controller allows
software control of the resets to all peripherals that are not critical to boot the processor. Um, and this is a
lot of jargon. What this means is that when we turn on the computer, uh, only a subset of functionality is enabled and
that subset of functionality is exactly what it needs to get the computer booted. You can imagine that that LED is
not necessary for it to start up. So most external things that aren't strictly necessary for the computer to
get running are actually turned off by default. So there's a um particular address in
memory that we can write data to that will let us turn things on. So the first thing we need to do is tell that our
processor like hey we actually do want to interact with these GPIO pins. Um, and we can see that the documentation
says three registers are defined and there's this reset one. This register contains a bit per each peripheral that
can be reset. If the bit is set to one, then the reset is asserted. If the bit is clear, then the re the reset is
deasserted. So what this means is we have a 32-bit register somewhere. Some address in memory can be interpreted as
the address by which we can interface with this register. And each bit position in that 32-bit value lets us
turn on or off some peripheral. If I actually come up here, I can see the one we're interested in is IO bank
zero. Um, that's just me telling you. You wouldn't have known that on your own without more reading of this
documentation. And you can see it's the uh fifth bit position um from this this reset register. So, we'd like to figure
out where the reset register is in memory. And if I scroll down here, um, we can see that it says the reset
controller registers. So there's several registers. The one we're interested in is just the reset one. Start at a base
address of this hex value. And then we're interested in this reset. So it says offset 0x0. What this means is that
you take the base address and add an offset and that gives you the true destination address. In our case, we're
adding zero. So it means the reset register is quite literally at this address. If we wanted to interface with
this reset done register, we'd look at offset hex 8. So we'd act add hex 8 to this value and that would let us
interface with this reset done register. So the very first thing we need to do is write to this reset register
and um set the bit position for IO bank zero which is the fifth bit position. set it
to zero and that'll let us tell the computer that hey like we actually do want to do some IO. So let's jump back
to the instructions and see what the first few set of instructions are doing that let us accomplish this. I'm going
to tell you what each of these instructions does. Um this very first one move s is moving a literal value of
32 into the register R1. So after this instruction runs, register 1 will have the literal value 32. But let's actually
spell that out in binary. Uh so we can see why that's happening. So this is 32 in hex. Um and the reason
32 is interesting is because where we see uh what we see is all the all the bits are zero except one. And that one
is actually the fifth bit position from the right. Uh which is interesting because we know that we needed to
eventually find some address in memory uh of this resets register and set that specific bit position uh to zero. So
that's just a little hint at where we're going. So after uh this first instruction executes, this is the state
of the machine. The next instruction um is LDR which stands for load a value into a register. And we're loading a
value into the register 2. And where what the value we're loading into is actually what uh whatever the program
counter plus 36 is. Look at that address in memory um and get the value from there. If we do the math, the program
counter because we're executing this instruction has to be at this address. So if we add 36 to it, uh we get this
address right here. And if we come down here, we actually see that that corresponds to this address right here.
And this should look very familiar because that is the base address of the reset register. So that's the value we
actually want to uh write into. So if I copy that, then the state of our computer. Oops.
Um, after the first instruction, I've done something wrong here. I' gone to a weird state there. Um, fix this up.
So, R2 now holds the address of this reset register, uh, where it's memory mapped to. The
next thing we actually need to do is read what the value in is. And so that's exactly what this next instruction does
where LDR says load into register 3 whatever uh the address in register 2 points to. So register 2 points to the
address of this this resets register. So we're actually going to load the contents of that register into R3. So R1
and R2 are the same after this command. R3 is going to look something like this.
It's a 32-bit value. We don't really know what each of the bit positions are except for the fact that we do know that
since the uh this LED is turned off at boot, um this fifth bit position right here is going to be a one. And what we'd
like to then do is actually rewrite that value um keeping every other bit position the same but flipping this one
to a zero. And so that's exactly what this bic instruction will do. Um it'll say this is the bitwise and of register
3 and the inverse of register 1. So let's break that down. Um let me go ahead and we know that register one has
this value. The inverse of register one would be if we flip every zero to a one and every one to a zero. And so that
looks exactly like that. And then we are doing um a bitwise and with register 3. So what effect would this have? Let's
just think about this reasonably. We said that register 3, register 2 and register 3 are still the
same. Um register three, I'm just going to move this out of the way so we can look at these next to each other.
Register three, we know that we don't really want to change the values in any of the bit positions. So a bitwise and
of any either a zero or a one with a one is just going to return the same value that's already in x. So in every place
that we are bitwising bitwise and an x with a one, it's just going to remain the same.
The interesting thing is this fifth bit position when we bitwise and it with zero it's actually just going to turn
off that one bit position because anything and zero through a logic gate is necessarily zero. So R3 becomes this
register where every bit position is unchanged except the fifth posit fifth bit position becomes a zero and that is
the value that we'd like to write back into this address in memory to tell our CPU hey we actually uh are we actually
do want to interact with these GPIO pins and so we've constructed the value that we'd like to write back and it's in
register 3 but we actually need to write it back to this address in memory uh for it to be usable. And that's what the
next instruction does. It says store um the contents of register 3, I what we'd like to write into the resets
register into the address held by register 2. And this is saying you can just add zero to it. So it's just the
same as not specifying anything. So what this will then do is actually um write this value this binary value into the
resets register. And so we've if we jump back to our docs here um we can see that you know if the bit is set to one then
the resets asserted. If the bit is cleared i.e set to zero then the reset is deasserted. So now this means that
we've written this value back in and have turned off the fifth fifth bit position. it tells the computer that we
actually do want to use IO bank zero. So that's our first step done. And I'm actually going to clear out all the
instructions we've seen until now just to keep things cleaner. Um so we've made sense of those instructions, but when we
tell the computer that we want to turn on IO bank zero, it actually takes some time to do so. And so we need to wait
until the computer fully acknowledges our request before we try to write to the LED. If we simply wrote this value
of saying, "Hey, I want to use IO bank zero," and then immediately turned on the light, it might not actually work
because the computer needs to do some stuff to actually enable IO bank zero. And so if I come here, there's this
reset bit reset done register, and it's the same structure where it says there's a bit for each peripheral except the
difference is that it gets set once the peripheral is out of reset. And there's an explicit comment that says this
allows software, i.e. the bite code that we are writing to wait for this status bit in case the peripheral has some
initial initialization to do before it can be used. So in our specific case there is some stuff that needs to happen
before we can just use the uh the LED. So we need to wait um for the bit position five in this reset done
register to tell us uh that it get that it we are safe to proceed. So the next question is where is this
resets done register and if we scroll back up uh oops or maybe it's down um we see that it is right here we get the
address. So it starts at the same base address that the resets register is at but this time it's hex 8 further away
from uh the base address. So now we'd expect the uh the address of this register to be 400C
008. And if I tab back to our code, we can see that that is exactly somewhere in
memory. Uh that address is stored somewhere in memory. So we can probably expect our code's going to try uh try
access that address in memory. So let's go ahead and do that. Um let's figure out what this next instruction does.
This next instruction says uh we are going to load the same fifth bit position mask into R2. So that's the
same little cheeky hack we did to get every bit zero except this fifth position index. Um because we're going
to do the same thing where we actually try wait and keep reading for that fifth bit position in this new register to
figure out when it becomes a one. And then we're going to see that we're going to load uh our next instruction says
load into register 1 whatever is 28 memory addresses below this current one. And that is actually right here
which is that exact address of this register that we're interested in interfacing.
So this is the address of the reset done register. Then what we do is we actually that's
our register one has the address that we need to read from memory. So we actually need to go read that value from memory
into another into another another register sorry. So that's what our next instruction does. It says load into
register 3 whatever the contents is at the address held in register one which is just the reset done register. And
that will look something like we don't really know. It may or may not be done by the time we read it. So
we can say maybe it looks something like this. Um except this one right here could be a
one or a zero. So I'm going to write it as a question mark. We don't know if it's going to be done or not. And so the
next thing we need to do is actually read that fifth bit position and figure out if it's if it's one or zero because
if it's one that means we are initialized and we can actually turn on the light. Otherwise we need to wait and
check again. So this tst instruction um is what will do that and it's called test bits. It'll do a bitwise and
between the two arguments. In this case that's register two and register three. And similarly it won't spit out a value
but it'll set flags the same way in our CPU doing an addition wouldn't would it would spit out a value but it also
implicitly update the flags register. This CPU architecture has the same thing where it has its own concept of a flags
register. So we already know that um bit like R2 bitwise anded with R3 uh it's either going to be zero if the
um if the LED is not ready or it'll be one um if it is ready because this question mark would be a
one. So one bit wise ended with one would imply that it is turned on one if ready. So we make that comparison and
then this branch uh this this next instruction says beq.n and
this pretty much means if the if the bit was a zero i.e. if the output of the last instruction was a zero we aren't
ready yet. So we need to branch um back to this address right here. So pretty much we'll check what the output of the
last instruction was and if it is a zero then we will jump to this address right here which is actually the one we just
came up from right here. So if it is zero what it'll try to do is actually just read that same register again and
do the same bitwise and check. Um and if it is a one then we just fall through this instruction as expected and we end
up here. So pretty much what this effect has is it'll keep checking the value of the reset done register until it spits
back a one which means at this point that we reach this uh this code right here the the LED is ready to use.
Let me go ahead and now clear out this code since we know what it does and we're
getting near the end but we still haven't done anything to turn on the light. Um, and so now that we've told
the computer that we actually want to use the GPIO pins, we need to go tell the computer how we actually want to use
the LED specifically. So the LED, if you read the documentation, corresponds to GPIO pin number 25. And there's several
different ways to interface with the GPIO pins besides reading or writing values to them directly from code. But
we aren't going to get into that in this video for the sake of our video. All we do want to be able to do is read and
write values to these as if they were just addressable regions in our RAM. So we need to go tell the code that that's
exactly how we want to use these. That is called SIO. Since we're single cycle IO um and it's required, we need to tell
the CPU that for GPO GP IO pin number 25, we'd like to be able to interface it with SIO, single cycle IO. And that'll
just pretty much allow us to get the remainder code running that lets us write uh to a particular address in
memory to actually turn on the LED. So I'll jump back to the documentation and we'll see that there is a uh a
register called GPIO25 control and it is a control including function select and overrides. So uh again I'm not going to
spell out how I knew to do this uh just for the sake of time but we pretty much just want to write the number five into
this register and the system will interpret that number five as uh SiO. So we need to figure out where this
register lives in memory. And we can see that uh the user bank IO register start at this base address. And then R number
25 has an offset of 0C8 or sorry 0cc. If I jump back in the code, coincidentally enough, this address lives right here.
So we'll probably expect our code to uh to do that pretty pretty to read that uh specific address and RAM pretty shortly.
Let's keep tracing what this does. So this will load uh the first instruction will load the literal value five um into
R2 because that's the value that we'd like to write into this register. And then the next instruction will load into
R3 whatever the value in RAM is at program counter i.e. current instruction plus 24 that turns out to be this
address that ends in 304 which is the address that we just identified of the specific GPIO25 controller. So that's
what R3 will look like. And then the last thing we do is we'll say store write whatever is in R2. So write the
number five to some address in memory and that address in memory is held in the value of R3. So this will actually
do the writing um write five into the GPIO25 control line. And so this will now tell
the computer we intend to use uh this GPIO25 which is the LED um in the normal quote unquote way of being able to read
and write from it as if it was just random or specific addresses in memory. So let me go ahead and clear that code
out. That the IO and GPIO stand for input output which means that each of the pins
can be used to either put in put out a value. So write a logical one or a logical zero or it can be used to read
in a value. So if it's connected to some circuit that outputs a value, it can read in a logical one or a logical zero.
In our case, we need to specify that um the computer doesn't know how we're going to use each pin. So, we need to go
tell it, hey, for this GPIO pin, we're going to use it either as input or output. For GPIO 25 specifically, that
is the LED. We know that it can only be used for output because it's literally connected to a light. So, we can either
turn that light on or off. But for consistency with how every other GPIO pin works, we need to go tell the uh
processor that we intend to use use it as output. So if I jump back to the docs here, there is this register um GPIO
output enable set. And if I jump into it, uh it kind of gives this implication that uh it's similar to the previous
registers we worked with where it's one register and each bit in it corresponds to one of the GPIO pins. So in our case,
we need to go ahead and tell uh the 25th bit position, i.e. the bit position that corresponds with GPIO number 25
that we intend to use it as an output. And we do that just by writing um writing a logical one to it. And so we
need to figure out where this register is. We can see they started a base register of hex D followed by a bunch of
O's. And then this specific one ends with has the offset hex 024. So if we add
those together, we'd get D 000000 several zeros and then 24. So, if I jump back to code, I can see that that
address lives right here. And what I'm actually going to do is come down here and clear out these three addresses
because we're not going to read them anymore. The code that that used them once has already been executed. By the
time we get to this top instruction, we know that we're not going to read those values again since we've accomplished
what we needed to. What we will do, though, is load um 128 into R3.
So if I do that, this is what the binary looks like. Notice that there's one bit position on followed by seven zeros.
We're not done with this. Uh it kind of is an arbitrary value for now, but we'll come back to that. Then what we'll do is
we'll load into register 2. um whatever address is hex 20 below what
we're at right now, which corresponds to this address ending in 308, which is the address of that GPIO um output set
register. Then what we'll do is we know we need to turn on the uh 25th bit but right now
the bit mask we have in register R3 corresponds to the uh seventh bit. There's notice how there's seven zeros
after here. Um so we end up doing is we want to left shift this content. I left shift means um chop off a zero at the
end and move it over to the right. We want to left shift 18 times. And that's exactly what this instruction right here
does. It'll say, "Hey, take register R3, its contents, left shift them 18 times, and then write that result back into
register R3." And so if I copy what R3 looks like after and R2 is unaffected by this instruction, we can see that um
this actually is masking the 25th bid position uh the one that corresponds to our GPIO number 25. And so what we end
up doing in this next instruction is storing the value in R3 into RAM at the address
specified by R2. So this writes into this um GPIO outset register. And so now we've told
the computer, hey, we want to use this specific register for output or this specific GPIO pin for output. The very
last thing we can finally do now is actually write a one to that pin so it'll turn on the light. So what we need
to do is jump back to the docs. And the register we actually want to write into is this GPIO outset. And the structure
is pretty much the same where each bit position corresponds to a GPIO pin. In our case, the 25th pos 25th bit position
corresponds to the 25th um GPIO pin. And if we write a one to that specific bit position, it'll turn on the LED.
So, oh, the last thing we need to do is figure out where the address of this register is. We see it has the same base
address of hex D followed by zeros. And then in this case, the offset is hex14. So, we'd expect the
uh address to be d some number of zeros ending in 14. If I tab back, that's exactly what this address and uh this
this value and this address has right here. So we see we in our next instruction, we load into register 2
that specific address uh hex 20 below where we are in the program counter. And then that's all that instruction
does. Then this store uh does the same write of this bit mask. And I apologize, I deleted R3, but it actually never
changed from the code above. So I'll add that back in. It'll store the same value that just has
a 25th bit position set to the address held in the R2 register, which is specifically the GPIO out set um
register. And so what this will actually do is after everything we've done, it'll actually finally go ahead and turn on
the light. But after we do this, the computer's trivial or I guess naive is a better
word in the sense that it executes this instruction. We want to end the program here, but the program counter is just
going to increment and keep executing what comes below it in memory. We want to halt our program effectively. Just
leave the LED on. So, this last instruction we have, I'll delete these two since we don't need them. Um,
is just a branch, an unconditional branch, meaning it'll always just branch no matter what, uh, to this address
right here. But this address is the one we're executing. So, pretty much all this line will do is keep jumping to
itself, which means the program counter can never make it further than what we're at. Um, which effectively is a
halt. The code can never make it to any other instruction in memory. So, what will happen if we put this computer uh
this program on our on our little microprocessor is it'll set up all the initialization needed to turn on the
LED. It'll turn it on and then it'll get stuck on this instruction. So, as long as it has power, it'll never do anything
else. So, what we can go ahead and do is actually put that program um onto the computer. And let me switch back to my
camera and let's see how it runs. So, I've gone ahead and installed the program on this uh microcontroller. I'll
skip the part of how I actually installed it because uh it's a pretty interesting side discussion, but
unfortunately with the timing constraints, I'm not going to be able to get to it in this video. But what I
should expect if the program we built is fully correct that as soon as I plug this in via the USB port and give it
electricity, the only thing that we should be able to perceive with our eyes, there's a lot more going on. We
obviously can't see instructions getting executed um in the same way we could with the old computer because it's it's
just so much smaller and there's not like all these these lights for our human interpretability. But what I would
expect is the LED right here that you can't see that well, that copper looking uh circle should just turn on. And
that's all that should happen. It shouldn't turn off. It should just turn on. Um, and so I have power. Let's go
ahead and do that. I'm going to go ahead and plug it in. And you can see uh it just turns on. And
if I unplug it, it kills power to the program. It's done. Obviously, it can't turn on the light without electricity as
a source. But if I plug it back in, it should rerun the exact same bite code that we programmed onto it in the past
half hour. Uh, which means that the LED should just once again turn on. And this is cool. The computer will hold the
program forever until it's rewritten. So no matter what power source I connect this into, as long as it gets
electricity, that light will just turn on. Obviously, this is a pretty trivial program. Um, but we could make this more
interesting. For example, let's say we wanted to blink the light. How would we update our program to do that? One
natural way is just pretty much we know how to turn it on and we know how to turn it off by writing a zero to that
same register effectively. Um, turns out there's actually a separate register, but that's a separate discussion. Uh,
the the concept is fundamentally the same. What we could do is just load a value, a really large value into a
register and keep subtracting it down by one and then once it gets to zero, turn off the light and then start the same
cycle uh the same sequence where you load a really high number, count it down to zero one at a time and once it hits
zero, turn the light back on. And the computer, I mean, it runs at a pretty fast clock speed, but if we're dealing
with very large numbers, counting down by one will actually take hundreds of milliseconds. um which means that it
gives the illusion of this light blinking and pretty much changing that number from lower to higher will mean
that the it'll slow down the blink cycle and if I make that countdown number lower it'll make it blink faster and
faster. Um so unfortunately we just got around to looking at this computer and there's a ton of interesting stuff we
can do. If I had more time, I would have loved to continue playing around with the GPIO pins and see how we can
interface with external sensors or even write, you know, text to a screen as I've done here. But, uh, unfortunately,
I'm bounded by YouTube's 12-hour upload limit. Um, and so this feels like the best stopping place I could reach for
this video. I wanted to take just a little bit to recap everything we've done. Uh, on one hand, we culminated
nearly 12-hour video with just turning on a light. Um, but the more you know about it, the more you realize like
that's a trivial way to look at things and there's actually so much more happening under the hood. So just as a
recall of everything we've done, we started off looking at binary. Uh, humans reason in base 10. We have the
digits 0 through 9. Computers only have the presence or absence of electricity. So we dumb our we we dumb it down to
base 2, which leaves the only digits available us to zero and one. But it turns out this is just as expressive as
decimal. Any number that we can interpret as humans can be converted to binary and vice versa. There's a one to
one mapping of decimal numbers and binary numbers. Once we had binary understood, we started looking at logic
gates and how we can make decisions by chaining logic gates together. And then we started looking at how we could store
data. We built the concept of a flip-flop and a register. So rather than a circuit just taking some inputs and
yielding an output, we could actually hold in a value. And then we started figuring out how we could chain both of
these ideas together to build more interesting things. We started off by building registers and then RAM. Um we
also built an ALU. We built the ability to add and subtract binary numbers using just logic gates.
And then we chain it together uh by adding a bus throwing in more registers and then the ability to interpret uh
instructions or values in memory as instructions and then physically with more logic gates in a readonly memory
execute those instructions sequentially. And then once we had that, we looked at a more modern processor which is uh not
too dissimilar from the original iPhone and saw how we could we looked at its instruction set and then also saw how we
could write programs for that computer. And um if we had more time, what I'd like to look into next is a few things.
Number one, how can a computer actually write to a display? I think you have a highle understanding, but we didn't get
to see it in practice, so I think it might be a little bit confusing. Number two is what actually happens when you
power on a computer. We said that RAM's kind of initialized as all zeros or random memory. So how is a program
physically stored on this chip and how is it such that when we actually give the computer power, it just starts
executing that program. Turns out there's a several really interesting things that happen early and this is
called like the boot sequence. Um and the third and last thing that I'd like to look into if I had more time in this
video is how computers run multiple programs at once. your computer, you can multitask, you can have a video playing
um while texting someone, while browsing the web. And uh the the computer we've architected here can only run one
program at a time. Uh but computers have this clever little trick that actually allows them to um give the illusion of
running multiple programs at once by quickly swapping between separate programs uh in quick succession so they
each get a little bit of runtime. Um, this video ended up being way longer than I expected. I'm not sure if anyone
will ever watch it, but it was awesome for me to make this. I am sure there's some gaps in parts of the videos. It's
my first time doing something like this, so if there's any questions, um, feel free to leave a comment. I promise I'll
monitor the comments and respond to anything that's ambiguous. And uh if time permits, uh maybe one day I can get
around to making a follow-up video if there's uh any number of people that actually watch this and have interest.
Um I don't know if I could do a whole another 12-hour video if no one's going to watch it. But yeah, uh thank you
everyone or anyone is probably a better word who made it this far. And I hope you learned something. This was probably
the most interesting part of my computer science degree and I think a lot of software developers out there don't have
a good enough understanding of what's happening at a low level. Um, but to take something that's very like foreign
and actually break it down and see what's happening down to the flow of electricity is very powerful. And now
you know that uh this computer I hold and most computers that you interface with are nothing more than logic gates
chained together in very interesting ways to manipulate electricity to do very interesting things. Um yeah, thanks
everyone.
Computers rely on electricity, which is naturally inconsistent. Representing data as simple on/off states (0 or 1) is far more reliable than trying to distinguish multiple voltage levels. This binary system (base-2) uses only two digits per position, making it a robust and efficient foundation for all computing operations.
To convert a binary number to decimal, multiply each binary digit (0 or 1) by its positional weight, which is a power of 2. The rightmost digit represents 2^0, the next to the left is 2^1, and so on. Sum all these products. For example, binary 1011 equals (1×2^3) + (0×2^2) + (1×2^1) + (1×2^0) = 8 + 0 + 2 + 1 = 11 decimal.
To convert a decimal number to binary, find the largest power of 2 that is less than or equal to your number, subtract it, and repeat with the remainder. Then, place a 1 in the position for each power of 2 you used and a 0 for any gaps. For example, 22 converts by subtracting 16, leaving 6; subtract 4, leaving 2; subtract 2, leaving 0. This gives 1 in positions 2^4 (16), 2^2 (4), and 2^1 (2) with zeros elsewhere, resulting in binary 10110.
Binary addition follows the same principles as decimal addition but with a base of 2. The rules are: 0+0=0, 0+1=1, 1+0=1, and 1+1=0 with a carry of 1 to the next column. If there is already a carry, 1+1+1=1 with another carry of 1. For example, adding binary 10110 (22 decimal) and 01001 (9 decimal) results in 11111 (31 decimal).
Computers use two's complement to represent negative numbers efficiently. In this system, the leftmost (most significant) digit is given a negative weight. This allows subtraction to be performed as addition of a negative number, simplifying the CPU's arithmetic logic unit. For example, in a 4-bit system, 1010 would represent -6 (as -8 + 0 + 2 + 0) instead of 10.
The video progresses from fundamental binary concepts to designing a complete CPU. It covers how a computer executes a single program, then explains how operating systems enable multitasking (running multiple programs simultaneously). It also explores the relationship between low-level hardware principles and high-level programming decisions, bridging abstract software design with concrete hardware implementation.
Keep this summary
Save it to LunaNotes and it becomes a real note in your library — editable, searchable, and ready to turn into flashcards or a diagram. Free to start.
Save to LunaNotesOr summarise for another video.
32K+ people have turned 65K+ videos into notes
“Very useful tool. I'm using it for the last couple weeks and I'm loving it so far. Makes learning on YouTube a great experience.”
This summary and transcript were automatically generated using AI with the Free YouTube transcript summary tool by LunaNotes.
Related summaries
Understanding Number Systems and Binary Conversion in Python
This video tutorial explains the fundamentals of various number systems including binary, octal, decimal, and hexadecimal, focusing on their significance in programming. Learn how to convert decimal numbers to binary using Python's built-in functions and understand manual conversion techniques for accuracy and better comprehension. Practical examples and homework exercises enhance your grasp on number system conversions essential for programming tasks such as working with IP addresses and bitwise operations.
Understanding Data Representation in C Programming
Explore how data representation works in computers, focusing on integers and binary systems in C programming.
Comprehensive Guide to Computer Architecture and RISC-V Overview
Explore the fundamentals of computer architecture focusing on CPU components, memory hierarchy, bus systems, and instruction execution. This session includes an introduction to various architectures like RISC-V and their practical applications in modern processors.
CS50 Harvard: From Binary Basics to Building Interactive AI Chatbots and Games
Discover Harvard's CS50 introductory course, exploring foundational computer science concepts from binary data representation to AI programming and game development with Scratch and Python. Learn how fundamental algorithms, data structures, and coding techniques empower you to solve complex problems and create dynamic applications, including chatbots and interactive games.
Understanding Integer Data Type: Size, Range, and Number Systems Explained
This summary explores the integer data type, its memory allocation, and how computers represent integer ranges using decimal and binary number systems. It also covers calculating integer range for different byte sizes, including the use of two's complement for signed integers.
Most viewed summaries
A Comprehensive Guide to Using Stable Diffusion Forge UI
Explore the Stable Diffusion Forge UI, customizable settings, models, and more to enhance your image generation experience.
Kolonyalismo at Imperyalismo: Ang Kasaysayan ng Pagsakop sa Pilipinas
Tuklasin ang kasaysayan ng kolonyalismo at imperyalismo sa Pilipinas sa pamamagitan ni Ferdinand Magellan.
Mastering Inpainting with Stable Diffusion: Fix Mistakes and Enhance Your Images
Learn to fix mistakes and enhance images with Stable Diffusion's inpainting features effectively.
Pamamaraan at Patakarang Kolonyal ng mga Espanyol sa Pilipinas
Tuklasin ang mga pamamaraan at patakaran ng mga Espanyol sa Pilipinas, at ang epekto nito sa mga Pilipino.
How to Install and Configure Forge: A New Stable Diffusion Web UI
Learn to install and configure the new Forge web UI for Stable Diffusion, with tips on models and settings.
Found this summary useful?
Take it with you. One click puts it in your own LunaNotes library.
Save to LunaNotes