In the previous unit, we talked about working with registers and memory, which is the basis of doing everything in, in low level machine language. And in, in this unit, we'll see things which are more familiar to high level programmers, like branching, variables, and iteration. So, let us begin with Branching. Branching is this fundamental ability to tell the computer to evaluate a certain boolean condition or boolean expression, and based on this value to decide whether, or not we want to jump, and execute some other section in our program. Or simply move on, and execute the next instruction in the program. Any programming language has various branching mechanisms. For example, if you write programs in Java, or in Python you have things like if else while, repeat, switch, and so on. In machine language, we typically have only one branching apparatus, and, and this, and this thing is called go to. And and here is how we we use this go to in in a particular example. In this program, we want to look at the certain data value, and decide, or determine whether, or not this value is, is positive, or, or not. So, we decide as a matter of convention that the value will reside in RAM(0). So, you know, the user of this program has a responsibility of putting a value in RAM(0), and then when the user press execute, the program spins its wheels, and at the end of the execution the program will put in R1, in RAM(1) either the value 1 if RAM(0) is positive, or the value 0 elsewhere. So, this is a typical if else, and unfortunately, we don't have a way to express this thing that directly in, in low level programming, low level program, programming is much more spartan and minimal. And therefore, we have to work hard to make this happen. In particular, here is some code that I have wrote, written to accomplish this, this operation, it's probably not the only way to do it, but it works. And I've used red ink to highlight the areas of the code that actually refer to this computation of if R0 is greater than 0, set R1 to 1. Let us ignore the rest of the problem for now. So, in between instructions 8 and 9, I'm taking care of setting R1 to 1, and in instructions 2 and 3, I check the value of the data input which is now residing in D. And based on this value, I decide whether or not I want to jump to instruction number 8, in which I will actually set R1 to 1. Now, if you are not sure exactly, how this works, I recommend that you stop the video, and simply run this thing in your head, or with a piece of paper, and convince yourself that you understand how the branching is actually working here. Now, as a matter of experiment, let me remove the line numbers, and also remove the documentation, and just stare at this piece of code, and if I will also remove the preamble documentation. I would argue that what we, what will remain is quite unreadable. You know, this is an, an example of cryptic code, very difficult to understand what this code is trying to do, and and yet, the code will work perfectly. You know, this is a flawless program, if you will load it into the computer after you translate it into binary code, it will work perfectly. It will deliver the signum obstruction, the user will be very happily, and yet, the program is not readable. Now, this is the perfect point to to invoke a quote by Donald Knuth. Who said that, instead of imaginaning, imagining that our main task as programmers is to instruct a computer what to do, let us concentrate rather on explaining to human beings, fellow programmers, what we intend, the com, the computer to do. This is super important because if we don't have this capability, if our programs will not be self-documenting, we won't be able to fix, and extend them. All right. So, what can we do to make this program more readable? Well, fortunately, we have a very nice feature in assembly languages, which is called symbolic references. So, lets take a look at what I did here. I have introduced a label that I made up, and I decided to call it positive because it is a sensible choice of, of a label because it describes roughly what i am trying to do. And this positive label appears twice in the code. In the first place that I want you to look at, I declare this label, and basically, what I am saying here is, here is a piece of code that I want to jump to, from some other place in the program. And I'm going to call this location in the code, POSITIVE. And when we designed the Hack assembly language, we decided that label the declarations will be made by a just writing the label, and enclosing it into round parenthesis. So, this is how we declare a label in the Hack assembly language, and this is how we use the label. So, instead of saying, @8, we now say, @POSITIVE, and it is the job of the assembler, and the translator to translate these labels into concrete numbers. And let us observe how this actually takes place. So, once again, the general rule is that @LABEL when tran, will translate into @n, where n is the instruction number following the (LABEL) declaration. Let us remember, that instructions have implicit numbers, and, and here they are. So, when I take this piece of code, and I load it into the instruction memory, here's what happens. First of all, whoever makes the translation, has to operate this according to a certain contract. You know, if you write the assembler for example, some things we're doing week 6. We will tell you that the length which makes some assumptions, and some conventions, and you have to write your assembler, or to carry out the translation according to these to this construct. So, first of all, notice that label declarations are not translated. Now, we have two label declarations here, POSITIVE and END. One is that just before line 8, and the other one is before line 10. They are not translated, they are ignored, and they generate no code, surprisingly enough. 'Kay. What about the references to the labels? Well, the references to, to the labels definitely get translated, and what used to be @POSITIVE becomes @8, because 8 is the instruction just after POSITIVE. And what used to be @n becomes @10 for the same reason. So the second part of our contract is that each reference to a label is replaced with a reference to the instruction number following that label's declaration. So, once we keep this thing in mind, we can happily use invent and use as many labels as we want in our program. And then we can use these labels to effect the notion of branching. Okay. So having dealt with branching. And obviously, it's something that we turn to, because you cannot write non-trivial program without branching. Let's move on and talk about another obstruction called variables. All right. So what is a variable? A variable is an obstruction of a container that has a name and a value. And in higher, high-level languages, we have different types of data, different types of variables and yet, in the lower level of machine language or at least in the, in the lower level of the Hack machine language, we have only one variable type. We have only 16-bit values to worry about. And this obstruction can be delivered or can be implemented by using a single register in our data memory. So we use a single register to represent everyone of our variables, if we want to create variables in our, in our program. So here is an example in which variables come into play and in this program I want to flip the values of RAM zero and RAM one. And I do it using a classical programming idiom that appears in practically every basic programming course. I create the variable, typically we call it something like temp to denote the fact that it's a temporary variable and I need it only for this purpose and I do some manipulations that end up delivering the necessary flip operation. So the question is how do you do it in in machine language? Where so far, we didn't have a notion of variables. So this is a good a good opportunity to, to introduce them. Well, here is how I decided to do it. And let us focus on the red instructions in this program. And in particular in the first red instructions, I declare a variable called temp and then I immediately go on to use it. Now what is the meaning of this cryptic command @temp? And notice that unlike labels and references, we don't have a corresponding label called temp. So what do we mean when we say @temp like this? Well, here's what we mean. Basically, we we present the following pledge to the computer. Please go to the memory unit. Find some available memory register. Let us assume that it's register number n and use it from now on to represent what I prefer to call the variable temp. So from now on, whenever you see a symbolic instruction @temp, I request that you treat it as if it were an @n instruction that we already know how to handle. Okay. So this is the meaning or that's how I realized this variable obstruction. So let us observe what happens when you translate this program and load it into instruction memory. Well the first observation is that when you look at the code, you see that what used to be @temp became @16. And because @temp appears twice in the program, we have two occurrences of the @16 instruction. So how does this happen? Well whoever wrote the assembler or implemented this translator worked according, according to a contract that we as the language designers have put together. So the contract has two rules as follows. First of all, a reference to a symbol that has no corresponding label declaration is treated a s a reference to a variable. We assume that the programmer wanted to create a variable. And the second rule is that variables are allocated to the RAM from address 16 onward. In this particular example, we have only one variable, so it ends up being allocated to RAM 16. If we'll have more variables in a program, we'll use RAM location 17, 18 and so on and so forth. And practically, we can use as, as many variables as you want. And I can say it safely, because typically, programmer use only, you know, half a dozen or a dozen variables something like this. And in the Hack language you can actually use thousands of variables, but no one actually comes up to this to so many variables. So in closing of, of this little section about variable. If you look at this piece of code you will realize that this program is relatively easy to read and debug. You know, we have a symbolic label and we have a symbolic variable @temp and it's much easier to understand what is going on here compared to the program that we wrote before when we used actual numbers to represent the addresses. This program has another very nice virtue, which is more subtle and this is the fact that this program is what is known as relocatable code. I can take this program and load it into memory not necessarily to address zero. I can put it anywhere I want in memory as long as I remember what is the base address that they used for this program. This is extremely important, because as you know, when you work with your personal computer, you typically have several programs executing at the same time. So you can imply from this that several programs are loaded into your main memory. And once we write this program carefully using symbolic references, we don't have to worry about where they will be located in memory. We can write something called loader that takes care of this o this technical detail. So symbolic programs are good. The next programing idiom that I would like to discuss is iteration. So for example, suppose we want to compute the sum of the series, 1 plus 2 plus 3 all the way up to n, where n is given input. How do we do it? Well, normally, you use some accumulator, a variable to accumulate the running total of the series. So you set this variable to 0 and then you do 0 plus 1, [COUGH] gives you 1. 1 plus 2, gives you 3. 3 plus 3, gives you 6 and so on and so forth until you reach n. Now, if you have to write this program in low-level language, it always pays off to begin with some low-level oriented pseudocode and this is what i did here. So, once you convince yourself that this Pseudo code actually delivers the required functionality. We can move on and translate this pseudo code on paper into symbolic machine language. So, let us begin with the first part of of this of this program in which we declare and initialize the three variables n, i, and sum. So first of all, notice that there's a one-to-one mapping between the name of the, the variable that we use in my pseudo code to the name of variables in the symbolic language, which is, which is kind of nice. So we have these three variables and let us assume that they translate only this part of the program and load it into the instruction memory, here is what I'm going to get. @n would be translated into @16. At i into @17, and @sum into @18. This should not be surprising because it follows the contract that we discussed earlier. Variables are allocated to consecutive RAM addresses from 16 onward. All right, so let's keep this in mind, and complete the translation of the pseudo code into machine language. The details of this program are not terribly important. Well they are, if you actually have to write this program, but they're not important to the thing that I want to emphasize in this unit. But I do expect you to, once again, stop the video now, and convince yourself that you understand what this code is doing. And, by the way, when you write such a program, if I told you if I gave you the this program as a, as an assignment, I highly recommend that you first write it in pseudo code. And then I recommend that you debug your pseudo code and convince yourself that the pseudo code actually works. And once you convince yourself that it works, you can reduce the task of reading, of writing the machine code to that of simply translating from pseudo code to machine language. And you see, this is much easier to do. You know, you look at the pseudo command like n equals R0, and you write a set of instructions in machine language that do the same. Or take a look at the instruction that follows the loop directive in the pseudo code. You know, it says, if i is greater than n, goto STOP. So it's much easier to simply focus on this instruction only and translate it into machine language. So this is one way to write low-level code and convince yourself that it works. And the second way to verify that the code works, which is no less important, is once your program is written, you have to simulate it or we recommend that you simulate it on paper, using some sort of what is known as a trace table. Now, a trace table is a piece of paper on which you write down the names of all the main players in your program or the main variables of interest in your program. And then you record in the table the values of these programs throughout the program's execution. So, these are the values of the these variables of interest in iteration 0. And then you will go through the loop. And you figure out what happens after the first iteration, after the second iteration, the third iteration, and so on, and so forth. Okay. So you do this for three or four iterations. And you try to convince itself that the program is actually working. So in closing here's our best practice advice for writing hack machine language programs. First of all, you have to design the program using some sort of pseudo code. And you can follow the pseudo code example that we used in the previous slide. Second, you have to take your pseudo code and re-express it using the hack assembly language. And finally, we recommend that you test this the resulting program on paper, using some sort of a trace table. And notice, that I, I did all these things without touching the computer. Everything was done on paper. But actually, I probably wrote the program using some text editor. But once again, text editor for me is like, is like using a piece of paper. And it's very important that you convince yourself that the program is working before you actually, you know unleash it on the on the actual computer. All right, so this has been the unit in which we talked about branching variables and iterations, and in the next unit we are going to talk about pointers and input/output, and this will be the end of our coverage of low-level hack programming.