Understanding a MIPS algorithm - mips

I'm very new into coding and we started with MIPS. We were kind of "thrown in to the cold water" and had to implement an algorithm that checks whether the elements of an array are sorted in ascending order. If it is sorted, a 1 is to be stored in $v0, otherwise a 0.
The solution that was given us:
.data
A: .word 1, 2, 3, 4, 5
l: .word 5
.text
main:
la $s0, A #address of A
lw $s1, l
add $t0, $0, $0 #counter for loop
addi $v0, $0, 1
sub $s1, $s1, $v0
for:
beq $t0, $s1, done
sll $t1, $t0, 2 #byte offset
add $t1, $t1, $s0
lw $t1, 0($t1) #$t1 = A[j]
addi $t2, $t0, 1
sll $t2, $t2, 2
add $t2, $t2, $s0
lw $t2, 0($t2) #$t2 = A[j + 1]
sub $t3, $t2, $t1 #$s3 = A[j+1] - A[j]?
slt $t4, $t3, $0
addi $t3, $0, 1
beq $t3, $t4 unsort #A isn’t sorted if A[j+1] - A[j] has a negative value
addi $t0, $t0, 1 #$t0 = $t0 + 1
j for
unsort:
add $v0, $0, $0 #set $v0 if array isn’t sorted
done:
I'm having trouble understanding this code/algorithm. First of all what is an Array and why do we need specifically 5 of them?
But much more important to me is understanding this code/algorithm. So I need someone who is kind enough and explains to me step by step and in simple word :D, how this code works.
Would be very helpful and thanks in advance.

Short answer
Basically, this program iterates over the array A in a loop with loop counter, say i. The index i is initialized with 0 and in every loop iteration incremented by 1 until it reaches l-1, where l is the size/length of the array A. In every iteration the algorithm checks if A[i] < A[i + 1] (i.e. if the i-th and the (i+1)-th element in A are in ascending order and thus are sorted). If so, it continues execution and $v0 remains 1. Otherwise it sets $v0 to 0 and terminates.
Long answer
Arrays and memory
An array is basically an ordered list of data – in this case it is an ordered list of words (a word in MIPS means: 32-bit value, and it consists of 4 bytes, each of which has 8 bits of information). So in this case, every word represents a 32-bit integer.
If we have an array A of length l, the elements are indexed from 0 to l-1. The first element in an array A (notation: A[0]) is saved at a certain address in memory, let's call it addr. The i-th element in A (A[i]) is then saved at the memory address addr + 4*i. This is because memory in MIPS is byte-addressable, i.e., every byte has his own address and because a word consists of 4 bytes, word addresses are offset by 4 (see below).
.data section
A: .word 1, 2, 3, 4, 5
l: .word 5
With that you will realize that you don't have 5 arrays but only one (called A) and it contains the values 1, 2, 3, 4 and 5. Thus, the length is 5 and it is specified in the word l. You could add more values to your array but then you would have to adjust the length because otherwise, strange things will happen (or at least the result will be random). Your data is specified in this .data section and is therefore somewhere stored in your program's memory space.
MIPS assembly
In order to understand the code in the .text section, you have to understand MIPS assembly. If I write about a register, just think about it as a placeholder for a 32-bit value. For example, $0 is the zero register and it always stores a 32-bit 0. Other registers are used to temporarily store values used in your program. The instructions you use are:
la rd, label ("load address")
(rd = address of label; stores the address of the word specified by "label" in the destination register rd)
lw rd, label ("load word")
or
lw rd, offset(rs)
(load data specified by label, or data at address rs+offset into destination register rd)
add rd, rs, rt
(rd = rs + rt; adds source registers rs and rt and stores result in destination register rd)
addi rd, rs, imm ("add immediate")
(rd = rs + imm; adds source register rs and immediate (16-bit constant) value and stores result in destination register rd)
sub rd, rs, rt ("subtract")
(rd = rs - rt; subtracts source register rt from source register rs and stores result in destination register rd)
beq rs, rt, label ("branch if equal")
(if value in rs is equal to value in rt, jump to label)
sll rd, rs, shamt ("shift logic left")
(rd = rs << 2; shift value in rs by two bits the left and store it in rd)
slt rd, rs, rt ("set less than")
(rd = (rs < rt) ? 1 : 0; if the value in rs is smaller than the value in rt, set rd to 32-bit 1, otherwise to 32-bit 0)
j label ("jump")
(jump to label)
.text section
main:
la $s0, A
lw $s1, l
add $t0, $0, $0
addi $v0, $0, 1
sub $s1, $s1, $v0
for:
beq $t0, $s1, done
sll $t1, $t0, 2 #byte offset
add $t1, $t1, $s0
lw $t1, 0($t1) #$t1 = A[j]
addi $t2, $t0, 1
sll $t2, $t2, 2
add $t2, $t2, $s0
lw $t2, 0($t2) #$t2 = A[j + 1]
sub $t3, $t2, $t1 #$s3 = A[j+1] - A[j]?
slt $t4, $t3, $0
addi $t3, $0, 1
beq $t3, $t4 unsort #A isn’t sorted if A[j+1] - A[j] has a negative value
addi $t0, $t0, 1 #$t0 = $t0 + 1
j for
unsort:
add $v0, $0, $0 #set $v0 if array isn’t sorted
done:
This assembly code is equivalent to the pseudocode (if you are not used to pseudocode or to while loops, please look it up in the internet):
s0 <- address of first element in A
s1 <- l
t0 <- 0
v0 <- 1
s1 <- s1-1
while (t0 != s1) do
t1 <- 4 * t0
t1 <- t1 + s0
t1 <- word at address t1
t2 <- t0 + 1
t2 <- 4 * t2
t2 <- t2 + s0
t2 <- word at address t2
t3 <- t2 - t1
if (t3 < 0) then t4 <- 1
else t4 <- 0
t3 <- 1
if (t3 == t4) then
v0 <- 0
return
else t0 <- t0 + 1
Hope, this helped. I think it is quite ambitious to start with assembly code if you haven't coded before. Good luck!

