rusty_emu/plans/vic20_architecture.md
Jan-Peter von Hunnius 80ef0d4475 docs: clean up completed timing plans, add VIC-20 architecture plan
Remove 6 completed timing architecture phase plans (5-9 + overview).
Add comprehensive VIC-20 architecture plan covering:
- 6502 CPU family trait hierarchy (MOS 6502, WDC 65C02)
- Generic SRAM/ROM memory components
- 6560/6561 VIC video chip design
- 6522 VIA I/O chip design
- Address bus architecture for memory-mapped I/O
- Expansion port design
- GUI adaptations for multi-system support
- 6-phase implementation roadmap
2026-05-14 21:21:46 +02:00

36 KiB
Raw Permalink Blame History

VIC-20 Architecture Plan

Overview and Goals

This document describes the architecture for adding Commodore VIC-20 support to the Rusty Emulator. The VIC-20 is a significantly more complex system than the Intel MCS-4, featuring a 16-bit address bus, memory-mapped I/O, a video display generator, and multiple support chips. This plan covers the full system from 6502 CPU implementation through to a bootable VIC-20 with video output.

Goals

  1. Reusable 6502 CPU family — A trait-based hierarchy supporting MOS 6502, WDC 65C02, and future variants with shared instruction decoding
  2. Generic memory components — Reusable SRAM/ROM chips usable across any 8-bit system
  3. Accurate VIC chip — Video, sound, and I/O from the 6560/6561 with framebuffer output
  4. 6522 VIA — Full implementation of timers, I/O ports, and shift register
  5. Bus architecture — Memory-mapped address decoding that replaces the MCS-4 pin-level bus
  6. System integration — JSON config, GUI display widget, keyboard input

Existing Infrastructure

The emulator already provides:


System Architecture

High-Level Block Diagram

                    +----------------------------------------------------------+
                    |                    VIC-20 System Bus                      |
                    |              16-bit Address / 8-bit Data                  |
                    +----------------------------------------------------------+
                         |          |          |          |          |
                    +----+    +-----+    +-----+    +-----+    +-----+---------+
                    |         |         |         |         |                   |
               +----+---+ +--+--+  +---+--+  +---+--+  +---+--+  +------------+
               | MOS    | | 6560|  | 6522 |  | 6522 |  | SRAM |  | Expansion  |
               | 6502   | | VIC |  | VIA 1|  | VIA 2|  | 5KB  |  | Port       |
               | CPU    | |     |  |      |  |      |  |      |  |            |
               +--------+ +--+--+  +--+---+  +--+---+  +------+  +------------+
                          |          |           |                    |
                     +----+---+    +-+------+   +-+------+
                     | Video  |    |Keyboard|   |Cassette|
                     | Output |    | Scan   |   |Serial  |
                     +--------+    +--------+   +--------+

VIC-20 Memory Map

  $0000 ────────────┐
                    │ Zero Page, Stack
  $0100             │ (CPU internal use)
                    │
  $0200 ────────────┤
                    │ 1KB SRAM (main RAM)
  $0400             │ (screen character memory)
                    │
  $1000 ────────────┤
                    │ 4KB Expansion RAM/ROM
  $2000             │ (cartridge area)
                    │
  $2000 ────────────┤
                    │ 8KB Expansion ROM
  $4000             │ (cartridge area)
                    │
  $4000 ────────────┤
                    │ 8KB Expansion ROM
  $6000             │ (cartridge area)
                    │
  $6000 ────────────┤
                    │ 8KB Expansion ROM
  $8000             │ (cartridge area)
                    │
  $8000 ────────────┤
                    │ 8KB Character ROM
  $A000             │ (visible to VIC chip)
                    │
  $8000 ────────────┤
                    │ 8KB BASIC ROM
  $A000             │
                    │
  $A000 ────────────┤
                    │ I/O Block
                    │   $9000-$93FF: VIC
                    │   $9400-$97FF: VIA 1
                    │   $9800-$9BFF: VIA 2
                    │   $9C00-$9FFF: Color RAM + I/O
  $A000             │
                    │
  $C000 ────────────┤
                    │ 4KB RAM (expansion)
  $D000             │
                    │
  $E000 ────────────┤
                    │ 8KB Kernel ROM
  $FFFC             │ Reset vector: $FCE2
  $FFFF ────────────┘

Component Communication Model

