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

1010 lines
36 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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<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:
```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<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
```rust
/// 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).
```rust
/// 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
```rust
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
```rust
/// 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).
```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<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:
```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<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:
```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<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`](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<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`](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<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.