Hey guys! been awhile since I posted about RustyNES. It's getting a lot better but I'm still struggling with some things that I discovered when implementing MMC5 that I can't quite figure out with regards to IRQs.
I spent way too many hours trying to find out why the nametable swapping wasn't working in Castlevania III when you get to the first vertical room (as referenced here viewtopic.php?f=3&t=17010&hilit=irq).
I actually got it working finally and don't suffer from the status bar jitter (though there is an extra black line when you walk through it so I'm sure I'm off by one scanline somewhere).
However, in order to fix it I had to change my CPU IRQ handling. What seems to happen with Castlevania is that it issues an IRQ on scanline 41 and then another on scanline 42 before the first IRQ has issued an RTI. Previously, my CPU was ignoring that second IRQ because the I flag was still set. That second IRQ is required to load the correct nametable swapping values into memory.
But now, my MMC3 games (notably Star Trek 25th Anniversary) don't work correctly (and they used to) because IRQs that used to be ignored are getting issued and so the screen jitters when it shouldn't.
Now - my question: How is the CPU supposed to handle IRQs when it's in the middle of handling one already? Is there a decay rate?
I've tried to read all the documentation on IRQ being low and have even looked at the source for Nestopia and FCEUX and I'm not able to discern exactly how they handle it.
Here are the relevant bits of code I'm using for IRQ handling. Note it's not cycle accurate, I'm just issuing one instruction at a time, capturing how many cycles that took and then catching up the PPU and mappers with 3x that number. Note also this is Rust code.
The one change that makes MMC3 work and breaks MMC5 is this:
I also did some testing inside FCEUX for both games and it seems to finish one IRQ, execute a single instruction, and then handle the next IRQ which is what I've got above for the new changes in the CPU. If the above IS how to handle IRQs, then my MMC3 IRQ timing is off and I'm not sure how to fix it.
This is my clock function for MMC3
It gets called for every VRAM read in the PPU while getting BG and sprite data and it seems to trigger correctly at cycle 260 at irq_latch-1 scanlines
Any clarification would be great!
Edit: When running Blaargs 1-clocking MMC3 tests - The "new" implementation of allowing IRQ to be set to pending while I flag is set fails with "Counter/IRQ/A12 clocking isn't working at all Failed #2".
If I use the old implementation of ignoring IRQs when I is set, it still fails, but at least gets to "Should decrement when A12 is toggled via PPUADDR Failed #3"
I spent way too many hours trying to find out why the nametable swapping wasn't working in Castlevania III when you get to the first vertical room (as referenced here viewtopic.php?f=3&t=17010&hilit=irq).
I actually got it working finally and don't suffer from the status bar jitter (though there is an extra black line when you walk through it so I'm sure I'm off by one scanline somewhere).
However, in order to fix it I had to change my CPU IRQ handling. What seems to happen with Castlevania is that it issues an IRQ on scanline 41 and then another on scanline 42 before the first IRQ has issued an RTI. Previously, my CPU was ignoring that second IRQ because the I flag was still set. That second IRQ is required to load the correct nametable swapping values into memory.
But now, my MMC3 games (notably Star Trek 25th Anniversary) don't work correctly (and they used to) because IRQs that used to be ignored are getting issued and so the screen jitters when it shouldn't.
Now - my question: How is the CPU supposed to handle IRQs when it's in the middle of handling one already? Is there a decay rate?
I've tried to read all the documentation on IRQ being low and have even looked at the source for Nestopia and FCEUX and I'm not able to discern exactly how they handle it.
Here are the relevant bits of code I'm using for IRQ handling. Note it's not cycle accurate, I'm just issuing one instruction at a time, capturing how many cycles that took and then catching up the PPU and mappers with 3x that number. Note also this is Rust code.
Code:
// Called as soon as the mappers set irq_pending
pub fn trigger_irq(&mut self) {
self.pending_irq = true;
}
// Called by the CPU clock function before fetching the next opcode if pending_irq is true
pub fn irq(&mut self) {
if self.get_flag(I) == 0 {
self.push_stackw(self.pc);
self.set_flag(B, false);
self.set_flag(U, true);
self.push_stackb(self.status);
self.set_flag(I, true);
self.pc = self.readw(IRQ_ADDR);
self.cycle_count = self.cycle_count.wrapping_add(7);
self.pending_irq = false;
}
}
pub fn trigger_irq(&mut self) {
self.pending_irq = true;
}
// Called by the CPU clock function before fetching the next opcode if pending_irq is true
pub fn irq(&mut self) {
if self.get_flag(I) == 0 {
self.push_stackw(self.pc);
self.set_flag(B, false);
self.set_flag(U, true);
self.push_stackb(self.status);
self.set_flag(I, true);
self.pc = self.readw(IRQ_ADDR);
self.cycle_count = self.cycle_count.wrapping_add(7);
self.pending_irq = false;
}
}
The one change that makes MMC3 work and breaks MMC5 is this:
Code:
// Called as soon as the mappers set irq_pending
pub fn trigger_irq(&mut self) {
if self.get_flag(I) == 0 { // Ignores IRQs while I is set
self.pending_irq = true;
}
}
pub fn trigger_irq(&mut self) {
if self.get_flag(I) == 0 { // Ignores IRQs while I is set
self.pending_irq = true;
}
}
I also did some testing inside FCEUX for both games and it seems to finish one IRQ, execute a single instruction, and then handle the next IRQ which is what I've got above for the new changes in the CPU. If the above IS how to handle IRQs, then my MMC3 IRQ timing is off and I'm not sure how to fix it.
This is my clock function for MMC3
Code:
fn clock_irq(&mut self, addr: u16) {
let next_clock = (addr >> 12) & 1;
if self.regs.last_clock == 0 && next_clock == 1 {
// Rising edge
let counter = self.regs.irq_counter;
if counter == 0 || self.regs.irq_reload {
self.regs.irq_counter = self.regs.irq_latch;
self.regs.irq_reload = false;
} else {
self.regs.irq_counter -= 1;
}
if self.regs.irq_counter == 0
&& (counter | self.mmc3_alt) == 0x01
&& self.regs.irq_enabled
{
self.irq_pending = true;
}
}
self.regs.last_clock = next_clock;
}
let next_clock = (addr >> 12) & 1;
if self.regs.last_clock == 0 && next_clock == 1 {
// Rising edge
let counter = self.regs.irq_counter;
if counter == 0 || self.regs.irq_reload {
self.regs.irq_counter = self.regs.irq_latch;
self.regs.irq_reload = false;
} else {
self.regs.irq_counter -= 1;
}
if self.regs.irq_counter == 0
&& (counter | self.mmc3_alt) == 0x01
&& self.regs.irq_enabled
{
self.irq_pending = true;
}
}
self.regs.last_clock = next_clock;
}
It gets called for every VRAM read in the PPU while getting BG and sprite data and it seems to trigger correctly at cycle 260 at irq_latch-1 scanlines
Any clarification would be great!
Edit: When running Blaargs 1-clocking MMC3 tests - The "new" implementation of allowing IRQ to be set to pending while I flag is set fails with "Counter/IRQ/A12 clocking isn't working at all Failed #2".
If I use the old implementation of ignoring IRQs when I is set, it still fails, but at least gets to "Should decrement when A12 is toggled via PPUADDR Failed #3"