graph TD
    CPU[MOS 6502 CPU]
    BUS[Address Decoder / Bus]
    VIC[6560 VIC Chip]
    VIA1[6522 VIA 1]
    VIA2[6522 VIA 2]
    SRAM[2114 SRAM x5]
    ROM_B[2364 BASIC ROM]
    ROM_K[2364 Kernel ROM]
    ROM_C[2332 Character ROM]
    EXP[Expansion Port]
    FB[Framebuffer]
    SND[Audio Output]
    KBD[Keyboard Matrix]

    CPU -->|address + RW + data| BUS
    BUS -->|chip select + address + data| SRAM
    BUS -->|chip select + address + data| ROM_B
    BUS -->|chip select + address + data| ROM_K
    BUS -->|chip select + address + data| ROM_C
    BUS -->|chip select + address + data| VIC
    BUS -->|chip select + address + data| VIA1
    BUS -->|chip select + address + data| VIA2
    BUS -->|chip select + address + data| EXP
    VIC -->|pixel data| FB
    VIC -->|waveform| SND
    VIA1 -->|row scan + column read| KBD

Component-by-Component Design

1. 6502 CPU Family Architecture

1.1 Trait Hierarchy

The current MOS6502 is a monolithic struct. For the VIC-20 and future systems, we need a trait hierarchy that separates instruction decoding from CPU state and allows variant-specific behavior.

/// Core behavior that all 6502-family CPUs share
pub trait Cpu6502Family: Component {
    /// Read a byte from the bus at the given address
    fn bus_read(&mut self, addr: u16) -> u8;
    /// Write a byte to the bus at the given address
    fn bus_write(&mut self, addr: u8, data: u8);
    /// Execute one machine cycle. Returns number of cycles consumed.
    fn step(&mut self) -> u8;
    /// Signal an IRQ (maskable interrupt)
    fn signal_irq(&mut self);
    /// Signal an NMI (non-maskable interrupt)
    fn signal_nmi(&mut self);
    /// Assert the reset line
    fn signal_reset(&mut self);
    /// Get the program counter
    fn pc(&self) -> u16;
    /// Get all registers for debugging
    fn registers(&self) -> Cpu6502Registers;
}

/// Variant-specific behavior for instruction differences
pub trait Cpu6502Variant {
    /// Execute an illegal/undocumented opcode
    fn execute_undefined(&self, opcode: u8, cpu: &mut dyn Cpu6502Family) -> u8;
    /// Whether this variant has the JMP indirect bug
    fn has_jmp_indirect_bug(&self) -> bool;
    /// Additional opcodes specific to this variant
    fn decode_extra(&self, opcode: u8) -> Option<Instruction>;
}

1.2 Instruction Decoder

The 6502 has 151 official opcodes (256 entries in the decode table). The decoder should be a shared module:

src/components/cpu/mos_6502/
├── mod.rs           — MOS6502 struct, Component impl
├── decoder.rs       — Opcode decode table and instruction enum
├── addressing.rs    — Addressing mode handlers
├── instructions.rs  — Instruction execution (ALU ops, branches, stack)
├── interrupts.rs    — IRQ, NMI, RESET handling
└── state.rs         — CPU state machine (fetch-decode-execute)

1.3 Key Differences from MCS-4

Aspect MCS-4 (4004) 6502 Family
Address bus 4-bit multiplexed 16-bit parallel
Data bus 4-bit 8-bit
I/O model Separate I/O pins Memory-mapped I/O
Clock phases 2-phase external Single-phase, internal
Interrupts None IRQ, NMI, RESET
Instruction cycle Fixed timing Variable (1-7 cycles)

1.4 Bus Interface

The 6502 communicates with the rest of the system via a bus trait rather than individual pin connections. This is fundamentally different from the MCS-4 model:

/// Trait for a memory-mapped bus that the CPU can read/write
pub trait AddressBus {
    /// Read a byte from the given address
    fn read(&self, addr: u16) -> u8;
    /// Write a byte to the given address
    fn write(&mut self, addr: u16, data: u8);
    /// Check if a device is asserting the bus (for DMA detection)
    fn is_bus_asserted(&self) -> bool;
}

The CPU holds a reference to an AddressBus implementor. The bus routes reads/writes to the correct component based on address decoding logic. This replaces the pin-per-bit approach used for the MCS-4's 4-bit bus.

1.5 WDC 65C02 Differences

The WDC65C02 currently wraps MOS6502 directly. In the new architecture, both should implement Cpu6502Family and share the same decoder. The 65C02 adds:

  • New instructions: STZ, BRA, PHX/PHY, PLX/PLY, TRB/TSB
  • New addressing modes: indirect without indexing, relative for BRA
  • Fixes: JMP indirect page-boundary bug, no illegal opcodes
  • Wait/Stop modes: WAI and STP instructions

2. Memory Chips (SRAM/ROM)

2.1 Generic Memory Component

Create reusable memory components in a new module structure:

