devices6502 0.1.0

Helper library for cpu6502 implementing memory devices
Documentation
//! Adjacent device combinator.
//!
//! This module provides the [`Adjacent`] struct, which allows two devices of equal size to be
//! combined into a single contiguous address space. The first device occupies the lower half of
//! the address space, and the second device occupies the upper half. This is useful for building
//! composite memory maps in 6502-based systems.

use crate::size_const::*;
use crate::Device;

/// Combines two devices of equal size into a single contiguous address space.
///
/// The `Adjacent<T1, T2>` struct allows you to treat two devices as a single device, where
/// `T1` occupies the lower half of the address space and `T2` occupies the upper half.
///
/// # Type Parameters
/// - `T1`: The first device type (must implement [`Device`] and [`Default`])
/// - `T2`: The second device type (must implement [`Device`] and [`Default`])
pub struct Adjacent<T1, T2> {
    first: T1,
    second: T2,
}

impl<T1, T2> Default for Adjacent<T1, T2>
where
    T1: Device + Default,
    T2: Device + Default,
{
    /// Creates a new `Adjacent` device with both sub-devices initialized to their defaults.
    ///
    /// # Panics
    /// Panics if the total address space exceeds 64KB or if the two devices are not the same size.
    fn default() -> Self {
        assert!((Self::addr_space_size() as usize) < SIZE_64K);

        assert_eq!(T1::addr_space_size(), T2::addr_space_size());

        Self {
            first: Default::default(),
            second: Default::default(),
        }
    }
}

impl<T1, T2> Device for Adjacent<T1, T2>
where
    T1: Device + Default,
    T2: Device + Default,
{
    /// Creates a new `Adjacent` device with both sub-devices initialized from the provided data slice.
    ///
    /// The data slice is split evenly between the two devices.
    fn with_data(data: &[u8]) -> Self
    where
        Self: Sized,
    {
        let second_start = T1::addr_space_size() as usize;
        Self {
            first: Device::with_data(&data[..second_start]),
            second: Device::with_data(&data[second_start..]),
        }
    }

    /// Initializes both sub-devices with the provided data slice.
    ///
    /// The data slice is split evenly between the two devices.
    fn init_data(&mut self, data: &[u8]) {
        let second_start = T1::addr_space_size() as usize;
        self.first.init_data(&data[..second_start]);
        self.second.init_data(&data[second_start..]);
    }

    /// Copies the current contents of both sub-devices into the provided destination buffer.
    fn cache_current_read_data(&self, destination: &mut [u8]) {
        let second_start = T1::addr_space_size() as usize;
        self.first
            .cache_current_read_data(&mut destination[..second_start]);
        self.second
            .cache_current_read_data(&mut destination[second_start..]);
    }

    /// Reads a byte from the combined address space.
    ///
    /// Addresses in the lower half are mapped to the first device, and addresses in the upper half
    /// are mapped to the second device.
    fn read(&self, addr: u16) -> u8 {
        let addr = Self::wrap_addr(addr);
        if (addr as u32) < T1::addr_space_size() {
            self.first.read(addr)
        } else {
            self.second.read(addr - T1::addr_space_size() as u16)
        }
    }

    /// Writes a byte to the combined address space.
    ///
    /// Addresses in the lower half are mapped to the first device, and addresses in the upper half
    /// are mapped to the second device.
    fn write(&mut self, data: u8, addr: u16) {
        if (addr as u32) < T1::addr_space_size() {
            self.first.write(data, addr)
        } else {
            self.second.write(data, addr - T1::addr_space_size() as u16)
        }
    }

    /// Returns the total size of the combined address space in bytes.
    fn addr_space_size() -> u32
    where
        Self: Sized,
    {
        T1::addr_space_size() * 2 // assert in Default::default they have the same size
    }

    /// Returns the number of address bits needed to address the combined device.
    fn addr_bits_count() -> u8
    where
        Self: Sized,
    {
        T1::addr_bits_count() + 1
    }

    /// Returns the total size of the combined address space in bytes (dynamic version).
    fn addr_space_size_dyn(&self) -> u32 {
        Self::addr_space_size()
    }

    /// Returns the number of address bits needed to address the combined device (dynamic version).
    fn addr_bits_count_dyn(&self) -> u8 {
        Self::addr_bits_count()
    }

    /// Returns `true` if both sub-devices are connected.
    fn is_connected(&self) -> bool {
        self.first.is_connected() && self.second.is_connected()
    }
}