-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Adds lab 2 solution and lab 4 writeup.
- Loading branch information
Showing
2 changed files
with
768 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,390 @@ | ||
--- | ||
layout: lab | ||
title: Lab 2 | ||
solution: true | ||
--- | ||
|
||
## Part A: Immediates and Negativity | ||
|
||
Recall some discussion in Lab 1 about immediate values. | ||
These are constant values (integers) that can be used with some instructions in place of a register. | ||
Instead of getting the value from the register, it simply uses the value you wrote in (makes use of the value that is "immediately" there.) | ||
Here's an example of simply setting a register to a particular value using the **load immediate** instruction: | ||
|
||
```python | ||
li t0, 0x1234 | ||
``` | ||
|
||
They can often make our code a bit easier to read in the case that there is some constant value we are using. | ||
As opposed, of course, to using a variable for some sort of constant (because those two terms are contradictory, no?) | ||
For instance, incrementing a value: | ||
|
||
```python | ||
addi t0, t0, 1 | ||
``` | ||
|
||
As you can see, the `addi` (the 'i' is for immediate) takes two registers and an immediate value. | ||
We can actually use the `add` instruction, and MARS will emit an `addi` for us when it sees an immediate value. | ||
It knows that is what we meant... so we don't have to be absolutely conscious of which instruction we are using, thankfully! | ||
This is equivalent in MARS: | ||
|
||
```python | ||
add t0, t0, 1 | ||
``` | ||
|
||
Immediate values can be up to 16 bits to fit into one instruction. | ||
(MARS is intelligent enough that if you use an immediate bigger than 16 bits, it will emit multiple instructions automatically to handle it. Thankfully!) | ||
Given this, answer the following questions: | ||
|
||
{:.question} | ||
| **A.1**: **How many unique values can a 16-bit immediate value hold?** | ||
|
||
{:.answer} | ||
> 2^16 or 65536; I recommend just using notation with powers of two! | ||
{:.question} | ||
| **A.2**: **Immediate values are 16-bit 2's complement signed values. Given that, what is the possible range of values?** | ||
|
||
{:.answer} | ||
> -2^15 to 2^15 - 1, inclusive | ||
{:note} | ||
> or -32768 to 32767 (again the powers of two notation is much nicer... and on an exam, you'd likely want to not do that math, right?) | ||
**Note**: You may need to look ahead to upcoming Tuesday's lecture for the answer to A.2. | ||
|
||
### Your Turn | ||
|
||
Write a small program (you will not turn this file in... you're just experimenting!) that loads the immediate value `0xBEEFCAFE` into `t0`. | ||
Recall that immediate values can only be 16 bits. | ||
Assemble this small program and simply look at the assembled code in the "Text Segment" window. | ||
|
||
![The Text Segment window with Code, Basic, and Source columns highlighted]({{site.baseurl}}/labs/02/mars_text_segment.png) | ||
|
||
You will see your code on the right-hand side in the "Source" column. | ||
To the immediate left of that is the "Basic" column, which shows the actual instructions. | ||
Recall that `li` is a pseudo-instruction (pseudo meaning "not real.") | ||
While looking at the "Basic" and "Code" columns, answer the following questions: | ||
|
||
{:.question} | ||
| **A.3**: **How many total bits is the value `0xBEEFCAFE`?** | ||
|
||
{:.answer} | ||
> 32 | ||
{:note} | ||
> Which is less than 16... hmm. | ||
{:.question} | ||
| **A.4**: **Which two instructions did a `li` instruction get turned into?** | ||
|
||
{:.answer} | ||
> lui and ori (load upper immediate, and or immediate) | ||
{:.question} | ||
| **A.5**: **What is the machine code for these instructions (The Code Column) in hexadecimal?** | ||
|
||
{:.answer} | ||
> 0x3c01beef and 0x3428cafe | ||
{:note} | ||
> Which each contain half of our immediate. It is safe to say `lui` handles the higher half of the immediate (16-bits) | ||
> and `ori` handles the lower half (16-bits). If you look at what those instructions do, then it will become clear how. | ||
**Note**: When answering A.5, look for evidence of the `0xBEEFCAFE` number. | ||
We will look at how the machine code is generated soon, and we will look at these two instructions in more detail. | ||
For now, just note how the immediate is most definitely represented in the machine code. | ||
It's not in the Data Segment (like variables)... it's smashed in right in the code itself. | ||
|
||
{:note} | ||
> Yep! At any rate, the immediate values are immediate because they are right there in the generated code itself. | ||
> Not in memory (the data segment at least) and not in a register somehow. | ||
## Part B: Getting Some Well-Needed Input | ||
|
||
Start by creating a new file and name it `dww9_lab2b.asm` (but with your own pitt username, please)! | ||
|
||
In this part, we will look at letting people interact with our programs. | ||
Seems like a useful feature! | ||
The first thing we will learn is how to tell the person what we want them to do, or at least to communicate in some way. | ||
We do this by using *strings*, which is to say a set of text characters, and then syscalls which can take strings as arguments and display them. | ||
|
||
### Printing Some Strings | ||
|
||
We will be using system call number `4` (refer to the MARS help under "System Calls", as we did in Lab 1 for more information) which is the Print String system call. | ||
We also need to allocate a variable for our string. | ||
This works very similarly to declaring an integer, as we saw in class, except instead of the type being `word`, it is `asciiz`, which stands for "Zero-terminated ASCII". | ||
That `z` means it automatically adds a zero byte (`0x00`) to the end for you. | ||
|
||
We will ask for a value: | ||
|
||
```python | ||
.data | ||
str: .asciiz "What is the first value?\n" | ||
|
||
.text | ||
.globl main | ||
main: | ||
la a0, str | ||
li v0, 4 | ||
syscall | ||
``` | ||
|
||
As you can see, we are using the `li` pseudoinstruction (it's not an actual MIPS instruction, but thankfully MARS, the assembler, will emit the actual instructions that the CPU actually executes for us) which we also used in the last lab. | ||
New to us, here, is the `la` instruction (**load address**) which sets the given register to the *memory address* of the given variable ("*str*", in this case). | ||
|
||
If we refer to the syscall help, we will see that the Print String syscall wants the address of the string in `a0` and, of course, `4` in `v0`. | ||
When these conditions are met, we can call `syscall`. | ||
|
||
Assemble (**F3**) and run (**F5**) your program. | ||
You should see your string printed out. | ||
Notice the `\n` in the string. | ||
This, like it does in Java, ensures that a newline is printed at the end of the string. | ||
Without this, the string gets printed and then the next item to be printed would appear directly after the question mark. | ||
This makes it a little cleaner. | ||
|
||
### Getting the Value | ||
|
||
Now, the useful part: interaction. | ||
We will be using another syscall for this. | ||
Thank goodness for these system calls! | ||
When you write code for embedded systems and directly for some architectures, it is still fairly common to have such rich routines available to you, so it's not even really cheating! | ||
|
||
The system call we need now is system call number `5` or the "Read Integer" system call. | ||
The way this one works is a little strange, honestly. | ||
When you invoke the `syscall` instruction, your program will pause until the person running it enters a number and presses enter. | ||
When this happens, the program will resume where it left off, and the number that was entered in will be stored in the `v0` register. (Refer to the syscall help) | ||
The `v` registers are often used for return values of functions or system calls. | ||
|
||
Here is the complete program, so far. I've added some helpful comments where I've given the system calls my own little names just for clarity: | ||
|
||
```python | ||
.data | ||
str: .asciiz "What is the first value?\n" | ||
|
||
# First Input | ||
a: .word 0 | ||
|
||
.text | ||
.globl main | ||
main: | ||
la a0, str # printString(str) | ||
li v0, 4 | ||
syscall | ||
|
||
li v0, 5 # a = getInteger() | ||
syscall | ||
sw v0, a | ||
|
||
li v0, 1 | ||
lw a0, a | ||
syscall | ||
|
||
li v0, 10 # exit() - stops the program | ||
syscall | ||
``` | ||
|
||
I added a little code to print out the given integer and then exit the program using syscall number 10. | ||
It is nice to explicitly exit our program, especially when we start adding functions and loops to our code. | ||
So, let's get in the habit now! | ||
|
||
So, now, we can get this value and, you know, do some math and stuff on it. | ||
Let's do that. | ||
We could print out the number or the value of a register to see what it has, but instead, let's use the debugger in MARS. | ||
Assemble this program (**F3**), and instead of running it, let's step through it. | ||
We can press the Step button, or press **F7**. | ||
This will run the program one instruction at a time. | ||
|
||
You will notice you have to step *twice* for the first instruction, which is the `la` instruction. | ||
That is because this is a pseudo-instruction. | ||
The `la` instruction is actually *two* instructions on MIPS, so you have to step through them one at a time. | ||
|
||
When you get to the second `syscall`, you will be able to type in a number. | ||
Once you type in a number, you will be able to step once more to print out that number and end the program. | ||
|
||
**Experimentation**: Try typing in something that isn't a number. What happens? | ||
|
||
### Your Turn | ||
|
||
Modify your program so that it accomplishes the following goals and submit your **single** `dww9_lab2b.asm` file: | ||
|
||
{:.question} | ||
| **B.1**: **Modify your program to ask for another input (including adding another string to prompt them!) and assign that to a new variable `b`.** | ||
|
||
{:.question} | ||
| **B.2**: **Add a variable `c` and assign this to the resulting value of `a` - `b`.** | ||
|
||
{:.question} | ||
| **B.3**: **Instead of printing the value of `a`, print the resulting `c`.** | ||
|
||
Here's the output I would expect to see in the completed program: | ||
|
||
``` | ||
What is the first input? | ||
45 | ||
What is the second input? | ||
78 | ||
45 - 78 = -33 | ||
``` | ||
|
||
**Hint**: You likely need 4 strings to produce the output above. Sometimes without newlines, to make the output nice and neat in conjunction with the print integer system call. Specifically, printing " - " (without a newline) between each Print Integer syscall will do the trick nicely. | ||
|
||
**Hint**: Make good use of variables! Otherwise, it may be difficult to juggle `v0` and `a0` registers. It's ok to write some extra code if it makes your life easier! (No need to be clever) | ||
|
||
## Part C: Exploring Memory | ||
|
||
As we learned in lecture, memory is essentially an array of bytes. | ||
Since our system is **byte-addressable**, each byte in memory has its own address, which is the position within that array that this byte is located. | ||
Let's write a small program to play around with memory. | ||
|
||
```python | ||
.data | ||
x: .byte 42 | ||
y: .half 42 | ||
z: .word 42 | ||
|
||
.text | ||
.globl main | ||
main: | ||
li v0, 1 # printByte(x) | ||
lb a0, x | ||
syscall | ||
|
||
li v0, 1 # printHalfWord(y) | ||
lh a0, y | ||
syscall | ||
|
||
li v0, 1 # printWord(z) | ||
lw a0, z | ||
syscall | ||
``` | ||
|
||
In this program, I have defined three variables: x, y, and z. | ||
Each variable has its own size. `x` is a single byte, `y` is a half word (2 bytes), and `z` is a word (4 bytes.) | ||
Then, the rest of the program prints out each variable using the corresponding **load** instruction, `lb`, `lh`, and `lw` respectively. | ||
|
||
Copy this program into MARS. | ||
When we assemble (**F3**), we can look at the Data Segment to see the layout of static memory. | ||
Here we can see 42 (0x2a) listed several times. | ||
MARS has allocated memory for us with at least as much space for each variable as we requested via `byte`, `half`, and the `word` directives in our code. | ||
|
||
Please answer the following questions: | ||
|
||
{:.question} | ||
| **C.1**: **What is the address of `x`?** | ||
|
||
{:.answer} | ||
> 0x10010000 | ||
{:.question} | ||
| **C.2**: **What is the address of `y`?** | ||
|
||
{:.answer} | ||
> 0x10010002 | ||
{:.question} | ||
| **C.3**: **What is the address of `z`?** | ||
|
||
{:.answer} | ||
> 0x10010004 | ||
**Hint**: Refer to the "Labels" section after assembling (**F3**) your code, as we saw in class. | ||
|
||
**Something to Think About**: Look at the addresses. MARS decided not to make it as compact as possible (`y` isn't immediately after `x`). After you complete the lab, think more about why. | ||
|
||
What happens if we do some strange things? | ||
|
||
### Reading (The Wrong) Values | ||
|
||
Let's rewrite our program to look like the following in order to try to read `x` as a word: | ||
|
||
```python | ||
.data | ||
x: .byte 42 | ||
y: .half 42 | ||
z: .word 42 | ||
|
||
.text | ||
.globl main | ||
main: | ||
li v0, 1 # printWord(x) | ||
lw a0, x | ||
syscall | ||
``` | ||
|
||
Run this program as determine the answer to the following question: | ||
|
||
{:.question} | ||
| **C.4**: **What is the number printed out for `x` when read as a word?** | ||
|
||
{:.answer} | ||
> 275255 or 0x002a002a | ||
As you can see, the computer does exactly what you tell it to do. | ||
It does not always stop you from performing these strange actions. | ||
(And it will give you strange results) | ||
Essentially, it read 4 bytes from the address of `x` instead of the 1 it should have. | ||
|
||
Replace `x` in the `lw a0, x` in the above program with `y` and run it again, answering the following question: | ||
|
||
{:.question} | ||
| **C.5**: **What happens when trying to read variable `y` as a word?** | ||
|
||
{:.answer} | ||
> It crashes. Specifically: A Runtime Exception: fetch address not aligned on word boundary | ||
Just because the machine is byte-addressable does not mean it can read *word* values at every possible address. | ||
In this case, MIPS does not allow you to read a 4-byte word when the address is not *aligned*. | ||
That is, the address must be divisible by 4. | ||
This is because the MIPS architecture that MARS is simulating will not read information across a word boundary. (That is, it will not read a word at address 6 because that would mean reading half of the word at address 4 and half of the word at address 8.) | ||
|
||
If you see such an error in your program, it is likely that you are reading a variable using the wrong **load** instruction type. | ||
Thankfully, MARS automatically places any `word` variable declaration at an address that is divisible by 4. | ||
Similarly, it will put any `half` variable declaration at an address divisible by 2, as the same rules apply. | ||
|
||
**Something to think about**: Consider why `byte` variables in such an architecture have no such restriction and can be placed anywhere. | ||
|
||
**Take away**: Try to stick with always using *word* type variables when possible. They have less surprising interactions, and there's no real need to "conserve" memory by using smalling variable types! (Do not be clever!) | ||
|
||
### Overwriting Variables (Oops!) | ||
|
||
Here is our final program: | ||
|
||
```python | ||
.data | ||
x: .byte 42 | ||
y: .half 42 | ||
z: .word 42 | ||
|
||
.text | ||
.globl main | ||
main: | ||
lb t0, x # t0 = x | ||
sw t0, x # x = t0 !? | ||
|
||
lh t0, y # printHalfWord(y) | ||
li v0, 1 | ||
syscall | ||
``` | ||
|
||
What we are doing here seems innocent enough. | ||
We are loading the value of x into `t0` and then storing it back again. | ||
This should be what we call a **no-op**, which is to say a "null operation", that is to say it should do nothing. | ||
|
||
However, copy the above program and run it and answer the following question: | ||
|
||
{:.question} | ||
| **C.6**: **When writing back the value of `x` as a word, what is the resulting value of `y`?** | ||
|
||
{:.answer} | ||
> 0 | ||
Ah! We wrote back `x` as a word! | ||
This means it wrote a 4 byte value to memory at the address of `x`. | ||
Unfortunately, the address of `y` (Refer to your answer to C.1 through C.3) is within 4 bytes of the address of `x`. | ||
Therefore, `y` will be overwritten. | ||
\*sad trombone noise\* | ||
|
||
**Something to Think About**: Recall the addresses of `x` and `y` and consider why `y` became the value it did. Consider: What would `y` become if `x` were originally a negative number? Try it out... did it do what you thought? **Hint**: Consider sign extension rules (from [potentially upcoming] Tuesday's lecture) and, of course, step through the program and look at the memory! | ||
|
||
**Take away**: Again, you should probably stick to `word` variables and `lw` and `sw` exclusively to avoid such nasty bugs. Note again that MARS totally let you do this and didn't warn you! |
Oops, something went wrong.