src/components/memory/
├── mod.rs              — Re-exports
├── intel_4001.rs       — (existing)
├── intel_4002.rs       — (existing)
├── intel_4003.rs       — (existing)
└── generic/
    ├── mod.rs          — Re-exports
    ├── sram.rs         — Generic SRAM
    ├── rom.rs          — Generic ROM
    └── sram_2114.rs    — 2114-specific 1Kx4-bit variant

2.2 Generic SRAM

/// Generic static RAM with configurable size and bus width
pub struct GenericSram {
    base: BaseComponent,
    data: Vec<u8>,
    size: usize,          // Size in bytes
    data_width: u8,       // 4 or 8 bits
    address_mask: u16,
    // Bus interface
    bus: Option<Arc<Mutex<dyn AddressBus>>>,
}

2.3 2114 SRAM (1K×4-bit)

The VIC-20 uses five 2114 chips. Each chip provides 1K×4 bits. Two chips are paired to form 1K×8 bits. The 2114 has:

  • 10 address inputs (A0-A9)
  • 4 data I/O lines (I/O1-I/O4)
  • Chip Select (CS)
  • Write Enable (WE)

For emulation purposes, we can model pairs of 2114s as a single 1K×8 block since the VIC-20 always pairs them.

2.4 Generic ROM

/// Generic ROM with configurable size
pub struct GenericRom {
    base: BaseComponent,
    data: Vec<u8>,
    size: usize,
    address_mask: u16,
    // Pin interface for chip select
    loaded: bool,
}

ROM files should be loadable from binary files via the JSON config properties.rom_file field.

2.5 VIC-20 ROM Set

ROM Chip Size Address Range File
BASIC 2364 8KB $8000-$9FFF basic.bin
Kernel 2364 8KB $E000-$FFFF kernel.bin
Character 2332 4KB $8000-$8FFF (VIC view) chargen.bin

3. 6560/6561 VIC Chip

The VIC chip is the most complex component. It generates video, sound, handles DRAM refresh, and reads joysticks.

3.1 Module Structure

src/components/video/
├── mod.rs           — Re-exports
├── mos_6560.rs      — VIC chip implementation
├── framebuffer.rs   — Framebuffer abstraction
└── character_rom.rs — Character generator

3.2 VIC Registers (16 registers, $9000-$9013)

Register Address Function
$9000 Interlace / Horizontal Center 4 bits
$9001 Horizontal Center 8 bits
$9002 Vertical Center / Video Mode 8 bits
$9003 Number of Columns 7 bits
$9004 Raster Line (read-only) 8 bits
$9005 Screen Memory / Character Memory 8 bits
$9006 Light Pen X 8 bits
$9007 Light Pen Y 8 bits
$9008 Paddle X 8 bits
$9009 Paddle Y 8 bits
$900A Sound / Bass Frequency 8 bits
$900B Sound / Alto Frequency 8 bits
$900C Sound / Soprano Frequency 8 bits
$900D Sound / Noise Frequency 8 bits
$900E Sound / Volume / Auxiliary Color 8 bits
$900F Background Color / Inverted Mode 8 bits

3.3 Video Generation

The VIC generates a 22-column × 23-row character display. Each character is 8×8 pixels. The output resolution is approximately 176×184 pixels (NTSC) or 176×200 pixels (PAL).

/// Framebuffer for VIC video output
pub struct Framebuffer {
    /// Pixel data: 176 x 184 (NTSC) or 176 x 200 (PAL)
    pixels: Vec<u8>,  // Index into color palette
    width: usize,
    height: usize,
    /// 16-color VIC palette (RGB)
    palette: [(u8, u8, u8); 16],
}

The VIC reads character data from screen memory (default $1E00-$1FFF) and character bitmaps from character ROM. For each scanline, it:

  1. Reads the character code from screen memory
  2. Reads the 8-byte bitmap from character ROM
  3. Applies foreground/background colors
  4. Outputs one scanline of pixels to the framebuffer

The framebuffer is shared with the GUI via Arc<Mutex<Framebuffer>>.

3.4 Sound Generation

The VIC has 4 sound channels:

  • Bass: 8-bit frequency divisor
  • Alto: 8-bit frequency divisor
  • Soprano: 8-bit frequency divisor
  • Noise: 8-bit frequency divisor

Each channel generates a square wave at the frequency determined by a clock divisor. The output is mixed and controlled by the 4-bit volume register.

For initial implementation, sound can be stubbed with the register interface. Audio output integration with the GUI can come in a later phase.

3.5 Timing

The VIC runs on the CPU clock (~1.02 MHz NTSC, ~1.11 MHz PAL). It generates one scanline per 71 CPU cycles (NTSC). A full frame is 262 scanlines (NTSC). The VIC steals cycles from the CPU for DRAM refresh and character fetches.

