devices6502 0.1.0

Helper library for cpu6502 implementing memory devices
Documentation
  • Coverage
  • 100%
    20 out of 20 items documented1 out of 1 items with examples
  • Size
  • Source code size: 65.37 kB This is the summed size of all the files inside the crates.io package for this release.
  • Documentation size: 775.65 kB This is the summed size of all files generated by rustdoc for all configured targets
  • Ø build duration
  • this release: 3s Average build duration of successful builds.
  • all releases: 3s Average build duration of successful builds in releases after 2024-10-23.
  • Links
  • CasiussDev/devices6502
    0 0 0
  • crates.io
  • Dependencies
  • Versions
  • Owners
  • CasiussDev

devices6502

Crates.io Documentation License

A Rust library providing composable, memory-mapped device implementations for 6502-based systems. This crate enables developers to build flexible memory maps for 6502 emulators, simulators, and other retro computing projects.

Features:

  • Fully composable, type-safe memory device implementations
  • Generic RAM and ROM devices with power-of-2 sizing constraints
  • Device mirroring, hot-swappable sockets, and transparent composition
  • Zero-copy operations for efficient memory access
  • Comprehensive test coverage for all device types and edge cases
  • no_std compatible core

Quick facts:

  • All devices implement the unified Device trait for consistent interfaces
  • Memory sizes are constrained to powers of 2 and capped at 64KB per device
  • Hot-swap sockets enable dynamic device insertion/removal at runtime
  • Address space composition (Adjacent) scales devices across full 16-bit address ranges

Contents

Getting started

Device Types

  • RAM & ROM Devices: Generic, configurable memory devices with power-of-2 sizing up to 64KB
  • Memory Mirroring: Repeat devices multiple times in the address space for efficient memory mapping
  • Hot-Swappable Sockets: Dynamically connect/disconnect devices at runtime (perfect for cartridge slots)
  • Device Composition: Combine multiple devices (e.g., separate read/write paths)
  • Zero-Copy Operations: Efficient memory operations with minimal overhead
  • Type-Safe Configuration: Compile-time guarantees for memory sizes via const generics
  • Comprehensive Testing: 16+ unit tests covering edge cases and panic conditions

Quick Start

Add to your Cargo.toml:

[dependencies]

devices6502 = "0.1"

Basic Usage

Ram

Random Access Memory with read/write support.

use devices6502::{Ram, SIZE_64K, Device};

let mut ram = Ram::<SIZE_64K>::new();
ram.write(0x42, 0x0000);
assert_eq!(ram.read(0x0000), 0x42);

Constraints:

  • Size must be a power of 2
  • Maximum size: 64KB (SIZE_64K)
  • Addresses wrap within device bounds

Rom

Read-Only Memory; write operations are silently ignored.

use devices6502::{Rom, SIZE_8K, Device};

let data = vec![0xFF; SIZE_8K];
let mut rom = Rom::<SIZE_8K>::with_data(&data);

// Read succeeds
assert_eq!(rom.read(0x0000), 0xFF);

// Write is ignored
rom.write(0x00, 0x0000);
assert_eq!(rom.read(0x0000), 0xFF);

Mirror<T, N>

Repeat a device N times in the address space. Useful when a small device needs to appear multiple times in memory.

use devices6502::{Mirror, Ram, Device, SIZE_1K};

// Create a mirrored device: 1KB RAM repeated 4 times (total 4KB)
type MirroredRam = Mirror<Ram<SIZE_1K>, 4>;
let mut mirrored = MirroredRam::new();

// Writing to address 0 and 1024 affects the same underlying RAM location
mirrored.write(0x42, 0x0000);
assert_eq!(mirrored.read(0x0400), 0x42);  // Mirrored at offset 1024

Constraints:

  • N must be a power of 2 and ≥ 2
  • Total address space must fit within 16 bits

Socket<ADDR_BITS>

A hot-swappable device socket for dynamic device insertion/removal at runtime.

use devices6502::{Socket, Ram, Device, SIZE_16K};

let mut socket = Socket::<14>::new();  // 14 address bits = 16KB address space

// Create and insert a device
let ram = Box::new(Ram::<SIZE_16K>::new());
socket.connect_device(ram);

// Use the socket like any other device
socket.write(0x42, 0x0000);
assert_eq!(socket.read(0x0000), 0x42);

// Hot-swap: remove and replace device
let old_device = socket.disconnect_device();
let new_ram = Box::new(Ram::<SIZE_16K>::new());
socket.connect_device(new_ram);

