 Okay guys, class begins. Could somebody at the very back help me with closing the doors on the far left there? Thank you. So, welcome back to this course. I hope you guys had fun in the labs this week. Any difficulties here, any questions? So, lab three, apparently first of all there was some logistic issues. I realized that our website was not that reliable and some people couldn't download the lecture, the lab manuals, right? Okay, so just try reloading it because every time I get this message I test it again but it just seemed to work out. So try reloading it multiple times and just feel free to let me know whenever there's another issue. Okay, but regarding lab three, are there any other questions? Okay, so just to recap, lab three is, in lab three what we were essentially doing is constructing a decoder that takes in binary input and outputs the corresponding number onto the seven second display. The main thing we want you to learn here is the concept, the decoder. The decoder taking in as input a binary sequence and decodes it into a form that is easily readable by humans. Okay? Right, so later on in the semester starting from lab five and six and so forth, we will begin to learn about MIPS programming and also how we can construct the MIPS processor. Before that, for lab four, you will learn another important concept which is finite state machines. Finite state machines allow you to define different states or different status, within a system, and they can generate corresponding output based on different inputs and the current state of the system, okay? And you will learn exactly how that can be done next week with a really simple example. All right, so my name is Dorianne as you might have seen before, and today we are gonna continue with what Anjian left off yesterday, okay? So mostly this week we're talking about MIPS processors and MIPS instructions. So I'm gonna do us first of all a quick review of the important parts of what was taught yesterday and also a little bit from last week maybe, just the important aspect so that we can talk about other important instructions I'm gonna introduce to you today. And then later we can continue on with the programming aspects of MIPS and we're gonna talk about the addressing modes of MIPS, how one can address different types of variables or different points in the program and different types of constants using the MIPS instruction. Finally there are some odds and ends and miscellaneous things to maybe be aware of so that you would find it easier to program MIPS assembly later on the labs. Okay, let's begin. So just to recap quickly on what we taught yesterday, this conditional branching, so in particular branch on equal. So recall yesterday that branch on equal is an instruction that allows you to jump to another certain point in the program when two values that you are comparing are exactly the same. So in this example we are putting four into the register S0, putting one into the register S1, shifting register S1 to the left logically by two positions, which essentially means that we're multiplying it by four and then that gives you four and then if you set a branch equal instruction on both of these registers S0 and S1 and jumping to target if they are equal, it would jump all the way over here in actual execution because both of these numbers are exactly the same. So labels indicate the instruction locations in the program. They cannot use reserved words like other instruction names and they must be followed by a column. This is important when you write the actual assembly code. I believe in lab six or lab seven, depending on how we're gonna design the labs in the end. Also later on, this will be important for you to break out of loops and I believe yesterday Anjan already covered the while loop extensively, right? Okay, so just another branching example. Here we have the jumping branch instruction and here jump simply says that regardless of whatever was done before and whatever is done afterwards, it's gonna jump directly to the target. So we don't even have to discuss what was done here and later after the jump instruction. So when you initiate a jump instruction, it's gonna jump directly to this part and execute the instructions from there on, okay? Now this is also important because we're gonna use the jump instruction to jump into sub-routines. So essentially different functions within your program, okay? So this is what we're gonna learn later on. Another type of jump routine is the jump to register. So in this case here, we are not jumping to a particular label in the text program as before. So in previous slide, you see that jump follows a particular label. So this label is hard coded into the assembly code. So when you actually run the program, it knows exactly where to jump to, okay? Now with JR, jump to register, the place where you wanna jump to is stored in a register. So it could be different depending on how the program takes its inputs and how the program processes this data, okay? So jump to register here will essentially jump to whatever value that is stored as S0. And in this case in the first line, we already loaded S0 with the hexadecimal number of two zero one zero. So it's gonna jump over there all the way here. So the add immediate and shift right arithmetic instructions will not be executed, okay? I hope the instructions for you so far, you've all recognized them. Any questions here? Okay, so this is just a repetition of what was taught yesterday. Good, so effectively everybody has a good memory. All right, so today, we're gonna talk about high level code constructs. Yesterday we talked about if statements, if L statements, while loops, and today we're gonna talk about the really common for loop, okay? I'm gonna take it off from there. So a for loop, essentially a for loop consists of four parts, you have the four here, and then right follows, right after the four text, you have the initialization statements, the condition and the loop operation, and then the loop body. This is similar, we're gonna approach this problem by assuming that everybody here understands C. So initialization here, essentially execute something before the loop begins. Condition is always tested at the beginning of every iteration. If the condition is not met, the loop is broken, okay? That we break from the loop and you continue on. The loop operation is what you do at the end of the loop, and the loop body is what you do during or within loop. So try to imagine there's this big brackets over here. All right, so this is the for loop. Now, how does the computer or the compiler transfer a for loop into an assembly language? This is what we're gonna see later on, and this is how you can also implement for loops in assembly. All right, so a high level code we have here is let's say we wanna sum numbers all the way from zero to 10 exclusively. So you start by initializing a sum called zero, you have an iterator i, and if you set a iterator zero, i to zero, all the way up to 10, incrementing it by one after every loop. And what you do within every, the loop body is simply adding the numbers together, then you get the sum. All right, so when this high level language is compiled into assembly, first we have some examples here. So for example, it might be that the assembler simply says, well, we're gonna use the register S0 to store the value i, and we're gonna use S1 to store the sum. So you might already have an idea of how this would go. All right, so first of all, we wanna load S1 with zero, and this is essentially the first step, loading sum with zero, and the second step is loading S0 with zero as well. And here, we're again adding S0, well, adding the zero register by itself and storing it as zero. Now, you could also do the same for S1, because both are zero. So here, we're just showing you multiple alternatives to assign zero to a particular register. Oftentimes, it can be as easy as adding the register zero, which is always zero with zero, or adding it by itself. So there are multiple ways to do this. And maybe as an exercise, you can think about how one can do this with exclusive four. All right, and then later, this register T zero is used as to store the upper limit of the loop, where the iterator is supposed to reach maximally, and we simply add 10 to register zero, making it 10, and you store it T zero over here. The for loop begins here, and first, like I said before, the condition must be checked at the beginning of every loop. So first of all, you would check whether or not I is the same as T, because this is what we're doing here, essentially, and if it's the same, then it means that the loop is complete, it means that this condition is not met anymore, and you would jump to the done label, whatever here is not performed or not executed. If this condition is not met, it means that you still have to do whatever it is in the loop body, and that includes adding the S zero or I into the current sum accumulation, and incrementing I, and then jumping back to the for, okay? So here, this is what we did before, we talked about before, we jumped to a for label. So notice the assembly test for the opposite case here, I equals to 10, rather than the test in a high-level code, I not equals to 10, okay? Because here we're programming assembly in a way that we're telling it to jump out of the loop on a certain condition, so intuitively it should be, the logic should be the inverse of whatever condition that is originally in the for loop, okay? Any questions? Yes, please. Right, so that would require other instructions, the set less than, actually. So maybe I can get to that later, was that not taught yet? Okay, so I will get to that later, because we have also a list, a short abbreviated list of instructions. So you have branch on equal, and what you just asked essentially is how can one compare whether or not a number is smaller than the other? So what you do is you would have another instruction to say that I'm gonna set a particular bit in the register to one, if the value in one register is smaller than the value in another register. And then after you execute this instruction, you can then check on the result of this instruction, which is the value in the register. If it's zero, then you do something, if it's one, then you do something else, okay? So this is what we can do later on. And this is also really important, because, yeah, good question, this will come up in the last as well. Any questions? So basically I'll cover the instructions later on. Any other questions? Okay, good. All right, so let's continue on. Ah, but actually it might be right here. So this is the high level code for another for loop. So we're instantiating a sum, zero, i, and we wanna add the powers of two from one to 100, okay? So this means that the next steps of every for loop body, we're gonna multiply i by two, because we want powers of two, okay? And i starts with one. So the i essentially can be one, two, four, and so forth. Here is the check to see whether or not i is greater than 101 or equal to. So the loop would only continue if i is smaller than this, and this is where you would compare whether or not the number is smaller than 101. If it's smaller, then you continue to do the loop. If it's bigger, then what you do is break out of the loop. So what one can do here, again, these are similar, these three lines were similar as before. So you initialize S1 to zero, you initialize S0 to one, which is i, and then you add T0, which is your threshold variable here, and you load it with 100, okay? Now here you have to set less than SLT. Okay, stands for set less than. What it does is it compares the values in S0 and S1. And if the value in S0 is smaller than the value in S1, then T1 will be set to one, okay? So set less than simply saying that if this is less than this, then this value will be set by setting a value. What I mean is setting it to the value of one, okay? If it is the same, it means that, so S0 here is one, T0 here is 101. So T1 here would mean that it would be one if i equals 101, all right? Now, what you do here, what we do here is another breakout branching, and it compares essentially the value of T1 to zero. So if this, if i is not smaller than T0, then this value would not be set, and it would remain the value, to be the value zero. So by comparing zero to zero, you would break out of the loop. So what I just explained is the condition where the loop would be broken, all right? So otherwise, it means that if this value is set to one, then this would not be satisfied. The quality here is not satisfied, and the program will continue on, okay? So the program essentially continues on when T1 is set to one, which means that S0 is smaller than T0. So hopefully this answers in detail your question. Yes? Yeah, I'm not sure about this assembly. I don't think I've seen a zero flag here. I'll have to read into the references. Maybe I can get back to you after the break. I think we are going to implement this zero flag though. So chances are there could be one. In lab five, when you implement the ALU, the arithmetic logic unit, we would ask you to output a special flag zero, which is set to one when the output of the ALU is all zeros. Okay, so this is how it can be done. Yes? Well, if it's load immediate, so first of all, I don't think there's any load immediate instructions. There are load words and store words. So by load, I think what you mean is loading from memory, right? I mean, or store. Oh, all right, we're adding zero just so that we can make S1 zero. So I mean, if there is a copy instruction, then what I could do is I'll just say copy from register zero to register S1, right? That would be the simplest way. Because at the beginning of this code, we want to initialize sum to zero. So like I said before, there are a lot of ways to initialize a particular value to zero. One of the ways would be to add the register zero to zero. And that's one of the ways. And other ways, there are actually tons of other ways that you can do this. So yeah, you're right, you can do a lot of ways, but I don't think that load word would be the most efficient because that would involve memory access. Okay, so this is what you see as mentioned before. Here, another way of initializing register to zero is to add the register zero by itself, okay? So the register zero, this dollar sign zero is always zero. Okay. So this is what Androm promised that he would leave you off with. And today, finally what I want to teach is arrays, okay? So you must have learned about arrays in the first semester, but here we're gonna learn how you can access arrays using assembly. So similar as before, arrays are useful for accessing large amounts of similar data, maybe an array of characters, an array of integers or floating numbers. The array element can be accessed by index, and this should already trigger you to think about maybe to access an array, not only do you need the index, you also need the base address, okay? The address of the first element in the array, okay? Another thing that you need to know about arrays is the size of the array. Given that we're really talking about primitive data structures here, you need to know the size of the array so you don't overshoot your index, okay? All right, so let's talk about examples first. So imagine now you have here a five-element array, and you need, again, the base address, right? So here, it's a really basic one. Exit S only, one, two, three, four, eight, zero, zero. That's the address of the first array in the element, the base address, which is array square bracket zero. So the array in the memory would look something like something like this. So the first element is at the base address. The second element is at the base address plus four. We use four here because we're talking about a word, so it's actually four bytes. And given that memories are, the addressing in memory is byte-based, this means that to access the first element, you need to have, you need to increment the index by four, okay? So the second element would be eight plus eight. The third element would be plus 12, which is C, and the fourth element plus 16, which is one, zero, okay? This is what you can see here. All right, so the high-level code for access and arrays, we can explain it using, again, an example. So imagine here we have a high-level code that instantiates an array of five, setting the array, the array element zero to itself multiplied by two, and the first element also twice its original value. What we do here in assembly is first of all, so now that we have this high-level code, let's think about how we can get the same result using assembly, okay? So as I mentioned before, we need to access the array using its base address, okay? So imagine that here we already tell you that the base address is the same as the previous slide, one, two, three, four, eight, zero, zero, zero. How does one access it? So this means that you need to first initialize this value, put this value somewhere in the program, okay? So that you can access it. And in this example, well, the common hints to you that you need to put this value in S zero. So what we do here is like this. We're gonna load the upper part of S zero with an immediate value, load upper immediate, okay? So we're loading an upper immediate with one, two, three, four, and this means that the upper half, the upper 16 bits of the register S zero would be filled one, two, three, four, okay? And the lower part, we're gonna use OR. OR immediate, and we're gonna, it's gonna, the calculation goes as follows. The arguments are the S zero register itself and eight zero, zero, zero. So if we or, if we perform this OR operation on these two arguments, essentially the original S zero being zero, it would have eight zero, zero, zero in its lower 16 bytes, and its upper 16 bytes, which is already one, two, three, four, will remain untouched, okay? So this, you have an OR immediate. Does anybody know why we have to do this? Like, why can't we do add immediate S zero, zero, and then one, two, three, four, eight, zero, zero? Why can't we do this in one shot, in one operation? Yes, exactly. So instructions can only be 32 bits long. So we cannot fit one, two, three, four, eight, zero, zero, zero into the operant here. Okay, I feel like that was a too simple question to ask. Okay, but everybody else understands, I hope. Okay, so this is one of the reasons why, all right? And we are gonna run into this issue later on. The lack of sufficient space for calculation or for adding values into numbers and I think one of the clear examples would be multiplication. As a hint, multiplication, when you're multiplying two 32 bit numbers, you would get maximally a 64 bit number. So how do you store that in one register? Well, you don't. So you're gonna have to do something different there. Okay, so this is how it's done. After initializing it, now we have the base address from which we can access the array elements. So, the base address would be the memory address of the array and we are gonna load it by, like this. So we're gonna load into the register T1, something from memory whose address is stored in the register S0 and the offset here is zero. Okay, so this effectively loads the zero width element from the array into register T1. This is just the syntax, okay? So what is in these brackets should always be a register that stores the base address. What is in front of the parenthesis would be the offset. Okay, so this is zero width element. And then what you do here is a shift left operation which multiplies T1, I'm sorry, it essentially multiplies T1 by two because you're shifting it to the left by one bit location. Now that you've done this, you can store the value back to the array. So this is clear so far? All right, so if this is clear then the second part should be clear as well, essentially it's exactly the same. Okay, so previously we were talking about arrays with fixed indices, right? So here we're talking about arrays with fixed indices, zero and one. But what do we do when we have arrays of indices that would change throughout the program? So what you need to do is again, you need to use another register to represent this value, right? So how can this be done? Let's take a look. So, did I lose my voice? Okay, no, here we go. Okay, so as mentioned before, let's assume that the base of the memory address is changed to two, three, B, eight and the lower part is F-000. So this is just an example, okay? And if you run to this in the exams we're gonna give you the base address. We're gonna give you the basically entire memory map, okay? So you're sure you know where to start from. So what we can do here is we're gonna load S-1 with zero. That effectively sets I to S-1, initializing it to zero. And then we again need the threshold variable to store 1,000 and this is what we do here, okay? We're storing 1,000 here. And the loop proceeds as follows. It's gonna compare the two registers. If they are smaller, if one is smaller than the other, it will be set to one and then the loop will continue because this branch equal compared to zero would not be satisfied and there would not be a jump. If however it is zero, meaning that S-1 is no longer smaller than T-2 which means that I is no longer smaller than 1,000s, this branch would be satisfied and it would jump out of the loop. And what you do here, you're essentially accessing the memory of the array. So this is actually the main need here. What we are doing here is we're getting S-1 which is I and we're left shifting it by two, multiplying it by four effectively. And this is simply, we do this again because memories are addressed by bytes. So if you wanna jump between one word to another, you need to increment the memory address by four, okay? So this is just increment of four. And then what you do here to access this particular array location is you add the base address with whatever you've calculated here as the offset. So effectively T-0 would become the addition between the base address and the number of elements you wanna jump through, the index multiplied by four effectively, okay? And then you have the new address here. So this T-0 is not the base address anymore but it's already pointing to the direct memory cell of the element of the array that you wanna access, okay? So later on you would just put T-0 as the base address but zero as the offset and then you load it into T-1. So this is the trick. This is how you can do it. And now you would shift this value to the left by three times effectively multiplying by eight and you would store the value back in, okay? So this is the store instruction over here. And then later you're gonna increment i by one and then you jump back to the loop. So hopefully this should be clear enough. Okay, any questions here? Yes, please. Yeah, well by three positions, yes. Well here you shift it three times and then the store word is you're storing whatever you just shifted effectively the result back into memory, right? Yeah, so I understand the confusion. So the question is whether or not the store word is necessary. Well, it is necessary because originally we're fetching this from memory, okay? And then now we need to store it back to memory. Otherwise the value would just stay in the register and you never store it back to the original variable, okay? Yes? Mm-hmm. Yes. Yes, store word actually is loading it from, well copying the value in register t one and storing it in the memory address indicated by zero parentheses, parentheses t zero. Yeah, so it's not that, ah, I see. So it's not exactly the same as the R-type instruction where you have like add immediate or set less than where the first argument is always the destination, right? With store word, the first argument is not the destination, it's the source and the second argument is the destination, okay? So I wanna be careful with this because in exams, if you're nervous, you could make these mistakes. Now if we spot that these are honest mistakes, of course, we wouldn't penalize you too much but try not to make them, all right? Okay, so right now what we have seen just a short recap on what I've observed so far is we're teaching you how to convert high level code, look at high level code, and writing the corresponding assembly nips, okay? And in exams it might so happen that we do test you on this so what we do is we want you to implement a particular algorithm and you're supposed to implement this algorithm in assembly. So a lot of times it's hard to grade these assembly codes because, you know, as assistants when we're grading we don't really know where your logic thoughts are when you're coding this assembly code and therefore we might accidentally take too many points away while you might have the good or close to correct idea. So one of the suggestions I can make is to, first of all, make your code quality better by writing it clearly and to help you with that you can first implement the code or write it on the side of the paper, the high level C code that would implement this function. After writing this high level C code and making sure that this high level C code is correct you convert this high level C code to assembly, okay? Step by step, baby steps you're gonna get things right. So when you convert high level code into assembly you do the corresponding conversion for initializing arrays or variables. You do the corresponding conversion process for for loops and while loops and if, L statements and then you fill in the blanks, okay? Just writing the high level code and not implementing the assembly code that doesn't mean that we're gonna give you the points but at least we know your thought process that makes it easier to judge whether or not your code is more correct than others, okay? All right, that's just a small remark from an assistant that's gonna oversee the exams. Okay, let's continue on to talk about procedures. As I mentioned before, in addition to talking about jump instructions I also talked about jump register instructions and jump register instructions and jump instructions, they're also, both of these are gonna be used in procedural calls. So procedures here are pretty much the same as what you learned in C. So again, you have the caller and call Lee. Well, in this case, the simple case, the caller would be the main function who calls some and some is the call Lee, the one that's being called. We don't need to get into the details of this. So this is just a definition. So as a caller, what is important here, we need to identify what is important here that matters when we write the corresponding assembly version. The first one is the caller needs to pass arguments to the call Lee, okay? And it needs to jump to the call Lee. Essentially, you store certain values as arguments and then you jump to the corresponding part of the subroutine you wanna call. The call Lee should perform the procedure by fetching the arguments, computing on it, and return the result to the caller. And after returning the result to the caller, essentially also by, also this means it has to put the return values into certain registers or certain locations. And therefore, and then it has to jump to the corresponding point from which the original jump to itself was made. Okay, so it needs to return to the point of call. One other important requirement is that the call Lee should not overwrite registers needed by the caller, okay? Right, so here are some calling conventions for MIPS. The call procedure jump and link, return from procedure jump register, argument values and return value, okay? Jump and link simply is the instruction you will use when you're calling a particular procedure. JR jump register is the instruction you will use when you're returning to the function that has called you, okay? That this allows a procedure to return to its original calling program counter. The argument values that the caller would use are conventionally 0 through A3. And the return value that the call Lee would use to store its return value conventionally would be V0. And maybe V1 if you need to return two 32-bit numbers. All right, avoid here simply means that it doesn't return a value and I don't think this slide tells us much. So let's remove, let's go to the next part. This is how we convert a high level language of calling processes into assembly. So here you have the main function and what the main function does first is simply called simple who does nothing. So this is just a basic example. Jump and link simple and then what simple would do is to perform a jump register R A. Yes, so if you have four arguments in the function then you need to use stacks and this is welcome to cover later on as well. Yeah, but good catch. Yes. All right, so jump and link, JL stands for jump and link. It means that you're not simply jumping to this simple location. What you're doing is storing the next address of this JL into a special location so that simple, the one being called, knows where to return to to continue on with the execution without JR. Well, you can definitely access this address after you store this value but I don't think you can access it within the program because this is what the control unit of the processor is supposed to do. Because well, you can actually get values from the PC counter, yes, and you can compute this but it's jump and link that would allow you to store the next instruction address into the special register called R A so that simple here can jump to the register stored in this address, okay? So JR is, well, it's not jump to register, it's jump to this address stored in the register. Okay, so there's no return value here into a simple example of how you can make a function call. All right, so this is essentially what we're talking about before. So JL jumps to simple and saves the next location of the program counter in the return address R A, okay? In this case, it would be the one with the suffix O four, all right, and then JR R A would jump to the address that it's stored in this R return address, okay? Good, so this should allow you to distinguish clearly between the roles of JAL and JR. JAL is used by the caller and JR is used by the call E, the function that is being called, yes. Oh, and actually not essentially, well, so here after you, okay, so JAL call simple, simple is being executed here and it simply jumps back to the R A and R is pointed at the next instruction so this original instruction would not be executed. Well, it wouldn't go back to simple because R A stores the address of the next instruction which is add S zero, S one, S two. So sure, yes, so when it finishes all the way, suppose that there are no more jumps here and if you jump down to here and then of course it would jump again back, back to a particular location, right? That is true, but typically this is just an example. So in between when you have your programs, in between there will for sure be a lot of other functions and in particular at the very end of a function there would be this HALT instruction that simply tells the MIPS processor, hey, we're done, HALT execution and terminate. Yes, yes, yes. There are some registers and in particular, I think registers V, oh sorry, S zero through S nine and T zero through T nine. Okay, and this I will also cover in the later slides. Okay, so I'm trying to gauge what kind of a time frame I have. Okay, so the conventions, we can begin talking about a little bit of them. So argument values, to pass arguments, you can use the registers A zero and A through A three. So what you do before a caller passes an argument, calls a procedure, to pass an argument, what it would do is it would store these arguments in these four registers and then jump to the procedure. And what the called procedure would do is it would store the return value in V zero, sometimes also V one and returns it, okay. It would fetch, so the call E would know that the arguments it needs are in these registers and the one who calls the function would know that return values are in these registers. This is just a convention, okay. But please try to follow this in your own program and it'll help debugging a lot easier. Okay, so here we have an example. So this example is calculating difference of sums. So main function will call this, the main function will call this function later on. Difference of sums simply computes the sum of the first two arguments and the sum of the second of this last two arguments and then calculate the difference and then return this difference. Okay, so in this case the arguments are two, three, four, five and you would expect that before calling this function one needs to store two, three, four, five into the registers A zero, A one, A two, A three, okay. And what diffs, diff of sums need to do is to store this result in the register V zero, okay. Given the previous convention. And this is exactly what it does. So we're putting the value two into A one, three into, sorry, two into A zero, three to A one and so forth all the way to five to A three. And then you do this jump and link. When you jumped over here, this procedure written maybe by your colleague would know by convention that it needs to fetch the arguments from A zero, A one, A two and A three. It does the additions over here and then does the subtraction over here. The results are stored in S, the result is stored in S zero and then now it needs to return this value, right. So it's gonna copy whatever is in S zero to the register V zero and this is what it does here. So this is what it does to form the copy. So it's copying to the register V zero the values, the sum between the register S zero and the zero register. Of course, you can also go about another way and use add immediate and replacing the last argument with zero. It's up to you to implement how this would be done. Okay. And then after the addition, after setting V zero to the corresponding results, you can again call jump register to jump to the return address. Okay, recall that jump register, the register R A is set when J L is called and the value is set to point to the next instruction. Okay, so when you're jumping back, it's actually this addition instruction. Okay. Yes, true. Yes, you could do that too. That actually is a better way because S zero could be storing some other important value and here we're just overriding it without reason, right. So that could also be a reason to directly use V zero. Good point. Okay, so later on here, you will notice that in the previous code, what diffs of sums is doing is that it's overriding registers T zero, T one and S zero, okay. And it overrides the three registers here and this may have issues, okay. Because there might be some registers whose values you should store or whose values you should not change when you're performing computation within a procedure. This is what we're gonna talk about after the break. Also, so here diffs of sums can use the stack temporarily store registers and this, the use of the stack to store temporary values or store values from registers essentially provides you with the means of backing up data before, backing up whatever is in your register before you use these registers to compute a lot of things and we're gonna continue with this after the break. So take 15 guys. Okay guys, let's continue on. So second part, stacks. Before we took the break, we were talking about function calls and how they're making, they're using registers without saving them. They're using return addresses without saving them or without verifying whether or not they're valid. There are a lot of issues and a lot of times you can solve that by storing values using stacks and restoring these values when you end a function call, I'll explain later. But first I wanna make another just clear explanation here. So some people are confused about the return address when you call this jump register at the end of a function call. So this return address, the value of this return address register is assigned when we are making a call to JAL. So when the processor executes the instruction JAL, it would automatically compute the next address in the PC counter, which is the address pointed to this add instruction and store this address in the return address register. And this would allow diff sums to know where to jump back to when it's processing of the inputs are complete. So this RA is automatically set. Yes, you need stacks there. So you have nested subroutines. The value of RA would be repetitively overwritten without the original subroutine using it to jump back. That's why the original subroutine should store the return address before it calls JAL. So main doesn't do this because main is the main function, but for any other function that you see and also that you write later on, you should always create a space in the stack and store the return address before you call JAL, okay? So this is also one of the important things, important applications of using the stack. All right, so the stack. Stack, I guess it's pretty, it should be main known to you in the first few months of your studies here. It's memory used to store temporary variables within the function scope. It's like a stack of dishes, first in, first out. It expands, but of course, there is a maximum limit. It contracts when you remove items off from the stack. These are pretty plain. Okay, in MIPS, the stack grows down from a particular memory address, namely it decrements, okay? The memory address decrements is decreased whenever you put something in the stack, okay? SP is the stack pointer and always points to the top of the stack. So since the stack grows down, then the top of the stack is actually at the bottom. So there's nothing so zen about the same, but just remember that in MIPS, when we want to write the memory, we typically write it from the bottom up, but since the stack grows from the top, the stack always points to the last element, okay? The one with the least addresses, with the least address. Okay, so how procedures use the stack? So call procedures must have no other unintended side effects. So this is what we mentioned before. When you want to use any registers, you should store these registers, the value of these registers in the stack, and then restore them later, okay? This is just proper convention for subroutine to behave. So different sounds overrides three registers. It overrides T0, T1, and S0, okay? Depending on how you judge them to be important to store these values across different function calls, you can choose whether or not to store them and restore them afterwards. So what this of sums does in the previous example is that it overrides T0, T1, and S0, and then later it puts the output in V0, okay? So if T0 and T1 and S0 are important, you should back them up. If they're gonna be used by the function that you call, that they called you before, you should back them up. How do you do so? First, you create a space on the stack. So you decrease the stack pointer by 12, okay? The reason for this is 12 divided by four, which is the number of bytes you have in the memory cell of a word length. This is three words, which is actually three register spaces, okay? After you decrease the stack pointer, you then have space to store, to back up the values in the register, okay? So first is the store word of S0, you store it on the stack, and then as you store word, you do store word again to store T0 on the stack, and then you push another value T1 onto the stack, okay? So essentially you're making three spaces. The topmost one is gonna be S0, the middle one is T0, and the last one, the one right at the stack pointer is T1, okay? And now that you have backed them all up, they can do whatever voodoo magic you wanna do here. You can mess up all these registers as you want, use them all, it doesn't matter. And at the end, when you compute, after you finish computing the return value, now you're ready to jump back, right? Now, instead of immediately jumping back to the return address, what you can do, what you should do is store these register values. So first you pop the first value on the stack to T1. So this matches this. And then the second value to T0 and the last value to S0, all in the same order as you, well in the reverse order that you push them into the stack, okay? And after that, you're good. But you need to, again, restore the stack pointer back to its original value. So you're deallocating the stack space, okay? And after you're done here, you can do the jump register and jump to the calling function, okay? Now here, we still don't have a sequence of cascading calls of a function. So the diff of sums routine does not back up the value in RA. Note that it should if it's gonna make any other function calls here, okay? So a good convention is actually at the beginning of every function to immediately create a space for the return address by decreasing sp by four, so you have a space. And then storing the return address into this space in the stack, okay? And then you can pop it out later, and then you can jump back. This is to prevent the case where you might add certain function calls in the middle and that would really mess up your return address value. So the stack, what it does before, well I think that we'll just cover this. Whatever is before is on the top of the stack and this function doesn't care. So that's why it's a question mark. You push S0 and then T0 and then T1 in the stack. And then at the end of the function call, you pop them out one by one and then the stack pointer goes back to whatever it is before, okay? Yes, please. All right, I think, so the question is how many items can we push onto the stack without having like an overflow error or something? I think MIPS defines a particular maximum limit for you to put in your stack and I'll cover that later on. So MIPS essentially has a total of four gigabytes of memory space and some of it is reserved for system use like interrupts, exception handling. Some of it is reserved for the program code because the program is in the memory and some of it is reserved for static space which essentially are your global variables and then finally some of it is for your stack. So maybe we'll get to the maximum size later on. All right, so in this slide, the point really, in this example, the point really is to tell you that you should back up your register values but as we mentioned in the beginning of the class, when I was answering a question, there are certain values in the registers, certain registers that by convention, you don't need to be backed up because they are by definition used for temporary computations and here is the list. So here we have a preserved list of registers and a non-preserved list of registers. So the preserved list implies means that these values should be kept the same when you're calling when the function is being called. So the callee, the procedure must preserve these values namely it should push all of these values into the stack or preserve them at least. So it should preserve registers S0 through S7, the return address, the stack pointer, oh well actually the return address in S0 through S7, you can just push it into the stack and the stack pointer should be preserved namely if you create a space in the stack, like by decreasing SP by let's say 12 in the previous example, then you should add it back at the 12 back at the very end of the function before we return, okay? In addition, whatever is before the original stack pointer, whatever memory data that is before it, you should not modify, you can't modify them, okay? This is just a convention. For non-preserved registers, you have T0 through T9, A0 through A3 and then the return values, okay? Naturally with the return values, you cannot expect them to be reserved because the callee, the procedure that's being called needs to use them to store the results. And then whatever stack that is below SP, you can feel free to modify them, which means that in the previous example here, when we are releasing the stack here, the allocating the space, the values here, they don't matter. So they could be the same values as before or you can do it in a more secure fashion by zeroing them out, all right? So in this list of conventions, you can tell that T0 through T9, these registers are non-preserved registers, which means that there really isn't a need to push these into the stack, although sometimes it could be considered good practices. Question, yes, yes. So if you put another use of the stack is to pass arguments of more than four arguments, right, to a function. So you can also push these arguments into a stack if you have more than four arguments. And then the called procedure needs to therefore know how many arguments the callee has put into the stack. Well, it knows it anyway because it knows how many inputs it's taking and then fetches them correspondingly. So I believe, I'm not sure if I have an example, I don't think I have an example there, but I think it's in the textbook and you use that as a reference, okay? Right, so now that we know that these conventions, let's take a look at the code here. So back to diffs of sums, the first three lines involve modifying values in these registers, namely T0, T1, and S0, and therefore this means that, excuse me, if we are following the previous convention, then T0 and T1 need not be preserved, so we're not preserving them and we don't need to preserve the values of S0. And therefore, if it's one value, then we decrease the stack pointer by four, namely creating space, and then pushing the value of S0 into the memory address pointed to by the stack pointer. And at the end of this function, we are restoring it by reading out, reloading whatever is stored on the stack pointer with zero offset back into the register S0, which we have modified before. And now we are deallocating the stack space. We deallocated to return the SP pointer to its original value, and then we are returning back to the caller. Right, so here the problem is, when you're designing an assembly language, you look at the registers that you're modifying. If they are not one of these temporary registers, then you need to store them in a stack by good convention, okay? All right, multiple procedure calls. These questions have been raised numerous times in this lecture, so I guess it's really important that you know this, and it's good that you notice as well. So you have procedure one here, and it's gonna call procedure two later on in the program, which means that before calling this procedure two, it should store the return address in the stack, okay? Because if it doesn't store this return address, this return address register would be overwritten by this JAL instruction. So it is always good convention that at the beginning of a procedure that you always put the return address onto the stack. So you always decrease the stack pointer by four and push it in. And then later you can again decrease the stack by let's say another 12, you wanna take three arguments, but it's always good convention. So you can dynamically move the value of stack pointers throughout the program. So it's not like you have to create the exact number of stack space at the very beginning of the procedure. You can do it numerous times throughout the program, okay? So that's why it is always good practice that at the beginning of the program call, you always put the return address into the stack. And then later, after calling this procedure two, who might have messed up your return address, you still have a backup here. And then what we can do here is just load the word from the stack and return the stack pointer to its original location and then jump to our restored return address, okay? Now whatever procedure two might do to the return address or any other register, that doesn't matter because procedure two would be in charge of restoring your stack pointer so that whatever stack pointer you use here is gonna be consistent as whatever you had before the jump instruction, okay? This is procedure two's responsibility, okay? And also any other registers that you might have used in this program. Yes? Here? Well, I think, so you mean when writing. So in actual execution, definitely cannot because it has to be value, but I think Mars supports this. I'm not sure if that's the real case. So it depends on your assembler. Your assembler would take your machine, your assembly code, and it would convert it to a particular form, right? And if it supports this, then yeah, it's fine. And you can also indicate, again, as you might have seen in other examples, you can also indicate numbers of other radices like hexadecimal numbers, octal numbers, and the such. Even binary numbers, I believe. Okay. Any other questions? Right? All right, so these are what you do with multiple procedure calls. Now, recursive procedure calls. Well, recursive procedure calls are a little bit more complicated, but not much more. So we're using the typical whole example of calculating a factorial. Namely, when you wanna calculate a factorial, you first check if the number is smaller than one, if it is, or smaller than or equal to one, if it is, then you return one. Otherwise, decrement this number and send it to your own function and you multiply it by this input n, okay? This is how typically people would do it. It's a really good example, but it's rarely implemented like so in the real world. So factorial numbers. First of all, this, okay, so here you see an immediate thing, right? Immediately, you would see that it's storing the return address into the stack pointer. It's also storing the function argument into the stack pointer because you don't wanna lose this original number. Say you wanna calculate a factorial of six. You wanna store this number six into the stack as well because later on, when you call yourself, you're gonna put a new value into the argument register, okay? So now that you've backed up both the input argument and also the return address, you can start doing the actual body of the procedure. Now this involves first seeing if a is smaller than one. To see a is smaller than one, the technique used in this example is to load another register with two and checking if your input is smaller than two. So whether or not a is smaller than or equal to one, essentially it means whether or not a is smaller than two. If we're assuming that the numbers are all integers, which is the case here. Okay, so if this is true, then if a is smaller than one, if this lesson is true, then this branch would not be satisfied and can do what is between here. Otherwise you would go to else and you would make the procedure calls, okay? Let's skip this part for now and we just wanna focus on the jumping here. Well, actually in this part, at the end, if a is smaller than or equal to one, or then what you would do here is you add the values to V zero, you add the results essentially to V zero. Well, you add one to V zero and then you restore the stack pointer and you call the return to this function. So here you're adding it back by eight because originally you're decrementing the stack pointer by eight slots and here you're calling the jump return, okay? So what is interesting here, then is the else statement. In the else statement, you first decrease n by one and then you would call the factorial by the number itself. So a zero being six, say, or three, for example, let's say you use the example three, a zero would become two and then you can jump to the factorial number, which would go all the way back with the new arguments, okay, and whatever value that is returned from there, you're gonna see afterwards here. So at the end of the factorial function, you will return back to here and then this is where you restore the return address. You then again restore the arguments, restore the stack pointer. So again, you need to restore the related registers and then the stack pointer and then multiply the value by your first input and then you return, okay? So the idea in detail, if we look at only the stack, is the following. So at the beginning, let's say if I call, okay, so this example is also actually a factorial of three. So what you do is at the beginning, you push three into the stack and then you push the return address and then you call yourself because three is greater than one. So when you call yourself, the number is two because you decrease it by one and then the new function is gonna be, is gonna put a zero and r a into the stack again and then call itself which puts the argument and the return address in the stack again. The computation here takes, computation starts from the bottom of the stack, well actually the top of the stack and performs the computations, calls return address, calls which completes the computation over here in the second call and then calls return address here and then returns back to the original value, original state, okay? This, I try to make it quick because it might be a bit repetitive to teach recursive functions again but details are in the textbook. Okay, so to summarize, when you're designing procedural calls which inevitably you will, the caller has some duties, right? You need to put arguments in registers a zero to a one in to save any registers that are needed, particularly the return address that's more important and also maybe the values that you used in registers t zero through t nine because we call that t zero through t nine, they are not guaranteed to be preserved by whoever, whichever function that you call and then you jump and link to the callee. So after jumping and linking to the callee and after the callee returns to the function, returns to the original calling function, you restore the registers, the return address t zero through t nine and then you look for the results in v zero or maybe v one, okay? And then that's what the caller does. The callee would save the registers that might be disturbed when it's being called, the original registers s zero through s seven and perform the procedure that is defined to do, put the results in v zero or v one, restore the registers which are s zero through s seven, remember you don't have to restore t zero through t nine because the calling function is in charge of that and then it jumps, return, okay? Any questions here? All right, so you should be pretty clear. All right, so we have 20 minutes left and I wanna quickly go over certain types of addressing modes, all right? Other topics such as exception handling, they're also in the slides but they are of lesser importance so I will cover what is important here. So the addressing modes, right now we've seen a lot of, sorry, you have a question? Okay, sorry. So right now we've seen a lot of addressing modes like addressing basically a lot of different types of arguments in these assembly codes, right? Some of them are registers, some of it are immediate values which are values that are in the code directly, some of it are based on a particular base address and then you simply add the index later or the offset, some of it is PC relative like return address and also there are pseudo directs and pseudo direct is actually the labels that we use, okay? So there are a lot of types. The first register only addressing is simple. So register only addressing means that all the operands are found in the registers. The actual operands that you need, they are stored in the registers and these are your R type instructions are in the register. So all of the operands here are registers, okay? Simply fetch the values and you compute them. These are the most efficient ones. And then you have immediate addressing, 16 bit immediate address used as an operand. So you only have 16 bits to represent whatever value you want here, okay? And that is why in certain cases, we need to, when you wanna load immediate values into a register, a 32 bit register, you might need to do it twice because it's 32 bits. That's two times 16 bits. All right, another type is the base address, base addressing and that involves you specifying a base address which is stored, the value of which is stored in register like in the brackets in the parentheses here and then you specify the offset address. Remember that if you are addressing particular words in the memory, so words are four bytes, then the offset should increase or decrease by multiples of four, okay? This is a common mistake that is found in the labs and also in the exams and be sure to remember this. If it's an integer array and you wanna access the first element, then you need four here and then the base address here, okay? Remember also the base address cannot be an immediate value. It should be, it must be a register. Okay, PC relative addressing, this is essentially these calls, these else jumps, these else branches. So here what I mean, what it means, what PC relative means essentially is addresses that are relative to the current program counter. So the current program counter is pointed here and else is defined as the number of jumps from the program counter increment by four to the actual label. So else is defined as this, so in the actual machine code for branch equal T zero or just a zero else, the value is actually three. It means that PC plus four is over here and you jump one, two, three, three times the else, okay, that's what it means. So the reason that they wanna first increment PC to start counting from this one and not from the current instruction is because when you perform an instruction, you naturally already increment PC by four, okay? That's always the case for any 32-bit MIPS instruction. Yes, yes, I think so, I think you can work with that, yeah. But that makes the code a lot harder to read, so try to use the labels. This is one of the examples where in the exams you might wanna write the corresponding C code, so when we look at it, we know exactly what you mean and you wouldn't lose any points by accident, okay? Good, so the last one is pseudo-direct addressing. These are the jump and links instructions, okay? So jump and link, sum, and what the sum does is whatever, but here the sum has this particular memory address so the jump target address, or JTA, is actually this value in binary which corresponds to this address in hexadecimal. What we do here is we take the middle 26 bits. Well, we need to take these and ignore others because this is a jump command and just jump instruction, and any instruction only has maximally 32 bits, right? And six of these bits are the op codes, so you at most have 26 bits for specifying your jump address. So that's why we can only take 36 bits, but the good news is you're not losing anything here. First of all, all of these instructions are in words, so you're anyways jumping on the order of four bytes and that's why you don't need the last two digits, okay? Now, you don't need the first four digits because in a MIPS processor, maximally, you have four gigabytes of memory and also given that you don't have all of the memory for your instructions, these four bits would not be used. And as a matter of fact, the first four bits would be replaced by the current first four bits of the program counter. Now, this naturally means that there's a limit to the distance that you can jump to, okay? We wanna call a function, but for all intents and purposes in this course, you will not encounter this limitation. All right, so the middle 26 bits would be converted, would be copied to the field values over here, okay? Any questions? Okay, so these are the details and we just have to see how they are done in real life. All right, so in real life in the labs, what you would do is you would start from here. Remember that in high level code, this would be your C programs and they would compile your code into assembly and then the assembly code would be assembled into object files and they would be linked together. Other object files might come in, for example, your math library, which you don't implement by yourself, but naturally you have the corresponding libraries, the APIs for them that you use and once they're all linked together, you form executable, these executables are typically in binary format and they're copied to the memory when they're being loaded and the CPU can execute these instructions from the memory later on. All right, so what needs to be stored in the memory are essentially these instructions, text and also static data or global data that the program uses, like a constant of pi or whatever. Dynamic allocated memory should also be used and these are also the stacks, maybe the heap, but in neps we only care about the stack. How big is the memory? We're dealing with neps here, so they have at most two to the 32, two to the 32nd, that's four gigabytes of memory and they span from these addresses. The stack space, the memory space is mapped out as such. So the first and final parts are reserved for system use, like exception handlers or input and output devices because neps adopts a memory mapped IO process. Okay, there is the text here which contains your instruction codes, the static data contains your global variables and dynamic data here, that would be where you can use, where you store your registers, that would be where you use the stack, okay? All right, so here is a quick example of a C code and in the C code here we define global variables, not really optimistic but fine. In the main function they modify some of the global variables and cause a particular sum, subroutine. In the corresponding assembly code you then have the data part which indicates your static data, your global data and you have these three variables. The main function here and then you have the sum function here. These are parts of the text and that's why in the format there's a dot text just to indicate that these are where the program would begin, okay? So when the program is actually executed, these symbols here currently empty they would be allocated specific memory addresses as well as the starting locations of the main function and the sum function. These are just as examples and you might see different instantiations when you use the simulator. Okay, so I'm gonna leave this executable here, essentially these are the machine code instructions that you will see and they correspond to these assembly codes. Now in the labs what you will do is you will write these assembly codes and you're gonna verify that they are correct in the simulator and then there's gonna be a tool that converts these assembly codes into binary file and you simply put that binary file as an input to your MIPS CPU circuit that you also design. All right, so this is just more information on how these instructions would be put into the memory of a MIPS processor. The stack pointer begins at the zero position and here are the global variables. So some more odds and ends about MIPS. But before we talk about that any questions about memory layout? Yes, excuse me? I think there would not be an error. There is no memory protection that I'm aware of. So you can access them. But I don't think you have right access so I'm not sure about that depends on the implementation. But I rest assured whatever we implement in this course they are not gonna be protected as such. We're really implementing just a simple function. It's a simple MIPS processor which also supports only a small set of instructions. All right, finally some odds and ends. We're gonna talk about some pseudo instructions which are essentially macros that help you to make the coding experience much more pleasant. Exceptions and it's optional things we have floating instructions and the such. So pseudo instructions are here to help you to make your programming easier. For example, if you, as mentioned before, original MIPS instructions only allow you to load 16 bit maximally into a particular register. But if you wanna really load the entire thing you can also use LI and make it entire thing here. So this macro would be processed by your assembler and it would convert it into this, these two instructions. The same goes for multiplication. We wanna multiply something and store the result in S zero. It's gonna store the lower 32 bits into S zero. Clear would simply clear a particular register and what it does is it adds these two zero registers and stores the result in your destination register. Move is yet another macro. So it's not part of the MIPS language but it also does the same thing as move. No op simply does nothing. You always need this for some miscellaneous purposes. Exceptions. There are a lot of reasons that can cause exceptions. For example, hardware or software traps. Hardware can be improved by getting input from keyboard or any external IO. And when there's an exception there is always a particular fixed address that the MIPS processor would jump to and that is the exception handler and stored at this fixed address. Okay. And after the exception is processed it would return back to the original program. So these are just details on how exceptions are handled but they're not really the core part of the course. So I will just leave them as references. What I wanna do is skip these and talk about instructions that you will use and some of which you will implement in the labs. Okay, so there are sign and unsigned instructions, addition, subtraction, multiplication, division and finally set less than or comparison operators. So signed operators are these addition, addition, add, immediate and subtraction. And unsigned is the corresponding arguments but with U appended at the end. Okay. So these are just an overview of the instructions you will use. Multiplication the same and if you wanna use unsigned then they would be appended by U. All right, you have set less than which is exactly the same and basically all of these functions that I have shown so far, these are the functions that you will most commonly use for the labs and also for the exams. Now we are going into some special functions. These are functions that loads not just a not always a full 32 bit word but maybe half of it, okay? So one LH is load half word, that's only 16 bits and load byte is you're just only one byte, so just eight bits. And there are also the unsigned versions of it. So the unsigned version namely is sign, so the original signed version is gonna unsign extended so if it's gonna be represented in two's complement then if the signed bit is one then all of it's gonna be one. So that's why it's a signed operator, okay? The unsigned one simply zero extends it, okay? Zero extends whatever value that is not specified in the operand. All right, finally there's also floating point numbers but I was also told that floating point numbers is outside of the curriculum this semester so you can also use this as reference. But just to give you a small summary, floating points are not inherently covered by MIPS processor itself. It is oftentimes an optional co-processor that the MIPS processor would support. So it doesn't necessarily have to be implemented by MIPS processor. Although most of these modern MIPS processors you can get off the market, they do support it. They use another 32 registers and all of these registers given that they're 32 bits they can be concatenated into 64 bits so you can support the double floating type, okay? These are just information on how they can be done and also with the floating point instructions there's also a special type of opcode 17 and also a special field here, COP that allows you to specify different types of floating point instructions. So 17 is always sure as to indicate this is a floating point operation and which operation is in an ad, is it an addition, it's gonna be implemented in COP in the later five bits and it's here that you have single precision and double precision variants of addition and other arithmetic operations, okay? And there are also other operands that you can take care of and there are also other instructions that you can use to compare them and also load words and other miscellaneous instructions. But these, currently they are not part of the labs so you don't have to refresh them upon them but the other ones please try to keep them in mind and we're gonna use them in lab six. All right, just to sum up quickly, today we talked about common language constructs and how we can represent them in assembly. We talked about the stack which is really important. You wanna use that to back up your data, back up your register when you call a potential function or when you've just been called by another function and what is important is also this return address. A lot of times I see students forgetting to back up the return address in the final exam. Now that is something that we have to take care of, okay? The compile program, the processes is also what we covered today. Other often ends but those are mostly optional stuff you can learn and more details can be found in the textbook and this pretty much summarizes the lectures today and yesterday summarizes what you need to know about MIPS processors, okay? Now starting maybe the week after the next you will start to design your very own MIPS processor so be prepared to get excited guys, thanks.