badline 0.16.1 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/CLAUDE.md +1 -1
- data/README.md +102 -12
- data/doc/media.md +5 -2
- data/doc/pinned-behaviour.md +61 -0
- data/doc/snapshot-format.md +376 -69
- data/doc/snapshots.md +148 -14
- data/lib/badline/cbm2/acia.rb +10 -0
- data/lib/badline/cbm2/bus.rb +263 -0
- data/lib/badline/cbm2/cpu.rb +58 -0
- data/lib/badline/cbm2/crtc_video.rb +133 -0
- data/lib/badline/cbm2/ieee_drive.rb +18 -0
- data/lib/badline/cbm2/ieee_layout.rb +69 -0
- data/lib/badline/cbm2/ieee_ports.rb +104 -0
- data/lib/badline/cbm2/ieee_traps.rb +81 -0
- data/lib/badline/cbm2/keyboard_ports.rb +60 -0
- data/lib/badline/cbm2/keyed_modules.rb +67 -0
- data/lib/badline/cbm2/keyed_state.rb +91 -0
- data/lib/badline/cbm2/model.rb +44 -0
- data/lib/badline/cbm2/p500/control_ports.rb +80 -0
- data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
- data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
- data/lib/badline/cbm2/p500/model.rb +39 -0
- data/lib/badline/cbm2/p500/pages.rb +57 -0
- data/lib/badline/cbm2/p500.rb +262 -0
- data/lib/badline/cbm2/sid_page.rb +19 -0
- data/lib/badline/cbm2.rb +267 -0
- data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
- data/lib/badline/chips/acia/keyed_state.rb +47 -0
- data/lib/badline/chips/acia.rb +307 -0
- data/lib/badline/chips/crtc.rb +3 -2
- data/lib/badline/chips/riot/fast_forward.rb +72 -0
- data/lib/badline/chips/riot/keyed_parts.rb +69 -0
- data/lib/badline/chips/riot/timer.rb +80 -0
- data/lib/badline/chips/riot.rb +162 -0
- data/lib/badline/chips/rriot/keyed_state.rb +30 -0
- data/lib/badline/chips/rriot.rb +132 -0
- data/lib/badline/chips/tpi/keyed_state.rb +38 -0
- data/lib/badline/chips/tpi.rb +233 -0
- data/lib/badline/chips/vic/bank.rb +60 -5
- data/lib/badline/chips/vic/sprite.rb +4 -3
- data/lib/badline/drives/datasette/keyed_state.rb +9 -1
- data/lib/badline/drives/datasette.rb +27 -3
- data/lib/badline/drives/model8050/disk.rb +163 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
- data/lib/badline/drives/model8050/idle.rb +177 -0
- data/lib/badline/drives/model8050/ieee_port.rb +147 -0
- data/lib/badline/drives/model8050/ip_bus.rb +93 -0
- data/lib/badline/drives/model8050/keyed_state.rb +160 -0
- data/lib/badline/drives/model8050/mechanism.rb +230 -0
- data/lib/badline/drives/model8050/parts.rb +37 -0
- data/lib/badline/drives/model8050.rb +243 -0
- data/lib/badline/frontend/app.rb +10 -3
- data/lib/badline/frontend/boot.rb +1 -1
- data/lib/badline/frontend/controls.rb +51 -15
- data/lib/badline/frontend/gamepads.rb +3 -3
- data/lib/badline/frontend/keys.rb +61 -2
- data/lib/badline/frontend/media_slots.rb +1 -1
- data/lib/badline/frontend/menu/menu_media.rb +14 -11
- data/lib/badline/frontend/menu/menu_pages.rb +18 -4
- data/lib/badline/frontend/menu/menu_ports.rb +40 -4
- data/lib/badline/frontend/menu/pause_menu.rb +1 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -7
- data/lib/badline/frontend/timeline.rb +19 -10
- data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
- data/lib/badline/kernal_trap/dos/status.rb +3 -0
- data/lib/badline/kernal_trap/dos.rb +18 -5
- data/lib/badline/kernal_trap/ieee.rb +4 -2
- data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
- data/lib/badline/machine/ieee_drives.rb +194 -0
- data/lib/badline/machine/settling.rb +4 -0
- data/lib/badline/machine.rb +23 -6
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +38 -9
- data/lib/badline/media.rb +15 -6
- data/lib/badline/options/family.rb +15 -3
- data/lib/badline/options/help.rb +16 -2
- data/lib/badline/options/table.rb +18 -4
- data/lib/badline/options/validation.rb +27 -3
- data/lib/badline/options.rb +21 -7
- data/lib/badline/pet/cassettes.rb +22 -1
- data/lib/badline/pet/ieee_drive.rb +5 -64
- data/lib/badline/pet/ieee_ports.rb +7 -0
- data/lib/badline/pet.rb +15 -4
- data/lib/badline/plus4/acia.rb +4 -298
- data/lib/badline/plus4/keyed_parts.rb +0 -44
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/8050/dos-2.7.rom +0 -0
- data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
- data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
- data/lib/badline/roms/README +111 -0
- data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
- data/lib/badline/roms/cbm2/basic128.rom +0 -0
- data/lib/badline/roms/cbm2/basic256.rom +0 -0
- data/lib/badline/roms/cbm2/characters-600.rom +0 -0
- data/lib/badline/roms/cbm2/characters-700.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
- data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
- data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
- data/lib/badline/snapshot/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +7 -2
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +38 -3
- data/lib/badline/snapshot/image.rb +62 -9
- data/lib/badline/snapshot/vice/cbm2.rb +93 -0
- data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
- data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
- data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
- data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
- data/lib/badline/snapshot/vice/cias.rb +13 -9
- data/lib/badline/snapshot/vice/p500.rb +96 -0
- data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
- data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
- data/lib/badline/snapshot/vice/pet.rb +7 -3
- data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
- data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
- data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
- data/lib/badline/snapshot/vice/pet_restore.rb +70 -6
- data/lib/badline/snapshot/vice/pet_via.rb +6 -5
- data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
- data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
- data/lib/badline/snapshot/vice.rb +3 -0
- data/lib/badline/snapshot.rb +11 -3
- data/lib/badline/storage/crt_file.rb +7 -1
- data/lib/badline/storage/d64_image.rb +7 -0
- data/lib/badline/storage/d80_image.rb +17 -1
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/disk_image/bam.rb +18 -3
- data/lib/badline/storage/disk_image/formatting.rb +69 -0
- data/lib/badline/storage/disk_image/writing.rb +35 -0
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage.rb +1 -0
- data/lib/badline/support/cbm2_checkpoint.rb +48 -0
- data/lib/badline/support/p500_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline.rb +1 -0
- metadata +72 -2
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
class P500
|
|
6
|
+
# The machine's own fields as the B series keeps them (CBM2::KeyedState),
|
|
7
|
+
# and the whole machine as keyed snapshot modules (Snapshot::Keyed), in
|
|
8
|
+
# the order a load applies them: BL-MACHINE with the setup, the RAM
|
|
9
|
+
# with the segment registers, the colour RAM, the VIC, the SID, the
|
|
10
|
+
# CIA, the ACIA, both TPIs, the CPU, device 8 through the traps and a
|
|
11
|
+
# true 2031 on the IEEE-488 bus.
|
|
12
|
+
#
|
|
13
|
+
# A load lays the pages out before the chips load, and works out what
|
|
14
|
+
# each unit derives from its fields, the VIC's view of memory among
|
|
15
|
+
# them, once every unit is loaded.
|
|
16
|
+
module KeyedModules
|
|
17
|
+
# The SID's deferred cycles and writes, run up to this cycle, and the
|
|
18
|
+
# VIC's sprite collisions latched up to the beam.
|
|
19
|
+
def settle_chips_for_snapshot
|
|
20
|
+
@sid.catch_up!
|
|
21
|
+
@vic.fold_collisions!
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def save_modules
|
|
25
|
+
bus = @bus
|
|
26
|
+
sections = [Snapshot::Keyed.section("BL-MACHINE") { |out| save_machine_fields(out) }]
|
|
27
|
+
sections << Snapshot::Keyed.section("BL-CPU") { |out| @cpu.save_fields(out) }
|
|
28
|
+
sections << Snapshot::Keyed.section("BL-RAM") { |out| bus.save_fields(out) }
|
|
29
|
+
sections << Snapshot::Keyed.section("BL-COLORRAM") { |out| @color_ram.save_fields(out, "ram") }
|
|
30
|
+
sections << Snapshot::Keyed.section("BL-VIC") { |out| @vic.save_fields(out) }
|
|
31
|
+
sections << Snapshot::Keyed.section("BL-SID") { |out| @sid.save_fields(out) }
|
|
32
|
+
sections << Snapshot::Keyed.section("BL-CIA") { |out| @cia.save_fields(out) }
|
|
33
|
+
sections << Snapshot::Keyed.section("BL-ACIA") { |out| @acia.save_fields(out) }
|
|
34
|
+
sections << Snapshot::Keyed.section("BL-TPI1") { |out| @tpi1.save_fields(out) }
|
|
35
|
+
sections << Snapshot::Keyed.section("BL-TPI2") { |out| @tpi2.save_fields(out) }
|
|
36
|
+
sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
|
|
37
|
+
save_drive_modules(sections)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Raises Snapshot::FormatError for the modules of a machine built
|
|
41
|
+
# another way.
|
|
42
|
+
def load_modules(modules)
|
|
43
|
+
check_snapshot_setup(Snapshot::CBM2Setup.read_fields(modules.machine))
|
|
44
|
+
load_machine_fields(modules.machine)
|
|
45
|
+
@bus.load_fields(modules.required("BL-RAM"))
|
|
46
|
+
@bus.restored!
|
|
47
|
+
@color_ram.load_fields(modules.required("BL-COLORRAM"), "ram")
|
|
48
|
+
@vic.load_fields(modules.required("BL-VIC"))
|
|
49
|
+
@sid.load_fields(modules.required("BL-SID"))
|
|
50
|
+
@cia.load_fields(modules.required("BL-CIA"))
|
|
51
|
+
@acia.load_fields(modules.required("BL-ACIA"))
|
|
52
|
+
@tpi1.load_fields(modules.required("BL-TPI1"))
|
|
53
|
+
@tpi2.load_fields(modules.required("BL-TPI2"))
|
|
54
|
+
@cpu.load_fields(modules.required("BL-CPU"))
|
|
55
|
+
trap_dos = modules.optional("BL-TRAPDOS")
|
|
56
|
+
trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
|
|
57
|
+
load_drive_modules(modules)
|
|
58
|
+
restored!
|
|
59
|
+
self
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def restored!
|
|
65
|
+
@cpu.restored!
|
|
66
|
+
@vic.restored!
|
|
67
|
+
@sid.restored!
|
|
68
|
+
@cia.restored!
|
|
69
|
+
@acia.restored!
|
|
70
|
+
update_video_bus
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def check_snapshot_setup(setup)
|
|
74
|
+
ours = Snapshot::CBM2Setup.of(self)
|
|
75
|
+
return if setup == ours
|
|
76
|
+
|
|
77
|
+
raise Snapshot::FormatError, "the snapshot is of a #{setup}, not a #{ours}"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
include CBM2::KeyedState
|
|
82
|
+
include KeyedModules
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
class P500
|
|
6
|
+
# The P500s badline builds by name: the 510 of xcbm5x0's -model, PAL
|
|
7
|
+
# with the 6569 and NTSC with the 6567R8, each with its own region's
|
|
8
|
+
# mains unless told otherwise. Both have 128K of RAM, segments 0 and 1,
|
|
9
|
+
# and the one KERNAL and BASIC.
|
|
10
|
+
module Model
|
|
11
|
+
Profile = Data.define(:name, :region) do
|
|
12
|
+
def ram_kb = RAM_SEGMENTS.length * 64
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
M510 = Profile.new(name: "510", region: Region::PAL)
|
|
16
|
+
M510_NTSC = Profile.new(name: "510ntsc", region: Region::NTSC)
|
|
17
|
+
|
|
18
|
+
ALL = [M510, M510_NTSC].freeze
|
|
19
|
+
DEFAULT = "510"
|
|
20
|
+
|
|
21
|
+
RAM_SEGMENTS = [0, 1].freeze
|
|
22
|
+
|
|
23
|
+
# 901234-02, the KERNAL xcbm5x0 runs, and its BASIC 128, 901236-02
|
|
24
|
+
# and 901235-02.
|
|
25
|
+
KERNALS = { "02" => "cbm2/p500-kernal.rom" }.freeze
|
|
26
|
+
BASIC = "cbm2/p500-basic.rom"
|
|
27
|
+
|
|
28
|
+
def self.named(name)
|
|
29
|
+
model = ALL.find { |candidate| candidate.name == name }
|
|
30
|
+
raise ArgumentError, "no P500 model named #{name}" if model.nil?
|
|
31
|
+
|
|
32
|
+
model
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.include?(name) = ALL.any? { |model| model.name == name }
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
class P500
|
|
6
|
+
# Page $D8 of segment 15: the VIC-II's 64 registers, repeated over the
|
|
7
|
+
# page.
|
|
8
|
+
class VICPage
|
|
9
|
+
def initialize(vic)
|
|
10
|
+
@vic = vic
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def peek(addr) = @vic.peek(0xd000 | (addr & 0x3f))
|
|
14
|
+
|
|
15
|
+
def poke(addr, value)
|
|
16
|
+
@vic.poke(0xd000 | (addr & 0x3f), value)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# The colour RAM's four bits at $D400-$D7FF of segment 15, which the
|
|
21
|
+
# CPU reads with 0 in the high four, as xcbm5x0 does.
|
|
22
|
+
class ColorPages
|
|
23
|
+
def initialize(color_ram)
|
|
24
|
+
@color_ram = color_ram
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def peek(addr) = @color_ram.nibble(addr)
|
|
28
|
+
|
|
29
|
+
def poke(addr, value)
|
|
30
|
+
@color_ram.poke(addr, value)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# A TPI's page, $DE or $DF of segment 15. TPI1's CA and CB are STATVID
|
|
35
|
+
# and VICDOTSEL, and TPI2's PC6 and PC7 the VIC's bank lines, which
|
|
36
|
+
# an access to either chip can move, so the machine takes them again
|
|
37
|
+
# after each one (P500#update_video_bus).
|
|
38
|
+
class TPIPage
|
|
39
|
+
def initialize(tpi, machine)
|
|
40
|
+
@tpi = tpi
|
|
41
|
+
@machine = machine
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def peek(addr)
|
|
45
|
+
value = @tpi.peek(addr)
|
|
46
|
+
@machine.update_video_bus
|
|
47
|
+
value
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def poke(addr, value)
|
|
51
|
+
@tpi.poke(addr, value)
|
|
52
|
+
@machine.update_video_bus
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/cbm2/p500/model"
|
|
4
|
+
require "badline/cbm2/p500/pages"
|
|
5
|
+
require "badline/cbm2/p500/control_ports"
|
|
6
|
+
require "badline/cbm2/p500/ieee_layout"
|
|
7
|
+
require "badline/cbm2/p500/keyed_state"
|
|
8
|
+
|
|
9
|
+
module Badline
|
|
10
|
+
class CBM2
|
|
11
|
+
# The P500, the CBM 510: the B series' 6509, SID, CIA, ACIA and TPIs on
|
|
12
|
+
# the same segment bus (Bus, CPU), at the VIC-II's clock of about
|
|
13
|
+
# 1 MHz, with a VIC-II at $D800 in place of the 6845. RAM fills segments
|
|
14
|
+
# 0 and 1.
|
|
15
|
+
#
|
|
16
|
+
# Segment 15 holds what the B series' does but for $C000-$D7FF: the
|
|
17
|
+
# character ROM at $C000, which the CPU reads too, the 1K static video
|
|
18
|
+
# matrix at $D000 and the colour RAM's nibbles at $D400. What the VIC
|
|
19
|
+
# reads follows the P500's video bus (Chips::VIC::Bank#map_video_bus):
|
|
20
|
+
# TPI1's CA is STATVID and its CB VICDOTSEL, and TPI2's PC6 and PC7
|
|
21
|
+
# drive VA14 and VA15, which pick the 16K of segment 0 the VIC sees.
|
|
22
|
+
# BA halts the CPU as on the C64, and the VIC's IRQ reaches the CPU
|
|
23
|
+
# along with TPI1's.
|
|
24
|
+
#
|
|
25
|
+
# TPI1 is the interrupt controller as on the B series, with the mains
|
|
26
|
+
# on I0, the CIA on I2 and the ACIA on I4. TPI2 scans the B series'
|
|
27
|
+
# key matrix (KeyboardPorts), and the CIA reads the two control ports
|
|
28
|
+
# (ControlPorts), whose paddles reach the SID's pots and whose port 1
|
|
29
|
+
# fire line is the VIC's light pen input. Device 8 answers through traps on the KERNAL's
|
|
30
|
+
# IEEE-488 primitives as on the B series (IEEETraps, with the P500's
|
|
31
|
+
# IEEELayout), or a true 2031, 8050 or 8250 on the bus answers, on the
|
|
32
|
+
# same ports as the B series' (IEEEPorts, IEEEDrive).
|
|
33
|
+
class P500
|
|
34
|
+
include Machine::KeyboardBuffer
|
|
35
|
+
include Machine::Clocking
|
|
36
|
+
include Machine::Settling
|
|
37
|
+
include IEEETraps
|
|
38
|
+
include IEEEDrive
|
|
39
|
+
|
|
40
|
+
# The segment BASIC keeps its program text in.
|
|
41
|
+
TEXT_SEGMENT = 0
|
|
42
|
+
|
|
43
|
+
# The ACIA at $DD00, whose IRQ reaches TPI1's I4.
|
|
44
|
+
class ACIA < Chips::ACIA
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :model, :bus, :cpu, :vic, :sid, :cia, :acia, :tpi1, :tpi2, :keyboard, :joystick1, :joystick2,
|
|
48
|
+
:control_ports, :color_ram, :ieee, :mains_hz, :kernal, :cycles, :init_threshold
|
|
49
|
+
|
|
50
|
+
def ram = @bus.ram
|
|
51
|
+
|
|
52
|
+
# The VIC refreshes the DRAM in its own half of the cycle, so the
|
|
53
|
+
# refresh takes none of the CPU's.
|
|
54
|
+
def refresh? = false
|
|
55
|
+
|
|
56
|
+
def family = :cbm2
|
|
57
|
+
|
|
58
|
+
def peek(addr) = @bus.peek(addr)
|
|
59
|
+
|
|
60
|
+
def poke(addr, value) = @bus.poke(addr, value)
|
|
61
|
+
|
|
62
|
+
def install_debug_register(&) = @bus.install_debug_register(&)
|
|
63
|
+
|
|
64
|
+
def keyboard_buffer = CBM2::KEYBOARD_LAYOUT
|
|
65
|
+
|
|
66
|
+
def sound_source = @sid
|
|
67
|
+
|
|
68
|
+
# The VIC, whose display a front end shows.
|
|
69
|
+
def video = @vic
|
|
70
|
+
|
|
71
|
+
def region = @model.region
|
|
72
|
+
|
|
73
|
+
# +model+ names one of Model::ALL, and +mains_hz+ the mains, 50 or
|
|
74
|
+
# 60, the model's region's unless given.
|
|
75
|
+
def initialize(model: Model::DEFAULT, mains_hz: nil, debug: false)
|
|
76
|
+
@model = Model.named(model)
|
|
77
|
+
@mains_hz = mains_hz || @model.region.mains_hz
|
|
78
|
+
raise ArgumentError, "the mains is 50 or 60 Hz, not #{@mains_hz}" unless [50, 60].include?(@mains_hz)
|
|
79
|
+
|
|
80
|
+
@kernal = Model::KERNALS.keys.first
|
|
81
|
+
clock_hz = @model.region.clock_hz
|
|
82
|
+
@bus = Bus.new(basic: Model::BASIC, kernal: Model::KERNALS.fetch(@kernal), ram_segments: Model::RAM_SEGMENTS,
|
|
83
|
+
screen_length: 0x400)
|
|
84
|
+
@vic = Chips::VIC.new(model: :mos6569, region: @model.region)
|
|
85
|
+
@color_ram = C64::ColorMemory.new(@vic, start: 0xd400)
|
|
86
|
+
map_video
|
|
87
|
+
@ieee = IEEEPorts.new(Drives::IEEEBus.new)
|
|
88
|
+
@tpi1 = Chips::TPI.new(peripheral: @ieee)
|
|
89
|
+
@joystick1 = Input::Joystick.new
|
|
90
|
+
@joystick2 = Input::Joystick.new
|
|
91
|
+
@control_ports = ControlPorts.new(@joystick1, @joystick2, @ieee.data)
|
|
92
|
+
@sid = Chips::SID.new(model: :mos6581, pots: @control_ports, at: 0xda00)
|
|
93
|
+
@cia = Chips::CIA.new(start: 0xdc00, peripheral: @control_ports, region: @model.region, tod_hz: @mains_hz)
|
|
94
|
+
@control_ports.connect(@cia)
|
|
95
|
+
@ieee.connect(@cia, @tpi1)
|
|
96
|
+
@lightpen_high = true
|
|
97
|
+
@acia = ACIA.new(self, clock_hz)
|
|
98
|
+
@keyboard = Input::Keyboard.new(matrix: KeyboardPorts::MATRIX)
|
|
99
|
+
keyboard_ports = KeyboardPorts.new(@keyboard, 0xc0)
|
|
100
|
+
@tpi2 = Chips::TPI.new(peripheral: keyboard_ports)
|
|
101
|
+
keyboard_ports.connect(@tpi2)
|
|
102
|
+
map_io
|
|
103
|
+
@cpu = CPU.new(@bus, debug:)
|
|
104
|
+
@cycles = 0
|
|
105
|
+
@mains_half_period = clock_hz / (2 * @mains_hz)
|
|
106
|
+
@mains_countdown = @mains_half_period
|
|
107
|
+
@mains_high = true
|
|
108
|
+
# The B series' refresh counter, which CBM2::KeyedState saves: 0
|
|
109
|
+
# here, where the VIC refreshes the DRAM.
|
|
110
|
+
@refresh = 0
|
|
111
|
+
@acia_irq = false
|
|
112
|
+
@acia_due = Chips::ACIA::NEVER
|
|
113
|
+
@init_handlers = []
|
|
114
|
+
@pending_keys = nil
|
|
115
|
+
@dos = nil
|
|
116
|
+
@ieee_trap = nil
|
|
117
|
+
@capture_output = nil
|
|
118
|
+
init_ieee_drives
|
|
119
|
+
@init_threshold = INIT_THRESHOLD
|
|
120
|
+
update_video_bus
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
# The model, its RAM and its mains as the pause menu names them, such
|
|
124
|
+
# as "510, 128K RAM, 50 HZ".
|
|
125
|
+
def model_label = "#{@model.name}, #{@model.ram_kb}K RAM, #{@mains_hz} HZ"
|
|
126
|
+
|
|
127
|
+
def timing
|
|
128
|
+
region = @model.region
|
|
129
|
+
Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
|
|
130
|
+
lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1, dots: 1)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The VIC and the mains ahead of the chips, then TPI1's inputs from
|
|
134
|
+
# the CIA and the ACIA, and the CPU last, halted while BA is low on a
|
|
135
|
+
# read.
|
|
136
|
+
def cycle!
|
|
137
|
+
handle_init if @cycles == @init_threshold
|
|
138
|
+
feed_keyboard if @pending_keys
|
|
139
|
+
|
|
140
|
+
vic = @vic
|
|
141
|
+
vic.cycle!
|
|
142
|
+
tick_mains
|
|
143
|
+
@cia.cycle!
|
|
144
|
+
sample_lightpen
|
|
145
|
+
@sid.cycle!
|
|
146
|
+
@acia.run!(@cycles) if @cycles >= @acia_due
|
|
147
|
+
tpi1 = @tpi1
|
|
148
|
+
tpi1.interrupt_line(2, !@cia.interrupted?)
|
|
149
|
+
tpi1.interrupt_line(4, !@acia_irq)
|
|
150
|
+
cpu = @cpu
|
|
151
|
+
cpu.irq = tpi1.irq? || vic.interrupted?
|
|
152
|
+
cpu.pending_write? || !vic.ba_low? ? cpu.cycle! : cpu.stall!
|
|
153
|
+
clock_ieee_drives
|
|
154
|
+
|
|
155
|
+
@cycles += 1
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# The ACIA's IRQ line, and the cycle it next has something to do at.
|
|
159
|
+
def acia_changed(irq, due)
|
|
160
|
+
@acia_irq = irq
|
|
161
|
+
@acia_due = due
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# STATVID and VICDOTSEL from TPI1's CA and CB, PC6 and PC7 in mode 0,
|
|
165
|
+
# and VA14 and VA15 from TPI2's PC6 and PC7, to the VIC's bank.
|
|
166
|
+
def update_video_bus
|
|
167
|
+
lines = @tpi1.port_c_output
|
|
168
|
+
@vic.vic_bank.video_bus_lines(lines.anybits?(0x40), lines.anybits?(0x80), @tpi2.port_c_output >> 6)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
# The RES line reaches the CPU and its segment registers, the SID, the
|
|
172
|
+
# CIA, the ACIA and both TPIs. The VIC has no reset pin.
|
|
173
|
+
def reset!
|
|
174
|
+
@sid.reset!
|
|
175
|
+
@cia.reset!
|
|
176
|
+
@acia.reset!
|
|
177
|
+
@tpi1.reset!
|
|
178
|
+
@tpi2.reset!
|
|
179
|
+
update_video_bus
|
|
180
|
+
@bus.reset!
|
|
181
|
+
@cpu.reset!
|
|
182
|
+
reset_ieee_drives
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Puts a program file's bytes where its first two say in BASIC's text
|
|
186
|
+
# segment, segment 0, and returns that address.
|
|
187
|
+
def load_prg(data)
|
|
188
|
+
load_addr = data[0] | (data[1] << 8)
|
|
189
|
+
bytes = data[2..] || []
|
|
190
|
+
bytes = bytes[0, 0x10000 - load_addr] if load_addr + bytes.length > 0x10000
|
|
191
|
+
bytes.each_with_index { |byte, i| @bus.poke_bank(TEXT_SEGMENT, load_addr + i, byte) }
|
|
192
|
+
load_addr
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
# The IEEELayout of 901234-02.
|
|
196
|
+
def trap_layout = IEEELayout::LAYOUT
|
|
197
|
+
|
|
198
|
+
# Puts a cartridge's ROM chips in (Bus#attach_cartridge) and switches
|
|
199
|
+
# the machine off and on, for the KERNAL to find it as it starts.
|
|
200
|
+
def attach_cartridge(chips)
|
|
201
|
+
@bus.attach_cartridge(chips)
|
|
202
|
+
power_cycle!
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
# Switches the machine off and on: RAM starts cleared, the VIC starts
|
|
206
|
+
# over, and the RES line resets everything else.
|
|
207
|
+
def power_cycle!
|
|
208
|
+
@bus.power_on!
|
|
209
|
+
@color_ram.clear!
|
|
210
|
+
@vic.power_on!
|
|
211
|
+
reset!
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
# The cycle by which the KERNAL has tested segments 0 and 1 and BASIC
|
|
215
|
+
# printed READY, with a margin.
|
|
216
|
+
INIT_THRESHOLD = 8_600_000
|
|
217
|
+
|
|
218
|
+
private
|
|
219
|
+
|
|
220
|
+
# The character ROM at $C000 and the colour RAM at $D400 of segment
|
|
221
|
+
# 15, and what the VIC reads.
|
|
222
|
+
def map_video
|
|
223
|
+
character_rom = ROM.load("character.rom", 0xc000)
|
|
224
|
+
@bus.map_video(character_rom, 0xc0, 0x10)
|
|
225
|
+
@bus.map_video(ColorPages.new(@color_ram), 0xd4, 0x04, writable: true)
|
|
226
|
+
bank = @vic.vic_bank
|
|
227
|
+
bank.map_character_rom(character_rom, base: 0xc000)
|
|
228
|
+
bank.map_video_bus(@bus.segment_ram[0], @bus.screen, @color_ram, 0xd400)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def map_io
|
|
232
|
+
@bus.map_io(0xd8, VICPage.new(@vic))
|
|
233
|
+
@bus.map_io(0xda, SIDPage.new(@sid))
|
|
234
|
+
@bus.map_io(0xdc, @cia)
|
|
235
|
+
@bus.map_io(0xdd, @acia)
|
|
236
|
+
@bus.map_io(0xde, TPIPage.new(@tpi1, self))
|
|
237
|
+
@bus.map_io(0xdf, TPIPage.new(@tpi2, self))
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
# The VIC's light pen input, CIA PA6 as the CIA drives it and control
|
|
241
|
+
# port 1's fire line pulls it, whose falling edge latches the beam.
|
|
242
|
+
def sample_lightpen
|
|
243
|
+
high = @cia.port_a_lines.anybits?(0x40) && @control_ports.fire1_high?
|
|
244
|
+
return if high == @lightpen_high
|
|
245
|
+
|
|
246
|
+
@lightpen_high = high
|
|
247
|
+
@vic.lightpen_level(high)
|
|
248
|
+
end
|
|
249
|
+
|
|
250
|
+
# The mains, low for half its period and high for the other half, on
|
|
251
|
+
# TPI1's I0, whose falling edge latches the tick.
|
|
252
|
+
def tick_mains
|
|
253
|
+
@mains_countdown -= 1
|
|
254
|
+
return unless @mains_countdown.zero?
|
|
255
|
+
|
|
256
|
+
@mains_countdown = @mains_half_period
|
|
257
|
+
@mains_high = !@mains_high
|
|
258
|
+
@tpi1.interrupt_line(0, @mains_high)
|
|
259
|
+
end
|
|
260
|
+
end
|
|
261
|
+
end
|
|
262
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CBM2
|
|
5
|
+
# Page $DA of segment 15: the SID's 32 registers, repeated over the
|
|
6
|
+
# page.
|
|
7
|
+
class SIDPage
|
|
8
|
+
def initialize(sid)
|
|
9
|
+
@sid = sid
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def peek(addr) = @sid.peek(0xda00 | (addr & 0x1f))
|
|
13
|
+
|
|
14
|
+
def poke(addr, value)
|
|
15
|
+
@sid.poke(0xda00 | (addr & 0x1f), value)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|