devices6502 0.1.0

Helper library for cpu6502 implementing memory devices
Documentation
//! Random Access Memory (RAM) device implementation.
//!
//! This module provides a generic RAM implementation that can be configured with different sizes.
//! The RAM size must be:
//! - Greater than 0 bytes
//! - A power of 2
//! - Addressable with at most 16 bits (≤ 64KB)
//!
//! # Examples
//! ```
//! use devices6502::{Ram, Device, SIZE_16K};
//!
//! // Create a 16KB RAM
//! let mut ram = Ram::<SIZE_16K>::new();
//!
//! // Write some data
//! ram.write(0x42, 0x00);
//!
//! // Read it back
//! assert_eq!(ram.read(0x00), 0x42);
//! ```

use super::device::Device;
use super::size_const::*;

/// A Random Access Memory device implementation.
///
/// `Ram<SIZE>` represents a memory device of `SIZE` bytes that supports both read and write operations.
/// The memory contents are automatically initialized to zero upon creation.
///
/// The `SIZE` parameter must be:
/// - Greater than 0
/// - A power of 2
/// - Less than or equal to 65536 (64KB)
///
/// # Type Parameters
///
/// * `SIZE` - The size of the RAM, in bytes, must be a power of 2
#[derive(Copy, Clone)]
pub struct Ram<const SIZE: usize> {
    data: [u8; SIZE],
}

impl<const SIZE: usize> Ram<SIZE> {
    /// Creates a new RAM device with all bytes initialized to zero.
    ///
    /// This is equivalent to calling `Ram::default()`.
    pub fn new() -> Self {
        Self::default()
    }

    /// Returns the number of address bits needed to address this RAM.
    ///
    /// For example, a 256-byte RAM would return 8, as it needs 8 bits to address
    /// all locations (2^8 = 256).
    pub const fn bits_count() -> u8 {
        SIZE.ilog2() as u8
    }

    /// Returns the size of this RAM in bytes.
    pub const fn size() -> u32 {
        SIZE as u32
    }

    /// Returns a reference to the RAM's contents as a slice.
    ///
    /// This can be useful for debugging or memory dumps.
    pub fn as_slice(&self) -> &[u8] {
        self.data.as_slice()
    }
}

impl<const SIZE: usize> Default for Ram<SIZE> {
    /// Creates a new RAM device with default settings (all bytes set to zero).
    ///
    /// # Panics
    ///
    /// This function will panic if:
    /// - The RAM size is 0
    /// - The RAM size is greater than 64KB
    /// - The RAM size is not a power of 2
    fn default() -> Self {
        // Shame we cannot have this as compile-time...
        assert!(Self::size() > 0, "Ram size must be at least 1 byte");
        assert!(
            (Self::size() as usize) <= SIZE_64K,
            "Ram size must be addressable with at most 16 bit"
        );
        assert_eq!(
            2usize.pow(Self::addr_bits_count() as u32),
            Self::size() as usize,
            "Ram size must be a power of 2"
        );
        Self { data: [0; SIZE] }
    }
}

impl<const SIZE: usize> Device for Ram<SIZE> {
    /// Creates a new RAM device initialized with the provided data.
    ///
    /// # Arguments
    ///
    /// * `data` - A slice containing the initial data. Must be exactly `SIZE` bytes long.
    fn with_data(data: &[u8]) -> Self {
        let mut new_ram = Self::default();
        new_ram.init_data(data);
        new_ram
    }

    /// Initializes the RAM with the provided data.
    ///
    /// # Arguments
    ///
    /// * `data` - A slice containing the data to copy. Must be exactly `SIZE` bytes long.
    fn init_data(&mut self, data: &[u8]) {
        self.data.copy_from_slice(data);
    }

    /// Copies the current RAM contents to the provided destination buffer.
    ///
    /// # Arguments
    ///
    /// * `destination` - The buffer to copy the RAM contents to. Must be exactly `SIZE` bytes long.
    fn cache_current_read_data(&self, destination: &mut [u8]) {
        destination.copy_from_slice(&self.data);
    }

