ull65 0.2.0

ull65 is a no_std CPU emulator for the MOS 6502 and WDC 65C02.
Documentation
# ull65

`ull65` is a `no_std` CPU core for the MOS 6502 and WDC 65C02 (with plans for more).

## Core concepts

The primary goal of `ull65` is to be easy to use and extend, with a secondary focus on being portable and (eventually)
cycle-accurate. To that end, the public API is mostly focused on ergonomics for the end-user, with a few key
features that make it easy to customize the core for specific use cases:

- **Pluggable buses**
    - `ull65` leverages the [`ull::Bus`]../ull/README.md abstraction, so memory maps and peripherals live in your bus
      implementation in a consistent API across all systems.
- **Instruction-sets**
    - swap or patch opcode tables with the `InstructionSet` trait instead of duplicating the CPU core for each variant,
      making it easy to implement new CPU cores without much effort.
- **Deterministic stepping**
    - drive the CPU with `run`, `run_until`, or single-cycle `tick` calls and keep DMA/peripheral time in lockstep with
      the processor.

## Architecture overview

`ull65` is organized around a few composable building blocks:

- `Cpu<B>` is the core execution engine parameterized over a `Bus`
  implementation. It owns the registers/flags, exposes helpers like
  `with_program`, `with_reset_vector`, `run`, `run_until`, and single-cycle
  `tick`, and keeps track of elapsed cycles.
- `Bus` is a trait you implement to wire memory and peripherals. The CPU uses it
  for every instruction fetch/data access plus timing hooks:
    - `read`/`write` for memory accesses
    - `on_tick` to let the bus advance its own clocks
    - `request_dma`/`poll_dma_cycle` to model DMA bursts
- `InstructionSet` is a high-level description of a CPU flavor. Implement this
  trait to tell the core which opcode table to run, whether decimal mode is
  available, and so on.
- `InstructionTable` is a dense array of 256 `Instruction` entries. You usually
  get one by calling `Mos6502::base_table()` or `Wdc65c02s::base_table()` and
  optionally patching a few slots.
- `Instruction` is the executable payload stored in each table slot. It contains
  the cycle count and a function pointer (`fn(&mut Cpu<B>, &mut B)`) that
  performs the opcode’s work.
- `RunConfig`/`RunPredicate` are control structures for `run_until`, letting you
  stop on BRK, on predicates (e.g., “PC reached $C000”), or after a cycle limit.
- `Nibble`/`Byte`/`Word` are tiny newtypes to handle things like wrapping addition or subtraction and added conveniences
  for working with the various types of the 6502 without having to use `as` or `::from` calls everywhere.

## Error handling

The original 6502 has no concept of traps or recoverable faults because addresses wrap at
16 bits, arithmetic wraps at 8/16 bits, and undefined memory just returns
whatever the bus supplies. `ull65` mirrors that behavior by leveraging wrapped arithemtic internally, so the CPU always
continues executing unless you halt it explictly via `RunConfig` options, BRK handlers, or your own `Bus` logic.
If you need to detect error conditions (e.g., illegal opcodes, out-of-range accesses), add the checks inside your `Bus`
implementation or a patched `InstructionSet` so the behavior stays explicit.

## Basic usage

The general workflow for `ull65` is:

1. Implement a `Bus` that mirrors your target machine.
2. Choose or define an `InstructionSet`.
3. Instantiate `Cpu::<YourBus>` and load code using whichever constructor fits:
   `with_program::<YourInstructionSet>` for ad-hoc buffers, or
   `with_reset_vector::<YourInstructionSet>` when you want the ROM’s reset
   vector to run.
4. Drive it via `run`, `run_until`, or `tick`, letting the bus handle timing and optional
   DMA callbacks.

```rust
use ull::{AccessType, SimpleBus, Word};
use ull65::instruction::mos6502::Mos6502;
use ull65::processor::run::RunConfig;
use ull65::Cpu;

fn main() {
    let mut bus = SimpleBus::default();
    let program = vec![0xA9, 0x01, 0x8D, 0x00, 0x02, 0x00]; // LDA #$01; STA $0200; BRK
    let mut cpu = Cpu::with_program::<Mos6502>(&mut bus, Word(0x8000), &program, Word(0x8000));
    let summary = cpu.run_until(&mut bus, RunConfig { stop_on_brk: true, ..RunConfig::default() });
    let value: u8 = bus.read(Word(0x0200), AccessType::DataRead).into();
    println!("Summary: {summary:?}, memory[$0200]={value:02X}");
} 
```

## Customizing instruction sets

`InstructionSet` is the abstraction that tells the CPU which opcode table to
execute. Each instruction table is just an array of 256 `Instruction`s:

```rust
pub struct Instruction<B: Bus> {
    pub cycles: u8,
    pub execute: fn(&mut Cpu<B>, &mut B),
}
```

The stock tables (`Mos6502` and `Wdc65c02s`) cover the common CPU variants.
Start from whichever base table matches your target (`Mos6502::base_table()`
or `Wdc65c02s::base_table()`) and then patch it further if needed, or construct
an entirely custom ISA.

### Toggle feature flags

Many 6502 derivatives only differ by feature flags (e.g., BCD mode). Set the
associated constants on your `InstructionSet` to opt in/out without touching the
table itself:

```rust
impl InstructionSet for Ricoh2a03 {
    fn instruction_table<B: Bus + 'static>() -> InstructionTable<B> {
        Mos6502::base_table()
    }

    const SUPPORTS_DECIMAL_MODE: bool = false;
}
```

### Patch specific opcodes

When you need to replace individual opcodes you can use `with` on the instruction table to patch in a new
`Instruction`. This lets you intercept undocumented opcodes, add tracing, or emulate chips that diverge in only a
handful of instructions:

```rust
impl InstructionSet for MyCustomCpu {
    fn instruction_table<B: Bus + 'static>() -> InstructionTable<B> {
        Mos6502::base_table::<B>().with(
            0x00,
            Instruction {
                cycles: 7,
                execute: custom_brk::<B>,
            },
        )
    }
}
```

Here we keep the MOS behavior and only replace `BRK` with a custom trap handler.

## Examples

The examples directory (`crates/ull65/examples`) contains runnable snippets that
double as API demonstrations. Run them with `cargo run --example <name>`.