3.6 Component Interface

pub struct Mos6560 {
    base: BaseComponent,
    registers: [u8; 16],
    framebuffer: Arc<Mutex<Framebuffer>>,
    screen_memory: Arc<Mutex<dyn AddressBus>>,
    char_memory: Arc<Mutex<dyn AddressBus>>,
    // Timing
    cycle_counter: u64,
    raster_line: u8,
    // Sound state
    sound_channels: [SoundChannel; 4],
    volume: u8,
    variant: VicVariant, // NTSC (6560) or PAL (6561)
}

enum VicVariant {
    Ntsc, // 6560 - ~1.02 MHz
    Pal,  // 6561 - ~1.11 MHz
}

4. 6522 VIA (Versatile Interface Adapter)

The VIC-20 has two 6522 VIAs. VIA 1 handles keyboard scanning and cassette. VIA 2 handles the serial bus (IEEE-488), user port, and joystick.

4.1 Module Structure

src/components/io/
├── mod.rs           — Re-exports
├── mos_6522.rs      — VIA implementation
└── keyboard.rs      — VIC-20 keyboard matrix

4.2 VIA Registers (16 registers)

Register Name Function
$0 ORB/IRB Port B Data
$1 ORA/IRA Port A Data
$2 DDRB Port B Direction
$3 DDRA Port A Direction
$4 T1CL Timer 1 Counter Low
$5 T1CH Timer 1 Counter High
$6 T1LL Timer 1 Latch Low
$7 T1LH Timer 1 Latch High
$8 T2CL Timer 2 Counter Low
$9 T2CH Timer 2 Counter High
$A SR Shift Register
$B ACR Auxiliary Control Register
$C PCR Peripheral Control Register
$D IFR Interrupt Flag Register
$E IER Interrupt Enable Register
$F ORA/IRA Port A (no handshake)

4.3 Key Features

  • Port A and Port B: 8-bit bidirectional I/O with programmable direction (DDRA/DDRB)
  • Timer 1: 16-bit with free-run or one-shot mode, generates interrupts
  • Timer 2: 16-bit one-shot or pulse counting mode
  • Shift Register: Serial I/O under timer control
  • Handshake Lines: CA1, CA2, CB1, CB2 for control signals
  • IRQ Output: Can interrupt the CPU on timer expiry or port events

4.4 VIA 1 — Keyboard and Cassette

  • Port A: Keyboard column output (write)
  • Port B: Keyboard row input (read)
  • CB1: Cassette read
  • CB2: Cassette write
  • Timer 1: Cassette timing

4.5 VIA 2 — Serial Bus and User Port

  • Port A: Serial bus data (SRQ, ATN, CLK, DATA)
  • Port B: User port
  • CA1: Serial bus SRQ in
  • CB2: Serial bus data out

4.6 Keyboard Matrix