Related

MIPS - how to write a for loop while also accessing and saving arrays

I won't lie, I'm a total beginner at programming in general with my first time being last year when I started programming in Computer Programming I. A lot of this is still over my head so uh... go easy on me will ya?
The question poised is as follows:
"1. (5 pts) Translate the following C code to MIPS. Assume that the variables i and j are assigned to registers $s0 and $s1, respectively. Assume that the base address of the arrays A and B are in registers $s6 and $s7, respectively. Assume that the elements of the arrays A and B are 4-byte words:
for (i = 0; i< j; i++)
B[i] = A[i+1] - A[i];
For my program I wrote:
.data
arrayA: .word 5,8, 12, 13, 28
sizeA: .word 5
arrayB: .space 4
i: .word 0
_j: .word 5
.text
# for (i = 0; i< j; i++)
# B[i] = A[i+1] - A[i];
main:
la $s6, arrayA #loads the base address of arrayA into register s6
la $s7, arrayB #loads the base address of arrayB into register s7
lw $s0, i #Loads zero into register s0 for i
lw $s1, _j #loads the value of 4 into register s2 for j
Loop:
beq $s0, $s1, Exit #Does the operation i < j in the for-loop
addi $s0, $s0, 1 #Does the operation i++ in the for-loop
mul $t0, $s0, 4 #Get address of i
add $t1, $t0, $s6 #Finds and stores the base address of a[i]
add $t2, $t0, $s7 #Finds and stores the base address of b[i]
lw $t3, ($s6) #get the value of a[i]
lw $t4, 4($10) #get the value of a[i + 1]
sub $t5, $t4, $t3 #subtracts a[i + 1] and a[i]
sw $t5, ($t2) #Stores the above into b[i]
add $a0, $zero, $t5
li $v0, 1
syscall
J Loop #loops back to beginning
Exit:
li $v0 10
syscall #syscall to exit the program
When I run it, it spits out 33333, which means it isn't incrementing. How do I fix this?
arrayA: .word 5,8, 12, 13, 28
may be wrong. You didn't add a space between 5, and 8 while giving spaces between the other elements. My QtSpim Version 9.1.21 didn't accept that.
arrayB: .space 4
is wrong. You are allocating only 4 bytes while space for 4 words (16 bytes) is required.
_j: .word 5
is wrong. The stored value is 5 despite of the comment saying "loads the value of 4 into register s2 for j". Using value 5 is wrong also because the array a has only 5 elements.
addi $s0, $s0, 1 #Does the operation i++ in the for-loop
is in the wrong place. The increment should be after the loop body.
lw $t3, ($s6) #get the value of a[i]
lw $t4, 4($10) #get the value of a[i + 1]
is wrong. The address of a[i] is stored in $t1, so you should use that.
J Loop #loops back to beginning
may be wrong. You used an uppercase letter for instruction name only here. At least my QtSpim Version 9.1.21 didn't accept that.
li $v0 10
may be wrong. You didn't use a comma for separating the operands while you used that in previous use of li: li $v0, 1. I'm surprised by seeing my QtSpim Version 9.1.21 accepting this.
Fixed code:
.data
arrayA: .word 5, 8, 12, 13, 28
sizeA: .word 5
arrayB: .space 4 * 4
i: .word 0
_j: .word 4
.text
# for (i = 0; i< j; i++)
# B[i] = A[i+1] - A[i];
main:
la $s6, arrayA #loads the base address of arrayA into register s6
la $s7, arrayB #loads the base address of arrayB into register s7
lw $s0, i #Loads zero into register s0 for i
lw $s1, _j #loads the value of 4 into register s2 for j
Loop:
beq $s0, $s1, Exit #Does the operation i < j in the for-loop
mul $t0, $s0, 4 #Get address of i
add $t1, $t0, $s6 #Finds and stores the base address of a[i]
add $t2, $t0, $s7 #Finds and stores the base address of b[i]
lw $t3, ($t1) #get the value of a[i]
lw $t4, 4($t1) #get the value of a[i + 1]
sub $t5, $t4, $t3 #subtracts a[i + 1] and a[i]
sw $t5, ($t2) #Stores the above into b[i]
add $a0, $zero, $t5
li $v0, 1
syscall
addi $s0, $s0, 1 #Does the operation i++ in the for-loop
j Loop #loops back to beginning
Exit:
li $v0, 10
syscall #syscall to exit the program
Don't mix friendly register names with the raw register names — very confusing.
lw $t3, ($s6) #get the value of a[i]
lw $t4, 4($10) #get the value of a[i + 1]
Here you're mixing $t3, $t4, $s6, and $10, which is actually $t2.
Your assembly code is doing the following C code (modulo the above bug):
int *s6 = A;
int *s7 = B;
for ( int i = 0; i != j; ) {
i++;
int *t1 = s6 + i; // i is automatically scaled by C
int *t2 = s7 + i; // ditto
int t3 = *s6; // A[0]
int t4 = s6[1]; // A[1]
int t5 = t4 - t3;
*t2 = t5; // B[i]=...
}
Can you see why it keeps loading A[0] & A[1]?
You should have noticed these problems during single stepping debugging.  After each step, check that every effect you're expecting happens as you expect.  When it doesn't, look for typos or logic problems.

