Mupen64Plus


Do you realize the insane amounts of awesomeness a video of the N64 emu on the Pandora will be?

Just picture it. "N64 emu is launched. You see a black screen. Then, "It'sa me, Mario!" and then it goes to Mario's face.

Then then, whoever is demoing it, shows off the analog controls of the Pandora perfectly working with Mario tip-toeing about.

Awesome-sauce Ahoy!
 
i just readed this thread for the first time.

YAY! n64 emulation is on my top3 of wanting emulators for the pandora :)
I really look forward to playing some Super Mario and Perfect Dark on the pandora :) although perfect dark will be hard.

Thanks for all the work and work thats coming developer(s) :)
 
Ari64 said:
You think RDRAM is slow and you want to read from ROM? The N64 cartridge interface is only a few MHz, not much faster than SNES. I don't remember the exact timing, there were timing diagrams on Valery Pudov's website at one time, but I can't find them now.

No, I want to read from icache ;p But this was in reference to less performance useful code, anyway.

Ari64 said:
BTW The original mupen64plus used the opposite strategy. Instead of checking the code blocks for changes, it checked every write to a code page to see if hit a compiled instruction. So basically it trapped a lot of writes.

Yes, this is what I would have expected. The better approach is going to depend on what the games are doing, without knowing that I can't really say anything about it. I think with your approach it might be good to eventually flush the modify-checked blocks because otherwise loaded code modules overwriting preexisting code could end up being checked forever.

If you do want to keep the explicit check per-store (instead of MMU protection) then another option is to use a byte-granular table offset from RAM by RAM size. This will hurt cache and TLB usage (and RAM usage of course.. although just by another 4/8MB) but it'd prevent any false positives and would allow you to use an add instead of the ldr, sparing register pressure a bit.

Something else I've been wondering, what kind of stubs are the memory bvcs going to exactly? Are you loading PC in each of them? Because that'd be another argument for using hashed PCs. Then you could just blvc to a common handler. That would save a lot on translation cache (even when considering the amount needed for the hashing instead) and would improve spatial locality of reference in icache usage. It doesn't seem like it'd make a big difference, but I know padding out blocks to full cacheline multiples on PSP was hurting me substantially and having all this generally unused epilogue code would probably be worse.

One more thing: I find it very unlikely that games will be using a base pointer in RAM to access something that isn't, or is in a separate mirror. I don't know what the mirroring is like on the N64 in general, but if you wanted to access something at the start of RAM you could go through r0 instead. So tell me if I'm wrong about usage, but if this is the case you should range check the base instead and fold the offset with the load (where possible). This would reduce both an add/subtract and the need for a temporary register.
 
Last edited by a moderator:
Exophase said:
Also I have some questions about your branch generation code that I didn't get to before. :B

Returning to the previous example:

Code:
  a400005c: BNE r8,r0,a4000044
  a4000060: ADDI r9,r9,4