The VIC-20 keyboard is an 8×8 matrix scanned by VIA 1:

        PA0  PA1  PA2  PA3  PA4  PA5  PA6  PA7
  PB0   1    Q    C    SP   3    E    F    R
  PB1   2    W    S    Z    4    D    G    T
  PB2   =    +    ;    L    .    :    @    O
  PB3   3    -    ,    P    /    M    *    ?
  PB4   5    0    N    >    2    9    B    I
  PB5   6    ^    V    <    1    8    X    U
  PB6   7    _    H    [    `    ;    C    ]
  PB7   HOME DEL  /    RET  ↑    ←    CLR  →

The keyboard module maps host keyboard events to matrix row/column assertions.


5. Address Decoding and Bus Architecture

5.1 The Bus Component

Unlike the MCS-4 where every pin is individually connected, the VIC-20 uses a shared bus with address decoding. A new Bus component routes CPU reads/writes to the correct chip:

src/bus/
├── mod.rs           — Re-exports
├── address_bus.rs   — AddressBus trait
├── address_decoder.rs — VIC-20 specific decoder
└── system_bus.rs    — Shared bus implementation

5.2 Address Decoder

/// Routes CPU bus cycles to the correct component
pub struct Vic20AddressDecoder {
    /// Addressable devices mapped to address ranges
    devices: Vec<AddressRange>,
    /// Open bus value (returned when no device responds)
    open_bus: u8,
}

struct AddressRange {
    start: u16,
    end: u16,
    mask: u16,     // Address bits to pass to device
    device: Arc<Mutex<dyn BusDevice>>,
}

pub trait BusDevice: Send + Sync {
    /// Read from this device at the given offset
    fn read(&self, offset: u16) -> u8;
    /// Write to this device at the given offset
    fn write(&mut self, offset: u16, data: u8);
    /// Get the device name
    fn device_name(&self) -> String;
}

5.3 BusDevice Trait

The BusDevice trait is simpler than the full Component trait. Components that sit on the bus (VIC, VIA, SRAM, ROM) implement both Component (for the threading model) and BusDevice (for bus transactions).

/// A component that can respond to bus reads/writes
pub trait BusDevice: Send + Sync {
    fn read(&self, offset: u16) -> u8;
    fn write(&mut self, offset: u16, data: u8);
    fn device_name(&self) -> String;
}

5.4 VIC-20 Address Map Implementation

impl Vic20AddressDecoder {
    pub fn new() -> Self {
        // Build the address map:
        // $0000-$00FF: Zero Page (SRAM)
        // $0100-$01FF: Stack (SRAM)
        // $0200-$03FF: System variables + screen (SRAM)
        // $1000-$1FFF: Main SRAM (screen memory lives here)
        // $2000-$3FFF: Expansion block 1
        // $4000-$5FFF: Expansion block 2
        // $6000-$7FFF: Expansion block 3
        // $8000-$8FFF: Character ROM (VIC view)
        // $8000-$9FFF: BASIC ROM (CPU view)
        // $9000-$93FF: VIC registers
        // $9400-$97FF: VIA 1 registers
        // $9800-$9BFF: VIA 2 registers
        // $9C00-$9FFF: Color RAM + I/O
        // $C000-$CFFF: Expansion RAM
        // $E000-$FFFF: Kernel ROM
    }
}

5.5 Relationship to Existing Pin Model

The bus architecture coexists with the existing pin model. The CPU uses the AddressBus trait for memory-mapped access, but control signals (IRQ, NMI, RESET, RDY) still use pins. This hybrid approach allows:

  • Fast bus transactions (no per-bit pin manipulation)
  • Accurate control signal timing via pins
  • DMA devices can assert the bus via the is_bus_asserted() method

6. Expansion Port

6.1 Design

The expansion port exposes the full CPU bus plus chip select decoding:

/// Models the VIC-20 cartridge expansion port
pub struct ExpansionPort {
    base: BaseComponent,
    // Connected ROM/RAM devices
    blocks: [Option<Arc<Mutex<dyn BusDevice>>>; 4], // Blocks 1-4
    // I/O mapping
    io_device: Option<Arc<Mutex<dyn BusDevice>>>,
    // DMA state
    dma_active: bool,
    bus_requested: bool,
}

6.2 Cartridge Model

A cartridge is a JSON config that maps ROM/RAM into the expansion blocks:

{
  "name": "VIC-20 Cartridge",
  "blocks": [
    { "address": "$2000", "size": 8192, "rom_file": "cart_a.bin" },
    { "address": "$6000", "size": 8192, "rom_file": "cart_b.bin" }
  ]
}

Initial implementation will focus on static ROM cartridges. Bank switching and DMA can be added later.


7. System Configuration

7.1 JSON Config Format

The existing SystemConfig format needs extensions for bus-based systems:

{
  "name": "VIC20",
  "description": "Commodore VIC-20",
  "version": "1.0",
  "metadata": {
    "architecture": "vic20",
    "cpu_speed": 1022700,
    "region": "NTSC"
  },
  "components": {
    "CPU": {
      "single": {
        "component_type": "mos_6502",
        "name": "MOS6502",
        "properties": { "clock_speed": 1022700 }
      }
    },
    "VIC": {
      "single": {
        "component_type": "mos_6560",
        "name": "VIC",
        "properties": { "variant": "NTSC" }
      }
    },
    "VIA1": {
      "single": {
        "component_type": "mos_6522",
        "name": "VIA1",
        "properties": { "role": "keyboard_cassette" }
      }
    },
    "VIA2": {
      "single": {
        "component_type": "mos_6522",
        "name": "VIA2",
        "properties": { "role": "serial_userport" }
      }
    },
    "BASIC_ROM": {
      "single": {
        "component_type": "rom_2364",
        "name": "BASIC_ROM",
        "properties": {
          "size": 8192,
          "rom_file": "roms/vic20/basic.bin",
          "address_base": "0x8000"
        }
      }
    },
    "KERNEL_ROM": {
      "single": {
        "component_type": "rom_2364",
        "name": "KERNEL_ROM",
        "properties": {
          "size": 8192,
          "rom_file": "roms/vic20/kernel.bin",
          "address_base": "0xE000"
        }
      }
    },
    "CHAR_ROM": {
      "single": {
        "component_type": "rom_2332",
        "name": "CHAR_ROM",
        "properties": {
          "size": 4096,
          "rom_file": "roms/vic20/chargen.bin",
          "address_base": "0x8000"
        }
      }
    },
    "SRAM": {
      "single": {
        "component_type": "sram_5kb",
        "name": "MAIN_SRAM",
        "properties": {
          "size": 5120,
          "address_base": "0x0000"
        }
      }
    }
  },
  "bus": {
    "type": "memory_mapped",
    "decoder": "vic20",
    "devices": [
      { "component": "MAIN_SRAM", "ranges": ["0x0000-0x03FF", "0x1000-0x1FFF"] },
      { "component": "VIC", "ranges": ["0x9000-0x93FF"] },
      { "component": "VIA1", "ranges": ["0x9400-0x97FF"] },
      { "component": "VIA2", "ranges": ["0x9800-0x9BFF"] },
      { "component": "BASIC_ROM", "ranges": ["0x8000-0x9FFF"] },
      { "component": "KERNEL_ROM", "ranges": ["0xE000-0xFFFF"] },
      { "component": "CHAR_ROM", "ranges": ["0x8000-0x8FFF"], "bus_master": "VIC" }
    ]
  },
  "connections": {
    "cpu_irq": {
      "connection_type": "interrupt",
      "sources": [
        {"component": "VIC", "pin": "IRQ"},
        {"component": "VIA1", "pin": "IRQ"},
        {"component": "VIA2", "pin": "IRQ"}
      ],
      "target": {"component": "CPU", "pin": "IRQ"}
    },
    "cpu_nmi": {
      "connection_type": "interrupt",
      "source": {"component": "VIA2", "pin": "IRQ"},
      "target": {"component": "CPU", "pin": "NMI"}
    }
  }
}

7.2 ConfigurableSystem Adaptations

The existing ConfigurableSystem needs these changes:

  1. Bus support: Add a bus field to SystemConfig for memory-mapped bus configuration
  2. Bus wiring: After creating components, wire them to the shared bus
  3. CPU injection: Give the CPU an Arc reference to the bus for read/write operations
  4. Component registry: Register new component types (mos_6502, mos_6560, mos_6522, sram, rom)
  5. ROM loading: Support loading binary ROM files from the properties.rom_file path

8. GUI Adaptations

8.1 New GUI Components

src/gui/
├── mod.rs           — (existing)
├── components.rs    — (existing) MCS-4 widgets
├── console.rs       — (existing)
├── state.rs         — (existing)
├── display.rs       — NEW: VIC video display widget
├── keyboard.rs      — NEW: Keyboard input mapper
└── system_selector.rs — NEW: System type selection

8.2 Display Widget

The display widget renders the VIC framebuffer to an egui texture:

/// Displays the VIC-20 video output
pub struct VicDisplay {
    framebuffer: Option<Arc<Mutex<Framebuffer>>>,
    texture: Option<egui::TextureHandle>,
    scale: f32,  // Display scaling factor
}

The widget polls the framebuffer each frame and uploads it as an egui texture. NTSC output is 176×184 pixels, so it needs scaling to be visible.

8.3 Keyboard Mapping

Map host keyboard events to VIC-20 keyboard matrix positions:

/// Maps host keyboard input to VIC-20 keyboard matrix
pub struct VicKeyboardMapper {
    /// Current state of the 8x8 matrix
    matrix: [[bool; 8]; 8],
    /// Host key -> VIC matrix position mapping
    key_map: HashMap<egui::Key, (usize, usize)>,
}

8.4 System Selector

The GUI needs a way to select which system to load at startup. A simple dropdown or dialog that lists available JSON configs:

  • configs/mcs4_basic.json — Intel MCS-4
  • configs/mcs4_max.json — Intel MCS-4 (full)
  • configs/vic20.json — Commodore VIC-20

The GuiApp struct would load the appropriate config based on selection.

8.5 System-Specific Control Panels

Different systems need different GUI layouts:

  • MCS-4: Current layout (4-bit registers, nibble memory view)
  • VIC-20: 8-bit registers, 16-bit address space, video display, keyboard status

The GUI should detect the system architecture from the config metadata and render the appropriate panel.


File Structure

New Files to Create

src/
├── bus/
│   ├── mod.rs                  — Bus module re-exports
│   ├── address_bus.rs          — AddressBus trait + BusDevice trait
│   ├── address_decoder.rs      — Generic address decoder
│   └── system_bus.rs           — Shared bus with R/W routing
│
├── components/
│   ├── cpu/
│   │   ├── mos_6502.rs         — REWRITE: Full 6502 with instruction decoder
│   │   ├── mos_6502/           — NEW: 6502 instruction decoder modules
│   │   │   ├── mod.rs          — Sub-module re-exports
│   │   │   ├── decoder.rs      — Opcode decode table
│   │   │   ├── addressing.rs   — Addressing mode handlers
│   │   │   ├── instructions.rs — Instruction execution
│   │   │   ├── interrupts.rs   — IRQ/NMI/RESET handling
│   │   │   └── state.rs        — CPU state machine
│   │   └── wdc_65c02.rs        — REWRITE: 65C02 using shared decoder
│   │
│   ├── memory/
│   │   ├── generic/            — NEW: Generic memory components
│   │   │   ├── mod.rs
│   │   │   ├── sram.rs         — Generic SRAM
│   │   │   ├── rom.rs          — Generic ROM with file loading
│   │   │   └── sram_2114.rs    — 2114 1Kx4-bit SRAM
│   │   └── vic20_sram.rs       — NEW: VIC-20 5KB SRAM block
│   │
│   ├── video/
│   │   ├── mod.rs              — NEW: Video module re-exports
│   │   ├── mos_6560.rs         — NEW: VIC chip
│   │   ├── framebuffer.rs      — NEW: Framebuffer abstraction
│   │   └── character_rom.rs    — NEW: Character generator
│   │
│   └── io/
│       ├── mod.rs              — NEW: I/O module re-exports
│       ├── mos_6522.rs         — NEW: VIA implementation
│       └── keyboard.rs         — NEW: VIC-20 keyboard matrix
│
├── gui/
│   ├── display.rs              — NEW: Video display widget
│   ├── keyboard_input.rs       — NEW: Keyboard input mapper
│   └── system_selector.rs      — NEW: System type selector
│
configs/
└── vic20.json                  — NEW: VIC-20 system configuration

roms/
└── vic20/                      — NEW: ROM directory (not in git)
    ├── basic.bin               — 8KB BASIC ROM
    ├── kernel.bin              — 8KB Kernel ROM
    └── chargen.bin             — 4KB Character ROM

Files to Modify

File Change
src/lib.rs Add bus module
src/components/cpu/mod.rs Add mos_6502 sub-module
src/components/memory/mod.rs Add generic and vic20_sram modules
src/components/mod.rs Add video and io modules
src/system_config.rs Add bus config, new component types, ROM loading
src/gui/mod.rs Add display, keyboard_input, system_selector modules
src/gui/components.rs Add system-specific rendering
Cargo.toml No new dependencies expected initially

Implementation Phases

Phase 1: Bus Infrastructure and 6502 CPU

Deliverable: A 6502 CPU that can execute instructions from a ROM via a memory-mapped bus.

Steps:

  1. Create src/bus/ module with AddressBus trait, BusDevice trait, and AddressDecoder
  2. Create generic SRAM and ROM components in src/components/memory/generic/
  3. Rewrite mos_6502.rs with full instruction decoder:
    • All 151 official opcodes
    • All addressing modes (Immediate, Zero Page, Zero Page X/Y, Absolute, Absolute X/Y, Indirect, Indirect X, Indirect Y, Relative, Implied, Accumulator)
    • Variable cycle counts per instruction
    • IRQ/NMI/RESET interrupt handling
  4. Wire CPU to bus (CPU holds Arc<SystemBus>, calls bus.read()/bus.write())
  5. Unit tests: All 6502 instructions tested against known behavior
  6. Integration test: Load a small 6502 machine code program, run it, verify register/memory state

Test milestone: A 6502 test program that computes Fibonacci numbers in RAM, verifiable via unit test.

Phase 2: Basic VIC-20 Boot

Deliverable: A VIC-20 system that boots from ROM and enters the BASIC prompt loop (no display yet).

Steps:

  1. Create VIC-20 address decoder in src/bus/address_decoder.rs
  2. Create VIC-20 5KB SRAM block
  3. Add ROM loading from binary files to the generic ROM component
  4. Create configs/vic20.json with full system definition
  5. Register VIC-20 components in SystemFactory
  6. Create stub VIA components (registers readable/writable but no I/O functionality)
  7. Create stub VIC component (registers readable/writable but no video generation)
  8. Integration test: Load VIC-20 ROMs, boot CPU, verify it reaches the BASIC warm start loop

Test milestone: CPU boots, reads reset vector from $FFFC/$FFFD, begins executing Kernel ROM code.

Phase 3: VIC Video Output

Deliverable: Visible video output from the VIC chip rendered in the GUI.

Steps:

  1. Implement VIC character mode rendering in mos_6560.rs
  2. Create framebuffer abstraction in framebuffer.rs
  3. Create character ROM reader
  4. Implement VIC register read/write via BusDevice trait
  5. Create GUI display widget in src/gui/display.rs
  6. Wire framebuffer to GUI via Arc<Mutex<Framebuffer>>
  7. Implement VIC timing (scanline counting, raster register)
  8. Integration test: Boot VIC-20, verify framebuffer contains expected startup screen

Test milestone: The VIC-20 startup screen renders correctly in the framebuffer (cursor blinking on empty blue screen).

Phase 4: VIA and Keyboard Input

Deliverable: Functional keyboard input — user can type on the host keyboard and see characters appear on the VIC-20 screen.

Steps:

  1. Implement full 6522 VIA with Port A, Port B, DDR registers
  2. Implement VIA Timer 1 and Timer 2 with interrupt generation
  3. Create keyboard matrix module in src/components/io/keyboard.rs
  4. Create keyboard input mapper in src/gui/keyboard_input.rs
  5. Wire VIA 1 Port A/B to keyboard matrix
  6. Wire VIA IRQ to CPU IRQ pin
  7. Integration test: Simulate keypress, verify character appears in screen memory

Test milestone: Type PRINT "HELLO" on the host keyboard, see it echoed on the VIC-20 display.

Phase 5: Sound and Polish

Deliverable: Sound output and a polished multi-system GUI.

Steps:

  1. Implement VIC sound generation (3 square waves + noise)
  2. Add audio output to the GUI (via cpal or rodio crate — evaluate which is better)
  3. Create system selector in GUI (src/gui/system_selector.rs)
  4. Add VIC-20-specific control panel (register viewer, memory dump with 16-bit addresses)
  5. Refine display widget (scanline effects, proper aspect ratio, NTSC/PAL switching)
  6. Implement VIA shift register and handshake lines
  7. Add DRAM refresh timing to VIC chip
  8. Performance optimization (bus caching, framebuffer dirty tracking)

Test milestone: Full VIC-20 with sound, keyboard, and video running at real-time speed.

Phase 6: Expansion Port

Deliverable: Support for cartridge ROM images.

Steps:

  1. Create expansion port component
  2. Add cartridge ROM loading to system config
  3. Support 4 expansion blocks ($2000, $4000, $6000, $8000)
  4. Test with a known cartridge ROM image
  5. Add cartridge slot to GUI (drag-and-drop ROM loading)

Test milestone: Load a cartridge ROM, verify it appears in the correct address range and the CPU can execute from it.


Risks and Open Questions

Risks

  1. 6502 Instruction Accuracy: The 6502 has many undocumented opcodes and edge cases (e.g., the JMP indirect page-boundary bug). Getting every cycle-accurate behavior right is complex. Mitigation: Start with official opcodes only, add undocumented opcodes later. Use the well-known "Nestest" ROM for validation.

  2. VIC Timing Complexity: The VIC chip steals CPU cycles for memory access, creating a complex timing interplay. Mitigation: Initially run the VIC and CPU at independent timing; add cycle-stealing later as an optimization.

  3. Performance: The VIC-20 generates video at ~60 FPS with per-scanline rendering. This must integrate smoothly with the existing VirtualClock system. Mitigation: Render scanlines in batches, use dirty tracking to avoid re-rendering unchanged frames.

  4. Bus Architecture Coexistence: The new bus-based architecture is fundamentally different from the MCS-4 pin-based model. Both must coexist in the same codebase. Mitigation: The bus is a new module alongside the existing pin system. Components choose which model to use.

  5. ROM Copyright: The VIC-20 system ROMs are still under copyright. Mitigation: Do not distribute ROMs. Users must provide their own ROM files. Document the expected ROM file format and MD5 checksums.

Open Questions

  1. Audio Library: Should we use cpal, rodio, or another crate for audio output? This decision can be deferred to Phase 5.

  2. Cycle Accuracy Level: Should we aim for instruction-level accuracy (simpler) or cycle-level accuracy (more faithful)? The existing MCS-4 uses cycle-level timing. Recommendation: Start with instruction-level for the 6502, add cycle-level later.

  3. 65C02 Priority: The WDC 65C02 is used in some systems but not the VIC-20. Should we implement it fully in Phase 1, or stub it and focus on the NMOS 6502? Recommendation: Focus on NMOS 6502 first; the trait hierarchy supports adding 65C02 later.

  4. PAL vs NTSC: The VIC-20 has different timing for PAL (6561) and NTSC (6560) variants. Should we support both from the start? Recommendation: Start with NTSC, add PAL as a configuration option.

  5. Save States: Should we plan for save state support from the beginning? Recommendation: Not in the initial implementation, but the BusDevice trait makes it straightforward to add later (serialize all device state).

  6. Debugger Integration: Should the 6502 get a disassembler and breakpoint system? Recommendation: Plan for it in the GUI but implement after the basic system works. The register viewer in the existing GUI is a starting point.