...just dumping this here for later (incomplete, had to stop because claude got stuck in the analysis phase)
Based on my reading of the full implementation, here are the cycle-correctness issues I found:
1. Timer underflow detection is off by one cycle
In _m6526_tick_timer() (line ~456), the underflow check tests counter == 0 after decrementing, but checks PIP_TIMER_COUNT at position 1 (the previous cycle's count-enable). On real hardware, the underflow happens when the counter transitions from 1→0 with counting active this cycle, not the previous one. The pipeline position check should be at position 0:
// current:
t->t_out = (0 == t->counter) && _M6526_PIP_TEST(t->pip, M6526_PIP_TIMER_COUNT, 1);
// should likely be position 0, since the counter was just decremented based on position 0
2. Timer B cascade mode doesn't go through the count pipeline
In _m6526_tick_pipeline() (~line 519), when Timer B is in "count Timer A underflows" mode, ta.t_out directly sets the pipeline bit at position 2. But on real hardware, the Timer A underflow signal that feeds Timer B already has a 1-cycle delay built into the pipeline. Setting it at position 2 introduces an extra cycle of latency compared to PHI2 mode, making Timer B cascade counting 1 cycle late.
3. IMR update has wrong delay ordering
In _m6526_tick_pipeline() (~line 556), imr is updated from imr1 after the IRQ pipeline check if (c->intr.icr & c->intr.imr). This means the interrupt-pending check uses the old imr value. However, in _m6526_write_icr(), newly written IMR bits go into imr1 AND immediately trigger the IRQ pipeline if they match ICR. The combination means that on the cycle of the write, the IRQ is triggered twice through two different paths — once immediately in _write_icr and once through the pipeline check with the stale imr. This can cause the IRQ to fire one cycle too early in some edge cases.
4. _m6526_update_irq checks t_out before the pipeline shifts
The IRQ update function sets icr bits based on t_out, and then _m6526_tick_pipeline checks icr & imr to feed the IRQ delay pipeline. But the pipeline shift happens at the end of _tick_pipeline. This means the IRQ for a timer underflow appears on the pin one cycle after t_out + one pipeline cycle = 2 cycles total delay, whereas the real CIA has a 1-cycle delay from underflow to IRQ assertion.
5. NMI test failure
The test results in c64.h confirm nmi: FAIL (1 error at 00/5), and the CIA-2 (NMI) timer tests show "some red", both consistent with the Timer B cascade and IRQ timing issues above.
6. Reading ICR during the same cycle as a timer B underflow
The "Timer B Bug" implementation uses _M6526_PIP_TEST(c->intr.pip, M6526_PIP_READ_ICR, 0) but the read-ICR flag is set at pipeline position 0 and tested at position 0 in the same tick — it should be tested at position 0 after the pipeline has shifted, meaning the suppression is effective one cycle too early.
The most impactful issues are #1 (underflow detection pipeline position) and #4 (IRQ latency off by one). These would explain the failing nmi test and the CIA-2 timer test failures.
...just dumping this here for later (incomplete, had to stop because claude got stuck in the analysis phase)
Based on my reading of the full implementation, here are the cycle-correctness issues I found:
1. Timer underflow detection is off by one cycle
In
_m6526_tick_timer()(line ~456), the underflow check testscounter == 0after decrementing, but checksPIP_TIMER_COUNTat position 1 (the previous cycle's count-enable). On real hardware, the underflow happens when the counter transitions from 1→0 with counting active this cycle, not the previous one. The pipeline position check should be at position 0:2. Timer B cascade mode doesn't go through the count pipeline
In
_m6526_tick_pipeline()(~line 519), when Timer B is in "count Timer A underflows" mode,ta.t_outdirectly sets the pipeline bit at position 2. But on real hardware, the Timer A underflow signal that feeds Timer B already has a 1-cycle delay built into the pipeline. Setting it at position 2 introduces an extra cycle of latency compared to PHI2 mode, making Timer B cascade counting 1 cycle late.3. IMR update has wrong delay ordering
In
_m6526_tick_pipeline()(~line 556),imris updated fromimr1after the IRQ pipeline checkif (c->intr.icr & c->intr.imr). This means the interrupt-pending check uses the oldimrvalue. However, in_m6526_write_icr(), newly written IMR bits go intoimr1AND immediately trigger the IRQ pipeline if they match ICR. The combination means that on the cycle of the write, the IRQ is triggered twice through two different paths — once immediately in_write_icrand once through the pipeline check with the staleimr. This can cause the IRQ to fire one cycle too early in some edge cases.4.
_m6526_update_irqcheckst_outbefore the pipeline shiftsThe IRQ update function sets
icrbits based ont_out, and then_m6526_tick_pipelinechecksicr & imrto feed the IRQ delay pipeline. But the pipeline shift happens at the end of_tick_pipeline. This means the IRQ for a timer underflow appears on the pin one cycle aftert_out+ one pipeline cycle = 2 cycles total delay, whereas the real CIA has a 1-cycle delay from underflow to IRQ assertion.5. NMI test failure
The test results in c64.h confirm
nmi: FAIL (1 error at 00/5), and the CIA-2 (NMI) timer tests show "some red", both consistent with the Timer B cascade and IRQ timing issues above.6. Reading ICR during the same cycle as a timer B underflow
The "Timer B Bug" implementation uses
_M6526_PIP_TEST(c->intr.pip, M6526_PIP_READ_ICR, 0)but the read-ICR flag is set at pipeline position 0 and tested at position 0 in the same tick — it should be tested at position 0 after the pipeline has shifted, meaning the suppression is effective one cycle too early.The most impactful issues are #1 (underflow detection pipeline position) and #4 (IRQ latency off by one). These would explain the failing
nmitest and the CIA-2 timer test failures.