ull65 0.3.0

ull65 is a no_std CPU emulator for the MOS 6502 and WDC 65C02.
Documentation
use std::collections::VecDeque;
use ull::{Address, Bus, Byte, DmaRequest, DmaResult, Word};
use ull65::instruction::mos6502::Mos6502;
use ull65::processor::cpu::Cpu;
use ull65::{AccessType, ResetVectorExt};

struct TestBus {
    mem: [u8; 0x10000],
    ticks: u64,
    instr_ticks: u64,
    dma_ticks: u64,
    dma_queue: VecDeque<u8>,
}

impl Default for TestBus {
    fn default() -> Self {
        Self {
            mem: [0; 0x10000],
            ticks: 0,
            instr_ticks: 0,
            dma_ticks: 0,
            dma_queue: VecDeque::new(),
        }
    }
}

impl Bus for TestBus {
    type Access = AccessType;
    type Data = Byte;

    fn read<A>(&mut self, addr: A, _access: AccessType) -> Byte
    where
        A: Address,
    {
        Byte(self.mem[addr.as_usize()])
    }

    fn write<A, V>(&mut self, addr: A, value: V, _access: AccessType)
    where
        A: Address,
        V: Into<Self::Data>,
    {
        let byte: Byte = value.into();
        self.mem[addr.as_usize()] = byte.0;
    }

    fn on_tick(&mut self, cycles: u8) {
        self.ticks += u64::from(cycles);
    }

    fn request_dma(&mut self, request: DmaRequest) -> DmaResult {
        self.dma_queue.push_back(request.length as u8);
        DmaResult::Pending
    }

    fn poll_dma_cycle(&mut self) -> Option<u8> {
        if let Some(cycles) = self.dma_queue.pop_front() {
            self.dma_ticks += u64::from(cycles);
            Some(cycles)
        } else {
            None
        }
    }
}

#[test]
fn dma_cycles_are_accounted() {
    let mut bus = TestBus::default();
    let mut cpu: Cpu<TestBus> = Cpu::with_instruction_set::<Mos6502>();

    bus.write_block(Word(0x8000), &[0xEA, 0xEA, 0xEA], AccessType::DataWrite);
    bus.set_reset_vector(Word(0x8000));
    cpu.reset(&mut bus);

    for step in 0..3 {
        let cycles = cpu.step(&mut bus);
        bus.instr_ticks += u64::from(cycles);
        bus.on_tick(cycles);

        if step == 1 {
            let _ = bus.request_dma(DmaRequest {
                source: Word(0),
                destination: Word(0),
                length: 4,
            });
        }

        while let Some(dma_cycles) = bus.poll_dma_cycle() {
            bus.on_tick(dma_cycles);
        }
    }

    assert_eq!(bus.ticks, bus.instr_ticks + bus.dma_ticks);
    assert_eq!(bus.dma_ticks, 4);
}

#[test]
fn read_block_fetches_consecutive_bytes() {
    let mut bus = TestBus::default();
    bus.write_block(Word(0x8000), &[1, 2, 3, 4], AccessType::DataWrite);
    let mut buf = [0u8; 4];
    bus.read_block(Word(0x8000), &mut buf, AccessType::DataRead);
    assert_eq!(buf, [1, 2, 3, 4]);
}

#[test]
fn write_block_stores_consecutive_bytes() {
    let mut bus = TestBus::default();
    bus.write_block(Word(0x9000), &[0xAA, 0xBB, 0xCC], AccessType::DataWrite);
    assert_eq!(bus.read(Word(0x9000), AccessType::DataRead).0, 0xAA);
    assert_eq!(bus.read(Word(0x9001), AccessType::DataRead).0, 0xBB);
    assert_eq!(bus.read(Word(0x9002), AccessType::DataRead).0, 0xCC);
}