badline 0.14.0 → 0.15.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 +23 -0
- data/CLAUDE.md +15 -5
- data/README.md +68 -8
- data/doc/pinned-behaviour.md +145 -0
- data/doc/snapshot-format.md +188 -38
- data/doc/snapshots.md +18 -1
- data/lib/badline/audio/bare_player.rb +4 -8
- data/lib/badline/audio/machine_player.rb +2 -12
- data/lib/badline/c128/banks.rb +1 -3
- data/lib/badline/c128/bus.rb +19 -1
- data/lib/badline/c128/mmu_pages.rb +7 -0
- data/lib/badline/c64/bus/extra_sids.rb +80 -0
- data/lib/badline/c64/bus.rb +1 -0
- data/lib/badline/c64/cartridge/action_replay.rb +6 -2
- data/lib/badline/c64/cartridge/comal80.rb +1 -0
- data/lib/badline/c64/cartridge/dinamic.rb +3 -1
- data/lib/badline/c64/cartridge/final_cartridge3.rb +1 -0
- data/lib/badline/c64/cartridge/fun_play.rb +2 -1
- data/lib/badline/c64/cartridge/game_system.rb +2 -1
- data/lib/badline/c64/cartridge/keyed_state.rb +155 -34
- data/lib/badline/c64/cartridge/magic_desk.rb +2 -1
- data/lib/badline/c64/cartridge/ocean.rb +1 -0
- data/lib/badline/c64/cartridge/pagefox.rb +1 -0
- data/lib/badline/c64/cartridge/rgcd.rb +2 -1
- data/lib/badline/c64/cartridge/super_games.rb +3 -2
- data/lib/badline/c64/cartridge/zaxxon.rb +1 -0
- data/lib/badline/c64/cartridge.rb +3 -3
- data/lib/badline/c64/keyed_modules.rb +6 -2
- data/lib/badline/chips/ted/beam.rb +13 -14
- data/lib/badline/chips/ted/fetches.rb +199 -0
- data/lib/badline/chips/ted/keyed_state.rb +156 -0
- data/lib/badline/chips/ted/modes.rb +134 -0
- data/lib/badline/chips/ted/palette.rb +43 -0
- data/lib/badline/chips/ted/picture.rb +182 -0
- data/lib/badline/chips/ted/sound/output.rb +86 -0
- data/lib/badline/chips/ted/sound.rb +284 -0
- data/lib/badline/chips/ted.rb +53 -6
- data/lib/badline/chips/tia.rb +12 -0
- data/lib/badline/drives/datasette/keyed_state.rb +7 -2
- data/lib/badline/drives/datasette.rb +22 -5
- data/lib/badline/frontend/boot.rb +3 -1
- data/lib/badline/frontend/controls.rb +7 -3
- data/lib/badline/frontend/keys.rb +14 -2
- data/lib/badline/frontend/media_slots.rb +11 -1
- data/lib/badline/frontend/menu/menu_dialogs.rb +2 -1
- data/lib/badline/frontend/menu/menu_media.rb +13 -3
- data/lib/badline/frontend/menu/menu_pages.rb +8 -8
- data/lib/badline/frontend/menu/menu_ports.rb +13 -1
- data/lib/badline/frontend/screen.rb +33 -12
- data/lib/badline/frontend/screens.rb +2 -1
- data/lib/badline/frontend/sound.rb +4 -4
- data/lib/badline/frontend/timeline.rb +19 -6
- data/lib/badline/machine/settling.rb +1 -1
- data/lib/badline/machine.rb +26 -6
- data/lib/badline/media/plus4_media.rb +68 -0
- data/lib/badline/media/program.rb +20 -6
- data/lib/badline/media.rb +33 -12
- data/lib/badline/options/family.rb +1 -0
- data/lib/badline/options/help.rb +30 -6
- data/lib/badline/options/table.rb +15 -2
- data/lib/badline/options/validation.rb +26 -4
- data/lib/badline/options.rb +37 -9
- data/lib/badline/plus4/bus.rb +77 -14
- data/lib/badline/plus4/cassette_sense.rb +21 -0
- data/lib/badline/plus4/keyboard_port.rb +35 -10
- data/lib/badline/plus4/keyed_modules.rb +99 -0
- data/lib/badline/plus4/keyed_parts.rb +143 -0
- data/lib/badline/plus4/keyed_state.rb +41 -0
- data/lib/badline/plus4.rb +92 -16
- data/lib/badline/snapshot/container.rb +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +7 -6
- data/lib/badline/snapshot/field_doc.rb +46 -7
- data/lib/badline/snapshot/image.rb +12 -3
- data/lib/badline/snapshot/plus4_setup.rb +39 -0
- data/lib/badline/snapshot.rb +9 -4
- data/lib/badline/storage/g64_image.rb +1 -1
- data/lib/badline/storage/tap.rb +21 -12
- data/lib/badline/support/checkpoint.rb +3 -2
- data/lib/badline/support/plus4_checkpoint.rb +42 -0
- data/lib/badline/version.rb +1 -1
- metadata +15 -1
|
@@ -2,12 +2,19 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Plus4
|
|
5
|
-
# The keyboard: the 6529 at $FD30-$FD3F, whose eight
|
|
6
|
-
# key matrix's lines, and the TED's K0-K7 inputs,
|
|
7
|
-
# (Chips::TED#keyboard_port=). The KERNAL's scan
|
|
8
|
-
# line's mask to $FD30 and $FF08 and reads the keys
|
|
9
|
-
# and its key table at $E026 puts each key at line
|
|
10
|
-
# bit.
|
|
5
|
+
# The keyboard and the joysticks: the 6529 at $FD30-$FD3F, whose eight
|
|
6
|
+
# outputs drive the key matrix's lines, and the TED's K0-K7 inputs,
|
|
7
|
+
# which $FF08 latches (Chips::TED#keyboard_port=). The KERNAL's scan
|
|
8
|
+
# ($DB11) writes each line's mask to $FD30 and $FF08 and reads the keys
|
|
9
|
+
# back from $FF08, and its key table at $E026 puts each key at line
|
|
10
|
+
# times 8 plus input bit.
|
|
11
|
+
#
|
|
12
|
+
# The joysticks share K0-K7. The byte written to $FF08 selects them,
|
|
13
|
+
# bit 2 low joystick 1 and bit 1 low joystick 2, as BASIC's JOY does
|
|
14
|
+
# ($FA and $FD). A selected joystick pulls up, down, left and right
|
|
15
|
+
# onto K0-K3 and its fire button onto K6 (joystick 1) or K7
|
|
16
|
+
# (joystick 2), and those lines settle against the matrix with the
|
|
17
|
+
# keys'.
|
|
11
18
|
#
|
|
12
19
|
# The 6529 is a quasi-bidirectional latch: a read returns what was
|
|
13
20
|
# written to it.
|
|
@@ -28,8 +35,10 @@ module Badline
|
|
|
28
35
|
|
|
29
36
|
attr_reader :keyboard, :output
|
|
30
37
|
|
|
31
|
-
def initialize(keyboard)
|
|
38
|
+
def initialize(keyboard, joystick1, joystick2)
|
|
32
39
|
@keyboard = keyboard
|
|
40
|
+
@joystick1 = joystick1
|
|
41
|
+
@joystick2 = joystick2
|
|
33
42
|
@output = 0xff
|
|
34
43
|
end
|
|
35
44
|
|
|
@@ -43,9 +52,25 @@ module Badline
|
|
|
43
52
|
@output = value
|
|
44
53
|
end
|
|
45
54
|
|
|
46
|
-
# The K0-K7 inputs the TED latches on a write of +
|
|
47
|
-
# high but where a held key joins a line the 6529 pulls low
|
|
48
|
-
|
|
55
|
+
# The K0-K7 inputs the TED latches on a write of +value+ to $FF08:
|
|
56
|
+
# high but where a held key joins a line the 6529 pulls low, or a
|
|
57
|
+
# joystick +value+ selects holds a switch closed.
|
|
58
|
+
def latch(value) = @keyboard.scan(@output, joystick_lines(value)).last
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def joystick_lines(value)
|
|
63
|
+
lines = 0xff
|
|
64
|
+
lines &= joystick_inputs(@joystick1.port_bits, 0x40) if value.nobits?(0x04)
|
|
65
|
+
lines &= joystick_inputs(@joystick2.port_bits, 0x80) if value.nobits?(0x02)
|
|
66
|
+
lines
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# A joystick's switches, Input::Joystick's bits 0-4, on K0-K7: the
|
|
70
|
+
# directions on K0-K3 and the fire button on +fire+.
|
|
71
|
+
def joystick_inputs(bits, fire)
|
|
72
|
+
bits.anybits?(0x10) ? bits | 0xf0 : (bits | 0xf0) & ~fire & 0xff
|
|
73
|
+
end
|
|
49
74
|
end
|
|
50
75
|
end
|
|
51
76
|
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The whole machine as keyed snapshot modules (Snapshot::Keyed): BL-MACHINE
|
|
6
|
+
# with the setup, the cartridges' ROMs by slot, the CPU with its port,
|
|
7
|
+
# the TED, its sound, the keyboard's 6529, the user port's 6529 and the
|
|
8
|
+
# ACIA where the model has them, the TCBM interface's TIAs where one is
|
|
9
|
+
# plugged in, the RAM with the ROM bank latch and the ROM/RAM switch
|
|
10
|
+
# under bus.*, the datasette, device 8 through the traps and the true
|
|
11
|
+
# drives.
|
|
12
|
+
#
|
|
13
|
+
# A load maps the banks before the chips load, and works out what each
|
|
14
|
+
# unit derives from its fields once every unit is loaded.
|
|
15
|
+
module KeyedModules
|
|
16
|
+
def save_modules
|
|
17
|
+
bus = @bus
|
|
18
|
+
sections = [Snapshot::Keyed.section("BL-MACHINE") { |out| save_machine_fields(out) }]
|
|
19
|
+
sections << Snapshot::Keyed.section("BL-CART") { |out| bus.save_slot_fields(out) } if bus.cartridge?
|
|
20
|
+
save_chip_modules(sections)
|
|
21
|
+
sections << Snapshot::Keyed.section("BL-RAM") do |out|
|
|
22
|
+
bus.ram.save_fields(out, "ram")
|
|
23
|
+
bus.save_fields(out, "bus.")
|
|
24
|
+
end
|
|
25
|
+
sections << Snapshot::Keyed.section("BL-TAPE") { |out| @datasette.save_fields(out) }
|
|
26
|
+
sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
|
|
27
|
+
save_drive_modules(sections)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Raises Snapshot::FormatError for the modules of a machine built
|
|
31
|
+
# another way.
|
|
32
|
+
def load_modules(modules)
|
|
33
|
+
machine = modules.machine
|
|
34
|
+
check_snapshot_setup(Snapshot::Plus4Setup.read_fields(machine))
|
|
35
|
+
load_machine_fields(machine)
|
|
36
|
+
bus = @bus
|
|
37
|
+
memory = modules.required("BL-RAM")
|
|
38
|
+
bus.ram.load_fields(memory, "ram")
|
|
39
|
+
bus.load_fields(memory, "bus.")
|
|
40
|
+
bus.load_slot_fields(modules.optional("BL-CART") || Snapshot::Keyed::Reader.new("BL-CART"))
|
|
41
|
+
bus.restored!
|
|
42
|
+
load_chip_modules(modules)
|
|
43
|
+
@datasette.load_fields(modules.required("BL-TAPE"))
|
|
44
|
+
trap_dos = modules.optional("BL-TRAPDOS")
|
|
45
|
+
trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
|
|
46
|
+
load_drive_modules(modules)
|
|
47
|
+
modules_restored!
|
|
48
|
+
self
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def save_chip_modules(sections)
|
|
54
|
+
sections << Snapshot::Keyed.section("BL-CPU") do |out|
|
|
55
|
+
@cpu.save_fields(out)
|
|
56
|
+
@port.save_fields(out, "port.")
|
|
57
|
+
end
|
|
58
|
+
sections << Snapshot::Keyed.section("BL-TED") { |out| @ted.save_fields(out) }
|
|
59
|
+
sections << Snapshot::Keyed.section("BL-TEDSOUND") { |out| @sound.save_fields(out) }
|
|
60
|
+
sections << Snapshot::Keyed.section("BL-KEYPORT") { |out| @keyboard_port.save_fields(out) }
|
|
61
|
+
user_port = @user_port
|
|
62
|
+
sections << Snapshot::Keyed.section("BL-USERPORT") { |out| user_port.save_fields(out) } if user_port
|
|
63
|
+
acia = @acia
|
|
64
|
+
sections << Snapshot::Keyed.section("BL-ACIA") { |out| acia.save_fields(out) } if acia
|
|
65
|
+
@tias.each_with_index do |tia, index|
|
|
66
|
+
sections << Snapshot::Keyed.section("BL-TIA#{index + 1}") { |out| tia.save_fields(out) }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def load_chip_modules(modules)
|
|
71
|
+
cpu = modules.required("BL-CPU")
|
|
72
|
+
@port.load_fields(cpu, "port.")
|
|
73
|
+
@ted.load_fields(modules.required("BL-TED"))
|
|
74
|
+
@sound.load_fields(modules.required("BL-TEDSOUND"))
|
|
75
|
+
@keyboard_port.load_fields(modules.required("BL-KEYPORT"))
|
|
76
|
+
@user_port&.load_fields(modules.required("BL-USERPORT"))
|
|
77
|
+
@acia&.load_fields(modules.required("BL-ACIA"))
|
|
78
|
+
@tias.each_with_index { |tia, index| tia.load_fields(modules.required("BL-TIA#{index + 1}")) }
|
|
79
|
+
@cpu.load_fields(cpu)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def modules_restored!
|
|
83
|
+
@cpu.restored!
|
|
84
|
+
@ted.restored!
|
|
85
|
+
@sound.restored!(@ted)
|
|
86
|
+
@port.restored!
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def check_snapshot_setup(setup)
|
|
90
|
+
ours = Snapshot::Plus4Setup.of(self)
|
|
91
|
+
return if setup == ours
|
|
92
|
+
|
|
93
|
+
raise Snapshot::FormatError, "the snapshot is of a #{setup}, not a #{ours}"
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
include KeyedModules
|
|
98
|
+
end
|
|
99
|
+
end
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
class CPUPort
|
|
6
|
+
# The port's registers in a keyed snapshot module (Snapshot::Keyed),
|
|
7
|
+
# under `prefix`. The serial bus's lines and the datasette's write
|
|
8
|
+
# line are driven again from them once everything is loaded
|
|
9
|
+
# (#restored!), without recording an edge on the tape.
|
|
10
|
+
module KeyedState
|
|
11
|
+
def save_fields(out, prefix)
|
|
12
|
+
out.int("#{prefix}ddr", @ddr).int("#{prefix}data", @data)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def load_fields(input, prefix)
|
|
16
|
+
@ddr = input.byte("#{prefix}ddr", @ddr)
|
|
17
|
+
@data = input.byte("#{prefix}data", @data)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def restored!
|
|
21
|
+
@serial_bus.host_lines = serial_lines
|
|
22
|
+
@datasette.write_level = levels.anybits?(CLK_OUT)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
include KeyedState
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
class KeyboardPort
|
|
30
|
+
# The 6529's latch in a keyed snapshot module (BL-KEYPORT).
|
|
31
|
+
module KeyedState
|
|
32
|
+
def save_fields(out) = out.int("output", @output)
|
|
33
|
+
|
|
34
|
+
def load_fields(input)
|
|
35
|
+
@output = input.byte("output", @output)
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
include KeyedState
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class UserPort
|
|
43
|
+
# The 6529's latch in a keyed snapshot module (BL-USERPORT).
|
|
44
|
+
module KeyedState
|
|
45
|
+
def save_fields(out) = out.int("output", @output)
|
|
46
|
+
|
|
47
|
+
def load_fields(input)
|
|
48
|
+
@output = input.byte("output", @output)
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
include KeyedState
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
class ACIA
|
|
56
|
+
# The command and control registers in a keyed snapshot module
|
|
57
|
+
# (BL-ACIA).
|
|
58
|
+
module KeyedState
|
|
59
|
+
def save_fields(out) = out.int("command", @command).int("control", @control)
|
|
60
|
+
|
|
61
|
+
def load_fields(input)
|
|
62
|
+
@command = input.byte("command", @command)
|
|
63
|
+
@control = input.byte("control", @control)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
include KeyedState
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
class Bus
|
|
71
|
+
# The ROM bank latch, whether ROM is selected and the last byte on the
|
|
72
|
+
# bus, under `prefix`, and the cartridges' ROMs by slot (#save_slot_fields),
|
|
73
|
+
# in keyed snapshot modules (Snapshot::Keyed). The TED learns which
|
|
74
|
+
# memory is selected, and the pages are mapped again, once they are
|
|
75
|
+
# loaded (#restored!). The BASIC, KERNAL and function ROMs come with
|
|
76
|
+
# the model.
|
|
77
|
+
module KeyedState
|
|
78
|
+
# The cartridge slots, as xplus4 names them: cartridge 1 or 2, the
|
|
79
|
+
# low ($8000) or the high ($C000) half.
|
|
80
|
+
SLOTS = %w[c1lo c2lo c1hi c2hi].freeze
|
|
81
|
+
|
|
82
|
+
def save_fields(out, prefix)
|
|
83
|
+
out.int("#{prefix}low_bank", @low_bank).int("#{prefix}high_bank", @high_bank)
|
|
84
|
+
out.bool("#{prefix}rom_selected", @rom_selected).int("#{prefix}data", @data)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def load_fields(input, prefix)
|
|
88
|
+
@low_bank = input.masked("#{prefix}low_bank", @low_bank, 0x03)
|
|
89
|
+
@high_bank = input.masked("#{prefix}high_bank", @high_bank, 0x03)
|
|
90
|
+
@rom_selected = input.bool("#{prefix}rom_selected", @rom_selected)
|
|
91
|
+
@data = input.byte("#{prefix}data", @data)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Whether a cartridge's ROM is in any slot.
|
|
95
|
+
def cartridge?
|
|
96
|
+
index = 0
|
|
97
|
+
while index < SLOTS.length
|
|
98
|
+
return true unless slot_rom(index).nil?
|
|
99
|
+
|
|
100
|
+
index += 1
|
|
101
|
+
end
|
|
102
|
+
false
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Each slot's ROM, 16K, under its name. An empty slot writes
|
|
106
|
+
# nothing.
|
|
107
|
+
def save_slot_fields(out)
|
|
108
|
+
SLOTS.each_with_index do |name, index|
|
|
109
|
+
rom = slot_rom(index)
|
|
110
|
+
rom&.save_fields(out, name)
|
|
111
|
+
end
|
|
112
|
+
out
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# A slot the module holds nothing for is left empty.
|
|
116
|
+
def load_slot_fields(input)
|
|
117
|
+
SLOTS.each_with_index do |name, index|
|
|
118
|
+
bytes = input.bytes(name, "".b).bytes
|
|
119
|
+
if index < 2
|
|
120
|
+
@low_roms[2 + index] = slot(bytes, 0x8000)
|
|
121
|
+
else
|
|
122
|
+
@high_roms[index] = slot(bytes, 0xc000)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def restored!
|
|
128
|
+
@ted.rom_selected = @rom_selected
|
|
129
|
+
map_reads
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
private
|
|
133
|
+
|
|
134
|
+
def slot_rom(index) = index < 2 ? @low_roms[2 + index] : @high_roms[index]
|
|
135
|
+
|
|
136
|
+
# A slot's ROM from its bytes at `start`, or none.
|
|
137
|
+
def slot(bytes, start) = bytes.empty? ? nil : ROM.new(bytes, length: bytes.length, start:)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
include KeyedState
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The machine's own fields in a keyed snapshot module (Snapshot::Keyed,
|
|
6
|
+
# BL-MACHINE): the family, its setup by name (Snapshot::Plus4Setup), the
|
|
7
|
+
# cycle count, and the host's typing still to feed and on_init handlers
|
|
8
|
+
# still to run under host.*. The badline version that wrote it is
|
|
9
|
+
# written for reports and never read.
|
|
10
|
+
module KeyedState
|
|
11
|
+
# How many more on_init handlers the machine a snapshot was taken of had
|
|
12
|
+
# yet to run than this one has. Nil until the machine is restored.
|
|
13
|
+
attr_reader :init_handlers_lost
|
|
14
|
+
|
|
15
|
+
# Whether a snapshot saves now (Machine::Settling): the CPU between
|
|
16
|
+
# instructions or jammed.
|
|
17
|
+
def settled_for_snapshot? = @cpu.boundary? || @cpu.jammed?
|
|
18
|
+
|
|
19
|
+
# The sound's voices, run up to this cycle.
|
|
20
|
+
def settle_chips_for_snapshot = @sound.catch_up!
|
|
21
|
+
|
|
22
|
+
def save_machine_fields(out)
|
|
23
|
+
out.text("family", "plus4").text("written_by", VERSION)
|
|
24
|
+
Snapshot::Plus4Setup.of(self).write_fields(out)
|
|
25
|
+
out.int("cycles", @cycles)
|
|
26
|
+
pending = @pending_keys
|
|
27
|
+
out.ints("host.pending_keys", pending) if pending
|
|
28
|
+
out.int("host.init_pending", booting? ? @init_handlers.length : 0)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def load_machine_fields(input)
|
|
32
|
+
@cycles = input.int("cycles", @cycles)
|
|
33
|
+
pending = input.ints("host.pending_keys", [])
|
|
34
|
+
@pending_keys = pending.empty? ? nil : pending.dup
|
|
35
|
+
@init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
include KeyedState
|
|
40
|
+
end
|
|
41
|
+
end
|
data/lib/badline/plus4.rb
CHANGED
|
@@ -7,8 +7,13 @@ require "badline/plus4/cpu"
|
|
|
7
7
|
require "badline/plus4/cpu_port"
|
|
8
8
|
require "badline/plus4/keyboard_port"
|
|
9
9
|
require "badline/plus4/user_port"
|
|
10
|
+
require "badline/plus4/cassette_sense"
|
|
10
11
|
require "badline/plus4/acia"
|
|
11
12
|
require "badline/plus4/bus"
|
|
13
|
+
require "badline/snapshot/plus4_setup"
|
|
14
|
+
require "badline/plus4/keyed_parts"
|
|
15
|
+
require "badline/plus4/keyed_state"
|
|
16
|
+
require "badline/plus4/keyed_modules"
|
|
12
17
|
|
|
13
18
|
module Badline
|
|
14
19
|
# The Commodore 264 series: the Plus/4, the C16 and the C116. A 7501 on
|
|
@@ -17,13 +22,17 @@ module Badline
|
|
|
17
22
|
# 6529. Only the TED pulls IRQ, and nothing pulls NMI, which the 7501
|
|
18
23
|
# doesn't have.
|
|
19
24
|
#
|
|
20
|
-
#
|
|
21
|
-
# serves disks through the KERNAL traps (C64::KernalTraps),
|
|
22
|
-
#
|
|
25
|
+
# The TED paints the picture a front end shows (#video, #timing). Device 8
|
|
26
|
+
# serves disks through the KERNAL traps (C64::KernalTraps), or a true
|
|
27
|
+
# 1541 or 1581 on the serial bus does (C64::TrueDrives), cartridges go
|
|
28
|
+
# in the two cartridges' slots, and tapes play on the datasette, whose read
|
|
29
|
+
# line's level the KERNAL times with the TED's timers.
|
|
23
30
|
class Plus4
|
|
24
31
|
include IntegerHelper
|
|
25
32
|
include Machine::KeyboardBuffer
|
|
26
33
|
include Machine::Clocking
|
|
34
|
+
include Machine::Settling
|
|
35
|
+
include C64::TrueDrives
|
|
27
36
|
include C64::KernalTraps
|
|
28
37
|
|
|
29
38
|
# The cycle by which the KERNAL has booted and BASIC printed READY,
|
|
@@ -31,15 +40,42 @@ module Badline
|
|
|
31
40
|
# Plus/4, 126,000 on an NTSC one and sooner on a C16.
|
|
32
41
|
INIT_THRESHOLD = 200_000
|
|
33
42
|
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
# The cycle by which a 1541 switched on with the machine has run its
|
|
44
|
+
# DOS's reset, the RAM test and the ROM checksum, and waits on the
|
|
45
|
+
# bus, with a margin, in TED cycles.
|
|
46
|
+
DRIVE_BOOT_CYCLES = 1_200_000
|
|
47
|
+
|
|
48
|
+
attr_reader :model, :bus, :cpu, :ted, :sound, :port, :keyboard, :keyboard_port, :joystick1, :joystick2, :acia,
|
|
49
|
+
:user_port, :datasette, :tias, :iec_bus, :cycles, :ram_kb, :drive1541, :init_threshold
|
|
36
50
|
|
|
37
51
|
def family = :plus4
|
|
38
52
|
|
|
39
|
-
|
|
53
|
+
# The joysticks reach the TED's keyboard latch, so there are no C64
|
|
54
|
+
# control ports to plug a pot device into.
|
|
55
|
+
def control_ports = nil
|
|
40
56
|
|
|
41
57
|
def ram = @bus.ram
|
|
42
58
|
|
|
59
|
+
# What a front end plays: the TED's sound, which answers #record and
|
|
60
|
+
# #drain_samples.
|
|
61
|
+
def sound_source = @sound
|
|
62
|
+
|
|
63
|
+
# The chip whose #display a front end shows.
|
|
64
|
+
def video = @ted
|
|
65
|
+
|
|
66
|
+
# The TED's clock and raster, and the part of its picture xplus4 shows:
|
|
67
|
+
# all 384 pixels of a line, and 288 lines from line 276 on PAL or 242
|
|
68
|
+
# from line 248 on NTSC, running on past the frame's last line to its
|
|
69
|
+
# first, as the top border is the end of the frame. The TED draws 8
|
|
70
|
+
# pixels a cycle, so each shows one window pixel wide, as on the C64.
|
|
71
|
+
def timing
|
|
72
|
+
profile = @ted.profile
|
|
73
|
+
ntsc = profile == Chips::TED::NTSC
|
|
74
|
+
Timing.new(clock_hz: profile.clock_hz, cycles_per_line: Chips::TED::Beam::CYCLES_PER_LINE,
|
|
75
|
+
lines_per_frame: ntsc ? 262 : 312, crop: ntsc ? [0, 248, 384, 242] : [0, 276, 384, 288],
|
|
76
|
+
pixel_width: 1)
|
|
77
|
+
end
|
|
78
|
+
|
|
43
79
|
# Reads and writes memory as the CPU sees it, through the bus.
|
|
44
80
|
def peek(addr) = @bus.peek(addr)
|
|
45
81
|
|
|
@@ -56,7 +92,9 @@ module Badline
|
|
|
56
92
|
|
|
57
93
|
@ted = Chips::TED.new(@model.ted)
|
|
58
94
|
@keyboard = Input::Keyboard.new(matrix: KeyboardPort::MATRIX)
|
|
59
|
-
@
|
|
95
|
+
@joystick1 = Input::Joystick.new
|
|
96
|
+
@joystick2 = Input::Joystick.new
|
|
97
|
+
@keyboard_port = KeyboardPort.new(@keyboard, @joystick1, @joystick2)
|
|
60
98
|
@ted.keyboard_port = @keyboard_port
|
|
61
99
|
@iec_bus = Drives::SerialBus.new
|
|
62
100
|
@datasette = Drives::Datasette.new(level: true)
|
|
@@ -64,24 +102,31 @@ module Badline
|
|
|
64
102
|
plus4_parts = @model.plus4_parts
|
|
65
103
|
@acia = plus4_parts ? ACIA.new : nil
|
|
66
104
|
@user_port = plus4_parts ? UserPort.new(@datasette) : nil
|
|
67
|
-
io = Bus::IOPage.new(keyboard_port: @keyboard_port, acia: @acia, user_port: @user_port
|
|
105
|
+
io = Bus::IOPage.new(keyboard_port: @keyboard_port, acia: @acia, user_port: @user_port,
|
|
106
|
+
cassette_sense: plus4_parts ? nil : CassetteSense.new(@datasette))
|
|
68
107
|
@bus = Bus.new(ted: @ted, port: @port, io:, model: @model, ram_kb: @ram_kb)
|
|
108
|
+
@ted.connect(@bus)
|
|
69
109
|
@tias = tcbm ? [Chips::TIA.new, Chips::TIA.new] : []
|
|
70
110
|
@bus.plug_tcbm(drive8: @tias[0], drive9: @tias[1]) if tcbm
|
|
71
111
|
@cpu = CPU.new(@bus, debug:)
|
|
72
112
|
@cycles = 0
|
|
113
|
+
@sound = Chips::TED::Sound.new(self, @model.ted.clock_hz)
|
|
114
|
+
@ted.sound = @sound
|
|
73
115
|
@init_handlers = []
|
|
74
116
|
@pending_keys = nil
|
|
75
117
|
@dos = nil
|
|
76
118
|
@serial_trap = nil
|
|
77
119
|
@save_trap = nil
|
|
78
120
|
@capture_output = nil
|
|
121
|
+
@drive1541 = nil
|
|
122
|
+
@drive1581 = nil
|
|
123
|
+
@init_threshold = INIT_THRESHOLD
|
|
79
124
|
end
|
|
80
125
|
|
|
81
126
|
# One TED cycle: the TED moves on and decides the CPU's clock, then the
|
|
82
127
|
# CPU runs its cycles in it, none while the TED holds the bus.
|
|
83
128
|
def cycle!
|
|
84
|
-
handle_init if @cycles ==
|
|
129
|
+
handle_init if @cycles == @init_threshold
|
|
85
130
|
feed_keyboard if @pending_keys
|
|
86
131
|
|
|
87
132
|
ted = @ted
|
|
@@ -95,35 +140,41 @@ module Badline
|
|
|
95
140
|
clock_cpu(ted)
|
|
96
141
|
clock_cpu(ted) if slots == 2
|
|
97
142
|
end
|
|
143
|
+
@drive1541&.host_cycle!
|
|
144
|
+
@drive1581&.host_cycle!
|
|
98
145
|
|
|
99
146
|
@cycles += 1
|
|
100
147
|
end
|
|
101
148
|
|
|
102
149
|
# The RESET line: the CPU, the port, the 6529s, the ACIA, the bank
|
|
103
|
-
# latch and ROM selection
|
|
150
|
+
# latch and ROM selection, and through the serial bus's RESET line, the
|
|
151
|
+
# drives. The TED has no reset pin.
|
|
104
152
|
def reset!
|
|
105
153
|
@port.reset!
|
|
106
154
|
@keyboard_port.reset!
|
|
107
155
|
@user_port&.reset!
|
|
108
156
|
@dos&.reset!
|
|
157
|
+
@drive1541&.reset!
|
|
158
|
+
@drive1581&.reset!
|
|
109
159
|
@acia&.reset!
|
|
110
160
|
@tias.each(&:reset!)
|
|
111
161
|
@bus.reset!
|
|
112
162
|
@cpu.reset!
|
|
113
163
|
end
|
|
114
164
|
|
|
115
|
-
# Switches the machine off and on: RAM
|
|
116
|
-
# power-on state, and the RES line resets everything else.
|
|
165
|
+
# Switches the machine off and on: RAM, the TED and its sound start
|
|
166
|
+
# from their power-on state, and the RES line resets everything else.
|
|
117
167
|
def power_cycle!
|
|
118
168
|
@bus.power_on!
|
|
119
169
|
@ted.power_on!
|
|
170
|
+
@sound.power_on!
|
|
120
171
|
reset!
|
|
121
172
|
end
|
|
122
173
|
|
|
123
174
|
# Calls the block with the exit code a VICE testprog writes to $FDCF.
|
|
124
175
|
def install_debug_register(&) = @bus.install_debug_register(&)
|
|
125
176
|
|
|
126
|
-
# Puts a cartridge's ROM chips in
|
|
177
|
+
# Puts a cartridge's ROM chips in their slots (Bus#attach_cartridge)
|
|
127
178
|
# and switches the machine off and on, as a cartridge goes in with the
|
|
128
179
|
# power off. The KERNAL starts it at reset when it signs itself with CBM
|
|
129
180
|
# at $8007.
|
|
@@ -132,6 +183,24 @@ module Badline
|
|
|
132
183
|
power_cycle!
|
|
133
184
|
end
|
|
134
185
|
|
|
186
|
+
# Takes both cartridges out (Bus#detach_cartridges) and switches the
|
|
187
|
+
# machine off and on.
|
|
188
|
+
def detach_cartridges
|
|
189
|
+
@bus.detach_cartridges
|
|
190
|
+
power_cycle!
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
# The cartridge slots with a ROM in them (Bus#cartridge_slots).
|
|
194
|
+
def cartridge_slots = @bus.cartridge_slots
|
|
195
|
+
|
|
196
|
+
# The model and its RAM as the pause menu names them, such as
|
|
197
|
+
# "PLUS/4 PAL, 64K RAM".
|
|
198
|
+
def model_label
|
|
199
|
+
name = @model.plus4_parts ? "PLUS/4" : "C16"
|
|
200
|
+
standard = @model.ted == Chips::TED::NTSC ? "NTSC" : "PAL"
|
|
201
|
+
"#{name} #{standard}, #{@ram_kb}K RAM"
|
|
202
|
+
end
|
|
203
|
+
|
|
135
204
|
# Stores a PRG's bytes from its load address on, into RAM, and returns
|
|
136
205
|
# that address.
|
|
137
206
|
def load_prg(data)
|
|
@@ -152,9 +221,16 @@ module Badline
|
|
|
152
221
|
|
|
153
222
|
def trap_bus = @bus
|
|
154
223
|
|
|
155
|
-
#
|
|
156
|
-
# the bus
|
|
157
|
-
|
|
224
|
+
# A drive that powers on with the machine takes longer to boot than
|
|
225
|
+
# the KERNAL, and doesn't answer the bus until it has, so the
|
|
226
|
+
# machine's on_init handlers wait for it too.
|
|
227
|
+
def drive1541_attached
|
|
228
|
+
@init_threshold = [@init_threshold, DRIVE_BOOT_CYCLES].max if @cycles < @init_threshold
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def drive1581_attached
|
|
232
|
+
@init_threshold = [@init_threshold, Drives::Model1581::BOOT_CYCLES].max if @cycles < @init_threshold
|
|
233
|
+
end
|
|
158
234
|
|
|
159
235
|
# BA stops the CPU at a read, and a write goes ahead.
|
|
160
236
|
def clock_cpu(ted)
|
|
@@ -7,7 +7,7 @@ module Badline
|
|
|
7
7
|
# its default, with keys that differ only in a unit's number, such as
|
|
8
8
|
# voice1 to voice3, on one row.
|
|
9
9
|
class Markdown
|
|
10
|
-
NAMES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128" }.freeze
|
|
10
|
+
NAMES = { "c64" => "C64", "vic20" => "VIC-20", "c128" => "C128", "plus4" => "Plus/4" }.freeze
|
|
11
11
|
TAG_TYPES = { Keyed::INT => "int", Keyed::BOOL => "bool", Keyed::BYTES => "bytes", Keyed::INTS => "ints",
|
|
12
12
|
Keyed::TEXT => "text" }.freeze
|
|
13
13
|
|
|
@@ -31,11 +31,12 @@ module Badline
|
|
|
31
31
|
`int & 0x..` are ints cut to the latch's width. A text that lists
|
|
32
32
|
its values takes one of them. An optional int is absent when the
|
|
33
33
|
latch holds nothing. A true drive's modules are named from its
|
|
34
|
-
device number, `BL-D8` to `BL-D11
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
device number, `BL-D8` to `BL-D11`, and the SIDs past the first
|
|
35
|
+
are `BL-SID2` onwards, in the order they were fitted. Keys with
|
|
36
|
+
`<N>` stand for each numbered unit. A default of "written only" is
|
|
37
|
+
a key the load doesn't read, or reads only when the snapshot has
|
|
38
|
+
it. Where the machines differ, the default is given for each, and a
|
|
39
|
+
key only some of them have names them.
|
|
39
40
|
MARKDOWN
|
|
40
41
|
|
|
41
42
|
def initialize(loads)
|