GP2X Gba Emu And Static Translation


Code in VRAM, have not heard of it but it could be done. The VRAM must be written in some multiple of 16 bits (but it can be read without restriction) because of the way the system is built. I store extra data in VRAM and an unused Palette if I can. If I am putting code somewhere other than ROM I put it in IWRAM because it sits on a 32bit bus and all other memory is on a 16bit bus.
 
TKF15H posted on Oct 27 2006 at 09:57 PM said:
This post is just what I'm thinking about concerning the emulator at the moment, what I'm currently
implementing, written down and shared in hopes of obtaining another point of view, even if it's just, "hah,
that's silly."

At first this was going to be a statically-recompiling emulator, but then plans changed when it was
observed that the cost did not justify the benefit in comparison to a Dynarec-based CPU core.
IBA is emulating an ARM processor on another ARM, so statically recompiling with GCC in the
middle wasn't worth it: Register allocation and the code in general would not be optimal (did a
test, results didn't look that good), CPSR flags have to be calculated (rather than using the
GP2X's), and it's rather cumbersome for the end-user (run in decompilation mode, exit,
compile, get back in again, rinse, lather, repeat). Since the whole thing is so experimental, one
should not be surprised with the changes. A GCC backend would probably do a better job with
a processor that has more registers than the ARM (PPC maybe?), but being so similar there is no
need for it and it ends up getting in the way.

