# 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: - [`Component`](src/component.rs:18) trait with `update()`, `update_at_time()`, `timing()`, virtual clock support - [`BaseComponent`](src/component.rs:142) for common pin/state management - [`VirtualClock`](src/virtual_clock.rs) with RealTime/MaxSpeed/SingleStep modes - [`ConfigurableSystem`](src/system_config.rs) with JSON-based system factory and thread management - [`ConnectionManager`](src/connection.rs:7) for bidirectional pin connections - [`ComponentTiming`](src/component_timing.rs) for per-component timing constraints - 6502 stubs: [`MOS6502`](src/components/cpu/mos_6502.rs:12) and [`WDC65C02`](src/components/cpu/wdc_65c02.rs:11) with skeleton structure --- ## 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 ```mermaid 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`](src/components/cpu/mos_6502.rs:12) 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. ```rust /// 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; } ``` #### 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: ```rust /// 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`](src/components/cpu/wdc_65c02.rs:11) currently wraps [`MOS6502`](src/components/cpu/mos_6502.rs:12) 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 ```rust /// Generic static RAM with configurable size and bus width pub struct GenericSram { base: BaseComponent, data: Vec, size: usize, // Size in bytes data_width: u8, // 4 or 8 bits address_mask: u16, // Bus interface bus: Option>>, } ``` #### 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 ```rust /// Generic ROM with configurable size pub struct GenericRom { base: BaseComponent, data: Vec, 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). ```rust /// Framebuffer for VIC video output pub struct Framebuffer { /// Pixel data: 176 x 184 (NTSC) or 176 x 200 (PAL) pixels: Vec, // 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>`. #### 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 ```rust pub struct Mos6560 { base: BaseComponent, registers: [u8; 16], framebuffer: Arc>, screen_memory: Arc>, char_memory: Arc>, // 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 ```rust /// Routes CPU bus cycles to the correct component pub struct Vic20AddressDecoder { /// Addressable devices mapped to address ranges devices: Vec, /// 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>, } 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). ```rust /// 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 ```rust 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: ```rust /// Models the VIC-20 cartridge expansion port pub struct ExpansionPort { base: BaseComponent, // Connected ROM/RAM devices blocks: [Option>>; 4], // Blocks 1-4 // I/O mapping io_device: Option>>, // 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: ```json { "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`](src/system_config.rs:150) format needs extensions for bus-based systems: ```json { "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`](src/system_config.rs) 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: ```rust /// Displays the VIC-20 video output pub struct VicDisplay { framebuffer: Option>>, texture: Option, 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: ```rust /// 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, } ``` #### 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`](src/gui/mod.rs:22) 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`](src/lib.rs) | Add `bus` module | | [`src/components/cpu/mod.rs`](src/components/cpu/mod.rs) | Add mos_6502 sub-module | | [`src/components/memory/mod.rs`](src/components/memory/mod.rs) | Add generic and vic20_sram modules | | [`src/components/mod.rs`](src/components/mod.rs) | Add video and io modules | | [`src/system_config.rs`](src/system_config.rs) | Add bus config, new component types, ROM loading | | [`src/gui/mod.rs`](src/gui/mod.rs) | Add display, keyboard_input, system_selector modules | | [`src/gui/components.rs`](src/gui/components.rs) | Add system-specific rendering | | [`Cargo.toml`](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`](src/components/cpu/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`, 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`](src/system_config.rs:205) 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`](src/components/video/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>` 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.