nba-emu / NanoBoyAdvance

A cycle-accurate Nintendo Game Boy Advance emulator.
GNU General Public License v3.0
955 stars 53 forks source link

Halt is not immediate #333

Open alyosha-tas opened 8 months ago

alyosha-tas commented 8 months ago

Here is a test that demonstrates that Halt takes (at least) 1 cycle to take effect:

https://github.com/alyosha-tas/gba-tests/blob/master/irq/halt_pc.gba

All it does is read what PC address is pushed to the stack by the IRQ handler after a halt has occurred.

Nanoboyadvance currently returns what would be the next address after the write to Halt control. On console it is the address that the branch (the next instruction after halt) branches to. So at least the branch cycle occurs before halting.

I'm pretty sure this will eventually lead to a solution to the DMA set test case in your current iteration of haltcnt.gba, but I haven't worked out the details yet.

fleroviux commented 8 months ago

Interesting. What you describe sounds to me like it would also be affected by general IRQ timing. In particular I know that I have an inaccuracy regarding the exact cycle that the irq_line_high && !cpsr_irq_disable condition is evaluated on by the CPU core. That is also what would be needed for NBA to pass the irq_delay test ROM. Though it is kind of hard to implement with my current architecture and might warrant some rewriting of the CPU core.

alyosha-tas commented 8 months ago

I updated the test to provide timing info as well. The timing indicates that IRQ happens after the branch instruction after halt.

The following model passes both my new test and the DMA test in haltcnt.gba (as well as all the other ones):

Halt takes one cycle to take effect. In other words one cycle in the cpu is run after the write to halt. Halt takes one cycle to exit after irq conditions are met.

The reason this works for the DMA test is that the DMA still has the cpu stopped for an extra cycle after the halt write, so the extra cycle the cpu could have been used after halt is not used in this case. Otherwise you would get 4153 instead of 4154.

Everything seems to work with this model, though I still need to do some edge case testing.

fleroviux commented 8 months ago

Halt takes one cycle to take effect. In other words one cycle in the cpu is run after the write to halt.

So there's a single clock cycle delay for the write to take effect and then is the CPU halted immediately on the next clock cycle or does the CPU finish its bus cycle if it's performing a memory access?

Also if this is true it supports something I have been suspecting for a while that there might be a general one clock cycle delay for IO writes to take effect. Current other known cases of this are as far as I know:

Finally I haven't probed the PPU about this yet, but I did notice something regarding WIN[x]H fetch timing that might indicate a write delay as well, see here.

alyosha-tas commented 8 months ago

I don't think there is a way for a multi-cycle bus access to occur following the write halt, at least i cant think of one.

I'm pretty sure writes to Wait control take effect immediately. I believe i tested this at some point.

Also I think the interrupt regs are immediate? At least if an interrupt flag is set and cleared by write to IF on the same cycle, the flag should not be set and no interrupt triggered.

fleroviux commented 8 months ago

I don't think there is a way for a multi-cycle bus access to occur following the write halt, at least i cant think of one.

True. Maybe it could be tested with that undocumented bit which allows swapping EWRAM and BIOS, though I'm not entirely sure if the HALTCNT protection can be bypassed that way. Alternately maybe could be tested with some open bus shenanigans (see: https://gist.github.com/merryhime/797c523724e2dc02ada86a1cfadea3ee)

Also I think the interrupt regs are immediate? At least if an interrupt flag is set and cleared by write to IF on the same cycle, the flag should not be set and no interrupt triggered.

As far as I could tell and remember writes to IME, IE and IF are delayed by one cycle, but so is requesting the IRQ in IF. For example if a timer with an enabled IRQ overflows I remember observing that the flag in IF is set only one cycle after the overflow. I need to see if I can find the mini test that I wrote for this back. But with that model what you describe should still work.

alyosha-tas commented 8 months ago

As far as I could tell and remember writes to IME, IE and IF are delayed by one cycle, but so is requesting the IRQ in IF. For example if a timer with an enabled IRQ overflows I remember observing that the flag in IF is set only one cycle after the overflow. I need to see if I can find the mini test that I wrote for this back. But with that model what you describe should still work.

Interesting, none of my IRQ regs are delayed by a cycle yet I pass all the available IRQ tests. I thought I had made some tests specifically to test this but I don't have anything written down about it. I'll look at making some when I get a chance.

I added tests halt_pc_2,3,4 to my tests. These trigger an IRQ at cycles 0,-1,+1 around where a halt occurs. NanoboyAdvacne currently fails test 3. The results are consistent with the model I mentioned.

fleroviux commented 8 months ago

Looking at my folder of unpolished and WIP tests I did find the test that proves that asserting an IRQ takes a cycle to show up in IF, but GBAHawk is already passing that (and from the looks of it also seems to delay setting the IRQ flag in the IF register). But I can't find proper tests for CPU writes to IME, IE and IF and I'm not sure anymore if I tested that properly or if it was something I did to fix another issue.

timer-irq-if-flag.zip

alyosha-tas commented 8 months ago

I sorted out my IRQ reg tests and NanoboyAdvance passes all of them, so I guess we are both talking about the same thing in different ways.