which gets compiled as follows:
Code:
0x07000068:     add     r1, r1, #4      ; 0x4     <- ADDI r9,r9,4
0x0700006c:     cmn     r10, #18        ; 0x12    <- Test cycle count
0x07000070:     bpl     0x70002bc                 
0x07000074:     tst     r0, r0                    <- Branch condition (tests r8)
0x07000078:     beq     0x7000088                 
0x0700007c:     add     r10, r10, #16   ; 0x10    <- Increment cycle count (adjusted for loop)              
0x07000080:     str     r3, [r11, #216]           
0x07000084:     b       0x7000004                 <- Jump to a4000044
0x07000088:     add     r10, r10, #18   ; 0x12    <- Increment cycle count
...
0x070002bc:     str     r0, [r11, #192]               <- Save registers
0x070002c0:     asr     lr, r0, #31                   
0x070002c4:     str     lr, [r11, #196]               
0x070002c8:     str     r1, [r11, #200]               
0x070002cc:     asr     lr, r1, #31                   
0x070002d0:     str     lr, [r11, #204]               
0x070002d4:     str     r3, [r11, #216]               
0x070002d8:     asr     lr, r3, #31                   
0x070002dc:     str     lr, [r11, #220]               
0x070002e0:     ldr     r1, [pc, #628]  ; 0x700055c   <- Loads a4000064
0x070002e4:     ldr     r2, [pc, #628]  ; 0x7000560   <- Loads a4000044
0x070002e8:     tst     r0, r0                        <- Branch condition
0x070002ec:     movne   r1, r2                        
0x070002f0:     str     r1, [r11, #80]                <- Sets return address
0x070002f4:     add     r10, r10, #18   ; 0x12        <- Update cycle count
0x070002f8:     bl      0x80b3194 <cc_interrupt>      
0x070002fc:     sub     r10, r10, #18   ; 0x12        <- Restore cycle count
0x07000300:     ldr     r0, [r11, #192]               <- Restore registers
0x07000304:     ldr     r1, [r11, #200]               
0x07000308:     ldr     r3, [r11, #216]               
0x0700030c:     ldr     r7, [r11, #88]                
0x07000310:     b       0x7000074                     <- return (interrupt not taken)

When the count overflows we jump to the second part, which calls cc_interrupt. If no interrupt is actually taken then this will return. Otherwise it jumps to the interrupt handler.

Exophase said:
- What's the AND for? (or maybe, what's r10 for)
R10 is the cycle count. The AND is used when an idle loop is detected. It's a MOD 4 operation that gets rid of the excess cycles.

Exophase said:
- If you aren't already (I'm assuming this because of your use of r11) then you should try merging persistent global store with the stack, especially since most of your memory executions are inlined already. You will of course need to branch to ASM functions to handle anything that will want the stack. If you need a dynamically indexed table and a statically indexed one I recommend storing it such that the static comes first then the dynamic, and keeping the stack pointer pointing to the dynamic portion. Then you can index it directly with a register, index the static part with a negative immediate offset, and get to a stack by decrementing (then incrementing) beneath this. This could potentially free a register, anyway.
It would be possible to put this stuff on the stack and use r11 for something else. This would make it difficult to access from elsewhere though, you'd have to pass around a lot of pointers.
 
Last edited by a moderator:
While not being able to understand the tech-talk between Exophase and Ari64 there's one thing that is for certain. Given the talent these coders have and the complexity of the things they talk about, it now seems clear that the Pandora will have a capable N64 emu. Exophase is right Ari64, you are about to enter into the community hall of fame right along side Exophase, Pickle, Zx-81 and many others. Thank you, for all you are doing, and know we will be singing your praises when we are playing our favorite N64 game. If any of you coders out there wonder why we give you such high regards it's not just because you do things for us, but it's because you have incredible knowledge and you are happy to take that knowledge and make something from it that brings us years of joy on our machines. In this day and age, there are so many people and companies who give us half baked or incomplete products/software and demand a huge price for it, yet you guys strive to give us as close to perfection as you can for nothing. That is admirable and you deserve so much more than the kudos we give you.

To all Dev's I raise my glass of cola in toast!
"You guys are the one's who actually make the machines fun to own. Cheers!"
Thanks again.
 
Okay, this one is quite small, but for register writeback when you're performing a full 64bit write you can use strd to save icache and leverage the 64bit bus to dcache (since there's only one load/store pipeline). Of course, this requires that the registers are setup for it...

Too bad they're not 32bit or you could more easily poke stms in there :/ Although, you could just rearrange how they're stored so that all 32 low words come first...
 
Ok, Mods, I think it may be a good idea to move the "Thanks" and "OMG this r awesome" posts to their own thread and leave the devs to this thread so they don't have to sift through cruft to see what they are doing.
 
Where's the donate button?

Where's his Pandora?

Where's the Video! (WE SERIOUSLY NEED A VIDEO OF THIS SOON!) :rolleyes:

This is awesome! Great Job!
 
Raz said:
Ari64 said:
I have rewritten the dynamic recompiler for Mupen64plus to generate ARM code. This will run on OpenPandora, TouchBook, and BeagleBoard.

I love you.

+1

This was what I wanted most to happen on the Pandora, thank you so much for this, Ari.

Oh, and Enverex, I don't see them as taking up too much space, I think it's fine that they are in this topic, that way Ari can see the appreciation we have for what he has done.

-God Ginrai
 
Last edited by a moderator:
Exophase said:
Okay, this one is quite small, but for register writeback when you're performing a full 64bit write you can use strd to save icache and leverage the 64bit bus to dcache (since there's only one load/store pipeline). Of course, this requires that the registers are setup for it...
Yes, but then you'd have to allocate registers in pairs. Since most N64 code is 32-bit, it usually makes sense to only allocate one 32-bit register. The downside is that it's more difficult when you have to sign-extend to 64 bits.
 
Last edited by a moderator:
At the very least, if your registers are going dead after you store them then you can start overwriting the already flushed ones with sign extended words in order to do at least some strds. Of course, right now your registers aren't quite dead, but I talk about that a little more later.

What I was thinking was to store the low words contiguously then the high words, try to allocate the registers in adjacent pairs where possible, even aligned on ARM end (eg, MIPS r1, r2 -> ARM r0, r1). Then strd back the pairs, overwrite them with their sign extensions, and strd those. I don't know how nicely this opportunity presents itself but it's possibly a worthwhile optimization. I know that the cycle count code probably isn't called very much, especially with games that have detected idle loops, but the sheer size of the generated code looks concerning.

Another thing you can do to reduce this is to place the check somewhere else instead of merging it with the branches. For instance, at the start of blocks or at mid-block entry points. You'd be at risk of splitting blocks no matter which way: right now you will if you hit an interrupt where the branch isn't taken, this way you would if you split at a mid-block point. But you wouldn't have to store two PCs nor repeat the test, allowing the registers to die. This should help tighten things up quite a bit in comparison.

An alternative is what I think I do in gpSP (it has been a while -_-), which is to only actually exit on taken branches, not any branches. This hurts cycle granularity but it guarantees you won't split blocks. I have a good feeling that you can afford the loss in granularity when you're somewhat arbitrarily choosing to execute 1/5th of its max theoretical cycles to begin with, and since IRQs don't sound very frequent.
 
Exophase said:
At the very least, if your registers are going dead after you store them then you can start overwriting the already flushed ones with sign extended words in order to do at least some strds. Of course, right now your registers aren't quite dead, but I talk about that a little more later.
There are two cases where the registers aren't quite dead. One you've already noticed, and the other is the JR instruction, where we have to save the register, but still need the address for the branch.

Exophase said:
What I was thinking was to store the low words contiguously then the high words, try to allocate the registers in adjacent pairs where possible, even aligned on ARM end (eg, MIPS r1, r2 -> ARM r0, r1). Then strd back the pairs, overwrite them with their sign extensions, and strd those. I don't know how nicely this opportunity presents itself but it's possibly a worthwhile optimization. I know that the cycle count code probably isn't called very much, especially with games that have detected idle loops, but the sheer size of the generated code looks concerning.
This would be possible in some cases.

Exophase said:
Another thing you can do to reduce this is to place the check somewhere else instead of merging it with the branches. For instance, at the start of blocks or at mid-block entry points. You'd be at risk of splitting blocks no matter which way: right now you will if you hit an interrupt where the branch isn't taken, this way you would if you split at a mid-block point. But you wouldn't have to store two PCs nor repeat the test, allowing the registers to die. This should help tighten things up quite a bit in comparison.

An alternative is what I think I do in gpSP (it has been a while -_- ), which is to only actually exit on taken branches, not any branches. This hurts cycle granularity but it guarantees you won't split blocks. I have a good feeling that you can afford the loss in granularity when you're somewhat arbitrarily choosing to execute 1/5th of its max theoretical cycles to begin with, and since IRQs don't sound very frequent.
You can move the checks to the entry points, and only exit on taken branches, but it doesn't help.

Consider a simple loop:

Code:
loop:
 ...
adds r10,r10,#20
bpl interrupt_handler
cmp r1,r2
jne loop
If you move the check to the entry point, you have
Code:
loop:
cmp r10,#0
bpl interrupt_handler
 ...
add r10,r10,#20
cmp r1,r2
jne loop
This isn't an improvement.
 
Last edited by a moderator:
No, I think you misunderstood what I was getting at... I wasn't trying to reduce the amount of work done in the check in the main blocks, I was trying to reduce the amount of work necessary for the counter overflow code in the epilogue. If you don't link it with a conditional branch then you don't have to redo the branch logic, including fetching two PCs.
 
Exophase said:
No, I think you misunderstood what I was getting at... I wasn't trying to reduce the amount of work done in the check in the main blocks, I was trying to reduce the amount of work necessary for the counter overflow code in the epilogue. If you don't link it with a conditional branch then you don't have to redo the branch logic, including fetching two PCs.
So you can save 3 instructions in a segment of code that rarely if ever gets run. If you're trying to reduce memory usage, there are bigger targets than this.
 
Last edited by a moderator:
Yeah, I prologue that's per external (or all, if you don't do this) branch, not per block.. and it also saves a constant whose load is probably an icache miss. If you have bigger targets you should probably do something about them, I just call what I see ;p
 
Ive taken a look and have started to begin cross compiling and I see some problems with the gl. There are things in there that arnt supported by nanogl, like lists.
I suppose the ideal thing would be to convert this to ES, 1.1 if we want to consider the wiz. Any ideas/suggestions?

Code:
opengl/OGLFT.cpp: In member function 'void OGLFT::Face::draw(GLfloat, GLfloat, unsigned char)':
opengl/OGLFT.cpp:643: error: 'glRasterPos2i' was not declared in this scope
opengl/OGLFT.cpp: In member function 'void OGLFT::Face::draw(GLfloat, GLfloat, GLfloat, unsigned char)':
opengl/OGLFT.cpp:656: error: 'glRasterPos2i' was not declared in this scope
opengl/OGLFT.cpp: In member function 'void OGLFT::Face::draw(GLfloat, GLfloat, wchar_t)':
opengl/OGLFT.cpp:670: error: 'glRasterPos2i' was not declared in this scope
opengl/OGLFT.cpp: In member function 'void OGLFT::Face::draw(GLfloat, GLfloat, GLfloat, wchar_t)':
opengl/OGLFT.cpp:683: error: 'glRasterPos2i' was not declared in this scope
opengl/OGLFT.cpp: In member function 'void OGLFT::Face::draw(GLfloat, GLfloat, const char*, float*)':
opengl/OGLFT.cpp:726: error: 'glRasterPos3i' was not declared in this scope
opengl/OGLFT.cpp: In member function 'void OGLFT::Face::draw(GLfloat, GLfloat, GLfloat, const char*)':
opengl/OGLFT.cpp:773: error: 'glRasterPos2i' was not declared in this scope
opengl/OGLFT.cpp: In member function 'void OGLFT::Face::draw(GLfloat, GLfloat, const wchar_t*)':
opengl/OGLFT.cpp:830: error: 'glRasterPos2i' was not declared in this scope
opengl/OGLFT.cpp: In member function 'void OGLFT::Face::draw(GLfloat, GLfloat, GLfloat, const wchar_t*)':
opengl/OGLFT.cpp:914: error: 'glGetDoublev' was not declared in this scope
opengl/OGLFT.cpp:918: error: 'gluUnProject' was not declared in this scope
opengl/OGLFT.cpp:923: error: 'glTranslated' was not declared in this scope
opengl/OGLFT.cpp:928: error: 'glRasterPos2i' was not declared in this scope
opengl/OGLFT.cpp: In member function 'virtual OGLFT::BBox OGLFT::Raster::measure(unsigned char)':
opengl/OGLFT.cpp:1030: error: 'glGetDoublev' was not declared in this scope
opengl/OGLFT.cpp:1036: error: 'gluUnProject' was not declared in this scope
opengl/OGLFT.cpp: In member function 'virtual OGLFT::BBox OGLFT::Raster::measure(wchar_t)':
opengl/OGLFT.cpp:1094: error: 'glGetDoublev' was not declared in this scope
opengl/OGLFT.cpp:1100: error: 'gluUnProject' was not declared in this scope
opengl/OGLFT.cpp: In member function 'virtual GLuint OGLFT::Raster::compileGlyph(FT_FaceRec_*, FT_UInt)':
opengl/OGLFT.cpp:1120: error: 'glGenLists' was not declared in this scope
opengl/OGLFT.cpp:1121: error: 'GL_COMPILE' was not declared in this scope
opengl/OGLFT.cpp:1121: error: 'glNewList' was not declared in this scope
opengl/OGLFT.cpp:1125: error: 'glEndList' was not declared in this scope
opengl/OGLFT.cpp: In member function 'virtual void OGLFT::Raster::clearCaches()':
opengl/OGLFT.cpp:1157: error: 'glDeleteLists' was not declared in this scope
opengl/OGLFT.cpp: In member function 'virtual void OGLFT::Monochrome::renderGlyph(FT_FaceRec_*, FT_UInt)':
opengl/OGLFT.cpp:1286: error: 'glBitmap' was not declared in this scope
 
Pickle said:
Ive taken a look and have started to begin cross compiling and I see some problems with the gl. There are things in there that arnt supported by nanogl, like lists.
I suppose the ideal thing would be to convert this to ES, 1.1 if we want to consider the wiz. Any ideas/suggestions?
I'll never get a Wiz, so it's not my problem. And I don't see the Wiz running at good speeds anyway. Do what you will, but I wouldn't go out of the way for it is all I'm saying.
 
Last edited by a moderator:
midna25 said:
I'll never get a Wiz, so it's not my problem. And I don't see the Wiz running at good speeds anyway. Do what you will, but I wouldn't go out of the way for it is all I'm saying.

And the point of your post is ?.

Trooper
 
Last edited by a moderator:
Funny a friend and I were talking about what crap RDRAM is....just today. XD he paid like 300$ for some RDRAM (128MB iirc) on a PIII system.... then he touched after it had been running it burned a piece of his skin off it was soo hot XD and apparenly it was acutally designed to run that hot Heh very interesting thread... and I get to learn MIPs assembly this semester too yay
 
Back
Top