Language…
7 users online: Anas, CONLUSH666, Danik2343, DinoMom, Firstnamebutt, masl, Zavok - Guests: 205 - Bots: 389
Users: 64,795 (2,380 active)
Latest user: mathew

[ASM] Basics of MIPS R4300i Assembly

EDIT:
Made an MIPS R4300i Assembly Tutorial. Still not done yet, but will be extended in the next days:
http://origami64.net/attachment.php?aid=485

I'm gonna explain some "basics" of the MIPS Assembly. A lot of confusion seems to go around here about it, especially the part on how you load and store correctly. A lot of SMW Hackers somehow got an interest into SM64 Hacking and wanted to write some ASM code, but failed because they thought it would be that easy like in SNES's CPU 65C816. But unfortunately, MIPS does not have the ability for direct, absolute or long addressing. Instead, to make MIPS "realize" that we want to load the value from an address we need to do this by 16-bit steps and use more steps than you would in SNES.

In 65C816's assembly, you load the value from an address by simply using the opcode LDA and the address(as operand) you want to load the value from.

Code
LDA $7E0019 ; Long Addressing Load. Loads the current value of $7E0019 into the accumulator.


Unfortunately, MIPS is not like that and requires at least two steps for this. While CPUs like 65C816 allow "direct" addressing, MIPS allows only (a kind of) indirect addressing, which is officially called "Base Addressing"

Code
LUI T0, $8034       ; Load upper half address into T0.
LW T1, $B21D(T0)    ; Load lower half address, MIPS notices it and automatically loads the value into T1.


In this case, T0 is our "base" which is added to the offest value in LW ( $B21D(T0) -> $8034B21D ) and loads the value from there. To conclude this: In base addressing the register acts as a pointer to an operand located at the memory location whose address is in the register.

0x8033B21D is the current amount of lives Mario haves. So, if Mario would have 4 lives, T1 would have the value $00000004 now.

So, how did this work? Well, first we load the upper half address $8033 into T0. So T0 is $80330000. LW simply loads the lower half address and binds it together with T0. The CPU automatically recognizes this is an actual address and will load the value from there into T1.

Now, if we actually want to change the lives and store it into the address, how would we do this? We do it in a similar way, just that we need now the value we want to store and we need the SW(Store Word) opcode now:

Code
LUI   T0, $8034      ; Load upper half address.
ADDIU T1, T1, $000A  ; Our value. T1 := $0000000A
SW    T1, $B21D(T0)  ; Store T1 into memory location of T0. ($8033B21D) 


Now, Mario would have 10 lives now.

Also, when you're in practice you always should put the code like this:

Code
ADDIU SP, SP, $FFFC
SW RA, $0000 (SP)

// Code goes here.

LW RA, $0000 (SP)
JR RA
ADDIU SP, SP, $0004


or:
Code
ADDIU SP, SP, $FFE8  ; Allocate 6 words (no idea why. blame nintendo)
SW RA, $0014 (SP)    ; Push return address to stack.

// Code goes here

LW RA, $0014 (SP)    ; Pop return address from stack.
JR RA                ; Jump to return address.
ADDIU SP, SP, $0018  ; Restore stack


The first code actually allocates one word space. Unfortunately, there are some hot discussions why SM64 wasted so much space for the return address( actually they allocated $FFE8 (24 bytes, wtf!) ) which is still weird. shyguyhex and me are still finding out why this was done. We both guessed that it haves something to do with the 8 multiplier rule. The rule says, that a 64-bit data object can be pushed to the stack, but eventually generate an address alignment error at runtime. So, it is required to make the stack pointer always the multiple of 8.

This code ensures that our return address is not lost. It's being pushed onto the stack. It's useful when you call a subroutine in your main subroutine (which logically replaces the old return address) and once the main subroutine is done, it will get back the old return address and jump to it.

[To be continued]

Mod edit: Fixed table stretch.

Neat tutorial, for those wondering how to get the addresses, you can use NEMU64 to RAM watch, MIPS unlike SNES's CPU 65C816 will not load the whole address, but will split it into two, in order to understand this, you need to know how the OPCODES work.
Is not that I am going into asm coding, but looking at you guys writing assembly code made me think of a question:
When writing into a register how can I be confident that I am not overwriting a value that it would have been used by other code? Are there safe place to where to put the code?
Originally posted by Kinopio
Is not that I am going into asm coding, but looking at you guys writing assembly code made me think of a question:
When writing into a register how can I be confident that I am not overwriting a value that it would have been used by other code? Are there safe place to where to put the code?

If you RAM watch you will see that the code only relies on the specific object, some may replace other registers but if you debug a little you can find out where the problem is from so don't worry about that, also there could be some free space at the end of the ROM, you can Jump And Link(JAL) there and continue your code like let's say at the end of the rom is the offset 666666 so the value will be;
Code
JAL 0x0066666

OR
Code
JAL $0066666
Is Tarek701 and Cackletta working on this tutorial together :)?
I've made a tutorial for MIPS R4300i Assembly. My cajetan64 googlesite is outdated and wrong on some parts. The new tutorial is better and is hopefully good explained. The tutorial is still not done:

http://dl.smwcentral.net/6947/mips-tut.zip

Btw, Cackletta(Mr.GreenThunder) is not working with me together on this project. I'm doing this alone as cackletta doesn't really have the required knowledge to help me on this.

Greetings,
Tarek701.

UPDATE(07/19/2014):
Updated the MIPS tutorial, added lesson for simple branches. (BEQ and BNE) + a little compare with 65C816 Assembly for those who switch from SMW Hacking to SM64 Hacking.

http://dl.smwcentral.net/6947/mips-tut.zip

Upcoming stuff:
- More Branches stuff (BLT, BGE, etc.)
- Stack Pointer Introduction
- Subroutines, use arguments + pass additional arguments to stack.
- Floating-Point Operations and explanation of their special formats.

Good luck.

EDIT:
If you find any errors, don't worry and post them here.

EDIT 2:
Please re-download. I made very tiny mistakes (forget the 2nd operand for two ADDIU operations), fixed them right now.

UPDATE(08/06/2014):
New update, people. Added more branch instructions and described them and started explaining stack and usage of stack. The explanation of subroutine usage and passing arguments to stack is soon to come, I still have to discuss out some things with shyguyhex about the stack frame.

Download here:
MIPS Tutorial

Have fun.