I'm new to MIPS and am confused on how to go about writing mips could for a certain question that is given where I'm asked to write mips code to pulse bit 6 at a memory location of 0xABCDABC0 for a total of 2*n+74 times and assuming n is in $s1.
I'm not sure exactly how to deal with accessing a specific bit at the given address, I know that I'd have to use or immediate to specifically turn on the bit with 1 and then and it to turn the bit back to 0 using 1. The Loop (without multiplication) I could do add $s2, $s1, $s1
then addi $s2, $s2,74 but how do you loop it?
The easiest part here is writing the loop, I can help you with that. I'm pretty rusty, but I'll do my best to get you started.
Looping can be achieved through temporary values, adding immediate values, branch instructions, and/or jump instructions.
The basic flow you're looking for sounds like:
Create a counting variable (the i part in for (int i=..; i < ...; i++), as an example)
Label the beginning instruction of your loop (that will look something like below)
label_name:
loop instructions
Perform the loop instructions
Increment your loop variable from step 1
Populate $s2 with 2*n+74 (that's the part you have now)
Branch to the beginning of the loop if your counter is not equal to your desired iteration count
Note: if you have enough loop instructions, you might have to use a more complicated branch/jump construct.
Example loop code:
andi $t1, $t1, 0 # set a counter variable to 0 (t1)
loop:
# do some work - whatever you want to accomplish by looping
addi $t1, $t1, 1 # increment the counter
add $s2, $s1, $s1 # this is the code you devised
addi $s2, $s2, 74
bne $s2, $t1, loop #branch to the beginning of the loop if you need more iterations
The next part you need is grabbing a value at a memory address, there are a few ways to go about that. You should look into the load/store instructions available in MIPS. In particular, I think you will want to utilize the lw (load word) instruction.
lw r1, label -> loads the word from memory stored at the addressed stored in 'label' into register r1
I Googled a little and http://pages.cs.wisc.edu/~cs354-2/cs354/karen.notes/MAL.instructions.html had some reasonable explanations and and examples of those instructions.
Accessing a specific bit requires utilizing some bit operations. In particular, I think you will find bit shifting and masking helpful.
MIPS offers a few instructions you might like:
sll d, s1, s2 d = logical left shift of s1 by s2 places
sra d, s1, s2 d = arithmetic right shift of s1 by s2 places
srl d, s1, s2 d = logical right shift of s1 by s2 places
and d, s1, s2 d = s1 & s2; bitwise AND
-- From the source I cited above
I hope that is sufficient to answer your question, and least get you going on the problem. I didn't want to just hand you the code, since it sounded like homework. If anything isn't clear or needs clarification, please just say so.
Hope at least some of all of that helps you!
Related
Supposing that f, g, h, i are stored in $s0~$s4 respectively and the base addresses of arrays A and B are in $S6 and $S7.
sll $t0, $s0, 2
add $t0, $s6, $t0
sll $tl, $sl, 2
add $tl, $s7, $tl
lw $s0, 0($t0)
addi $t2 , $t0, 4
lw $t0, 0($t2)
add $t0, $t0, $s0
SW $t0, 0($tl)
I'm not familiar with MIPS so I Wonder how to translate MIPS into C and how to minimize these MIPS instructions?
how to translate MIPS into C
You recognize the patterns, here for array indexing / array element access.
On a byte addressable machine (all modern hardware), a 4-byte integer occupies 4 bytes in memory, and each of those bytes has a unique memory address. Because of the way the hardware works, we only use one of those 4 addresses to refer to the whole 4-byte integer, namely we use the lowest address among the 4. The hardware can load a 4-byte integer from memory given that one address (the lowest).
Since each 4-byte integer in memory occupies 4 addresses, in an array of 4-byte integers, the memory address of the first element and the memory address of the second element are 4 addresses apart even though are sequential index positions (i.e. they are only 1 index position apart).
The formula for indexing a 4-byte integer array, then is to convert the index into a byte offset, then add the byte offset to the base address of the array. The first part of that: converting an index to a byte offset, is sometimes referred to as "scaling". Scaling is conceptually done by multiplication, so in A[i], i needs to be scaled by the size of the array elements of A. If 4-byte integers that means scaling (multiplying) the index by 4. A quick way of doing that is shifting by 2 bit positions, which has the same effect as multiplying by 4.
The C language automatically scales when doing array references, whereas assembly language requires explicit scaling. C can do this because it knows the type of the array, whereas assembly language does not.
In C we can do expressions like A[i]. The C language allows us to break that down somewhat into *(A+i), which separates the pointer arithmetic addition A+i from the dereferencing of that sum, dereferencing with the unary indirection operator, *. As previously mentioned, C automatically scales, so A+i becomes the equivalent of A+i*4, in which we can substitute shifting for multiplication: A+(i<<2).
Next, we need to know if the dereference is for read or for write. When A[i] is accessed for its value, we will see it on what we call the "right hand side" of an assignment operator, as in ... = A[i]. When A[i] is access to update/store a value, we will see it on what we call the left hand side of an assignment operator, as in A[i] = ....
So, the sequence for doing A[i] for read (right hand side) in C is the following in assembly:
sll $temp1, $i, 2
addu $temp2, $A, $temp1
lw $temp3, 0($temp1)
Where $tempN is some register (usually a designated temporary) chosen to hold an intermediate value. Since multiple instructions are needed to accomplish anything, sequences of instructions are interconnected with registers that hold the intermediate states. And also, in assembly we name registers, not variables, so in my above $i and $A should be a registers names representing those variables rather than variable names directly used.
The pattern for write/store array access is similar but ends with a sw instruction instead, to store some value into memory at the index position.
These instruction sequence are interconnected by the use of these registers, and the sequences can be interrupted or interspersed with other instructions — what we have to follow then is the above pattern by paying attention to to the register usages that interconnect them rather than the specific sequences.
In your sample code:
sll $t0, $s0, 2 # sourcing an index in $s0, scaling it into temp $t0
add $t0, $s6, $t0 # adding a base array in $s6, putting back into $t0
sll $tl, $sl, 2
add $tl, $s7, $tl
lw $s0, 0($t0) # accessing the value of $s6[$s0*4], aka A[f]
addi $t2 , $t0, 4
lw $t0, 0($t2)
add $t0, $t0, $s0
SW $t0, 0($tl)
We can see the pattern for a read access to an index in $s0, and an array in $s6, these, we are told, map to f and A, so those three instructions comprise A[f] to read a value from A at index f.
The rest are done similarly. Your job is to use this knowledge to find the other array indexing patterns in the above sequence. Find out how the results of the array indexing operations are used and you'll have the complete C code.
NOTE that the sample you've been given incorrectly uses add and addi when pointer arithmetic should use addu and addiu — we don't want signed integer overflow checking on pointer arithmetic, as pointers are unsigned.
One of the add instructions is not for pointer arithmetic, but should probably still have used addu if this is intended to be replicated in C, because the C language does not have a built in operator to trap on overflow.
How does MIPS's assembler labels and J type instruction work?
I am currently making a MIPS simulator using C++ and came into a big question. How exactly does MIPS assembler manage label's and their address while on a J type instruction?
Let's assume that we have a following code. Also let's assume that start: starts at 0x00400000. Comments after code represent where the machine codes will be stored in memory.
start:
andi $t0, $t0, 0 # 0x0040 0000
andi $t1, $t1, 0 # 0x0040 0004
andi $t2, $t2, 0 # 0x0040 0008
addi $t3, $t3, 4 # 0x0040 000C
loop:
addi $t2, $t2, 1 # 0x0040 0010
beq $t2, $t3, exit # 0x0040 0014
j loop # 0x0040 0018
exit:
addi $t0, $t0, 1000 # 0x0040 002C
As I am understanding right at the moment, j loop expression will set PC as 0x0040 0010.
When J type instruction uses 32 bits and with MSB 6 bits as its opcode, it only has 26 bits left to represent address of instruction. Then how is it possible to represent 32 bit address system using only 26 bits?
With the example above, it can represent 0x00400010 with only 24bits. However, in references, text segment is located from 0x00400000 to 0x10000000 which needs 32bit to represent.
I have tried to understand this using MARS simulator, however it just represents j loop as j 0x00400010 which seems nonsense to me since 0x00400010 is 32 bits.
My current guess
One of my current guesses is following.
Assembler saves the loop: label's address into some memory address that is reachable by 26 bits. Then when expression j loop is called, label loop is translated to the memory address that contains 0x00400010 For example, 0x00400010 is saved in some address like 0x00300000 and when j loop is called, loop is translated into 0x00300000 and it is able to get value from 0x00300000 and reach out 0x00400010. (This is just one of my guess)
You have a number of questions here.
First, let's try to differentiate between the assembler's operation and the MIPS machine code that it generates and the processor executes.
The assembler manages labels and address in two ways. First, it has a symbol table, which is like a dictionary, a data structure of key-value pairs where the names are keys and the addresses (that those names will refer to when the program is running) are the values in the pairs.
Second, the assembler manages the code and data sections with a location counter. That location counter advances each time the program provides some code or data. When new label is defined, the current location counter is then used as the address value in a new key-value pair.
The processor never sees the labels: they do not execute and they do not occupy any space in the code or data. The processor sees only machine code instructions, which on MIPS are all 32-bits wide. Each machine code instruction is divided into fields. There are instruction types or formats, which on MIPS are straightforward: I-Type, J-Type, and R-Type. These formats then define the instruction fields, and the assembler follows these encodings. All the instruction formats share the 6-bit opcode field, and this opcode field tells the processor what format the instruction is, which fields it therefore has, and thus how to interpret and execute the rest of the instruction.
The assembler removes labels from the assembly — labels and their names do not exist in the program binary. The label definitions themselves (label:) are omitted from the program binary but usages of labels are translated into numbers, so a machine code instruction that uses a label will have some instruction field that is numeric, and the assembler will provide a proper value for that numeric field so that the effect of the reaching or otherwise accessing what the label referred to is accomplished. (The label is no longer in the program binary, but the code or data memory that the label referred does remain).
The assembler sets up branch instructions, j instructions, and la/lw instructions, using numbers that tell the processor how far forward or backward to move the program counter, or, what address some data of interest is at. The lw/la instructions access data, and these use 2 x 32-bit instructions each holding 16 bits of the address of interest. Between the two instructions, they put together a full 32-bit address for data access. For branches to fully reach any 32-bit address, they would have to put together the 32-bit address in a similar manner (two instruction pair) and use an indirect/register branch.
I'm a newbie in MIPS (as I started learning MIPS assembly for my college) and I've got a problem in understanding how a recursive function works in MIPS.
For example, I've got this program (in C) to write it in MIPS:
int fact (int n)
{
if (n < 1) return 0;
else return n * fact(n - 1);
}
Can someone help me, with this or another example of a recursive function and explain me how it works?
The first thing I'd like to share is that the complexity in translating this into MIPS comes from the presence of mere function calling, rather than because recursion is involved — that fact is recursive is IMHO a red herring. To this end, I'll illustrate a non-recursive function that has every bit the complexity of the recursive function you've stated:
int fact (int n)
{
if (n < 1) return 0;
else return n * other(n - 1); // I've changed the call to "fact" to function "other"
}
My alteration is no longer recursive! However the MIPS code for this version will look identical to the MIPS code for your fact (with the exception, of course, that the jal fact which changes jal other). This is meant to illustrate that the complexity in translating this is due to the call within the function, and has nothing to do with who is being called. (Though YMMV with optimization techniques.)
To understand function calling, you need to understand:
the program counter: how the program interacts with the program counter, especially, of course in the context of function calling..
parameter passing
register conventions, generally
In C, we have explicit parameters. These explicit parameter, of course, also appear in assembly/machine language — but there are also parameters passed in machine code that are not visible in C code. Examples of these are the return address value, and the stack pointer.
What is needed here is an analysis of the function (independent of recursion):
The parameter n will be in $a0 on function entry. The value of n is required after the function call (to other), because we cannot multiply until that function call returns the right hand operand of *.
Therefore, n (the left hand operand to *) must survive the function call to other, and in $a0 it will not — since our own code will repurpose $a0 in order to call other(n-1), as n-1 must go into $a0 for that.
Also, the (in C, implicit) parameter$ra holds the return address value needed to return to our caller. The call to other will, similarly, repurpose the $ra register, wiping out its previous value.
Therefore, this function (yours or mine) needs two values to survive the function call that is within its body (e.g. the call to other).
The solution is simple: values we need (that are living in registers that are repurposed or wiped out by something we're doing, or the callee potentially does) need to be moved or copied elsewhere: somewhere that will survive the function call.
Memory can be used for this, and, we can obtain some memory for these purposes using the stack.
Based on this, we need to make a stack frame that has space for the two things we need (and would otherwise get wiped out) after calling other. The entry $ra must be saved (and later reloaded) in order for us to use it to return; also, the initial n value needs to be saved so we can use it for the multiply. (Stack frames are typically created in function prologue, and removed in function epilogue.)
As is often the case in machine code (or even programming in general) there are also other ways of handling things, though the gist is the same. (This is a good thing, and an optimizing compiler will generally seek the best way given the particular circumstances.)
Presence or absence of recursion does not change the fundamental analysis we need to translate this into assembly/machine language. Recursion dramatically increases the potential for stack overflow, but otherwise does not change this analysis.
Addendum
To be clear, recursion imposes the requirement to use a dynamically expandable call stack — though all modern computer systems provide such a stack for calling, so this requirement is easy to forget or gloss over on today's systems.
For programs without recursion, a call stack is not a requirement — local variables can be allocated to function-private global variables (including the return address), and this was done on certain older systems like the PDP-8, which did not offer specific hardware support for a call stack.
Systems that use stack memory for passing parameters and/or are register poor may not require the analysis described in this answer, since variables are already being stored in memory that survives nested function calls.
It is the partitioning of registers on modern register-rich machines that creates the requirement for the above analysis. These register-rich machines pass parameters and return values (mostly) in CPU registers, which is efficient but imposes the need to sometimes make copies as registers are repurposed from one function to another.
A way to implement the function you described is using the allocation of memory with addi to move the stack pointer to allocate (at the start) and free (at the end) some stack space. Then the sw instruction can save registers into that space. Use lw to restore them after a call, and/or when you're ready to return. So we can start with this instruction to allocate some memory:
addi $sp, $sp, -8 in $sp register, we sum -8
this is, we need 8 bytes, 4 for the $ra return and also 4 bytes for the int n. Now, we allocate in the following way:
sw $a0, 4($sp) #we are saving the int with register $a0 in position 4
sw $ra, 0($sp) #we are saving the return address with address $ra in position 0
Now, we need a temporary variable to store the 1 in the comparison above. Then we have:
addi $t0, $0, 2 in $t0 register, we sum 2 to $0
now the comparison operand is slt, in our case:
slt $t0, $a0, $t0 in $t0 register, we compare the value contained in $a0 register with that in $t0 register, if true $t0 is 1, else is 0
for if $t0 is zero, we need to have the following jump structure (observe that else is a label, this is, a structure to be followed according to a rule):
obs.: $0 is used to store zero
beq $t0, $0, $t0, else in $t0 we see if it's zero, if so, we continue our program, if not, we go to another instruction, this is, else.
continuing, we now have to return 0, as follows:
`addi $v0, $0, 0
and at the end we have to restore the stack as we very much know.
For the label else, we start with the notion that we need n becoming n-1, in the following manner:
`addi $a0, $a0, -1 #this is, we add $a0 and -1 to $a0
we have to use jal fact for it's clear we have a recursion.
the next step is to restore the address of return ra and the int n as we know, and also the stack.
It's evident that we have a multiplication, for this motif, we will apply the next instruction:
`mul $v0, $a0, $v0 #this is, we multiply $a0 with $v0, remembering that v0 stores the fact(n-1):
`mul $v0, $a0, $v0 #multiplies n and fact(n-1)
we have to keep in mind that it's necessary to use jr $ra to return.
I hope, I have cleared one or another point.
I have an assignment that requires us to write two functions in MIPS: One that determines if a number is prime or not, and another that finds all the primes between 3 and 102 inclusive and prints them out. Obviously, the second one uses the first; this is meant to be an exercise in jumping and branching. The source code we are given already cleans up the stack and registers, etc., for us, and provides a function to print the number in $a0, so all we need to do is the aforementioned functions. No matter what I do, my program will always just print "1" and exit, even though it should be looping over many numbers. I feel like the mistake I have made is simple, like not loading anything into a register that needs it or overlooking something similarly simple, but I'm still new to MIPS and debugging assembly code is honestly a nightmare. Here are the two functions I've written:
Here's the one for checking if a number is prime:
ori $s0,$a0,0 #make a copy of the argument
ori $t0,$s0,0 #using temp registers is good
ori $s1,$s0,0 #make another copy to use as the loop-end check
li $t2,2 #use 2 as the divisor, makes it easier to check
li $v0,0 #using "not prime" as the base case
prime_test:
slt $t1,$t2,$s1 #check if we're above the ceiling
beq $t1,$zero,found_prime
div $t0,$t2 #check if number is divisible by 2
mfhi $t3 #get remainder from hi
beq $t3,$zero,not_prime #if hi=0, it's not a prime
addi $t2,$t2,1
j prime_test
not_prime:
add $v0,$zero,$zero #return false (0) if not prime
found_prime:
jal print_number #if it's prime, print it
And here's the one for looping and checking from 3 to 102:
li $t0,102 #upper boundary
li $a0,3 #lower boundary
prime_loop:
slt $t5,$a0,$t0
beq $t5,$zero,found_all_primes
jal is_prime
slt $t2,$zero,$a0
bne $t2,$zero,prime_detected
prime_detected:
addi $a0,$a0,1 #increment the counter
j prime_loop
found_all_primes:
Honestly, I think the error is probably in the function that loops; I'm pretty sure my logic for testing if a number is prime is sound, but I could have easily overlooked something. Again, don't worry about it looking like registers aren't being restored properly, that's all taken care of already.
Im doing a homework where I need to write down the value of the control signals for 5 instructions and am trying to figure out the sample first (code at the bottom). The 5 instructions I need to do are
Address Code Basic Source
0x00400014 0x12120004 beq $16,$18,0x0004 15 beq $s0, $s2, exit
0x00400018 0x8e080000 lw $8,0x0000($16) 16 lw $t0, ($s0)
0x0040001c 0x02118020 add $16,$16,$17 17 add $s0, $s0, $s1
0x00400020 0xae08fffc sw $8,0xfffc($16) 18 sw $t0, -4($s0)
0x00400024 0x08100005 j 0x00400014 19 j loop
And the example he did is for addi $s1,$0,4 . Right now I have this for it:
Address Code Basic Source
0x00400028 0x20110004 addi $16,$0,4 20 addi $s1, $0, 4
where I think the 4 in the basic column is incorrect. What would be the right answer?
Heres the sample he did for that, and below that is the diagram he is referring to with the control signals:
##--------------------------
# Example
# addi $s1, $0, 4
# Although not supported as in Figure 4.24, the instruction can be easily
# supported with minor changes in the control circuit.
instruction_address=0x00400028
instruction_encoding=0x20110004
OPcode=0b001000
Jump=0
Branch=0
Jump_address=0x00440010 # not used in this instruction
Branch_address=0x0040003C # not used in this instruction
Read_register_1=0b00000
Read_register_2=0b10001
Sign_extend_output=0x00000004
ALUSrc=1 # pick the value from sign_extend_output
ALUOp=0b00 # assume the same value as load/store instruction
ALU_control_input=0b0010 # add operation, as in load/store instruction
MemRead=0
MemWrite=0
MemtoReg=0 # select the ALU result
RegDst=0
Write_register=0b10001 #register number for $s1
RegWrite=1
##--------------------------
Lets examine the breakdown of the first instruction: beq $s0, $s2, exit.
The instruction address is given under the address column above: 0x00400014. You have the encoding as well: 0x12120004. The encoding is the machine instruction. Lets represent the instruction in binary: 000100 10000 10010 0000000000000100.
This is an I-type instruction. The first group of six bits is the opcode, the second group of five is the source register, the third group of five is the temporary register, and the last group of sixteen is the immediate value.
The opcode is then 0b000100. Since this is an I-type instruction, we aren't jumping to a target, thus the Jump signal is 0. However, we are branching, so the Branch signal is 1.
To find the Jump_Address, even though it is ignored, examine the the least significant 26 bits: 10000 10010 0000000000000100. Since addresses are word-aligned, we can enlarge the range of reachable addresses by having the jump offsets be the signed difference between the next instruction and target address. In other words, if my target address is 8 bytes away from the next instruction (PC-relative addressing), I'll use 2 to represent the offset. And this is why we must shift the offset 2 bits to the left. So we end up with Jump_Address = 10 00010 01000 0000000000010000 or 0x8480010.
To find the Branch_Address, which will be used, examine the least significant 16 bits: 0000000000000100. That's sign extended and shifted 2 bits to the left to get: 0000000000000000 0000000000010000 or 0x00000010. This immediate value will be added to the program counter, which points to the next instruction: 0x00400018. So we finally end with Branch_Address = 0x00400028. I'm assuming the exit label points to the next instruction after the five you've posted above, right after the j instruction.
The registers are straightforward. Read_register_1 = 0b10000 and Read_register_2 = 0b10010.
The Sign_extend_output is just the immediate field sign-extended: 0x00000004.
On to the ALU control signals. ALUSrc controls the multiplexer between the register file and ALU. Since a beq instruction requires the use of two registers, we need to select the Read data 2 register from the register file. We aren't using the immediate field for an ALU computation, like with the addi instruction. Therefore, the ALUSrc is 0.
The ALUOp and ALU_control_input are hard-wired values that are created from the opcode. ALUOp = 0b01 and ALU_control_input = 0b0110. Pg. 323 of Computer Organization and Design, 4th. Edition Revised by Hennessey and Patterson and this web page have a table with the appropriate control signals for a beq instruction. Pg. 318 has a table with the ALU control bit mappings.
MemRead and MemWrite are 0 since we aren't accessing memory; MemToReg is X (don't care) since MemWrite is 0; RegWrite is 0 since we aren't writing to the register file; RegDst is X since RegWrite is 0; and lastly, to find Write_register, take bits 16-20 (look at the multiplexer between the instruction memory and register file), which are 0b10010.