commanderx16 / x16-emulator

Emulator for the Commander X16 8-bit computer
383 stars 60 forks source link

Replace CPU core with w65c02s.h #437

Open ziplantil opened 1 year ago

ziplantil commented 1 year ago

This replaces the fake6502-based emulator core with w65c02s.h, which should be more accurate.

ziplantil commented 1 year ago

More complex software seems to result in weird issues. For example, BASIC programs tend to print floating-point numbers and throw floating-point related errors. I'm tracking down the cause, but I have a few possible ideas:

  1. bug in w65c02s.h (not at all impossible)
  2. likely difference in when an interrupt is acknowledged. The current IRQ/NMI assert in the main emulator loop will cause the CPU to acknowledge the interrupt one instruction too late, because they are asserted after the last cycle of an instruction, but w65c02s.h latches, like the real deal, before the last cycle.
  3. the fact that in w65c02s.h the IRQ is a line held logically high/low, not just a simple function ("do an IRQ") like NMI.
mist64 commented 1 year ago

This may not be a bad idea, but I think it should be discussed with the wider community further. Have you brought this up on the Discord? Or can you ping some of the main contributors and ask them to comment here?

ziplantil commented 1 year ago

No, I haven't brought this up anywhere. I was hoping this PR could serve as the venue for discussing this change, which is fairly major and probably won't (and shouldn't) be merged on short notice.

Even after the current issues with tne new core have been ironed out (unfortunately not something I've had much time to look into this past week), I still feel there is a great opportunity here to rewrite much of the main loop. The w65c02s.h core really prefers running many instructions at once rather than single-stepping instructions (running N instructions or cycles at once is much better for performance in general), but doing so while maintaining accuracy with interrupts from other components is the tricky part.

mist64 commented 1 year ago

[...] I still feel there is a great opportunity here to rewrite much of the main loop. The w65c02s.h core really prefers running many instructions at once rather than single-stepping instructions (running N instructions or cycles at once is much better for performance in general), but doing so while maintaining accuracy with interrupts from other components is the tricky part.

Was was going to say: How about merging this code as an alternative CPU core – maybe even one that we can run in parallel to see where the differences/bugs are. But your main goal requires redesigning the interface of the main emulator to the CPU core.

Changing any of the architecture into emulating more instructions at a time is not only tricky because of interrupts, but also because of the state of other components that has to progress at the correct rate.

IIRC, VICE adopted an architecture 20 years ago or so where it was optimized to run the different components independently for as long as possible, instead of cycling through all components for every clock cycle. Here's a presentation (in German) by @fachat about this: https://www.youtube.com/watch?v=DZ6shiQ-MFQ

ziplantil commented 1 year ago

Fundamentally, the problem of keeping the other components in sync has only two difficult parts in it:

  1. Components that have externally facing effects, such as audio (the user can hear what the component is doing)
  2. interrupts.

Anything else is solved by using the read/write callbacks. In w65c02s.h, every single call to a read/write callback corresponds to a single CPU cycle. If the read/write targets MMIO, we run that peripheral for however many cycles we need to in order to catch up with the CPU. If there are interdependencies between components, this is also where that is handled (for example, we could run all peripherals on MMIO).

For user-facing devices, we simply need to cap the number of cycles. The audio device must be run every 1000 cycles, for instance.

Interrupts are tricky, because there may be an interrupt by a peripheral between MMIO accesses by the CPU. One solution is that every peripheral that raises interrupts is able to tell how many cycles can be safely run before an interrupt can occur.

If the plan is though to use w65c02s.h as an alternative core for now, it's indeed wise to postpone rewriting the timing loop.

fachat commented 1 year ago

Changing any of the architecture into emulating more instructions at a time is not only tricky because of interrupts, but also because of the state of other components that has to progress at the correct rate.

IIRC, VICE adopted an architecture 20 years ago or so where it was optimized to run the different components independently for as long as possible, instead of cycling through all components for every clock cycle. Here's a presentation (in German) by @fachat about this: https://www.youtube.com/watch?v=DZ6shiQ-MFQ

And here's the english version of the same presentation: https://youtu.be/84XfgIjFtDU

fachat commented 1 year ago

In short: As a general concept, VICE has a CPU clock tick. Each component (e.g. VIA etc) "knows" its state in its local clock tick, and can (either cycling, or even fast-forwarding) to a new clock tick when needed, e.g. forwarding to the CPU clock tick value when a timer register is being accessed. In addition, each component can register update events ("alarm" in VICE) for a specific CPU clock tick value, where a callback is called. This is e.g. used for Commodore PET video vsync interrupt every frame, or setting the interrupt flag of the CPU on e.g. VIA timer underflows. The main CPU loop can run undisturbed, and only needs to compare its clock tick value with the next upcoming alarm tick value, where it branches of the CPU execution loop to do the callback as needed.

ziplantil commented 1 year ago

I fixed the CPU issues - it was indeed a bug in w65c02s.h, which has been fixed now. The new core seems to work fine now as far as I can test it - it seems for some reason after the recent PR pulls, I can no longer use the keyboard on a Linux build (even on a build on the master branch - but pasting text appears to work fine).

mist64 commented 1 year ago

it seems for some reason after the recent PR pulls, I can no longer use the keyboard on a Linux build (even on a build on the master branch - but pasting text appears to work fine).

Update the ROM.

ziplantil commented 1 year ago

I'm converting this to draft for now - I'll continue working on w65c02s.h a bit more before I feel this will be ready to be merged.

indigodarkwolf commented 1 year ago

Well, I was late to the party and went to the diffs before reading the full conversation here, so my comments cover similar ground to that of @mist64.