Adjacent<T1, T2>

Combine two devices side-by-side in the address space.

use devices6502::{Adjacent, Ram, Rom, Device, SIZE_32K};

type MemoryMap = Adjacent<Ram<SIZE_32K>, Rom<SIZE_32K>>;
let mut memory = MemoryMap::new();

// First 32KB is RAM (0x0000-0x7FFF)
memory.write(0x42, 0x0000);
assert_eq!(memory.read(0x0000), 0x42);

// Second 32KB is ROM (0x8000-0xFFFF) - writes are ignored
memory.write(0x99, 0x8000);

ReadWrite<READ, WRITE>

Route read and write operations to different devices.

use devices6502::{ReadWrite, Ram, Rom, Device, SIZE_4K};

let rom_data = vec![0xFF; SIZE_4K];
let rom = Rom::<SIZE_4K>::with_data(&rom_data);
let mut ram = Ram::<SIZE_4K>::new();

let mut device = ReadWrite::new(rom, ram);

assert_eq!(device.read(0x0000), 0xFF);   // Reads from ROM
device.write(0x42, 0x0000);             // Writes to RAM

Common Memory Sizes

Convenient constants for standard sizes:

use devices6502::*;

let _: Ram<SIZE_1K> = Ram::new();      // 1 KB
let _: Ram<SIZE_2K> = Ram::new();      // 2 KB
let _: Ram<SIZE_4K> = Ram::new();      // 4 KB
let _: Ram<SIZE_8K> = Ram::new();      // 8 KB
let _: Ram<SIZE_16K> = Ram::new();     // 16 KB
let _: Ram<SIZE_32K> = Ram::new();     // 32 KB
let _: Ram<SIZE_64K> = Ram::new();     // 64 KB

Device Trait

All devices implement the Device trait:

pub trait Device {
    fn with_data(data: &[u8]) -> Self;
    fn init_data(&mut self, data: &[u8]);
    fn cache_current_read_data(&self, destination: &mut [u8]);
    fn read(&self, addr: u16) -> u8;
    fn write(&mut self, data: u8, addr: u16);
    fn addr_space_size() -> u32;
    fn addr_bits_count() -> u8;
}

Building a 6502 Memory Map

Composing multiple devices into a complete 64KB memory map:

use devices6502::{Adjacent, Ram, Rom, Mirror, Device, SIZE_32K, SIZE_8K};

// Lower 32KB: RAM
type LowerMemory = Ram<SIZE_32K>;

// Upper 32KB: 8KB ROM mirrored 4 times
type UpperMemory = Mirror<Rom<SIZE_8K>, 4>;

// Complete 64KB address space
type MemoryMap = Adjacent<LowerMemory, UpperMemory>;

let mut memory = MemoryMap::new();

// Initialize ROM
let rom_data = vec![0xFF; SIZE_8K];

// Use the memory
memory.write(0x42, 0x0000);  // Write to RAM
assert_eq!(memory.read(0x0000), 0x42);

memory.write(0x99, 0x8000);  // Write to ROM (ignored)

Panic Behavior

The crate will panic in the following scenarios:

  • Invalid device sizes: Creating RAM with size 0, not a power of 2, or > 64KB
  • Invalid mirror parameters: Creating Mirror with N < 2, N not power of 2, or total space > 64KB
  • Socket mismatches: Connecting a device with incompatible address bits
  • Empty socket operations: Reading/writing from an empty socket

These panics are intentional to catch configuration errors at development time.

Performance Considerations

  • Inline storage: RAM and ROM store data inline; no allocations
  • Address wrapping: Automatic wrapping for devices smaller than 16 bits
  • Generic specialization: Const generics enable compiler optimization and monomorphization
  • Zero-copy reads: Read operations don't allocate; use cache_current_read_data() for bulk operations

Testing

Run the full test suite:

cargo test

Tests cover:

  • RAM initialization and address wrapping
  • ROM write rejection
  • Device mirroring and composition
  • Socket hot-swapping
  • Memory mapping patterns

Development notes

  • Module layout

    • device: Device trait and core types
    • ram, rom: Standard memory implementations
    • mirror: Device repetition in address space
    • socket: Hot-swappable device container
    • adjacent: Side-by-side device composition
    • readwrite: Asymmetric read/write routing
    • size_const: Common memory size constants
  • Design philosophy

    • Prefer compile-time guarantees (const generics) over runtime checks
    • Use type-safe composition for flexible memory mapping
    • Panic on configuration errors to catch bugs early

License

Licensed under either of

at your option.

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.