When writing in MIPS, I am unsure whether to use li or addi. I am still unclear what the difference is.

For example, I have a piece of C code that I am trying to convert to MIPS for practice, but for the variable count, I don't know whether to use addi $t0,0 or li $t0, 0. Could I use either either or? And what is the difference?
Void haarPredict (int vector[], int N)
{
int half = N >> 1;
int count = 0;
for(int i = 0; i < half; i++)
{
int predictVal = vector[i];
int j = i + half;
vector[j] = vector[j] - predictVal
}
}
This is what I have so far after converting the above code to MIPS. Assuming $a0 is vector[] and $a1 is N. Again, I am not sure if li or addi is the correct thing to use.
srl $t0, $a1, 1 #t0 holds half. half = N >> 1
addi $t1, $t1, 0 #t1 holds count. count = 0
addi $t2, $t2, 0 #t2 holds i. i = 0
loop: slt $t3, $t2, $t0 #t3 holds 1 if i < half
beg $t3, $zero, exit #exit if t3 == 0
lw $t4, 0($a0) #t4 holds predictValue
addi $a0, $a0, 4 #4 bytes for next word address
addi $t5, $t2, $t0 #t5 holds j. j = i + half
lw $t6, $t6, $t4 #vector[j]=vector[j]-predivtVal
addi $t2, $t2, 1 #i++
j loop
exit: jr $ra
The li (Load immediate) instruction loads a specific numeric value into a register.
The addi (Add inmediate) adds a register and a sign-extended immediate value and stores the result in a register.
So, unless you are 100% sure a register has a zero value, then you shouldn't use an addi instruction to set a register.
For example:
addi $t1, $t1, 0 #t1 holds count. count = 0
You don't know if $t1 is zero at that particular moment. If thats a subroutine, you might be using a garbage value of $t1 (a value from before the invocation of the subroutine, before the jump to the address of the subroutine).
So the safe way is to set the register with li (thus, count=0), not taking into consideration the previous value of the register.

MIPS Assembly, matrix multiplication

