Implements hardware interrupt support following the W65C02S datasheet:
Interrupt handling:
- Add nmi_pending() and irq_pending() to Bus trait
- NMI: edge-triggered (falling edge detection)
- IRQ: level-triggered, maskable by I flag
- service_interrupt() pushes PC/status, sets I flag, jumps to vector
Wait states:
- Replace `halted: bool` with WaitState enum (Running,
WaitingForInterrupt, WaitingForReset)
- WAI instruction waits for interrupt, resumes on IRQ or NMI
- STP instruction waits for reset
Helper methods:
- Add push_address() for pushing 16-bit addresses to stack
- Add set_word() to Bus trait for writing 16-bit values
Testing:
- Integrate Klaus2m5 interrupt test suite
- Add unit tests for IRQ, NMI, and WAI behavior
- Concurrent BRK+IRQ+NMI test disabled (hardware timing edge case)
References:
- W65C02S Datasheet, Section 3.4 (IRQB) and 3.6 (NMIB)
- Klaus2m5/6502_65C02_functional_tests
Closes#64
This adds support for illegal opcodes:
lax, dcp, isc, slo, rla, sre, rra, arr, sbx, las, usbc, jam, nops, sax, xaa, alr, anc
The illegal opcodes implementation caused README doc tests to hang:
1. First example used 0xff as program terminator
- Previously 0xff was unimplemented (returned None), stopping execution
- Now 0xff is ISC (INC+SBC) AbsoluteX, causing infinite loop
- Fix: Use 0x02 (JAM) which explicitly halts the CPU
2. Second example loads euclid.bin which uses BRK (0x00) to terminate
- BRK jumps to IRQ vector at $FFFE/$FFFF
- Uninitialized memory contains 0x00, jumping to address $0000
- This causes infinite loop executing whatever is in low memory
- Fix: Mark as no_run since it depends on external file anyway
- fix: make euclid.bin example runnable as doc test
- Replace BRK with JAM ($02) to halt CPU after illegal opcodes impl
- Fix assembly label structure (algo/algo_ were dead infinite loops)
- Simplify linker.cfg to output raw binary at $0010 (was 64KB image)
- Enable README doc test (remove no_run marker)
The old linker config produced a 64KB memory image with code at $0400,
but README loaded at $0010 causing address mismatch. New config outputs
only the code segment at the correct load address.
Co-authored-by: mlund <mlund@localhost>
This add cycle times to the emulator. I did some initial research in
docs/CYCLE_TIMINGS.md. There are a few differences between the online
documentation around cycle times. I tried to find the most accurate
cycle timings possible.
In terms of implementation, I tried a few ideas and landed on calculating the cycle times when executing the instructions. That's pretty much what other emulators also do (olcNES in C++, bugzmanov/nes_ebook in Rust, NES-rust).
Things I took into account:
- Base cycles
- Page crossing penalties
- Decimal mode (65C02)
- Address space wrapping
Fixes: #119
* Complete the WDC 65C02 instruction set implementation
This commit finishes implementing the standard Western Design Center 65C02
instruction set by adding the last missing instructions.
The main additions are the two low-power instructions WAI (Wait for Interrupt)
and STP (Stop Processor), which allow the CPU to enter power-saving modes. I
also added support for the BIT instruction with indexed addressing modes (zero
page indexed and absolute indexed), and the indexed indirect jump instruction
JMP (abs,X).
Additionally, the Cmos6502 variant now has better documentation that aims to
explain all the improvements over the original NMOS 6502.
This closes issue #120.
* Return a boolean from single_step
Fixes critical bugs in the 6502 emulator and successfully passes
the Klaus2m5 functional test suite, one of the most complete test
suites for 6502 emulators (~30 million instructions).
Along the way, we fixed stack operation bugs that were preventing proper execution:
- RTI: Removed extra pull_from_stack() and fixed PCH retrieval
- RTS: Removed extra pull_from_stack() and fixed PCH retrieval
- PLA, PLP, PLX, PLY: Fixed to use single pull_from_stack() call
- pull_from_stack: Fixed to increment SP before reading (6502 SP points
to next available slot, not last used)
The emulator now passes one of the most rigorous 6502 test suite that I know of, which provides high confidence in our emulation correctness.
* Add engaging historical documentation for 6502 variants
- Add library-level documentation telling the story of the 6502
- Explain the historical context and cost revolution ( vs Intel 8080)
- Cover famous systems that used each variant (Apple II, C64, NES, etc.)
- Explain engineering trade-offs driven by cost, patents, and innovation
- Improve Variant trait documentation with historical context
- Make the library more approachable and educational for users
* Move history to README and address PR feedback
- Add History section to README.md before Credits
- Include industrial control as a major use case (embedded systems)
- Clarify Ricoh 2A03 sound generation was in custom ASIC, not emulated
- Simplify lib.rs documentation to focus on API usage
- README already included in rustdoc via include_str macro
* Add Wozniak quote about the 6502
Brief quote from iWoz explaining why he chose the 6502: pin-compatible
with the 6800 and half the price.
* update docs
Follow-up to #109. This adds proper SBC (Subtract with Carry) implementations
for all 6502 variants - NMOS 6502, Ricoh2A03 (NES), RevisionA, and 65C02. Each
variant now has dedicated `sbc_binary` and `sbc_decimal` methods that handle the
subtle differences in flag behavior and decimal mode support.
Changes:
- Rename `AdcOutput` to `ArithmeticOutput` for use with both ADC and SBC operations
- Fix incorrect borrow detection logic in SBC implementations
- Add variant-specific SBC methods with proper documentation and processor manual references
- Refactor `Ricoh2a03::adc_binary` to call `Nmos6502::adc_binary` (DRY)
- Change `carry_set` parameter from u8 to bool
- Update all variant implementations, call sites, and tests
- Added references to official processor manuals and behavior notes for each variant.
Fix ADC instruction implementation and improve status register management
- Split ADC instruction into separate binary and decimal mode methods
- Introduce structured `AdcOutput` with comparison traits
- Add cleaner status register access with `get_flag`/`set_flag`/`unset_flag` methods
- Improve instruction comments and documentation for Status flags
- Add tests for decimal mode functionality
- Remove deprecated decimal_mode feature flag
- Clean up CI workflow and improve carry addition logic
- Add detailed 8-cycle breakdown of the reset process
- Include reference to pagetable.com analysis
- Clarify that reset simulates BRK/IRQ/NMI sequence with reads
- Document specific stack addresses and SP decrement behavior
Replace empty reset() method with proper hardware-accurate implementation.
- Decrements stack pointer 3 times (fake stack operations)
- Reads reset vector from /
- Sets program counter and interrupt disable flag
- Includes test for reset behavior
- Correct decimal mode subtraction in SBC operation
- Implement accurate overflow detection for non-decimal mode
- Fix carry flag calculation as inverse of borrow
- Add comments explaining complex bit manipulation for overflow detection
- Update tests to verify correct behavior in both decimal and non-decimal modes
This commit addresses issues with the Subtract with Carry (SBC) operation,
ensuring correct behavior in both decimal and non-decimal modes, and
improves code readability with added comments.
* start on separating 6502 variants from cpu itself
* add a single variant: the NMOS one
* get examples & tests running again
* Add the Revision A variant, one that has no ROR
* disable failing lint in build-time dependencies
* Variant with no decimal mode
* Revert "disable failing lint in build-time dependencies"
This reverts commit c87975e937.
* some doc comments
* specify the variant in unit test now the API has changed
---------
Co-authored-by: Sam M W <you@example.com>
* extract decimal logic into separate function
* squash me
* I think we should be doing unsigned arithmetic here
* squash me
* remove unused function
* update the sign checks
* cargo fmt
* get tests to compile again
* get tests passing again
* squash me
* remove pointless conversion from u8 to u8
* cargo fmt
* accumulator is now u8
* add adc test from solid65
* pass the new test
---------
Co-authored-by: Sam M W <you@example.com>