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
1010 lines
36 KiB
Markdown
1010 lines
36 KiB
Markdown
# 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.
|