I'm trying to implement matrix multiplication using MIPS assembly. There is an error on the line "lw $t4, 0($t4)" on the second loop through the k_loop. The error is: "Runtime exception at 0x00400090: fetch address not aligned on word boundary 0x1000fffd". Could someone explain what the error means and what I could do to fix it? Thank you.
.data
matrixA: .word 1,2,3,4,5,6 #Content of matrixA in array form
matrixB: .word 5,6,7,8,9,10 #Content of matrixB in array form
sizeA: .word 3,2 #Defines matrixA as being a 3x2 matrix
sizeB: .word 2,3 #Defines matrixB as being a 2x3 matrix
result: .word 0:9 #Initialize result as being an array of length 9 populated with 0
tab: .asciiz "\t"
newLine: .asciiz "\n"
.globl _main
.text
_main: la $s0, matrixA #s0 set to base address of matrixA
la $s1, matrixB #s1 set to base address of matrixB
la $s2, sizeA #s2 set to base address of sizeA
nop
lw $s3, 4($s2) #s3 set to second val in sizeA (col #)
nop
lw $s2, 0($s2) #s2 set to first val in sizeA (row #)
la $s4, sizeB #s4 set to base address of sizeB
nop
lw $s5, 4($s4) #s5 set to second val in sizeB (col #)
nop
lw $s4, 0($s4) #s4 set to first val in sizeB (row #)
la $s6, result #s6 set to base adress of result
add $s7, $s5, $zero #s7 set to col # in result matrix
add $t0, $zero, $zero #Set t0 to zero. i = 0
add $t1, $zero, $zero #Set t1 to zero. j = 0
add $t2, $zero, $zero #Set t2 to zero. k = 0
li $t3, 0 #Result position set to zero
i_loop: beq $t0, $s2, i_end #End i_loop if i = rowsA
nop
j_loop: beq $t1, $s5, j_end #End j_loop if j = colsB
nop
k_loop: beq $t2, $s4, k_end #End k_loop if k = rowsB
nop
#loop body
li $t4, 0
li $t5, 0
li $t6, 0
#i * M + k - 1
mul $t4, $t0, $s3 #i * #col in matrixA
add $t4, $t4, $t2 #t4 + k
addi $t4, $t4, -4 #t4 -1
add $t4, $t4, $s0 #Now points to value at matrixA[i][k]
lw $t4, 0($t4) #Loads value at matrixA[i][k]
#k * M + j - 1
mul $t5, $t2, $s5 #k * #col in matrixB
add $t5, $t5, $t1 #t5 + j
addi $t5, $t5, -4 #t5 -1
add $t5, $t5, $s1 #t5 now points to value at matrixB[k][j]
lw $t5, 0($t5) #t5 loads value at matrixB[k][j]
#i * M + j - 1
mul $t6, $t0, $s7 #i * #col in result
add $t6, $t6, $t1 #t6 + j
addi $t6, $t6, -4 #t6 -1
add $t6, $t6, $s6 #t6 now points to value at result[i][j]
lw $t8, 0($t6) #t6 loads value at result[i][j]
mul $t7, $t4, $t5 #t7 = matrixA[i][k]*matrixB[k][j]
add $t9, $t8, $t7 #t8 = result[i][j] + matrixA[i][k]*matrixB[k][j]
sw $t9, 0($t6)
#end loop body
addi $t2, $t2, 1 #k++
j k_loop #Return to start of k_loop
k_end:
addi $t1, $t1, 1 #j++
li $t2, 0 #Resets k counter to 0
j j_loop #Return to start of j_loop
j_end:
addi $t0, $t0, 1 #i++
li $t1, 0 #Resets j counter to 0
j i_loop #Return to start of i_loop
i_end: #print
There are three distinct problems, masked by the alignment fault.
You are computing array indexes [for an int array]. Before these can be added to the base address of the matrix, they must be converted into byte offsets.
Otherwise, you'll get [as you did get] an alignment fault because mips requires that addresses for words (i.e. lw/sw) are four byte aligned.
The second problem is when you try to subtract one from index. You're using a value of -4 even though the comment says -1. So, on some level, you're mixing and matching index calculations and offset calculations
Consider a simple 1D int array/vector that starts at address 0x10010000. The index to address mapping would be:
index offset address
----- ------ --------
0 0 10010000
1 4 10010004
2 8 10010008
In your code, you have:
addi $t4,$t4,-4 # t4 - 1
add $t4,$t4,$s0 # Now points to value at matrixA[i][k]
lw $t4,0($t4) # Loads value at matrixA[i][k]
The final index (e.g. $t4) needs to be multiplied by sizeof(int) [which is 4] before adding in the matrix base address. The idiomatic way to do this is a left shift by 2.
Also, when adding addresses to addresses or offsets to addresses, you should use the unsigned version of add (i.e. addu) to guard against overflow/wrap which can occur for addresses.
addi $t4,$t4,-1 # t4 - 1
sll $t4,$t4,2 # convert index to byte offset
addu $t4,$t4,$s0 # Now points to value at matrixA[i][k]
lw $t4,0($t4) # Loads value at matrixA[i][k]
You'll need to add this extra step whenever/wherever you do these index calculations.
The third problem is the final address 0x1000fffd while not four byte aligned, is also below the lowest address allowed for the .data segment in mars (i.e 0x10010000), so if you hadn't got the alignment fault, you'd be accessing non-existent memory [which will issue a different type of fault]
So, you may want to double check your index calculations for correctness to prevent the equivalent of accessing int myarray[2]; myarray[-1] = 3; [which is UB]

