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
MIPS Simple Samples
|
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. |
|
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
|
|
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!
|
| |
Buy my Assembly programming book on Amazon in Print or Kindle!
Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
Buy my Assembly programming book on Amazon in Print or Kindle!
Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
Buy my Assembly programming book on Amazon in Print or Kindle!
Available worldwide! Search 'ChibiAkumas' on your local Amazon website!
Click here for more info!
|