Learn Mips Assembly Programming...

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

ChibiAkumas Tutorials

MIPS Hello World Series

Lesson H1 - Hello World on the N64 [N64]

MIPS Simple Samples

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!

MIPS Registers

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

Special Registers

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

Delay slots

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 Addressing Modes

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

Functions we'd like to have but don't

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
Lesson 1 - Getting Started with the MIPS
Lets start learning about the Mips... Lets learn how to do simple maths operations, and how to transfer data to and from memory.

There's a video of this lesson,  just click the icon to the right to watch it ->

Lesson1.asm


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!

Commands, Labels and jumps
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.

Loading Immediate values
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.

Moving between registers
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

Load Upper Immediate
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

Addition and Subtraction
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
Reading and Writing to and from Ram
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.

J - Jump

J is a simple jump to label command... we specify the label to jump to, and the code execution will continue there... there's not RETurn command like a call - if we want the code to continue after the jump, we probably want another jump back.
JR - Jump to Register

Rather than specifying a label as an 'immediate' value, we can use a register value
we would just load the address into the register, then specify that address
JAL - Jump and Link ... This is the equivalent of a Call / Gosub

Unlike other CPU's this function does not use the stack... the return address is loaded into RA. We return with JR RA
... which will jump to the return address - equivalent to a RETURN command

If we want to nest calls with JAL - we should push RA onto the stack at the start, and pop it at the end.


JALR - Jump and Link to Register

This function also uses a return address... however it jumps to the address in a register...

This function also allows an alternative return register (normally RA)



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.

Load And Store data sizes

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.


Lesson 3 - Conditions, Branches and Sets
There will be many times when we need to do things based on certain 'conditions', and it's branches which allow us to do this! In fact we can use branches just like JAL
We can also set registers based on certain conditions... Lets take a look!

Lesson3.asm

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


Branch Always!
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


Lesson 4 - Bit ops and more maths!
We've looked at basic maths, addressing modes and branches... but we've not covered all the maths functions of the MIPS yet...

Lets take a look at the other options

Lesson4.asm

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!