MIPS Palindrome Check

I am trying to write a MIPS program that checks if an input string is a palindrome. I tested the string "HelllleH" and when stepping through the program, I saw that during the first loop of PAL_CHECK t0 = 0 but t1 = 104. Logically, t0 = 0 and t1 = 0 also in the first loop. Can someone tell what's wrong in this program?
# a0 is input
# a1 is current character we are looking at
# a2 is length of string
# t0 is character at beginning of string
# t1 is character at end of string
# v0 stores whether string is palindrome or not (0 for false, 1 for true)
ispalindrome:
addi $a2, $0, 0 # length counter
FIND_LEN:
lbu $a1, 0($a0) # load character into $a1
beq $a1, $0, PAL_CHECK # Break if string is at the end
addi $a2, $a2, 1 # increment counter
addi $a0, $a0, 1 # increment str address
j FIND_LEN
PAL_CHECK:
# Is palindrome if length is less than 2
slti $t0, $a2, 2
bne $t0, $0, RETURN_TRUE
# Check first and last chars to see if they are the same
lbu $t0, 0($a0) # first char is t0
add $t1, $a2, $a0 # last char is t1
lbu $t1, 0($t1)
bne $t0, $t1, RETURN_FALSE # if they are not equal, return false
# continue recursion
addi $a2, $a2, -2
addi $a0, $a0, 1
j PAL_CHECK
RETURN_FALSE:
addi $v0, $0, 0
jr $ra
RETURN_TRUE:
addi $v0, $0, 1
jr $ra
While finding the length of the string you continually increment $a0 to point at the next character, until you find the NUL terminator at the end of the string. You never reset $a0 before the palindrome check loop, so it's still pointing at the NUL terminator when you begin that loop. So you'll actually be comparing data that's past your string.
It would make more sense to implement the check this way (I'm using C to illustrate the idea; I'll leave the MIPS translation to you):
a0 = the_string;
a1 = the_string + strlen(the_string) - 1;
while (a1 > a0) {
if (*a0 != *a1) return false;
a0++;
a1--;
}
return true;
By the way, a terminology nitpick: # continue recursion. Your implementation isn't recursive, it's iterative.

What does this MIPS program do?

I know the program loads the address of .word 5 which is the initial value, I'm getting confused as to what the program is actually doing. Is it comparing stacks of the word or adding.
.data
arg: .word 5
.text
.globl main
main:
la $t3, arg
lw $t2, 0($t3)
lw $t3, 0($t3)
addi $t1, $zero, 0
beqz $t2, fin
fori:
add $t1, $t1, $t2
addi $t3, $t3, -1
bnez $t3, fori
fin:
li $v0, 10
syscall
Looks like it does addition based on the first item in the space as it doesn't access the array from inside the loop.
So for instance if you input a character representing the ascii value of 5 (not '5' itself iirc) you might see something like this:
int t2 = 5, t3 = 5, t1 = 0;
do {
t1 += t2;
t3 -= 1;
} while(t3 > 0);
Someone has already answered but I shall provide the translation I did:
.data
arg: .word 5
.text
.globl main
main:
la $t3, arg # load-address of arg into t3
lw $t2, 0($t3) # load-word from the address in t3 + 0 offset, like t3[0]
lw $t3, 0($t3) # same but to t3
addi $t1, $zero, 0 # initialize t1 to 0? weird they don't use li
beqz $t2, fin # if t2 is zero jump to fin:
fori:
add $t1, $t1, $t2 # t1 = t1 + t2
addi $t3, $t3, -1 # t3 -= 1
bnez $t3, fori # if(t3 != 0) goto fori
fin:
li $v0, 10 # load immediate 10 into v0, which is the syscall to terminate the program
syscall
Disclaimer: I don't have any direct MIPS experience, I just have been around a number of assembly languages.
That said, I think what the program does is calculating the square of 'arg' by repeated addition - in this case 5 * 5 = 25. However, the result in $t1 doesn't seem to be stored anywhere, instead the program just exits.