    /// Reads a byte from the specified address.
    ///
    /// The address will be wrapped if it exceeds the RAM size.
    ///
    /// # Arguments
    ///
    /// * `addr` - The address to read from
    fn read(&self, addr: u16) -> u8 {
        let index = Self::wrap_addr(addr);
        self.data[index as usize]
    }

    /// Writes a byte to the specified address.
    ///
    /// The address will be wrapped if it exceeds the RAM size.
    ///
    /// # Arguments
    ///
    /// * `data` - The byte to write
    /// * `addr` - The address to write to
    fn write(&mut self, data: u8, addr: u16) {
        let index = Self::wrap_addr(addr);
        self.data[index as usize] = data;
    }

    /// Returns the total size of the RAM's address space in bytes.
    ///
    /// This is a static method that returns the same value as `size()`.
    fn addr_space_size() -> u32 {
        Ram::<SIZE>::size()
    }

    /// Returns the number of address bits needed to address this RAM.
    ///
    /// This is a static method that returns the same value as `bits_count()`.
    fn addr_bits_count() -> u8 {
        Ram::<SIZE>::bits_count()
    }

    /// Returns the total size of the RAM's address space in bytes.
    ///
    /// This is a dynamic method that returns the same value as `size()`.
    fn addr_space_size_dyn(&self) -> u32 {
        Ram::<SIZE>::size()
    }

    /// Returns the number of address bits needed to address this RAM.
    ///
    /// This is a dynamic method that returns the same value as `bits_count()`.
    fn addr_bits_count_dyn(&self) -> u8 {
        Ram::<SIZE>::bits_count()
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    extern crate alloc;
    use alloc::vec::Vec;

    #[test]
    fn createnewram_allzeros() {
        let ram = Ram::<{ SIZE_64K }>::new();

        for x in ram.data {
            assert_eq!(x, 0);
        }
    }

    #[test]
    fn createnewram_initdata_datacorrect() {
        let mut ram = Ram::<{ SIZE_64K }>::new();

        let data: Vec<_> = (0usize..SIZE_64K).map(|x| x as u8).collect();

        ram.init_data(&data[..]);

        for i in 0..u16::MAX {
            assert_eq!(ram.read(i), i as u8, "\nerroneous data at {}", i);
        }

        let mut ram = Ram::<32>::new();

        ram.init_data(&data[..32]);

        for i in 0..32 {
            assert_eq!(ram.read(i), i as u8, "\nerroneous data at {}", i);
        }
    }

    #[test]
    fn createnewram_fillwithdata_datacorrect() {
        let mut ram = Ram::<{ SIZE_64K }>::new();

        for i in 0..u16::MAX {
            ram.write(i as u8, i);
        }

        for i in 0..u16::MAX {
            assert_eq!(ram.read(i), i as u8, "\nerroneous data at {}", i);
        }

        let mut ram = Ram::<32>::new();

        for i in 0..u16::MAX {
            ram.write(i as u8, i);
        }

        for i in (u16::MAX - 32)..u16::MAX {
            assert_eq!(ram.read(i), i as u8, "\nerroneous data at {}", i);
        }
    }

    #[test]
    fn createnewsmallram_readwrite_nooutofbounds() {
        let ram = Ram::<16>::new();

        for i in 0..u16::MAX {
            let index = Ram::<16>::wrap_addr(i);
            assert_eq!(ram.data[index as usize], 0);
        }

        let ram = Ram::<32>::new();

        for i in 0..u16::MAX {
            let index = Ram::<32>::wrap_addr(i);
            assert_eq!(ram.data[index as usize], 0);
        }
    }

    #[test]
    #[should_panic(expected = "Ram size must be at least 1 byte")]
    fn createemptyram_panics() {
        let _ = Ram::<0>::new();
    }

    #[test]
    #[should_panic(expected = "Ram size must be addressable with at most 16 bit")]
    fn createtoobigram_panics() {
        let _ = Ram::<{ 2usize.pow(16) + 1 }>::new();
    }

    #[test]
    #[should_panic(expected = "Ram size must be a power of 2")]
    fn createnotpowe2ram_panics() {
        let _ = Ram::<{ SIZE_64K - 1 }>::new();
    }
}