I'm new to MIPS and while I sort of understand how to do basic tasks with it, I'm struggling heavily with procedures/functions.
I was hoping someone could explain how to solve a simple CPP program such as
void swap( int & a, int & b )
{
register int t;
t = a; a = b; b = t;
}
Into MIPS.
In class we talked about frame pointers and stack pointers, offsets, and I'm just lost. How do you pass int a, and int b to the function? How do you return a value and use it in MIPS? I've seen similar problems with arrays but was hoping if someone could help me with one with just two ints to be swapped. Thanks.
First, you should not use the keyword register in C++, as it is deprecated and in most cases as meaningful as whitespace.
How do you pass int a, and int b to the function?
You store a and b in the registers you will use in your subroutine.
If you want to swap two values in an array, let's say at 0x80080000:
lui $t0, 0x8008 ;$t0 = 0x80080000
lw $t1 0($t0) ;load (0x80080000) in $t1
lw $t2 4($t0) ;load (0x80080004) in $t2
sw $t1 4($t0) ;store $t1 in 0x80080004
sw $t2 0($t0) ;store $t2 in 0x80080000
If you want to swap the values of two registers (xor swap algorithm):
xor $t0, $t0, $t1
xor $t1, $t0, $t1
xor $t0, $t0, $t1
Or, if you care about readability:
add $t2, $r0, $t0
add $t0, $r0, $t1
add $t1, $r0, $t2
You can create a function that do swap.
Something like that :
swap: # Swap function
addi $sp,$sp,-4
sw $t0, 0($sp) # Stack[0] = $t0
add $t0,$a0,$zero # $t0 = $a0
add $a0,$a1,$zero # $a0 = $a1
add $a1,$t0,$zero # $a1 = $t0 (swapping $a0 and $a1)
lw $t0,0($sp)
addi $sp,$sp,4 # $t0 = Stack[0]
jr $ra # Return to the code
And use the function Swap, like this:
li $a0,100 # $a0 = 100
li $a1,33 # $a1 = 33
jal swap # Swapping $a0 & $a1
Note: The names of the registers aren't important, you can use other.
Related
Can you please explain to me what aL, aH, bL, bH stands for
aL = a low byte
aH = a high byte
bL = b low byte
bH = b high byte
In this case a is the minimum serial number count value and b is the maximum serial number count value.
I would want to ask, how do you break up a 32-bit hex (for example: CEED6644) into 4 bytes (var1 = CE, var2 = ED, var3 = 66, var4 = 44). In QB64 or QBasic. I would use this to store several data bytes into one array address.
Something like this:
DIM Array(&HFFFF&) AS _UNSIGNED LONG
Array(&HAA00&) = &HCEED6644&
addr = &HAA00&
SUB PrintChar
SHARED addr
IF var1 = &HAA& THEN PRINT "A"
IF var1 = &HBB& THEN PRINT "B"
IF var1 = &HCC& THEN PRINT "C"
IF var1 = &HDD& THEN PRINT "D"
IF var1 = &HEE& THEN PRINT "E"
IF var1 = &HFF& THEN PRINT "F"
IF var1 = &H00& THEN PRINT "G"
IF var1 = &H11& THEN PRINT "H"
And so on...
You could use integer division (\) and bitwise AND (AND) to accomplish this.
DIM x(0 TO 3) AS _UNSIGNED _BYTE
a& = &HCEED6644&
x(0) = (a& AND &HFF000000&) \ 2^24
x(1) = (a& AND &H00FF0000&) \ 2^16
x(2) = (a& AND &H0000FF00&) \ 2^8
x(3) = a& AND &HFF&
PRINT HEX$(x(0)); HEX$(x(1)); HEX$(x(2)); HEX$(x(3))
Note that you could alternatively use a generic RShift~& function instead of raw integer division since what you're really doing is shifting bits:
x(0) = RShift~&(a& AND &HFF000000&, 18)
...
FUNCTION RShift~& (value AS _UNSIGNED LONG, shiftCount AS _UNSIGNED BYTE)
' Raise illegal function call if the shift count is greater than the width of the type.
' If shiftCount is not _UNSIGNED, then you must also check that it isn't less than 0.
IF shiftCount > 32 THEN ERROR 5
RShift~& = value / 2^shiftCount
END FUNCTION
Building upon that, you might create another function:
FUNCTION ByteAt~%% (value AS _UNSIGNED LONG, position AS _UNSIGNED BYTE)
'position must be in the range [0, 3].
IF (position AND 3) <> position THEN ERROR 5
ByteAt~%% = RShift~&(value AND LShift~&(&HFF&, 8*position), 8*position)
END FUNCTION
Note that an LShift~& function was used that shifts bits to the left (multiplication by a power of 2). A potentially better alternative would be to perform the right-shift first and just mask the lower 8 bits, eliminating the need for LShift~&:
FUNCTION ByteAt~%% (value AS _UNSIGNED LONG, position AS _UNSIGNED BYTE)
'position must be in the range [0, 3].
IF (position AND 3) <> position THEN ERROR 5
ByteAt~%% = RShift~&(value, 8*position) AND 255
END FUNCTION
Incidentally, another QB-like implementation known as FreeBASIC has an actual SHR operator, used like MOD or AND, to perform a shift operation directly instead of using division, which is potentially faster.
You could also use QB64's DECLARE LIBRARY facility to create functions in C++ that will perform the shift operations:
/*
* Place in a separate "shift.h" file or something.
*/
unsigned int LShift (unsigned int n, unsigned char count)
{
return n << count;
}
unsigned int RShift (unsigned int n, unsigned char count)
{
return n >> count;
}
Here's the full corresponding QB64 code:
DECLARE LIBRARY "shift"
FUNCTION LShift~& (value AS _UNSIGNED LONG, shiftCount AS _UNSIGNED _BYTE)
FUNCTION RShift~& (value AS _UNSIGNED LONG, shiftCount AS _UNSIGNED _BYTE)
END DECLARE
x(0) = ByteAt~%%(a&, 0)
x(1) = ByteAt~%%(a&, 1)
x(2) = ByteAt~%%(a&, 2)
x(3) = ByteAt~%%(a&, 3)
END
FUNCTION ByteAt~%% (value AS _UNSIGNED LONG, position AS _UNSIGNED BYTE)
'position must be in the range [0, 3].
IF (position AND 3) <> position THEN ERROR 5
ByteAt~%% = RShift~&(value, 8*position) AND 255
END FUNCTION
If QB64 had a documented API, it might be possible to raise a QB64 error from the C++ code when the shift count is too high, rather than relying on the behavior of C++ to essentially ignore shift counts that are too high. Unfortunately, this isn't the case, and it might actually cause more problems than it's worth.
This snip gets the byte pairs of a hexidecimal value:
DIM Value AS _UNSIGNED LONG
Value = &HCEED6644&
S$ = RIGHT$("00000000" + HEX$(Value), 8)
PRINT "Byte#1: "; MID$(S$, 1, 2)
PRINT "Byte#2: "; MID$(S$, 3, 2)
PRINT "Byte#3: "; MID$(S$, 5, 2)
PRINT "Byte#4: "; MID$(S$, 7, 2)
I need set some bits in ByteData at position counted in bits.
How I can do this?
Eg.
var byteData = new ByteData(1024);
var bitData = new BitData(byteData);
// Offset in bits: 387
// Number of bits: 5
// Value: 3
bitData.setBits(387, 5, 3);
Yes it is quite complicated. I dont know dart, but these are the general steps you need to take. I will label each variable as a letter and also use a more complicated example to show you what happens when the bits overflow.
1. Construct the BitData object with a ByteData object (A)
2. Call setBits(offset (B), bits (C), value (D));
I will use example values of:
A: 11111111 11111111 11111111 11111111
B: 7
C: 10
D: 00000000 11111111
3. Rather than using an integer with a fixed length of bits, you could
use another ByteData object (D) containing your bits you want to write.
Also create a mask (E) containing the significant bits.
e.g.
A: 11111111 11111111 11111111 11111111
D: 00000000 11111111
E: 00000011 11111111 (2^C - 1)
4. As an extra bonus step, we can make sure the insignificant
bits are really zero by ANDing with the bitmask.
D = D & E
D 00000000 11111111
E 00000011 11111111
5. Make sure D and E contain at least one full zero byte since we want
to shift them.
D 00000000 00000000 11111111
E 00000000 00000011 11111111
6. Work out these two integer values:
F = The extra bit offset for the start byte: B mod 8 (e.g. 7)
G = The insignificant bits: size(D) - C (e.g. 14)
7. H = G-F which should not be negative here. (e.g. 14-7 = 7)
8. Shift both D and E left by H bits.
D 00000000 01111111 10000000
E 00000001 11111111 10000000
9. Work out first byte number (J) floor(B / 8) e.g. 0
10. Read the value of A at this index out and let this be K
K = 11111111 11111111 11111111
11. AND the current (K) with NOT E to set zeros for the new bits.
Then you can OR the new bits over the top.
L = (K & !E) | D
K & !E = 11111110 00000000 01111111
L = 11111110 01111111 11111111
12. Write L to the same place you read it from.
There is no BitData class, so you'll have to do some of the bit-pushing yourself.
Find the corresponding byte offset, read in some bytes, mask out the existing bits and set the new ones at the correct bit offset, then write it back.
The real complexity comes when you need to store more bits than you can read/write in a single operation.
For endianness, if you are treating the memory as a sequence of bits with arbitrary width, I'd go for little-endian. Endianness only really makes sense for full-sized (2^n-bit, n > 3) integers. A 5 bit integer as the one you are storing can't have any endianness, and a 37 bit integer also won't have any natural way of expressing an endianness.
You can try something like this code (which can definitely be optimized more):
import "dart:typed_data";
void setBitData(ByteBuffer buffer, int offset, int length, int value) {
assert(value < (1 << length));
assert(offset + length < buffer.lengthInBytes * 8);
int byteOffset = offset >> 3;
int bitOffset = offset & 7;
if (length + bitOffset <= 32) {
ByteData data = new ByteData.view(buffer);
// Can update it one read/modify/write operation.
int mask = ((1 << length) - 1) << bitOffset;
int bits = data.getUint32(byteOffset, Endianness.LITTLE_ENDIAN);
bits = (bits & ~mask) | (value << bitOffset);
data.setUint32(byteOffset, bits, Endianness.LITTLE_ENDIAN);
return;
}
// Split the value into chunks of no more than 32 bits, aligned.
do {
int bits = (length > 32 ? 32 : length) - bitOffset;
setBitData(buffer, offset, bits, value & ((1 << bits) - 1));
offset += bits;
length -= bits;
value >>= bits;
bitOffset = 0;
} while (length > 0);
}
Example use:
main() {
var b = new Uint8List(32);
setBitData(b.buffer, 3, 8, 255);
print(b.map((v)=>v.toRadixString(16)));
setBitData(b.buffer, 13, 6*4, 0xffffff);
print(b.map((v)=>v.toRadixString(16)));
setBitData(b.buffer, 47, 21*4, 0xaaaaaaaaaaaaaaaaaaaaa);
print(b.map((v)=>v.toRadixString(16)));
}
How can I print an array in Mips64? I've succeeded in printing the array in QtSPIM (MIPS32), with this code:
.data
array: .word 10 20 30 40 50
.text
#load base address of array
la $t1,array
#load number of elements
ld $t2,num
loop:
#load word
lw $a0, ($t1)
#print element
li $v0,1
syscall
#print space
la $a0, space
li $v0,4
syscall
addi $t1,4
#increase counter
addi $t0, 1
bne $t0,$t2,loop
#end
li $v0,10
syscall
I know that MIPS64 has daddi instead of addi but I am still missing something.
WinMips64 doesn't appear to use syscall for terminal output like SPIM / MARS, but rather implements it using memory-mapped I/O (see this example code).
The CONTROL port is located at address 0x10000, and the DATA port at address 0x10008.
Since you're printing integers you'll be interested mainly in these two output modes:
; Set CONTROL = 1, Set DATA to Unsigned Integer to be output
; Set CONTROL = 2, Set DATA to Signed Integer to be output
For example:
ori $a1,$0,0
lui $a1,1 ; $a1 = 0x10000 (CONTROL)
ori $a2,$a1,8 ; $a2 = 0x10008 (DATA)
lwu $a0,($t1) ; load an unsigned 32-bit value
ori $v0,$0,1 ; 1 == print unsigned integer
sd $a0, ($a2) ; set value to print
sd $v0, ($a1) ; ..and write the command to print it
Please note: I'm just trying to learn. Please do not yell at me for toying with assembly.
I have the following method:
uint32 test(int16 a, int16 b)
{
return ( a + b ) & 0xffff;
}
I created a .s file based on details I found here.
My .s file contains the following:
.macro BEGIN_FUNCTION
.align 2 // Align the function code to a 4-byte (2^n) word boundary.
.arm // Use ARM instructions instead of Thumb.
.globl _$0 // Make the function globally accessible.
.no_dead_strip _$0 // Stop the optimizer from ignoring this function!
.private_extern _$0
_$0: // Declare the function.
.endmacro
.macro END_FUNCTION
bx lr // Jump back to the caller.
.endmacro
BEGIN_FUNCTION addFunction
add r0, r0, r1 // Return the sum of the first 2 function parameters
END_FUNCTION
BEGIN_FUNCTION addAndFunction
add r0, r0, r1 // Return the sum of the first 2 function parameters
ands r0, r0, r2 // Ands the result of r0 and the third parameter passed
END_FUNCTION
So if I call the following:
addFunction(10,20)
I get what I would expect. But then if I try
int addOne = addFunction(0xffff,0xffff); // Result = -2
int addTwo = 0xffff + 0xffff; // Result = 131070
My addOne does not end up being the same value as my add two. Any ideas on what I am doing wrong here?
When you pass the int16_t parameter 0xffff to addFunction the compiler sign-extends it to fit in the 32-bit register (as it's a signed integer) making it 0xffffffff. You add two of these together to get 0xfffffffe and return it. When you do 0xffff + 0xffff both constants are already 32-bits and so there's no need to sign extend, the result is 0x0001fffe.
What you need to understand is that when you're using a signed integer and passing the 16-bit value 0xffff you're actually passing in -1, so it's no surprise that -1 + -1 = -2. It's also no suprise that 0xffff + 0xffff = 0xfffffffe.
If you change the (not shown) C declaration of addFunction to take two unsigned ints, then you'll get the result you desire (the compiler won't do the sign extension).
Your assembly code assumes a third passed parameter (in R2) but when you call the
function you are only passing 2 parameters. I think the contents of R2 could be anything in your addFunction.