A dynarec has been built for outputting blocks of code that supports all of the ARM's instructions
except, currently, the opcodes for co-processor communication. The code it outputs can be saved
to the SD so that ROM code does not have to be re-translated... so in a way, this emulator can still
be a static recompiler, it just does ASM->ASM instead of ASM->C->ASM. (If anybody goes
out and says, "I told you so" I'll pull out a large trout and deliver a few slaps before the day ends)

When the emulator starts, the processor attempts to execute whatever is at address 0x08000000,
which resides on the cartridge (GPROM). To know if the instruction at that address has been
previously compiled, a second table is needed. Due to the size of the GPROM (up to 32MB) a
table that keeps track of what's beed compiled and what hasn't needs to be as compact as possible.
Since the worst case is that all 32MB are filled with Thumb code, you could say that every short (16 bits)
needs one bit to indicate previous compilation. So, 32MBytes / 2Bytes = 16M Thumb instructions =
16Mbits are necessary for flagging each instruction = 2MBytes for the GPROM
Compilation Bit Map (GPROMcbm from now on).
Now that there's a table that tells the emulator that it has executed that instruction before, it needs
to know where the translated block is. We can't afford to have a 32bit pointer for each instruction
(it would require 64MB plus the GPROM's 32MB totalling 96MB!) so we'll have to over-write
whatever the GPROM is storing there with a pointer to the code's actual location. In the case of
ARM code, this is not a problem as the instructions are the same size as a pointer... but what about
thumb mode? A pointer can't be stored in 16bits! Well, where you can't store an absolute address, a
relative one will have to do.
Let's say the letters on the left are GBA instructions and the ones on the right are the translated GP2X
equivalent:
Code:
0  A   E
1		F
2		G
3   B   H
4   C   I
5		 J
With 16 bits, if a jump to C is necessary, you can't simply store the pointer to I. Therefore, instead of
a pointer, an offset is used. This gives us 65536 instructions that are possible jump targets, which isn't
enough. The solution (?):
Target = (R15 - 0x08000000) + GPROM[ R15 ] + CacheBlockPtr;
When A is compiled, a zero is put in its place, and the GPROMcbm is updated.
Then B is compiled, a (0+2) two is put in its place, and the GPROMcbm is updated again.
Finally C is compiled, (2+0) two is put in its place, and the GPROMcbm... you'll never guess... is
updated yet once more.
The number that is put in place of the old opcode is the number of "extra" instructions needed up till
now. This gives us far more than 65536 instructions due to the fact that most opcodes are translated
with a 1:1 proportion.

Ok, enough of the GPROM, let's move on to the rest...

The GBA has the following address-space regions:
ROM
Internal RAM
External RAM
IORAM
Palette RAM
Video RAM
Tile RAM
GPROM

I am going to assume code can be executed on the ROM, IRAM, ERAM, and GPROM and will
never be executed on the IORAM, Palette RAM, VRAM, or the Tile RAM.
Executable memory that can't be written to (ROM, GPROM) does not need over-write checking, and
so can be optimized and simplified in comparisson to the RAM.

Let's have a look at Interal / External RAM. These two banks are rather small - 32KB and 256KB - so
there's a lot more room to work with, and I'll need it. Since they're writable, the emulator needs to know
if the block of code it has written is valid. This means that for each GBA instruction, there needs to be a
pointer to the equivalent GP2X code and a pointer to the cDynarec object it came from.
The cDynarec class has a Key and CurrKey. When a cache block is compiled, the sum of each GBA
instruction is stored in Key and CurrKey. That way I hope to have a signature of each block. Each
time a STR/STM instruction is executed on an address that contains compiled code, the old value is
subtracted from the CurrKey and the new one is added. When code generated by cDynarec has to
be executed, it checks if Key equals CurrKey, and if it isn't, the CurrKey is searched for in a binary
tree. If it isn't found, a new block has to be generated, and the current one stored in the tree for later.
If it is found, the old block replaces the current one.

.... I hope that makes sense >_<

As for the other RAMs, I'd like to know... is it reasonable to assume that if a GBA instruction accesses
the IORAM (for example) it is unlikely to access another region? Because if it is, I'd like to do the
MMU search once and then make the generated code access that area directly.

I knew that. :rolleyes:

:p
 
Last edited by a moderator:
Disclaimer: I'm not sure how helpful any of this info would be, but maybe there's a couple interesting ideas. I'm kind of curious about the whole subject, so if there's anything I'm wrong on, let me know :)

Just a point of reference for memory management / caching for translated code: QEMU I believe allocates a single buffer and fills it as needed with translated code, and when it gets full, it totally dumps it and starts over. So, I guess it's probably not a huge concern to worry about making that the most efficient, at least until it's working and you're starting to look at places to make small improvements.

It's certainly a simple and less error prone approach, but if it ever does fill the buffer, there'll be a noticeable lag while it retranslates. If it never fills up, it's hard to improve upon it speed wise, but perhaps memory usage could be improved with better algorithms.

One other thing I thought of, is that when you guarantee code is never moved / swapped out of memory except deleting all at once, you can hard code jumps that reside in ROM instead of using a hash table every time. You may want to leave hooks back to the translator initially for cases where you haven't translated the target yet, because translating them immediately could lead to a situation where you fill the buffer with one translation (a huge mess), and it would make the execution speed less consistent if it translates a huge series at once. Anyway, you do still need a hash table or something similar for jump translation in the case of calculated jumps, etc.

It gets trickier once you start thinking about RAM resident or self modifying code though. I'd look into using the MMU to trap write accesses to RAM areas that have translated code in them, and using that to invalidate the related translation. You don't want to get stuck doing an extra lookup on every RAM write when the hardware can do it for you. Another approach is using a pure interpreter when executing from RAM, but this is questionable because if code executes from RAM, it probably was performance critical to start with.

Another thing to think about... how do you handle the situation if a RAM executing code segment modifies an instruction ahead of itself but still within the same translated sequence? You'd need some way to break out of translated code at the point of writing such that the following instructions can still be translated properly. This is probably a rare occurrence though, and might not even happen in real code.
 
Wow, I though the ASM->ASM horse was quite dead and the ASM->C->ASM beast was alive and well and almost ready for alpha release.

dynarec is something I have not done yet, static binary translation yes (just posted Asteroids for the GP2X tonight BTW). You are going from a thumb friendly environment to this arm friendly environment. Are you converting thumb instructions to arm or just leaving them thumb? Even though thumb is slower than arm on the gp2x and generally the other way around on the gba, the gp2x execution speed (with the cache) is probably fast enough to allow thumb to be fast enough to use as is.

What sounds very cool, and I dont know if you did this. Is that you could do most if not all of the dynarec on a PC in an emulator before taking it to the gp2x. Do all of the work in GBA land (dynarec GBA to GBA) to turn it into something you could do a (somewhat) static translation on. You would have hundreds of megabytes or gigabytes of memory and disk space to work with and not be limited to the gp2x's memory and sd space. And it would run a hell of a lot faster and take less time.

Have you found that most games execute strictly from ROM and use the ram areas just for data? Or is there a significant percentage that copy code over to ram and execute from ram? And of those is it a straight byte for byte copy or is the code encrypted or compressed before going to ram? I am still trying to turn this into a static translation problem in my mind...

Quite frankly I could care less about playing GBA games on the gp2x, I have a couple of gbas so that doesnt matter. What floats my boat is the method/problem solving used to get from gba to gp2x. So thank you for the update, and keep up the good work...
 
If I understand it, thumb is a strict subset of ARM, so it would be straight forward (if maybe a pain) to convert it all to ARM if desired.
 
The THUMB mnemonics's are generally a subset of the THUMB instruction set but there are issues with the way you do things between the two that can complicate some sequences. The ARM set is 32bits wide and the THUMB set is 16 bits wide so field widths are sometimes different. Going from THUMB to ARM is doable and might be worth a one to one translation but is could waste memory and invalidate some branches because the byte offset is no longer within range.

In the GBA the way to run fast ARM code is to load it into IWRAM which is on a 32bit wide bus. All of the other memory in the GBA is on a 16bit wide bus so it takes 2 memory cycles to fetch an ARM instruction. I have been playing with the instruction formats for a example piece of code for my class so I have been examining this recently. My goal was not to do the translation but to see what the limits of the instruction fields would do to any possible changes.

I teach a class on Machine Architecture and we use the GBA as an example of RISC architecture. I am working on the Power PC processor for another RISC machine (Game Cube).
 
I'd almost think translating it to ARM would be a better approach because if you need to insert hardware emulation code, recompiler callbacks, or things like that, you wouldn't want to have to write two versions (ARM and thumb) or have to constantly switch between the two.

If you assume that the code layout is totally changed during dynamic recompilation, then worrying about jump distances isn't as important, but it would be a little wasteful memory wise. If I were to write a recompiler, I'd have it work on translating short sequences (until it hits an unconditional jump or function return, etc, or until the code size hits a limit, and then it creates a callback to the recompiler to compile the next part, or jump to it if it was already compiled).

Essentially, every jump destination in the code would end up starting a separate recompiled sequence, which would be wasteful because many code chunks would be duplicated, but there's still room to improve that by tracking where labels exist and stopping recompilation at those boundaries (which would improve memory/cache usage, but introduce more jumps unless the recompiler's memory management can place sequential code chunks together).

I suspect performance would still be "good enough" even without worrying about these improvements though. Simply avoiding interpreting the code should help dramatically.
 
Thumb instructions map one to one to arm instructions, how do you think they execute them in the core?

My question/concern was the relative offsets, doubling the size of the instruction when going from thumb to arm doubles the distance for the relative addressing and is that going to be a problem. I thought it might...Perhaps it doesnt matter.

I still think overall even in thumb mode the gp2x is fast enough (with the cache on), to compensate for the speed lost. Actually is there speed lost? When comparing ARM and Thumb its a comparision of starting from a high level language and how fast will that code run when reduced down to arm or thumb instructions. In this case where the instructions are already thumb the high level language has already been reduced, the only way to make faster arm code would be to optimize the thumb assembly into arm assembly. A straight one to one conversion wont change performance (well other than our wonderful, mystery, memory interface problems on this platform).

I have to add (when the cache is on) because the gp2x is not much faster, if at all, than a gba with the cache off.
 
I don't want to disturb, but anybody saw this?
http://gpf.dcemu.co.uk/gpSPDC.shtml

It is the GPSP emulator (GBA Emulator for PSP) ported to Dreamcast with no assembly code. It is faster, smaller and specific to be a fast GBA emulator. The source code is also simple and clear to understand.

There is an small assembly optional part (.s of about 8kb, it should be not too difficult to port to ARM).

In PSP the emulator runs perfectly, fullspeed with sound.

Regards.
 
I don't want to disturb, but anybody saw this?
http://gpf.dcemu.co.uk/gpSPDC.shtml

It is the GPSP emulator (GBA Emulator for PSP) ported to Dreamcast with no assembly code. It is faster, smaller and specific to be a fast GBA emulator. The source code is also simple and clear to understand.

There is an small assembly optional part (.s of about 8kb, it should be not too difficult to port to ARM).

In PSP the emulator runs perfectly, fullspeed with sound.

Regards.

looks like this psp emulator is doing translation of gba arm asm to psp mips asm
wont be usefull for the gp2x
but maybe the C core could be used
 
Last edited by a moderator:
It is all in C but 8 kb of PSP ASM (or 20 kb of X86 ASM). But the ASM part is optional. Take a look anyway... I think it could be useful...
 
Currently I haven't touched the Thumb side of the GBA, but I'm leaving things ready for when I do. The demo I'm currently testing with is all ARM. I'm planning on translating the thumb code to ARM as the other way around looks quite a bit more cumbursome.
As for using the hardware MMU for emulating the GBA's memory, while possible, I'd like to use software emulation for now - it's easier to code, harder to get wrong, and easy to debug with GDB.
The PSP GBA emu looks interesting, I've downloaded the source and will have a look.
The MIPS processor has 32 registers, doesn't it? Here I was wishing ours had just one more than the GBA's. Bah, the dynarec I'm working on is going to have to flush registers all the time as I need at least one to work with.

Oh well, at least I have a good supply of tuna. Mmm, tuna... pitty it's not in a pie. Mmm, pie. Ok, I'd better go to bed now.
 
Currently I haven't touched the Thumb side of the GBA, but I'm leaving things ready for when I do. The demo I'm currently testing with is all ARM. I'm planning on translating the thumb code to ARM as the other way around looks quite a bit more cumbursome.
As for using the hardware MMU for emulating the GBA's memory, while possible, I'd like to use software emulation for now - it's easier to code, harder to get wrong, and easy to debug with GDB.
The PSP GBA emu looks interesting, I've downloaded the source and will have a look.
The MIPS processor has 32 registers, doesn't it? Here I was wishing ours had just one more than the GBA's. Bah, the dynarec I'm working on is going to have to flush registers all the time as I need at least one to work with.

Oh well, at least I have a good supply of tuna. Mmm, tuna... pitty it's not in a pie. Mmm, pie. Ok, I'd better go to bed now.

That's my emulator you downloaded :p

You might see what I meant by 16bit indirect tables to code. Instead of trying to encode 32bit pointers as 16bit, you just have a table of 32bit pointers and index them with a 16bit one. Because you aren't going to need more than 64k branch targets.

For hashing you would of course call the hashing function at runtime for indirect branches. Fortunately indirect branches occur significantly less frequently than direct ones.

I don't think letting the translation buffer grow to the SD is a good idea, especially if you use dynamic register allocation which prevents you from cleanly doing self-reentrant blocks, increasing the amount of redundancy in recompilation. Also if you recompile Thumb code to ARM.

Sure, you can HLE the SWI handler away, but that doesn't guarantee that the game's interrupt handler won't want to inspect code for some reason.

I think the big problem with assumptions like this is that even if no games would want to do that many do weird things unintentionally and only work if you emulate things exactly, just because they're buggy games. I'm still banging my head over a few games like this.

Actually, LDR/LDM trying to read code is very likely, it happens all the time when games load code into RAM (which almost every game does). Of course, that code may never be executed as it is IN ROM, so you might not even consider it code in the same sense.

Almost no games run code from VRAM of course, but the reason they'd do such a thing is space efficiency. Because they're using all the other RAM for other things and they don't need VRAM. And VRAM is fast, essentially as fast as IWRAM.
 
Last edited by a moderator:
I ended up using a cache for the GPROM as the offsets thing got messed up when large amounts of data got skipped by the dynarec (causing a negative offset on an unsigned variable, ie chaos).

By the way, I checked out gpSP, really nice code. :)
 
Elektranox posted on Dec 3 2006 at 03:45 AM said:
any news about the project?

Are you talking about GBA emulation on GP2X? :D
There are 3 releases out there for a couple of days. :rolleyes:
 
Last edited by a moderator:
Back
Top