jsmolka / gba-tests

A collection of Game Boy Advance tests.
MIT License
101 stars 9 forks source link

Tests 224/225 Question #10

Closed zaydlang closed 3 years ago

zaydlang commented 3 years ago

Hey,

First off, I love these tests, thanks so much for publishing them and creating these. I just had one question and the README said to open an issue for clarifications so, here I am.

In this file: https://github.com/jsmolka/gba-tests/blob/master/arm/data_processing.asm

Tests 224 and 225 contain instructions 0xE1A0001F and 0xE08F0010. My emulator fails on both these tests, and I'm having trouble resolving them because they contain undefined behavior. The undefined behavior is documented here on page 226 at the bottom of the page: https://www.intel.com/content/dam/www/programmable/us/en/pdfs/literature/third-party/archives/ddi0100e_arm_arm.pdf

Register PC can't be used as rm. And, I guess you all already know this instruction performs undefined behavior because you included it in an unorthodox way. I guess my question here is then, how do I resolve this? Is this some behavior that is specific to ARM7TDMI? My original guess was that using PC as rm causes it to be read as (PC + 8) due to pipelining. This is similar behavior to the LSL immediate addressing mode on page 225.

However, by looking at the tests we can see that this is not the case. Look at Test 224 for example. Using this logic, after line 320 runs, r0 is set to the address of the instruction at line 322. Which causes the CMP instruction after it to fail. Contrast this with Test 221, which adds 4 to PC to compensate for this.

What am I doing wrong?

jsmolka commented 3 years ago

Yes, this is undefined behavior. If you are using the program counter as rm or rn and shift by a register operand, its value is incremeted by 4 due to pipelining.

u32 op1 = regs[rn];
u32 op2 = regs[rm];
// ...
// if shift by register
if (rn == 15) op1 += 4;
if (rm == 15) op2 += 4;

The datasheet also mentions this:

If R15 (the PC) is used as an operand in a data processing instruction the register is used directly. The PC value will be the address of the instruction, plus 8 or 12 bytes due to instruction prefetching. If the shift amount is specified in the instruction, the PC will be 8 bytes ahead. If a register is used to specify the shift amount the PC will be 12 bytes ahead.

I don't know if this behavior is specific to the ARM7TDMI but it definitely happens on the GBA so that's what I'm going. I also recommend this datasheet. It looks easier and less verbose than the one you are using.

fleroviux commented 3 years ago

Some further explanation which maybe helps understanding the behavior: the ARM7TDMI-S has two register read ports (A bus and B bus), so in a single cycle it can read two GPRs at most.

But data processing with shift-by-register requires a total of three GPR reads. The two operands and the shift amount. The CPU reads the shift amount during the first instruction cycle (the fetch cycle if you will) and the operands in the cycle after. But r15 is advanced at the end of the first cycle, so at the time when the operands are read it is already +12 ahead, instead of +8.

grafik

zaydlang commented 3 years ago

Thanks a lot you both for the insightful comments, this makes sense! And yeah, that datasheet looks a lot easier to read. (And it's specific to the ARM7TDMI, which is a plus). I'll be closing the issue now. :)