Skip to content

C64 CIA claude investigation #114

@floooh

Description

@floooh

...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.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions