Mips is RISC CPU providing high
performance and low power Used heavily by SGI... the most popular uses of MIPS are the Playstation, the PSP and the N64. It's also used in a wide range of embedded systems. In this tutorial we'll be using MARS... a Mips simulator, and we'll be learning about the basics of MIPS The MIPS instruction set is also very similar to RISC-V (Though the bytecode is very different) |
The Mips CPU The Playstation 1 and N64 Mips CPUs |
Lesson H1 - Hello World on the N64 [N64] |
Lesson S1 - Bitmap Drawing on the N64 [N64] |
Lesson S2 - Sprite Movement and Joystick on the N64 [N64] |
If you want to learn MIPS get the Cheatsheet! it has all the MIPS commands, it covers the commands and how those commands compile to bytecode |
These
tutorials assume you have a basic understanding of concepts like
HEX and Registers... If you're not familiar with these, take a look at one of the other tutorials first! |
Bits | Reg
Num |
Name | Detail | |
00000 | 0 | $zero | Hard-wired zero | |
00001 | 1 | $at | Reserved for Assembler |
Used by psuedo-ops (macros) |
00010 | 2 | $v0 | Return Value |
Used for return values from function calls |
00011 | 3 | $v1 | Return Value | Used for return values from function calls |
00100 | 4 | $a0 | Argument for function |
Used to pass values to function calls |
00101 | 5 | $a1 | Argument for function | Used to pass values to function calls |
00110 | 6 | $a2 | Argument for function | Used to pass values to function calls |
00111 | 7 | $a3 | Argument for function | Used to pass values to function calls |
01000 | 8 | $t0 | Temporaries Caller | May be changed by sub |
01001 | 9 | $t1 | Temporaries Caller | May be changed by sub |
01010 | 10 | $t2 | Temporaries Caller | May be changed by sub |
01011 | 11 | $t3 | Temporaries Caller | May be changed by sub |
01100 | 12 | $t4 | Temporaries Caller | May be changed by sub |
01101 | 13 | $t5 | Temporaries Caller | May be changed by sub |
01110 | 14 | $t6 | Temporaries Caller | May be changed by sub |
01111 | 15 | $t7 | Temporaries Caller | May be changed by sub |
10000 | 16 | $s0 | Saved registers Callee | if changed by Sub must be backed up |
10001 | 17 | $s1 | Saved registers Callee | if changed by Sub must be backed up |
10010 | 18 | $s2 | Saved registers Callee | if changed by Sub must be backed up |
10011 | 19 | $s3 | Saved registers Callee | if changed by Sub must be backed up |
10100 | 20 | $s4 | Saved registers Callee | if changed by Sub must be backed up |
10101 | 21 | $s5 | Saved registers Callee | if changed by Sub must be backed up |
10110 | 22 | $s6 | Saved registers Callee | if changed by Sub must be backed up |
10111 | 23 | $s7 | Saved registers Callee | if changed by Sub must be backed up |
11000 | 24 | $t8 | Temporaries Caller | May be changed by sub |
11001 | 25 | $t9 | Temporaries Caller | May be changed by sub |
11010 | 26 | $k0 | Reserved for OS Kernel |
|
11011 | 27 | $k1 | Reserved for OS Kernel | |
11100 | 28 | $gp | Global area Pointer |
|
11101 | 29 | $sp | Stack Pointer |
|
11110 | 30 | $fp |
Frame Pointer |
|
11111 | 31 | $ra | Return Address Caller |
Name | Detail |
PC | Program Counter |
LO | LOw part of multiplication result |
HI | HIgh part of multiplication result |
All the registers function the same... but there
are 'Official Rules!' A calling function should use the Ax registers to send data to a subroutine... if the calling function needs the Tx registers to stay the same it should back them up - the Ax registers may also change... The Sx registers can be changed by the subroutine - but it's the subroutines job to back them up if it changes them... not the calling function! |
The
syntax may vary depending on your assembler! Some need the $ prefix before a register name, others don't use it... also some use # to denote a comment, whereas others use a semicolon ; |
Float Reg | Name | Detail |
f0 | ft0 | FP temporaries Caller |
f1 | ft1 | FP temporaries Caller |
f2 | ft2 | FP temporaries Caller |
f3 | ft3 | FP temporaries Caller |
f4 | ft4 | FP temporaries Caller |
f5 | ft5 | FP temporaries Caller |
f6 | ft6 | FP temporaries Caller |
f7 | ft7 | FP temporaries Caller |
f8 | fs0 | FP saved registers Callee |
f9 | fs1 | FP saved registers Callee |
f10 | fa0 | FP arguments/return values Caller |
f11 | fa1 | FP arguments/return values Caller |
f12 | fa2 | FP arguments Caller |
f13 | fa3 | FP arguments Caller |
f14 | fa4 | FP arguments Caller |
f15 | fa5 | FP arguments Caller |
f16 | fa6 | FP arguments Caller |
f17 | fa7 | FP arguments Caller |
f18 | fs2 | FP saved registers Callee |
f19 | fs3 | FP saved registers Callee |
f20 | fs4 | FP saved registers Callee |
f21 | fs5 | FP saved registers Callee |
f22 | fs6 | FP saved registers Callee |
f23 | fs7 | FP saved registers Callee |
f24 | fs8 | FP saved registers Callee |
f25 | fs9 | FP saved registers Callee |
f26 | fs10 | FP saved registers Callee |
f27 | fs11 | FP saved registers Callee |
f28 | ft8 | FP temporaries Caller |
f29 | ft9 | FP temporaries Caller |
f30 | ft10 | FP temporaries Caller |
f31 | ft11 | FP temporaries Caller |
Due to the processor pipeline, there will be times when a command is executed 'out of order'... happening before, or after the command it appears in the code... Here are the ones you need to watch.
1.Delayed branches: When a branch occurs (either conditional or non conditional) the command AFTER the branch in the code will occur BEFORE the branch when executed!
2. Load Delays: on MIPS-I When a load occurs the data will not be available until AFTER the next instruction... Basically the data load takes an extra command. This is no longer an issue on MIPS-II (The PSX is MIPS-I, the N64 is MIPS-II)....
Note: it seems the emulator may not actually simulate this 'bug' so our code may work even if it should not... though our armips will warn us!
MIPS is a 'Load and Store' architecture processor, meaning that many of the commands only work between registers.
Mode | Notes | Format | Example |
Immediate Addressing |
A fixed number is the parameter for an operation. | n | ANDI a0,a1,0xF |
Register Addressing |
A register itself will be used as a source or destination of an operation. | Rn | |
Register Indirect with Offset Addressing |
This Addressing mode uses the value from the address in a register, offset by a fixed numeric value. | n(Rm) | |
Program Counter Relative with Offset Addressing |
used for relative jump and branch operations. | BEQ label BNE label |
|
Psuedo Direct Addressing | 26 bits of the parameter are shifted left two bits, and the top 4
bits are taken from the program counter: %PPPPnnnnnnnnnnnnnnnnnnnnnnnnnn00 |
J label JAL label |
Push | .macro push(%reg) addi $sp,$sp,-4 sw %reg,0($sp) .end_macro |
Pop | .macro pop(%reg) lw %reg,0($sp) addi $sp,$sp,4 .end_macro |
Push multiple | addi $sp,$sp,-8 sw x1,4($sp) sw x2,8($sp) |
Pop multiple | lw x1,4($sp) lw x2,8($sp) addi $sp,$sp,8 |
PrintChar (on MARS simulator) | li $a7, '!' #; Char to print li $a7, 11 syscall |
Exit (on MARS simulator) | li $a7, 10 syscall |
A template program
To allow us to get started programming quickly and see the
results, we'll be using a 'template program'... This consists of 3 parts: A Header - this includes some parameters in the data segment for our program The Program - this is the body of our program where we do our work. A Generic Footer - this will return control to the system |
The DevTools on this
website come with headers to allow this program to compile for
either the N64 or PSX, but without them you couldn't compile
this program. It takes a lot more code to get either of these machines to actually boot! |
Lets take a look at a simple program!... There will be times we need to jump around the code... the simplest way to do this is the command 'J'... this will jump to another position in the code ... notice, commands like this are indented by a tab. Notice the line which is not indented and ends with a colon : - that makes it a label called 'InfLoop' ... labels tell the assembler to 'name' this position in the program - the assembler will convert the label to a byte number in the executable... thanks to the assembler we don't need to worry what number that ends up being... you'll also notice text in green starting with a Semicolon ; - this is a comment (REMark) - they have no effect on the code (Note the MARS simulator uses the # symbol for comments) You will notice there is a NOP command after the J and JAL commands - There is something known as a 'Delay slot' after jumps, and for now we need to fill them with NOP (No OPeration)... NOP itself does nothing - We'll discuss Delay slots again soon. |
We have 31 registers in total - but we'll be using a0-a7 for
testing... the t0-5 and s0-11 registers all work the same. To load a register we use the function LI (Load Immediate)... This sets a register to a fixed value in the code.. the destination register is on the left of the comma... the source value is on the right. We can use decimal, Hexadecimal (by starting the value with 0x) or Ascii (by putting a character in quotes '') 'Immediate values are values on the same source code line - rather than being taken from a register or memory address. |
|
The Registers will be loaded as specified. | |
If we want to give a number a label we can use a Symbol... these
are defined with EQU - a name
and value are specified The symbol can then be used in the source code, the assembler will convert the symbols back to their numeric values in the bytecode. They are similar to labels, both give a number to a text value... but a label's value is the final byte address of the code line, calculated by the assembler We can also load the value of a Label... but rather than LI, we use LA (Load Address) to load the label value into a register |
|
Here's the result! |
Some of these commands are
'Psuedo-ops'... this is where the assembler compiles one command
into multiple in the final binary... It makes things easier for us to let the assembler to do as much of the work as possible, so we won't differentiate between Psuedo and 'Real' commands. |
We can transfer values
between registers with MOVE As before, The destination register is on the left, the source is on the right. |
|
The value is copied from A0 to A1 and A2 |
LUI stands for Load Upper
Immediate... this command takes a 16 bit value, and loads it into
the top 16 bits of the 32 bit register (EG 0xFFFF---- ) We can't actually load 32 bits of data into a register in one go, our LI command is actually converted to multiple commands, such as LUI and ADDI |
|
here is the result |
We can add an immediate value (a fixed value) with ADDI. Somewhat surprisingly there is no SUBI, however we can use ADDI with a negative immediate |
|
here is the result | |
Of course we usually won't want to work with immediates. We can ADD or SUBtract one register from another, and store the result in a third. |
|
Here are the results |
The
MIPS is a 32 bit CPU - so WORDS are 32 bits (not 16 like on 8/16
bit systems)... A Half-Word is 16 bits!... but relax, a byte is still 8 bits! so at least everything hasn't changed! |
To read from an address we need to load it into a
register with LA (LoadAddress) -
any register can be used for this... we need to specify the source
address in brackets () - the destination register is on the left of
the comma as always LW can be used to Load a Word (32 bit) from the specified address SW can be used to Store a Word (32 bit) to the specified address. There are other size options, but we'll look at those later. |
|
A0 was loaded from the address in A2 A1 was stored to the address in A2 |
Typically
(but not always) the MIPS CPU is a LITTLE ENDIAN system... that
means the least significant byte of the 32 bit word is stored
first in memory, so it may look like it's 'Reversed'. Don't
worry! It will be in the correct order when we load it back. Actually the PSX runs the CPU in LITTLE ENDIAN mode - but the N64 uses BIG ENDIAN, so your results may vary! |
Jumps: J JR JAL JALR... and RET!
There's something known as a
'Delay slot' after a jump'... for now will fill it with a NOP -
which does nothing ! We'll discuss delay slots in detail in just a moment. |
Delay Slots
Delay slots are a bit of a weird concept! Jump delays mean the command after a jump actually occurs before it - this is weird, but helps the processor work more efficiently Load delays mean loading from memory takes more than one command, so the data loaded may not be available for the next command - though the emulators may not represent this limitation! |
Load Delays refer to the fact that a memory load takes more than
one command. This means that if the command after a load uses the destination of the load, then it may not have been loaded yet! In this example the ORI command is in the load delay slot, and uses the loaded A1, but it may not have been loaded yet - so may not work properly... we should put a NOP before the ORI command The PSX R3000 processor DOES have load delays, but it is not simulated by the emulator, but we will get an assembler warning. The N64 R4000 processor does not have this limitation (The command will be slower, but no error will occur) |
|
Branch Delays are where the command after a branch actually occurs
before it!... this is why we've been putting NOPs in after all out
jumps. This seems very weird, but allows the processor to save a little time, as the command after a jump is loaded before the jump is processed. In this example, we've put an ORI in the branch delay slot, and it actually occurs before the jump! |
|
The ORI command occurred before the jump. |
|
We can take advantage of this when doing a loop! Here we've used A0 as a loop counter, we decrease it by one each time... we can move the DEC command into the delay slot... it actually occurs before the branch - and we gain a little performance! |
|
Here are the results |
Lesson
2 - Addressing modes and more We've covered the absolute basics, now lets look at more of the options we have for working with values in memory. |
Lesson2.asm |
Addressing Modes
Being a RISC CPU, MIPS has a very limited number of addressing modes, but
for completeness lets take a look at each now.
Immediate Addressing is probably the
simplest mode, it's just a constant numeric parameter. A fixed number is the parameter for an operation. |
|
Here is the result. |
|
Register Addressing is very simple,
it's just when a register itself will be used as a source or
destination of an operation. |
|
Here is the result. | |
Register Indirect with Offset Addressing
is the mode used for reading from, and writing to memory. This Addressing mode uses the value from the address in a register, offset by a fixed numeric value. The offset can be positive, negative or zero... it can be omitted altogether if zero. |
|
Here are the results | |
Program Counter Relative with Offset
Addressing is used for branch commands. While we've used a label in the source code, it will be converted to a positive or negative offset in the final program. |
|
Psuedo Direct Addressing is used for
Jump commands. It's nearly an absolute address (a complete address) however the top 4 bits are taken from the program counter. |
We looked at loading and storing 32 bit values with LW and SW before, but there are many more options!
Also, how we load values will vary depending on if the value is negative... A loaded decimal 8 bit value of -1 has a 32 bit hex value of 0xFFFFFFF ... in this case We'll need to fill all the 'extra 24 bits' of the 32 bit register with the top bit 7 of the loaded byte.
LW will Load a Word - filling the full 32 bit
destination register. As the whole register is loaded This will work
for signed or unsigned values. LHU will Load a Half word as Unsigned - the 16 bit value will be treated as unsigned and effectively can have a value from 0 to +65535 LH will Load a Half word as signed - the 16 bit value will be treated as signed, and 'sign extended' to fill the 32 bit destination. Effectively the value can be -32768 to +32767 LBU will Load a Byte as Unsigned - the 8 bit value will be treated as unsigned and effectively can have a value from 0 to +255 LB will Load a Byte as signed - the 8 bit value will be treated as signed, and 'sign extended' to fill the 32 bit destination. Effectively the value can be -128 to +127 |
|
Here are the results |
|
SW will Store a Word - saving the
full 32 bit register. SH will Store a Half word - saving a 16 bit value. SB will Store a Byte - saving an 8 bit value. There is no need for special 'signed' and 'unsigned' commands for saving registers, these commands will work correctly for either signed or unsigned. |
|
Here are the results |
Unaligned Loads and Stores
Like many CPU's the MIPS CPU can only load and save 32 bit words on 32 bit 'aligned' addresses (where the bottom two bits of the address are zero).
The same is true of 16 bit Half words, which can only be loaded from 'even' addresses (Where the bottom bit of the address is zero).
However we do have some 'special' commands which we can use to load from unaligned addresses.
The MIPS cpu can't truly load unaligned data, not in one command,
but we do have commands to load partial data! LWL will Load the Word from the Left hand side from an unaligned addresses. LWR will Load the Word from the Right hand side from an unaligned addresses. These commands can be combined to load a full 32 bits from an unaligned address. These commands are a bit tricky to use, but we have some psuedo-ops which provide macros to make them more useful ULW will Unaligned Load a Word (32 bit signed or unsigned) ULH will Unaligned Load a Half Word (16 bit signed) ULHU will Unaligned Load a Half Word Unsigned (16 bit unsigned) There is no need for unaligned byte loading commands. |
|
Here are the results |
|
We also have commands for storing. SWL will Store part of a Word to the Left side of an unaligned address SWR will Store part of a Word to the Right side of an unaligned address These commands don't really save what we want. but we have psuedo-ops to make these more usable USW will Unaligned Store a Word (32 bits) USH will Unaligned Store a Half (16 bit) There is no need for unaligned byte, or signed/unsigned versions of save commands. |
|
Here are the results |
The Stack
The Stack pointer uses register SP (Register 29) to point to the
top of the stack... We actually have no 'proper' Stack commands!... to 'push' an item onto the stack, we subtract 4 from SP - then load the register we want to push to the SP address To 'pop' an item off the stack we do the reverse - loading the register from the address in SP - and then add 4 to the SP register. We can define these as macros to make our lives easier. |
|
Here we've loaded a value into A0...
pushed it onto the stack, loaded a different
value onto the stack and then performed a call... We also use the stack to backup the Return address when we call the subroutine Finally we pop the old value off the stack. We dump the state of the system at each stage |
|
The changes to the stack can be seen here... Each push to the stack can be seen in memory. |
Comparisons
Unlike other CPU's the MIPS does not have a flag register as such... when we want to do a conditional branch, we use a branch command with two registers to compare, and a label to jump to if the condition is true...
Lets look at all the options!...
The examples
shown here are all available for download!... there are various
possible values and conditions remmed out with # - You should try enabling different conditions, and providing different input values and see how things change! |
Equals - Not Equals - EQ / NE
if we want to perform actions if the two registers are the same -
or different - we can do this with BEQ and BNE BEQ will Branch if Equal BNE will Branch if Not Equal |
|
The results can be seen here |
Less - Greater - Unsigned - LTU / LEU / GEU / GTU
Because Hexadecimal signed numbers have their top bit as 1 we have
to use different compare commands for signed and unsigned numbers...
there is a U at the end of unsigned comparisons. We have 4 options: BLTU - Branch if Less Than Unsigned BGTU - Branch if Greater Than Unsigned BLEU - Branch if Less Than or Equals Unsigned BGEU - Branch if Greater Than or Equals Unsigned |
|
The results are shown here |
Less - Greater - Signed- LT / LE / GE / GT
If we're working with Signed numbers, we have alternate versions -
these do not have U at the end We have 4 options: BLT - Branch if Less Than signed BGT - Branch if Greater Than signed BLE - Branch if Less Than or Equals signed BGE - Branch if Greater Than or Equals signed |
|
The results are shown here |
Comparing with Zero
Many off the MIPS functions compare to zero, and there aremany
functions to do this. |
|
Here is the result |
Here are the full set of possible Conditions:
Some even perform a 'Link', setting the RA register to the return address!
Bcc |
Description |
Condition |
---|---|---|
BEQ rs,rt,offset |
Branch on Equals |
rs = rt |
BGEZ rs,offset |
Branch on Greater or Equal to Zero |
rs >= 0 |
BGEZAL rs,offset |
Branch on Greater or Equal to Zero And Link |
rs >= 0 |
BGTZ rs,offset |
Branch on Greater Than Zero |
rs > 0 |
BLEZ rs,offset |
Branch on Less than or Equal to Zero |
rs <= 0 |
BLTZ rs,offset |
Branch on Less Than Zero |
rs < 0 |
BLTZAL rs,offset |
Branch on Less Than Zero And Link |
rs < 0 |
BNE rs,rt,offset |
Branch on Not Equal |
rs <> rt |
There are many other conditions which are performed via psuedo operations
Bcc |
Description |
Condition |
---|---|---|
BGE rs,rt,offset |
Branch on Greater than or Equals |
rs >= rt |
BGEU rs,rt,offset |
Branch on Greater than or Equals (Unsigned) |
rs >= rt |
BGT rs,rt,offset |
Branch on Greater Than |
rs > rt |
BGTU rs,rt,offset |
Branch on Greater Than Unsigned |
rs > rt |
BLE rs,rt,offset |
Branch on Less than or Equals |
rs <= rt |
BLEU rs,rt,offset |
Branch on Less than or Equals Unsigned |
rs <= rt |
BLT rs,rt,offset |
Branch on Less Than |
rs < rt |
BLTU rs,rt,offset |
Branch on Less Than Unsigned |
rs <> rt |
BEQZ rs,offset |
Branch on EQual to Zero |
rs = 0 |
BNEZ rs,offset |
Branch on Not Equal to Zero |
rs <> 0 |
Jumps go to an absolute address, so are not really relocatable,
but branches are a 'relative offset' to the current position. B will perform a Branch, which is the relative equivalent of the J jump command. BAL will perform a Branch and Link, which like JAL puts the return address in the RA register. This is actually a psuedo op using BGEZAL, so unlike JAL it is not possible to put the return address in any other register than RA |
|
Here are the results |
Set based on conditions
We have some slightly strange commands, which will set a register to either 1 or 0 based on a condition. SLTU will Set the destination if src1 is Less Than src2 (both Unsigned) SGTU will Set the destination if src1 is Greater Than src2 (both Unsigned) SLT will Set the destination if src1 is Less Than src2 (both signed) SGT will Set the destination if src1 is Greater Than src2 (both signed) There are a wide range of other options! - there is a table of them below! |
|
Here are the results. Try changing the values of A1 and A2 for different results! |
Here is the full range of SET commands.
Command |
Function |
SGT dest,src1,src2 | Set Greater |
SGE dest,src1,src2 | Set Greater/Equal |
SGEU dest,src1,src2 | Set Greater/Equal Unsigned |
SGTU
dest,src1,src2 |
Set Greater Unsigned |
SLT dest,src1,src2 | Set Less Than |
SLE dest,src1,src2 | Set Less/Equal |
SLEU dest,src1,src2 | Set Less/Equal Unsigned |
SLTU dest,src1,src2 | Set Less Unsigned |
SNE dest,src1,src2 | Set Not Equal |
The purpose of these commands
may not seem immediately apparent, but these
are actually used with the Branch commands to form some of the
branch psuedo-ops. |
SysCall
Syscall is a special operating system 'Trap' On the MIPS simulator MARS it will take a parameter in $v0, and can be used to perform various I/O tasks with the console. Here we use SYSCALL with $v0=4 to show the string in $a0 to the screen, and return to the operating system (exit) with SYSCALL and $v0=10 |
|
Here are the results |
Logical ops - AND, OR, XOR, NOR!
We have XOR, AND
and OR functions... As always they have a Desitnation on the left, and two parameters for the logical operation... OR, AND and XOR have an Immediate version, where the second parameter is a fixed number - these are ORI ANDI and XORI |
|
The results are shown here. | |
NOR is a bit of a rarity! it stands
for 'Not OR' - and returns a bit of 1 if Neither parameter a OR b is
1 There is also a NOT function, which will flip all the bits of a register. so "NOT a1" as the same effect as "XORI a1,a1,0xFFFFFFFF" would |
|
Here are the results |
Here is a comparison of the various results of various logical operations:
A |
AND |
B |
= |
|
A |
OR |
B |
= |
|
A |
XOR |
B |
= |
|
A |
BIC |
B |
= |
|
A |
NOR |
B |
= |
0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | |||||||||
0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 0 | 0 | 1 | 0 | |||||||||
1 | 0 | 0 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 | 0 | |||||||||
1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 | 1 | 0 |
Bit Shifts... and rotates!
The MIPS offers three kinds of bitshifts, each of
which supports a register shift amount, or an immediate one Where the instruction ends with a V the Value will be taken from a register (eg SLLV) SLL is Shift Left Logical Left - all bits move to the left - any bits pushed out the register are lost SRL is Shift Right Logical - all bits move to the right - any bits pushed out the register are lost SRA is Shift Right Ari thematic - all bits move to the right - any bits pushed out the register are lost - any new bits on the left contain the previous leftmost bit - maintaining the sign of the register. |
|
We've performed 4 shifts of the registers. Notice each shift to the left doubles the value, and a shift to the right halves it - provided no bits are 'pushed out' of the registers. |
|
We can also perform Rotate commands. Unlike shifts, with rotation
any bits that 'leave' the register return on the other side, so with
a 1 bit shift to the left, the new bit 0 is the old bit 31, and all
other bits shift in a leftwards motion. We have two options - they cannot work with immediates only work with a shift amount in a register. ROL will rotate left. ROR will rotate right. |
|
Here are the results |
Shifting Left doubles
the value in a register, Shifting Right will halve it, and it's
faster than Multiplication and Division commands! There is no SLA command (shift arithmetic left) because there is no need for one.. SLL will do the job you need! More problematic is the lack of rotation commands... ROR/ROL don't exist!... you'll have to use AND/OR and the shift commands that do exist to simulate them. |
Lesson
5 - More Maths! There's a few other maths commands left to look at, as well as the MULT and DIV commands! Lets get through the last of the commands. |
Lesson5.asm
|
Signed Numbers
We've already done lots of work with signed and unsigned numbers,
but there's a few more commands we've not covered yet! If we want to convert a number from positive to negative, we can flip all the bits (NOT) and add 1... but we have a command to give this result for us. NEG will convert a negative number to a positive, and a positive to a negative. If we want t convert a number to a positive number (whatever it was) we can use ABS. If we use this command, whether our register contained -5 or +5, the result would be +5 |
|
Here are the results | |
Finally we have NEGU, which is
almost the same as NEG. A signed 32 bit register has a range of
-0x80000000 to +0x7FFFFFFF... any value out of this range will cause
an 'overflow trap' If we want to allow a value outside this range we can use NEGU (NEGate Unsigned), and convert our very large positive number in the same way without the trap. |
|
Here are the results |
Multiplication
We can perform signed multiplication with MULT The result is NOT stored in either of the two passed parameters, it's stored in the special HI LO registers. |
|
Here are the results |
|
We need to use special commands to access the HI and LO registers. MFHI will Move From the HI register to the specified register. MFLO will Move From the LO register to the specified register. MTHI will Move To the HI register from the specified register. MTLO will Move To the HI register from the specified register. |
|
Here are the results |
|
The MULT command is a signed command, but for unsigned values over
0x7FFFFFFF we will need to use MULTU This will correctly multiply unsigned numbers |
|
Here are the results |
Division
We have two possible commands for Division. DIV will divide parameter 1 by parameter 2, treating both as signed numbers DIVU will do the same, treating both as unsigned numbers After the division, LO will contain the quotient (The integer result of the division), HI is the remainder |
|
Here are the results |
Phew! We've covered the basics! MIPS has extensions for 64 bit and floating point, but they are beyond what this tutorial was intended to cover... you should at least have enough now to get started! |