badline 0.14.0 → 0.15.1
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 +86 -0
- data/CLAUDE.md +22 -5
- data/README.md +101 -11
- data/doc/media.md +19 -0
- data/doc/pinned-behaviour.md +145 -0
- data/doc/snapshot-format.md +244 -46
- data/doc/snapshots.md +18 -1
- data/exe/badline-ruby +1 -1
- data/lib/badline/audio/bare_player.rb +4 -8
- data/lib/badline/audio/machine_player.rb +4 -26
- 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/c64/true_drives.rb +13 -1
- data/lib/badline/c64.rb +6 -0
- 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 +28 -0
- data/lib/badline/chips/vic/register_log.rb +4 -1
- data/lib/badline/drives/datasette/keyed_state.rb +14 -4
- data/lib/badline/drives/datasette.rb +22 -5
- data/lib/badline/drives/engine/core.rb +6 -14
- data/lib/badline/drives/engine/idle.rb +42 -4
- data/lib/badline/drives/engine/idle_cpu.rb +18 -0
- data/lib/badline/drives/engine/serial_connection.rb +25 -0
- data/lib/badline/drives/engine/sleep.rb +1 -0
- data/lib/badline/drives/engine.rb +1 -0
- data/lib/badline/drives/model1541/byte_ready.rb +19 -3
- data/lib/badline/drives/model1541/mechanism.rb +10 -1
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551/bus.rb +138 -0
- data/lib/badline/drives/model1551/cable.rb +104 -0
- data/lib/badline/drives/model1551/cpu.rb +14 -0
- data/lib/badline/drives/model1551/keyed_state.rb +65 -0
- data/lib/badline/drives/model1551/port.rb +74 -0
- data/lib/badline/drives/model1551/tia.rb +92 -0
- data/lib/badline/drives/model1551.rb +170 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/frontend/boot.rb +23 -2
- 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 +14 -5
- data/lib/badline/frontend/menu/menu_media.rb +102 -14
- data/lib/badline/frontend/menu/menu_pages.rb +16 -11
- data/lib/badline/frontend/menu/menu_ports.rb +13 -1
- data/lib/badline/frontend/player/player_controller.rb +11 -4
- 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 +30 -6
- data/lib/badline/media/plus4_media.rb +68 -0
- data/lib/badline/media/program.rb +20 -6
- data/lib/badline/media/true_drive.rb +7 -5
- data/lib/badline/media.rb +33 -12
- data/lib/badline/options/family.rb +1 -0
- data/lib/badline/options/help.rb +32 -6
- data/lib/badline/options/option.rb +2 -1
- data/lib/badline/options/table.rb +29 -4
- data/lib/badline/options/validation.rb +66 -4
- data/lib/badline/options.rb +71 -11
- data/lib/badline/plus4/acia/frames.rb +55 -0
- data/lib/badline/plus4/acia.rb +276 -16
- data/lib/badline/plus4/bus.rb +245 -46
- 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 +116 -0
- data/lib/badline/plus4/keyed_parts.rb +172 -0
- data/lib/badline/plus4/keyed_state.rb +44 -0
- data/lib/badline/plus4/ram_expansion.rb +145 -0
- data/lib/badline/plus4/sid_card.rb +95 -0
- data/lib/badline/plus4/sound_mix.rb +51 -0
- data/lib/badline/plus4/tcbm_drive.rb +120 -0
- data/lib/badline/plus4/user_port.rb +11 -2
- data/lib/badline/plus4.rb +184 -26
- data/lib/badline/roms/README +9 -0
- data/lib/badline/roms/dos1551.rom +0 -0
- 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 +48 -7
- data/lib/badline/snapshot/image.rb +12 -3
- data/lib/badline/snapshot/plus4_setup.rb +81 -0
- data/lib/badline/snapshot.rb +9 -4
- 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 +29 -1
data/lib/badline/plus4/bus.rb
CHANGED
|
@@ -10,9 +10,14 @@ module Badline
|
|
|
10
10
|
# $C000-$FCFF - the high ROM bank, or RAM; $FC00-$FCFF is always the
|
|
11
11
|
# KERNAL's while ROM is selected
|
|
12
12
|
# $FD00-$FD0F - the 6551 ACIA (Plus/4 only)
|
|
13
|
-
# $FD10-$FD1F - the user port's 6529 (Plus/4 only)
|
|
13
|
+
# $FD10-$FD1F - the user port's 6529 (Plus/4 only), or with a RAM
|
|
14
|
+
# expansion, the 6529 at $FD10 and the expansion's
|
|
15
|
+
# registers at $FD15 and $FD16
|
|
14
16
|
# $FD30-$FD3F - the keyboard's 6529 (KeyboardPort)
|
|
17
|
+
# $FD40-$FD5F - a SID card's SID, where one is fitted there
|
|
15
18
|
# $FDD0-$FDDF - the ROM bank latch
|
|
19
|
+
# $FE80-$FE9F - a SID card's SID, where one is fitted there
|
|
20
|
+
# $FEC0-$FEFF - a 1551's TCBM interface, where one is plugged in
|
|
16
21
|
# $FF00-$FF1F - the TED
|
|
17
22
|
# $FF3E/$FF3F - a write selects ROM or RAM at $8000-$FFFF
|
|
18
23
|
# $FF40-$FFFF - the high ROM bank, or RAM
|
|
@@ -27,23 +32,32 @@ module Badline
|
|
|
27
32
|
#
|
|
28
33
|
# RAM smaller than 64K repeats through the 64K, as the C16's 16K does
|
|
29
34
|
# without A14 and A15. Where nothing answers, a read finds the last byte
|
|
30
|
-
# on the bus.
|
|
35
|
+
# on the bus. A RAM expansion (RAMExpansion) picks the RAM each page
|
|
36
|
+
# reads and writes, the CPU's and the TED's.
|
|
37
|
+
#
|
|
38
|
+
# The TED fetches through the same banks: the video matrix and the
|
|
39
|
+
# attributes from ROM or RAM at $8000-$FFFF as the CPU sees it, with no
|
|
40
|
+
# I/O in the way (#ted_fetch), and character and bitmap data from RAM,
|
|
41
|
+
# or from ROM with $FF12 bit 2 set, where below $8000 and in an empty
|
|
42
|
+
# bank it finds the last byte on the bus (#ted_pattern_fetch, Plus/4
|
|
43
|
+
# openmem). Each byte it fetches is left on the bus.
|
|
31
44
|
class Bus
|
|
32
45
|
include Addressable
|
|
33
46
|
|
|
34
47
|
RAM_POWER_ON = 0x00
|
|
35
48
|
|
|
36
|
-
# RAM through a size it repeats at
|
|
49
|
+
# RAM through a size it repeats at, from +offset+ on in +ram+.
|
|
37
50
|
class MirroredRAM
|
|
38
|
-
def initialize(ram, mask)
|
|
51
|
+
def initialize(ram, mask, offset = 0)
|
|
39
52
|
@ram = ram
|
|
40
53
|
@mask = mask
|
|
54
|
+
@offset = offset
|
|
41
55
|
end
|
|
42
56
|
|
|
43
|
-
def peek(addr) = @ram.peek(addr & @mask)
|
|
57
|
+
def peek(addr) = @ram.peek((addr & @mask) + @offset)
|
|
44
58
|
|
|
45
59
|
def poke(addr, value)
|
|
46
|
-
@ram.poke(addr & @mask, value)
|
|
60
|
+
@ram.poke((addr & @mask) + @offset, value)
|
|
47
61
|
end
|
|
48
62
|
end
|
|
49
63
|
|
|
@@ -73,14 +87,22 @@ module Badline
|
|
|
73
87
|
end
|
|
74
88
|
end
|
|
75
89
|
|
|
76
|
-
# Page $FD: the ACIA, the user port, the keyboard port
|
|
77
|
-
# bank latch, by the address's bits 4-7. +acia
|
|
78
|
-
# nil where the machine has none
|
|
90
|
+
# Page $FD: the ACIA, the user port, the keyboard port, a SID card and
|
|
91
|
+
# the ROM bank latch, by the address's bits 4-7. +acia+, +user_port+
|
|
92
|
+
# and +sid_card+ are nil where the machine has none, and
|
|
93
|
+
# +cassette_sense+ answers in the user port's place on a machine
|
|
94
|
+
# without one (CassetteSense). The SID card decodes its 32 bytes
|
|
95
|
+
# alone, with no mirrors, as xplus4's does. A RAM expansion
|
|
96
|
+
# (RAMExpansion) leaves the user port only $FD10 of $FD10-$FD1F, and
|
|
97
|
+
# has its register at $FD16.
|
|
79
98
|
class IOPage
|
|
80
|
-
def initialize(keyboard_port:, acia: nil, user_port: nil)
|
|
99
|
+
def initialize(keyboard_port:, acia: nil, user_port: nil, cassette_sense: nil, sid_card: nil)
|
|
81
100
|
@bus = nil
|
|
101
|
+
@ram_expansion = nil
|
|
82
102
|
@acia = acia
|
|
103
|
+
@sid_card = sid_card
|
|
83
104
|
@user_port = user_port
|
|
105
|
+
@cassette_sense = cassette_sense
|
|
84
106
|
@keyboard_port = keyboard_port
|
|
85
107
|
end
|
|
86
108
|
|
|
@@ -88,13 +110,15 @@ module Badline
|
|
|
88
110
|
# a write to $FDD0-$FDDF sets.
|
|
89
111
|
def connect(bus)
|
|
90
112
|
@bus = bus
|
|
113
|
+
@ram_expansion = bus.ram_expansion
|
|
91
114
|
end
|
|
92
115
|
|
|
93
116
|
def peek(addr)
|
|
94
117
|
case addr & 0xf0
|
|
95
118
|
when 0x00 then @acia ? @acia.peek(addr) : @bus.data
|
|
96
|
-
when 0x10 then @
|
|
119
|
+
when 0x10 then @ram_expansion ? expansion_peek(addr) : user_port_peek(addr)
|
|
97
120
|
when 0x30 then @keyboard_port.peek(addr)
|
|
121
|
+
when 0x40, 0x50 then @sid_card ? @sid_card.peek(addr) : @bus.data
|
|
98
122
|
else @bus.data
|
|
99
123
|
end
|
|
100
124
|
end
|
|
@@ -102,11 +126,37 @@ module Badline
|
|
|
102
126
|
def poke(addr, value)
|
|
103
127
|
case addr & 0xf0
|
|
104
128
|
when 0x00 then @acia&.poke(addr, value)
|
|
105
|
-
when 0x10 then @user_port&.poke(addr, value)
|
|
129
|
+
when 0x10 then @ram_expansion ? expansion_poke(addr, value) : @user_port&.poke(addr, value)
|
|
106
130
|
when 0x30 then @keyboard_port.poke(addr, value)
|
|
131
|
+
when 0x40, 0x50 then @sid_card&.poke(addr, value)
|
|
107
132
|
when 0xd0 then @bus.select_banks(addr)
|
|
108
133
|
end
|
|
109
134
|
end
|
|
135
|
+
|
|
136
|
+
private
|
|
137
|
+
|
|
138
|
+
def expansion_peek(addr)
|
|
139
|
+
case addr & 0x0f
|
|
140
|
+
when 0x00 then user_port_peek(addr)
|
|
141
|
+
when RAMExpansion::CONTROL then @ram_expansion.control
|
|
142
|
+
else @bus.data
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def expansion_poke(addr, value)
|
|
147
|
+
if addr.nobits?(0x0f)
|
|
148
|
+
@user_port&.poke(addr, value)
|
|
149
|
+
else
|
|
150
|
+
@bus.ram_expansion_written(addr, value)
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def user_port_peek(addr)
|
|
155
|
+
return @user_port.peek(addr) if @user_port
|
|
156
|
+
return @cassette_sense.read(@bus.data) if @cassette_sense
|
|
157
|
+
|
|
158
|
+
@bus.data
|
|
159
|
+
end
|
|
110
160
|
end
|
|
111
161
|
|
|
112
162
|
# VICE's debug cartridge register at $FDCF, a spot in the I/O page
|
|
@@ -130,26 +180,50 @@ module Badline
|
|
|
130
180
|
end
|
|
131
181
|
end
|
|
132
182
|
|
|
133
|
-
# Page $FE
|
|
134
|
-
# that talks to drive 9 at $FEC0-$FEDF and drive 8's at $FEE0-$FEFF
|
|
135
|
-
#
|
|
183
|
+
# Page $FE, where a 1551's TCBM interface puts its 6523 TIA: the one
|
|
184
|
+
# that talks to drive 9 at $FEC0-$FEDF and drive 8's at $FEE0-$FEFF,
|
|
185
|
+
# and where a SID card can put its SID, at $FE80-$FE9F, decoding its
|
|
186
|
+
# 32 bytes alone as xplus4's does. The rest of the page, and a place
|
|
187
|
+
# with nothing in it, is open.
|
|
136
188
|
class TCBMPage
|
|
137
|
-
|
|
189
|
+
# The TIA at $FEE0-$FEFF and the one at $FEC0-$FEDF, or nil.
|
|
190
|
+
attr_accessor :drive8, :drive9
|
|
191
|
+
|
|
192
|
+
# The SIDCard at $FE80-$FE9F, or nil.
|
|
193
|
+
attr_accessor :sid_card
|
|
194
|
+
|
|
195
|
+
def initialize(bus)
|
|
138
196
|
@bus = bus
|
|
139
|
-
@drive8 =
|
|
140
|
-
@drive9 =
|
|
197
|
+
@drive8 = nil
|
|
198
|
+
@drive9 = nil
|
|
199
|
+
@sid_card = nil
|
|
141
200
|
end
|
|
142
201
|
|
|
143
202
|
def peek(addr)
|
|
144
|
-
return
|
|
203
|
+
return low_peek(addr) if addr < 0xfec0
|
|
145
204
|
|
|
146
|
-
addr < 0xfee0 ? @drive9
|
|
205
|
+
tia = addr < 0xfee0 ? @drive9 : @drive8
|
|
206
|
+
tia ? tia.peek(addr) : @bus.data
|
|
147
207
|
end
|
|
148
208
|
|
|
149
209
|
def poke(addr, value)
|
|
150
|
-
|
|
210
|
+
if addr < 0xfec0
|
|
211
|
+
sid_card = @sid_card
|
|
212
|
+
sid_card.poke(addr, value) if sid_card && sid_card?(addr)
|
|
213
|
+
return
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
tia = addr < 0xfee0 ? @drive9 : @drive8
|
|
217
|
+
tia&.poke(addr, value)
|
|
218
|
+
end
|
|
151
219
|
|
|
152
|
-
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def sid_card?(addr) = (addr & 0xe0) == 0x80
|
|
223
|
+
|
|
224
|
+
def low_peek(addr)
|
|
225
|
+
sid_card = @sid_card
|
|
226
|
+
sid_card && sid_card?(addr) ? sid_card.peek(addr) : @bus.data
|
|
153
227
|
end
|
|
154
228
|
end
|
|
155
229
|
|
|
@@ -168,7 +242,7 @@ module Badline
|
|
|
168
242
|
end
|
|
169
243
|
|
|
170
244
|
def poke(addr, value)
|
|
171
|
-
return @bus.
|
|
245
|
+
return @bus.top_ram.poke(addr, value) if addr >= 0xff40
|
|
172
246
|
|
|
173
247
|
case addr
|
|
174
248
|
when 0xff3e then @bus.rom_selected = true
|
|
@@ -183,17 +257,29 @@ module Badline
|
|
|
183
257
|
# What the top page reads above $FF3F: the high ROM bank or RAM.
|
|
184
258
|
attr_reader :top_memory
|
|
185
259
|
|
|
260
|
+
# The RAM the CPU sees at $FF00-$FFFF, which a write above $FF3F
|
|
261
|
+
# reaches.
|
|
262
|
+
attr_reader :top_ram
|
|
263
|
+
|
|
264
|
+
# The RAMExpansion fitted, or nil.
|
|
265
|
+
attr_reader :ram_expansion
|
|
266
|
+
|
|
186
267
|
# The CPU sees ROM at $8000-$FFFF.
|
|
187
268
|
attr_reader :rom_selected
|
|
188
269
|
|
|
189
270
|
# +io+ is the IOPage, +model+ a Model profile, whose KERNAL it maps
|
|
190
271
|
# and whose function ROMs it maps where the model has them, and
|
|
191
|
-
# +ram_kb+ the RAM fitted.
|
|
192
|
-
|
|
272
|
+
# +ram_kb+ the RAM fitted. +ram_expansion+ names the kind of
|
|
273
|
+
# RAMExpansion to fit over 64K of it.
|
|
274
|
+
def initialize(ted:, port:, io:, model: Model::PLUS4, ram_kb: model.ram_kb, ram_expansion: nil)
|
|
193
275
|
addressable_at(0, length: 2**16)
|
|
194
276
|
size = ram_kb * 1024
|
|
195
277
|
@ram = Memory.new(Array.new(size, RAM_POWER_ON), length: size, start: 0)
|
|
196
278
|
@ram_view = MirroredRAM.new(@ram, size - 1)
|
|
279
|
+
@ram_expansion = ram_expansion ? RAMExpansion.new(ram_expansion, @ram, @ram_view) : nil
|
|
280
|
+
@cpu_ram = Array.new(256, @ram_view)
|
|
281
|
+
@ted_ram = Array.new(256, @ram_view)
|
|
282
|
+
@top_ram = @ram_view
|
|
197
283
|
@ted = ted
|
|
198
284
|
@open_bus = OpenBus.new(self)
|
|
199
285
|
@low_roms = [ROM.load("plus4/basic.rom", 0x8000), nil, nil, nil]
|
|
@@ -202,31 +288,44 @@ module Badline
|
|
|
202
288
|
@low_roms[1] = ROM.load("plus4/function-low.rom", 0x8000)
|
|
203
289
|
@high_roms[1] = ROM.load("plus4/function-high.rom", 0xc000)
|
|
204
290
|
end
|
|
291
|
+
@port = port
|
|
205
292
|
@zero_page = ZeroPage.new(port, @ram_view)
|
|
206
293
|
@io_page = io
|
|
207
294
|
io.connect(self)
|
|
208
295
|
@top_page = TopPage.new(self, ted)
|
|
209
|
-
@tcbm_page =
|
|
296
|
+
@tcbm_page = TCBMPage.new(self)
|
|
210
297
|
@debug_register = nil
|
|
211
298
|
@data = 0
|
|
212
299
|
@read_pages = Array.new(256, @open_bus)
|
|
213
300
|
@write_pages = Array.new(256, @ram_view)
|
|
301
|
+
@ted_pages = Array.new(256, @ram_view)
|
|
302
|
+
@ted_rom_pages = Array.new(256, @open_bus)
|
|
214
303
|
@low_bank = @high_bank = 0
|
|
215
304
|
@rom_selected = true
|
|
216
305
|
@top_memory = @ram_view
|
|
217
|
-
|
|
218
|
-
map_reads
|
|
306
|
+
map_ram
|
|
219
307
|
end
|
|
220
308
|
|
|
221
309
|
def power_on!
|
|
222
310
|
@ram.clear!(Array.new(@ram.length, RAM_POWER_ON))
|
|
311
|
+
@ram_expansion&.power_on!
|
|
223
312
|
reset!
|
|
224
313
|
end
|
|
225
314
|
|
|
226
|
-
# The RESET line selects ROM and the first bank of each
|
|
315
|
+
# The RESET line selects ROM and the first bank of each, and sets a
|
|
316
|
+
# RAM expansion's register.
|
|
227
317
|
def reset!
|
|
228
318
|
@low_bank = @high_bank = 0
|
|
229
|
-
|
|
319
|
+
@ram_expansion&.reset!
|
|
320
|
+
@rom_selected = true
|
|
321
|
+
@ted.rom_selected = true
|
|
322
|
+
map_ram
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
# A write to a RAM expansion's registers in $FD11-$FD1F.
|
|
326
|
+
def ram_expansion_written(addr, value)
|
|
327
|
+
@ram_expansion.poke(addr, value)
|
|
328
|
+
map_ram
|
|
230
329
|
end
|
|
231
330
|
|
|
232
331
|
def rom_selected=(selected)
|
|
@@ -254,26 +353,64 @@ module Badline
|
|
|
254
353
|
|
|
255
354
|
# Plugs in a 1551's TCBM interface, the TIAs at $FEC0-$FEFF.
|
|
256
355
|
def plug_tcbm(drive8:, drive9:)
|
|
257
|
-
@tcbm_page =
|
|
258
|
-
|
|
259
|
-
map_reads
|
|
356
|
+
@tcbm_page.drive8 = drive8
|
|
357
|
+
@tcbm_page.drive9 = drive9
|
|
260
358
|
end
|
|
261
359
|
|
|
262
|
-
# Puts a
|
|
263
|
-
|
|
264
|
-
|
|
360
|
+
# Puts a SIDCard at $FE80-$FE9F.
|
|
361
|
+
def plug_sid_card(sid_card)
|
|
362
|
+
@tcbm_page.sid_card = sid_card
|
|
363
|
+
end
|
|
364
|
+
|
|
365
|
+
# Puts +tia+ where the TCBM interface for +device+, 8 or 9, answers,
|
|
366
|
+
# or takes the one there out with nil.
|
|
367
|
+
def plug_tia(device, tia)
|
|
368
|
+
if device == 9
|
|
369
|
+
@tcbm_page.drive9 = tia
|
|
370
|
+
else
|
|
371
|
+
@tcbm_page.drive8 = tia
|
|
372
|
+
end
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Puts a cartridge's ROM chips in their slots, as a PLUS4 CARTRIDGE
|
|
376
|
+
# .crt numbers them: a chip's bank picks cartridge 1 (0) or 2 (1), in
|
|
377
|
+
# ROM banks 2 and 3, and its address the low ($8000) or the high
|
|
378
|
+
# ($C000) slot. A chip shorter than 16K repeats through the slot.
|
|
379
|
+
# Slots the chips don't name keep what they held.
|
|
265
380
|
def attach_cartridge(chips)
|
|
266
381
|
chips.each do |chip|
|
|
267
|
-
|
|
382
|
+
raise ArgumentError, "no Plus/4 cartridge slot in bank #{chip.bank}" unless chip.bank.between?(0, 1)
|
|
383
|
+
|
|
384
|
+
rom = chip.data
|
|
385
|
+
length = rom.length
|
|
386
|
+
data = Array.new(0x4000) { |i| length.zero? ? 0xff : rom[i % length] }
|
|
268
387
|
if chip.address < 0xc000
|
|
269
|
-
@low_roms[2] = ROM.new(data, length: 0x4000, start: 0x8000)
|
|
388
|
+
@low_roms[2 + chip.bank] = ROM.new(data, length: 0x4000, start: 0x8000)
|
|
270
389
|
else
|
|
271
|
-
@high_roms[2] = ROM.new(data, length: 0x4000, start: 0xc000)
|
|
390
|
+
@high_roms[2 + chip.bank] = ROM.new(data, length: 0x4000, start: 0xc000)
|
|
272
391
|
end
|
|
273
392
|
end
|
|
274
393
|
map_reads
|
|
275
394
|
end
|
|
276
395
|
|
|
396
|
+
# Empties both cartridges' slots, ROM banks 2 and 3, which then read
|
|
397
|
+
# as the open bus.
|
|
398
|
+
def detach_cartridges
|
|
399
|
+
@low_roms[2] = @low_roms[3] = @high_roms[2] = @high_roms[3] = nil
|
|
400
|
+
map_reads
|
|
401
|
+
end
|
|
402
|
+
|
|
403
|
+
# The slots that hold a cartridge's ROM, by xplus4's names: c1lo,
|
|
404
|
+
# c1hi, c2lo and c2hi.
|
|
405
|
+
def cartridge_slots
|
|
406
|
+
slots = []
|
|
407
|
+
slots << "c1lo" unless @low_roms[2].nil?
|
|
408
|
+
slots << "c1hi" unless @high_roms[2].nil?
|
|
409
|
+
slots << "c2lo" unless @low_roms[3].nil?
|
|
410
|
+
slots << "c2hi" unless @high_roms[3].nil?
|
|
411
|
+
slots
|
|
412
|
+
end
|
|
413
|
+
|
|
277
414
|
def peek(addr)
|
|
278
415
|
@data = @read_pages[addr >> 8].peek(addr)
|
|
279
416
|
end
|
|
@@ -283,9 +420,42 @@ module Badline
|
|
|
283
420
|
@write_pages[addr >> 8].poke(addr, value)
|
|
284
421
|
end
|
|
285
422
|
|
|
423
|
+
# A TED fetch of the video matrix or the attributes.
|
|
424
|
+
def ted_fetch(addr)
|
|
425
|
+
@data = @ted_pages[addr >> 8].peek(addr)
|
|
426
|
+
end
|
|
427
|
+
|
|
428
|
+
# A TED fetch of character or bitmap data, from ROM when +rom+.
|
|
429
|
+
def ted_pattern_fetch(addr, rom)
|
|
430
|
+
@data = rom ? @ted_rom_pages[addr >> 8].peek(addr) : @ted_ram[addr >> 8].peek(addr)
|
|
431
|
+
end
|
|
432
|
+
|
|
286
433
|
private
|
|
287
434
|
|
|
435
|
+
# Lays out the RAM a RAM expansion banks in, then every page.
|
|
436
|
+
def map_ram
|
|
437
|
+
expansion = @ram_expansion
|
|
438
|
+
if expansion
|
|
439
|
+
expansion.map(@cpu_ram, @ted_ram)
|
|
440
|
+
@zero_page = ZeroPage.new(@port, @cpu_ram[0])
|
|
441
|
+
@top_ram = @cpu_ram[0xff]
|
|
442
|
+
end
|
|
443
|
+
map_writes
|
|
444
|
+
map_reads
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# Copies the RAM the CPU sees into +pages+, up to the I/O pages.
|
|
448
|
+
def copy_ram(pages)
|
|
449
|
+
cpu_ram = @cpu_ram
|
|
450
|
+
page = 1
|
|
451
|
+
while page < 0xfd
|
|
452
|
+
pages[page] = cpu_ram[page]
|
|
453
|
+
page += 1
|
|
454
|
+
end
|
|
455
|
+
end
|
|
456
|
+
|
|
288
457
|
def map_writes
|
|
458
|
+
copy_ram(@write_pages) if @ram_expansion
|
|
289
459
|
@write_pages[0x00] = @zero_page
|
|
290
460
|
@write_pages[0xfd] = @debug_register || @io_page
|
|
291
461
|
@write_pages[0xfe] = @tcbm_page
|
|
@@ -293,19 +463,48 @@ module Badline
|
|
|
293
463
|
end
|
|
294
464
|
|
|
295
465
|
def map_reads
|
|
296
|
-
@
|
|
466
|
+
if @ram_expansion
|
|
467
|
+
copy_ram(@read_pages)
|
|
468
|
+
else
|
|
469
|
+
@read_pages.fill(@ram_view, 0x00, 0xfd)
|
|
470
|
+
end
|
|
297
471
|
@read_pages[0x00] = @zero_page
|
|
298
|
-
map_roms if @rom_selected
|
|
299
|
-
@top_memory = @rom_selected ? @high_roms[@high_bank] || @open_bus : @
|
|
472
|
+
map_roms(@read_pages, @high_bank) if @rom_selected
|
|
473
|
+
@top_memory = @rom_selected ? @high_roms[@high_bank] || @open_bus : @top_ram
|
|
300
474
|
@read_pages[0xfd] = @io_page
|
|
301
475
|
@read_pages[0xfe] = @tcbm_page
|
|
302
476
|
@read_pages[0xff] = @top_page
|
|
477
|
+
map_ted_reads
|
|
478
|
+
end
|
|
479
|
+
|
|
480
|
+
def map_ted_reads
|
|
481
|
+
expansion = @ram_expansion
|
|
482
|
+
if expansion
|
|
483
|
+
copy_ted_ram
|
|
484
|
+
else
|
|
485
|
+
@ted_pages.fill(@ram_view, 0x80, 0x80)
|
|
486
|
+
end
|
|
487
|
+
high_bank = expansion&.kernal_for_ted? ? 0 : @high_bank
|
|
488
|
+
high_rom = @high_roms[high_bank] || @open_bus
|
|
489
|
+
map_roms(@ted_pages, high_bank) if @rom_selected
|
|
490
|
+
map_roms(@ted_rom_pages, high_bank)
|
|
491
|
+
@ted_pages.fill(high_rom, 0xfd, 3) if @rom_selected
|
|
492
|
+
@ted_rom_pages.fill(high_rom, 0xfd, 3)
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
def copy_ted_ram
|
|
496
|
+
ted_ram = @ted_ram
|
|
497
|
+
page = 0
|
|
498
|
+
while page < 0x100
|
|
499
|
+
@ted_pages[page] = ted_ram[page]
|
|
500
|
+
page += 1
|
|
501
|
+
end
|
|
303
502
|
end
|
|
304
503
|
|
|
305
|
-
def map_roms
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
504
|
+
def map_roms(pages, high_bank)
|
|
505
|
+
pages.fill(@low_roms[@low_bank] || @open_bus, 0x80, 0x40)
|
|
506
|
+
pages.fill(@high_roms[high_bank] || @open_bus, 0xc0, 0x3c)
|
|
507
|
+
pages[0xfc] = @high_roms[0]
|
|
309
508
|
end
|
|
310
509
|
end
|
|
311
510
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# The C16's and C116's cassette sense, which has no 6529 to go through:
|
|
6
|
+
# a read of $FD10-$FD1F enables a 74LS125 that drives the sense line
|
|
7
|
+
# onto data bit 2, low while a key is down on the deck (C16 schematic
|
|
8
|
+
# 251788, sheet 1, U11). Nothing drives the other bits, which keep the
|
|
9
|
+
# last byte on the bus, and a write goes nowhere.
|
|
10
|
+
class CassetteSense
|
|
11
|
+
TAPE_SENSE = 0x04
|
|
12
|
+
|
|
13
|
+
def initialize(datasette)
|
|
14
|
+
@datasette = datasette
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# The byte a read finds, from +data+, the last byte on the bus.
|
|
18
|
+
def read(data) = @datasette.sense_low? ? data & ~TAPE_SENSE : data | TAPE_SENSE
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -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,116 @@
|
|
|
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 SID card's SID where one is fitted, the
|
|
8
|
+
# keyboard's 6529, the user port's 6529 and the ACIA where the model
|
|
9
|
+
# has them, the TCBM interface's TIAs where one is plugged in, the RAM
|
|
10
|
+
# with the ROM bank latch and the ROM/RAM switch under bus.*, a RAM
|
|
11
|
+
# expansion's registers and banks, the datasette, device 8 through the
|
|
12
|
+
# traps and the true drives.
|
|
13
|
+
#
|
|
14
|
+
# A load maps the banks before the chips load, and works out what each
|
|
15
|
+
# unit derives from its fields once every unit is loaded.
|
|
16
|
+
module KeyedModules
|
|
17
|
+
def save_modules
|
|
18
|
+
bus = @bus
|
|
19
|
+
sections = [Snapshot::Keyed.section("BL-MACHINE") { |out| save_machine_fields(out) }]
|
|
20
|
+
sections << Snapshot::Keyed.section("BL-CART") { |out| bus.save_slot_fields(out) } if bus.cartridge?
|
|
21
|
+
save_chip_modules(sections)
|
|
22
|
+
sections << Snapshot::Keyed.section("BL-RAM") do |out|
|
|
23
|
+
bus.ram.save_fields(out, "ram")
|
|
24
|
+
bus.save_fields(out, "bus.")
|
|
25
|
+
end
|
|
26
|
+
expansion = bus.ram_expansion
|
|
27
|
+
sections << Snapshot::Keyed.section("BL-RAMEXP") { |out| expansion.save_fields(out) } if expansion
|
|
28
|
+
sections << Snapshot::Keyed.section("BL-TAPE") { |out| @datasette.save_fields(out) }
|
|
29
|
+
sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
|
|
30
|
+
save_drive_modules(sections)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Raises Snapshot::FormatError for the modules of a machine built
|
|
34
|
+
# another way.
|
|
35
|
+
def load_modules(modules)
|
|
36
|
+
machine = modules.machine
|
|
37
|
+
check_snapshot_setup(Snapshot::Plus4Setup.read_fields(machine))
|
|
38
|
+
load_machine_fields(machine)
|
|
39
|
+
bus = @bus
|
|
40
|
+
memory = modules.required("BL-RAM")
|
|
41
|
+
bus.ram.load_fields(memory, "ram")
|
|
42
|
+
bus.load_fields(memory, "bus.")
|
|
43
|
+
bus.load_slot_fields(modules.optional("BL-CART") || Snapshot::Keyed::Reader.new("BL-CART"))
|
|
44
|
+
load_ram_expansion(modules)
|
|
45
|
+
bus.restored!
|
|
46
|
+
load_chip_modules(modules)
|
|
47
|
+
@datasette.load_fields(modules.required("BL-TAPE"))
|
|
48
|
+
trap_dos = modules.optional("BL-TRAPDOS")
|
|
49
|
+
trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
|
|
50
|
+
load_drive_modules(modules)
|
|
51
|
+
modules_restored!
|
|
52
|
+
self
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def load_ram_expansion(modules)
|
|
58
|
+
expansion = @bus.ram_expansion
|
|
59
|
+
return if expansion.nil?
|
|
60
|
+
|
|
61
|
+
expansion.load_fields(modules.required("BL-RAMEXP"))
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def save_chip_modules(sections)
|
|
65
|
+
sections << Snapshot::Keyed.section("BL-CPU") do |out|
|
|
66
|
+
@cpu.save_fields(out)
|
|
67
|
+
@port.save_fields(out, "port.")
|
|
68
|
+
end
|
|
69
|
+
sections << Snapshot::Keyed.section("BL-TED") { |out| @ted.save_fields(out) }
|
|
70
|
+
sections << Snapshot::Keyed.section("BL-TEDSOUND") { |out| @sound.save_fields(out) }
|
|
71
|
+
sid_card = @sid_card
|
|
72
|
+
sections << Snapshot::Keyed.section("BL-SID") { |out| sid_card.save_fields(out) } if sid_card
|
|
73
|
+
sections << Snapshot::Keyed.section("BL-KEYPORT") { |out| @keyboard_port.save_fields(out) }
|
|
74
|
+
user_port = @user_port
|
|
75
|
+
sections << Snapshot::Keyed.section("BL-USERPORT") { |out| user_port.save_fields(out) } if user_port
|
|
76
|
+
acia = @acia
|
|
77
|
+
sections << Snapshot::Keyed.section("BL-ACIA") { |out| acia.save_fields(out) } if acia
|
|
78
|
+
@tias.each_with_index do |tia, index|
|
|
79
|
+
sections << Snapshot::Keyed.section("BL-TIA#{index + 1}") { |out| tia.save_fields(out) }
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def load_chip_modules(modules)
|
|
84
|
+
cpu = modules.required("BL-CPU")
|
|
85
|
+
@port.load_fields(cpu, "port.")
|
|
86
|
+
@ted.load_fields(modules.required("BL-TED"))
|
|
87
|
+
@sound.load_fields(modules.required("BL-TEDSOUND"))
|
|
88
|
+
@sid_card&.load_fields(modules.required("BL-SID"))
|
|
89
|
+
@keyboard_port.load_fields(modules.required("BL-KEYPORT"))
|
|
90
|
+
@user_port&.load_fields(modules.required("BL-USERPORT"))
|
|
91
|
+
@acia&.load_fields(modules.required("BL-ACIA"))
|
|
92
|
+
@tias.each_with_index { |tia, index| tia.load_fields(modules.required("BL-TIA#{index + 1}")) }
|
|
93
|
+
@cpu.load_fields(cpu)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def modules_restored!
|
|
97
|
+
@cpu.restored!
|
|
98
|
+
@ted.restored!
|
|
99
|
+
@sound.restored!(@ted)
|
|
100
|
+
@sid_card&.restored!
|
|
101
|
+
@port.restored!
|
|
102
|
+
@acia&.restored!
|
|
103
|
+
schedule
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def check_snapshot_setup(setup)
|
|
107
|
+
ours = Snapshot::Plus4Setup.of(self)
|
|
108
|
+
return if setup == ours
|
|
109
|
+
|
|
110
|
+
raise Snapshot::FormatError, "the snapshot is of a #{setup}, not a #{ours}"
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
include KeyedModules
|
|
115
|
+
end
|
|
116
|
+
end
|