badline 0.15.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 +63 -0
- data/CLAUDE.md +7 -0
- data/README.md +40 -10
- data/doc/media.md +19 -0
- data/doc/snapshot-format.md +61 -13
- data/exe/badline-ruby +1 -1
- data/lib/badline/audio/machine_player.rb +2 -14
- data/lib/badline/c64/true_drives.rb +13 -1
- data/lib/badline/c64.rb +6 -0
- data/lib/badline/chips/tia.rb +16 -0
- data/lib/badline/chips/vic/register_log.rb +4 -1
- data/lib/badline/drives/datasette/keyed_state.rb +8 -3
- 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 +20 -1
- data/lib/badline/frontend/menu/menu_dialogs.rb +13 -5
- data/lib/badline/frontend/menu/menu_media.rb +92 -14
- data/lib/badline/frontend/menu/menu_pages.rb +11 -6
- data/lib/badline/frontend/player/player_controller.rb +11 -4
- data/lib/badline/machine.rb +9 -5
- data/lib/badline/media/true_drive.rb +7 -5
- data/lib/badline/options/help.rb +4 -2
- data/lib/badline/options/option.rb +2 -1
- data/lib/badline/options/table.rb +14 -2
- data/lib/badline/options/validation.rb +49 -9
- data/lib/badline/options.rb +34 -2
- data/lib/badline/plus4/acia/frames.rb +55 -0
- data/lib/badline/plus4/acia.rb +276 -16
- data/lib/badline/plus4/bus.rb +181 -45
- data/lib/badline/plus4/keyed_modules.rb +22 -5
- data/lib/badline/plus4/keyed_parts.rb +33 -4
- data/lib/badline/plus4/keyed_state.rb +5 -2
- 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 +103 -21
- data/lib/badline/roms/README +9 -0
- data/lib/badline/roms/dos1551.rom +0 -0
- data/lib/badline/snapshot/field_doc.rb +7 -5
- data/lib/badline/snapshot/plus4_setup.rb +52 -10
- data/lib/badline/storage/g64_image.rb +1 -1
- data/lib/badline/version.rb +1 -1
- metadata +15 -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,7 +32,8 @@ 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.
|
|
31
37
|
#
|
|
32
38
|
# The TED fetches through the same banks: the video matrix and the
|
|
33
39
|
# attributes from ROM or RAM at $8000-$FFFF as the CPU sees it, with no
|
|
@@ -40,17 +46,18 @@ module Badline
|
|
|
40
46
|
|
|
41
47
|
RAM_POWER_ON = 0x00
|
|
42
48
|
|
|
43
|
-
# RAM through a size it repeats at
|
|
49
|
+
# RAM through a size it repeats at, from +offset+ on in +ram+.
|
|
44
50
|
class MirroredRAM
|
|
45
|
-
def initialize(ram, mask)
|
|
51
|
+
def initialize(ram, mask, offset = 0)
|
|
46
52
|
@ram = ram
|
|
47
53
|
@mask = mask
|
|
54
|
+
@offset = offset
|
|
48
55
|
end
|
|
49
56
|
|
|
50
|
-
def peek(addr) = @ram.peek(addr & @mask)
|
|
57
|
+
def peek(addr) = @ram.peek((addr & @mask) + @offset)
|
|
51
58
|
|
|
52
59
|
def poke(addr, value)
|
|
53
|
-
@ram.poke(addr & @mask, value)
|
|
60
|
+
@ram.poke((addr & @mask) + @offset, value)
|
|
54
61
|
end
|
|
55
62
|
end
|
|
56
63
|
|
|
@@ -80,14 +87,20 @@ module Badline
|
|
|
80
87
|
end
|
|
81
88
|
end
|
|
82
89
|
|
|
83
|
-
# Page $FD: the ACIA, the user port, the keyboard port
|
|
84
|
-
# bank latch, by the address's bits 4-7. +acia
|
|
85
|
-
# nil where the machine has none, and
|
|
86
|
-
# user port's place on a machine
|
|
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.
|
|
87
98
|
class IOPage
|
|
88
|
-
def initialize(keyboard_port:, acia: nil, user_port: nil, cassette_sense: nil)
|
|
99
|
+
def initialize(keyboard_port:, acia: nil, user_port: nil, cassette_sense: nil, sid_card: nil)
|
|
89
100
|
@bus = nil
|
|
101
|
+
@ram_expansion = nil
|
|
90
102
|
@acia = acia
|
|
103
|
+
@sid_card = sid_card
|
|
91
104
|
@user_port = user_port
|
|
92
105
|
@cassette_sense = cassette_sense
|
|
93
106
|
@keyboard_port = keyboard_port
|
|
@@ -97,13 +110,15 @@ module Badline
|
|
|
97
110
|
# a write to $FDD0-$FDDF sets.
|
|
98
111
|
def connect(bus)
|
|
99
112
|
@bus = bus
|
|
113
|
+
@ram_expansion = bus.ram_expansion
|
|
100
114
|
end
|
|
101
115
|
|
|
102
116
|
def peek(addr)
|
|
103
117
|
case addr & 0xf0
|
|
104
118
|
when 0x00 then @acia ? @acia.peek(addr) : @bus.data
|
|
105
|
-
when 0x10 then user_port_peek(addr)
|
|
119
|
+
when 0x10 then @ram_expansion ? expansion_peek(addr) : user_port_peek(addr)
|
|
106
120
|
when 0x30 then @keyboard_port.peek(addr)
|
|
121
|
+
when 0x40, 0x50 then @sid_card ? @sid_card.peek(addr) : @bus.data
|
|
107
122
|
else @bus.data
|
|
108
123
|
end
|
|
109
124
|
end
|
|
@@ -111,14 +126,31 @@ module Badline
|
|
|
111
126
|
def poke(addr, value)
|
|
112
127
|
case addr & 0xf0
|
|
113
128
|
when 0x00 then @acia&.poke(addr, value)
|
|
114
|
-
when 0x10 then @user_port&.poke(addr, value)
|
|
129
|
+
when 0x10 then @ram_expansion ? expansion_poke(addr, value) : @user_port&.poke(addr, value)
|
|
115
130
|
when 0x30 then @keyboard_port.poke(addr, value)
|
|
131
|
+
when 0x40, 0x50 then @sid_card&.poke(addr, value)
|
|
116
132
|
when 0xd0 then @bus.select_banks(addr)
|
|
117
133
|
end
|
|
118
134
|
end
|
|
119
135
|
|
|
120
136
|
private
|
|
121
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
|
+
|
|
122
154
|
def user_port_peek(addr)
|
|
123
155
|
return @user_port.peek(addr) if @user_port
|
|
124
156
|
return @cassette_sense.read(@bus.data) if @cassette_sense
|
|
@@ -148,26 +180,50 @@ module Badline
|
|
|
148
180
|
end
|
|
149
181
|
end
|
|
150
182
|
|
|
151
|
-
# Page $FE
|
|
152
|
-
# that talks to drive 9 at $FEC0-$FEDF and drive 8's at $FEE0-$FEFF
|
|
153
|
-
#
|
|
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.
|
|
154
188
|
class TCBMPage
|
|
155
|
-
|
|
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)
|
|
156
196
|
@bus = bus
|
|
157
|
-
@drive8 =
|
|
158
|
-
@drive9 =
|
|
197
|
+
@drive8 = nil
|
|
198
|
+
@drive9 = nil
|
|
199
|
+
@sid_card = nil
|
|
159
200
|
end
|
|
160
201
|
|
|
161
202
|
def peek(addr)
|
|
162
|
-
return
|
|
203
|
+
return low_peek(addr) if addr < 0xfec0
|
|
163
204
|
|
|
164
|
-
addr < 0xfee0 ? @drive9
|
|
205
|
+
tia = addr < 0xfee0 ? @drive9 : @drive8
|
|
206
|
+
tia ? tia.peek(addr) : @bus.data
|
|
165
207
|
end
|
|
166
208
|
|
|
167
209
|
def poke(addr, value)
|
|
168
|
-
|
|
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
|
|
219
|
+
|
|
220
|
+
private
|
|
221
|
+
|
|
222
|
+
def sid_card?(addr) = (addr & 0xe0) == 0x80
|
|
169
223
|
|
|
170
|
-
|
|
224
|
+
def low_peek(addr)
|
|
225
|
+
sid_card = @sid_card
|
|
226
|
+
sid_card && sid_card?(addr) ? sid_card.peek(addr) : @bus.data
|
|
171
227
|
end
|
|
172
228
|
end
|
|
173
229
|
|
|
@@ -186,7 +242,7 @@ module Badline
|
|
|
186
242
|
end
|
|
187
243
|
|
|
188
244
|
def poke(addr, value)
|
|
189
|
-
return @bus.
|
|
245
|
+
return @bus.top_ram.poke(addr, value) if addr >= 0xff40
|
|
190
246
|
|
|
191
247
|
case addr
|
|
192
248
|
when 0xff3e then @bus.rom_selected = true
|
|
@@ -201,17 +257,29 @@ module Badline
|
|
|
201
257
|
# What the top page reads above $FF3F: the high ROM bank or RAM.
|
|
202
258
|
attr_reader :top_memory
|
|
203
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
|
+
|
|
204
267
|
# The CPU sees ROM at $8000-$FFFF.
|
|
205
268
|
attr_reader :rom_selected
|
|
206
269
|
|
|
207
270
|
# +io+ is the IOPage, +model+ a Model profile, whose KERNAL it maps
|
|
208
271
|
# and whose function ROMs it maps where the model has them, and
|
|
209
|
-
# +ram_kb+ the RAM fitted.
|
|
210
|
-
|
|
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)
|
|
211
275
|
addressable_at(0, length: 2**16)
|
|
212
276
|
size = ram_kb * 1024
|
|
213
277
|
@ram = Memory.new(Array.new(size, RAM_POWER_ON), length: size, start: 0)
|
|
214
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
|
|
215
283
|
@ted = ted
|
|
216
284
|
@open_bus = OpenBus.new(self)
|
|
217
285
|
@low_roms = [ROM.load("plus4/basic.rom", 0x8000), nil, nil, nil]
|
|
@@ -220,11 +288,12 @@ module Badline
|
|
|
220
288
|
@low_roms[1] = ROM.load("plus4/function-low.rom", 0x8000)
|
|
221
289
|
@high_roms[1] = ROM.load("plus4/function-high.rom", 0xc000)
|
|
222
290
|
end
|
|
291
|
+
@port = port
|
|
223
292
|
@zero_page = ZeroPage.new(port, @ram_view)
|
|
224
293
|
@io_page = io
|
|
225
294
|
io.connect(self)
|
|
226
295
|
@top_page = TopPage.new(self, ted)
|
|
227
|
-
@tcbm_page =
|
|
296
|
+
@tcbm_page = TCBMPage.new(self)
|
|
228
297
|
@debug_register = nil
|
|
229
298
|
@data = 0
|
|
230
299
|
@read_pages = Array.new(256, @open_bus)
|
|
@@ -234,19 +303,29 @@ module Badline
|
|
|
234
303
|
@low_bank = @high_bank = 0
|
|
235
304
|
@rom_selected = true
|
|
236
305
|
@top_memory = @ram_view
|
|
237
|
-
|
|
238
|
-
map_reads
|
|
306
|
+
map_ram
|
|
239
307
|
end
|
|
240
308
|
|
|
241
309
|
def power_on!
|
|
242
310
|
@ram.clear!(Array.new(@ram.length, RAM_POWER_ON))
|
|
311
|
+
@ram_expansion&.power_on!
|
|
243
312
|
reset!
|
|
244
313
|
end
|
|
245
314
|
|
|
246
|
-
# 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.
|
|
247
317
|
def reset!
|
|
248
318
|
@low_bank = @high_bank = 0
|
|
249
|
-
|
|
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
|
|
250
329
|
end
|
|
251
330
|
|
|
252
331
|
def rom_selected=(selected)
|
|
@@ -274,9 +353,23 @@ module Badline
|
|
|
274
353
|
|
|
275
354
|
# Plugs in a 1551's TCBM interface, the TIAs at $FEC0-$FEFF.
|
|
276
355
|
def plug_tcbm(drive8:, drive9:)
|
|
277
|
-
@tcbm_page =
|
|
278
|
-
|
|
279
|
-
|
|
356
|
+
@tcbm_page.drive8 = drive8
|
|
357
|
+
@tcbm_page.drive9 = drive9
|
|
358
|
+
end
|
|
359
|
+
|
|
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
|
|
280
373
|
end
|
|
281
374
|
|
|
282
375
|
# Puts a cartridge's ROM chips in their slots, as a PLUS4 CARTRIDGE
|
|
@@ -334,12 +427,35 @@ module Badline
|
|
|
334
427
|
|
|
335
428
|
# A TED fetch of character or bitmap data, from ROM when +rom+.
|
|
336
429
|
def ted_pattern_fetch(addr, rom)
|
|
337
|
-
@data = rom ? @ted_rom_pages[addr >> 8].peek(addr) : @
|
|
430
|
+
@data = rom ? @ted_rom_pages[addr >> 8].peek(addr) : @ted_ram[addr >> 8].peek(addr)
|
|
338
431
|
end
|
|
339
432
|
|
|
340
433
|
private
|
|
341
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
|
+
|
|
342
457
|
def map_writes
|
|
458
|
+
copy_ram(@write_pages) if @ram_expansion
|
|
343
459
|
@write_pages[0x00] = @zero_page
|
|
344
460
|
@write_pages[0xfd] = @debug_register || @io_page
|
|
345
461
|
@write_pages[0xfe] = @tcbm_page
|
|
@@ -347,10 +463,14 @@ module Badline
|
|
|
347
463
|
end
|
|
348
464
|
|
|
349
465
|
def map_reads
|
|
350
|
-
@
|
|
466
|
+
if @ram_expansion
|
|
467
|
+
copy_ram(@read_pages)
|
|
468
|
+
else
|
|
469
|
+
@read_pages.fill(@ram_view, 0x00, 0xfd)
|
|
470
|
+
end
|
|
351
471
|
@read_pages[0x00] = @zero_page
|
|
352
|
-
map_roms(@read_pages) if @rom_selected
|
|
353
|
-
@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
|
|
354
474
|
@read_pages[0xfd] = @io_page
|
|
355
475
|
@read_pages[0xfe] = @tcbm_page
|
|
356
476
|
@read_pages[0xff] = @top_page
|
|
@@ -358,16 +478,32 @@ module Badline
|
|
|
358
478
|
end
|
|
359
479
|
|
|
360
480
|
def map_ted_reads
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
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
|
|
366
502
|
end
|
|
367
503
|
|
|
368
|
-
def map_roms(pages)
|
|
504
|
+
def map_roms(pages, high_bank)
|
|
369
505
|
pages.fill(@low_roms[@low_bank] || @open_bus, 0x80, 0x40)
|
|
370
|
-
pages.fill(@high_roms[
|
|
506
|
+
pages.fill(@high_roms[high_bank] || @open_bus, 0xc0, 0x3c)
|
|
371
507
|
pages[0xfc] = @high_roms[0]
|
|
372
508
|
end
|
|
373
509
|
end
|
|
@@ -4,11 +4,12 @@ module Badline
|
|
|
4
4
|
class Plus4
|
|
5
5
|
# The whole machine as keyed snapshot modules (Snapshot::Keyed): BL-MACHINE
|
|
6
6
|
# with the setup, the cartridges' ROMs by slot, the CPU with its port,
|
|
7
|
-
# the TED, its sound, the
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
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.
|
|
12
13
|
#
|
|
13
14
|
# A load maps the banks before the chips load, and works out what each
|
|
14
15
|
# unit derives from its fields once every unit is loaded.
|
|
@@ -22,6 +23,8 @@ module Badline
|
|
|
22
23
|
bus.ram.save_fields(out, "ram")
|
|
23
24
|
bus.save_fields(out, "bus.")
|
|
24
25
|
end
|
|
26
|
+
expansion = bus.ram_expansion
|
|
27
|
+
sections << Snapshot::Keyed.section("BL-RAMEXP") { |out| expansion.save_fields(out) } if expansion
|
|
25
28
|
sections << Snapshot::Keyed.section("BL-TAPE") { |out| @datasette.save_fields(out) }
|
|
26
29
|
sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
|
|
27
30
|
save_drive_modules(sections)
|
|
@@ -38,6 +41,7 @@ module Badline
|
|
|
38
41
|
bus.ram.load_fields(memory, "ram")
|
|
39
42
|
bus.load_fields(memory, "bus.")
|
|
40
43
|
bus.load_slot_fields(modules.optional("BL-CART") || Snapshot::Keyed::Reader.new("BL-CART"))
|
|
44
|
+
load_ram_expansion(modules)
|
|
41
45
|
bus.restored!
|
|
42
46
|
load_chip_modules(modules)
|
|
43
47
|
@datasette.load_fields(modules.required("BL-TAPE"))
|
|
@@ -50,6 +54,13 @@ module Badline
|
|
|
50
54
|
|
|
51
55
|
private
|
|
52
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
|
+
|
|
53
64
|
def save_chip_modules(sections)
|
|
54
65
|
sections << Snapshot::Keyed.section("BL-CPU") do |out|
|
|
55
66
|
@cpu.save_fields(out)
|
|
@@ -57,6 +68,8 @@ module Badline
|
|
|
57
68
|
end
|
|
58
69
|
sections << Snapshot::Keyed.section("BL-TED") { |out| @ted.save_fields(out) }
|
|
59
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
|
|
60
73
|
sections << Snapshot::Keyed.section("BL-KEYPORT") { |out| @keyboard_port.save_fields(out) }
|
|
61
74
|
user_port = @user_port
|
|
62
75
|
sections << Snapshot::Keyed.section("BL-USERPORT") { |out| user_port.save_fields(out) } if user_port
|
|
@@ -72,6 +85,7 @@ module Badline
|
|
|
72
85
|
@port.load_fields(cpu, "port.")
|
|
73
86
|
@ted.load_fields(modules.required("BL-TED"))
|
|
74
87
|
@sound.load_fields(modules.required("BL-TEDSOUND"))
|
|
88
|
+
@sid_card&.load_fields(modules.required("BL-SID"))
|
|
75
89
|
@keyboard_port.load_fields(modules.required("BL-KEYPORT"))
|
|
76
90
|
@user_port&.load_fields(modules.required("BL-USERPORT"))
|
|
77
91
|
@acia&.load_fields(modules.required("BL-ACIA"))
|
|
@@ -83,7 +97,10 @@ module Badline
|
|
|
83
97
|
@cpu.restored!
|
|
84
98
|
@ted.restored!
|
|
85
99
|
@sound.restored!(@ted)
|
|
100
|
+
@sid_card&.restored!
|
|
86
101
|
@port.restored!
|
|
102
|
+
@acia&.restored!
|
|
103
|
+
schedule
|
|
87
104
|
end
|
|
88
105
|
|
|
89
106
|
def check_snapshot_setup(setup)
|
|
@@ -53,14 +53,43 @@ module Badline
|
|
|
53
53
|
end
|
|
54
54
|
|
|
55
55
|
class ACIA
|
|
56
|
-
# The
|
|
57
|
-
# (BL-ACIA).
|
|
56
|
+
# The registers, the status, the shift register and when the
|
|
57
|
+
# transmitter's character ends, in a keyed snapshot module (BL-ACIA).
|
|
58
|
+
# What the device on the user port has still to send stays with the
|
|
59
|
+
# host, and the receiver starts on it again once loaded (#restored!).
|
|
60
|
+
# A snapshot from before the transmitter's timing was saved starts
|
|
61
|
+
# its character clock on load.
|
|
58
62
|
module KeyedState
|
|
59
|
-
def save_fields(out)
|
|
63
|
+
def save_fields(out)
|
|
64
|
+
out.int("command", @command).int("control", @control).int("errors", @errors)
|
|
65
|
+
out.bool("receiver_full", @receiver_full).bool("transmitter_empty", @transmitter_empty).bool("irq", @irq)
|
|
66
|
+
out.int("receive_data", @receive_data).int("transmit_data", @transmit_data).int("shifting", @shifting)
|
|
67
|
+
out.int("transmit_at", @transmit_at).int("transmit_remainder", @transmit_remainder)
|
|
68
|
+
end
|
|
60
69
|
|
|
61
70
|
def load_fields(input)
|
|
62
71
|
@command = input.byte("command", @command)
|
|
63
72
|
@control = input.byte("control", @control)
|
|
73
|
+
@errors = input.masked("errors", 0, PARITY_ERROR | FRAMING_ERROR | OVERRUN)
|
|
74
|
+
@receiver_full = input.bool("receiver_full", false)
|
|
75
|
+
@transmitter_empty = input.bool("transmitter_empty", true)
|
|
76
|
+
@irq = input.bool("irq", false)
|
|
77
|
+
@receive_data = input.byte("receive_data", 0)
|
|
78
|
+
@transmit_data = input.byte("transmit_data", 0)
|
|
79
|
+
@shifting = input.int("shifting", IDLE)
|
|
80
|
+
@transmit_remainder = input.int("transmit_remainder", 0)
|
|
81
|
+
transmit_at = input.optional_int("transmit_at")
|
|
82
|
+
@transmit_at = transmit_at || NEVER
|
|
83
|
+
@clock_lost = transmit_at.nil?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def restored!
|
|
87
|
+
@receive_at = NEVER
|
|
88
|
+
@lines = modem_lines
|
|
89
|
+
start_transmitting if @clock_lost && transmitter_on?
|
|
90
|
+
@clock_lost = false
|
|
91
|
+
start_receiving
|
|
92
|
+
changed
|
|
64
93
|
end
|
|
65
94
|
end
|
|
66
95
|
|
|
@@ -126,7 +155,7 @@ module Badline
|
|
|
126
155
|
|
|
127
156
|
def restored!
|
|
128
157
|
@ted.rom_selected = @rom_selected
|
|
129
|
-
|
|
158
|
+
map_ram
|
|
130
159
|
end
|
|
131
160
|
|
|
132
161
|
private
|
|
@@ -16,8 +16,11 @@ module Badline
|
|
|
16
16
|
# instructions or jammed.
|
|
17
17
|
def settled_for_snapshot? = @cpu.boundary? || @cpu.jammed?
|
|
18
18
|
|
|
19
|
-
# The sound's voices, run up to this cycle.
|
|
20
|
-
def settle_chips_for_snapshot
|
|
19
|
+
# The sound's voices and the SID card's, run up to this cycle.
|
|
20
|
+
def settle_chips_for_snapshot
|
|
21
|
+
@sound.catch_up!
|
|
22
|
+
@sid_card&.catch_up!
|
|
23
|
+
end
|
|
21
24
|
|
|
22
25
|
def save_machine_fields(out)
|
|
23
26
|
out.text("family", "plus4").text("written_by", VERSION)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Plus4
|
|
5
|
+
# Hannes's RAM expansion, 256K or 1M in 64K banks, and Csory's 256K on
|
|
6
|
+
# the same board, from Solder's register notes and the board's GAL
|
|
7
|
+
# equations (plus4world, "256K RAM (Hannes)").
|
|
8
|
+
#
|
|
9
|
+
# The control register is a 6529 at $FD16, which reads back what was
|
|
10
|
+
# written. The expansion decodes $FD10-$FD1F further, so the user port
|
|
11
|
+
# answers only at $FD10, and the rest of the range is open.
|
|
12
|
+
#
|
|
13
|
+
# - Bits 0-1, and bits 2-3 on the 1M: the bank the CPU sees above the
|
|
14
|
+
# switch bound. All ones is the standard bank, the RAM below the
|
|
15
|
+
# bound, which bits 0-3 pick inverted: bank 3 of the 256K and bank
|
|
16
|
+
# 15 of the 1M.
|
|
17
|
+
# - Bit 6: the TED fetches from the CPU's bank when set, and from the
|
|
18
|
+
# standard bank when clear.
|
|
19
|
+
# - Bit 7: the switch bound, $4000 when set, $1000 when clear.
|
|
20
|
+
# - Bits 4-5: on the Csory board, two different bits switch Csory's
|
|
21
|
+
# mapping on (the notes' gate version; its table names 10 and its
|
|
22
|
+
# GAL 01).
|
|
23
|
+
#
|
|
24
|
+
# The register is all ones at power-on and after a reset, so the
|
|
25
|
+
# machine starts in the standard bank, with the TED following it.
|
|
26
|
+
#
|
|
27
|
+
# Csory's mapping puts one 16K block of the 256K, picked by bits 0-3
|
|
28
|
+
# of a write-only latch at $FD15, in the 16K window of the CPU's 64K
|
|
29
|
+
# that bits 6-7 pick. The standard bank holds blocks 0-3. The TED sees
|
|
30
|
+
# the block too while bit 6 of $FD16 is set. On the Csory board the
|
|
31
|
+
# TED reads the KERNAL wherever it fetches from the high ROM, whichever
|
|
32
|
+
# bank the ROM latch selects.
|
|
33
|
+
class RAMExpansion
|
|
34
|
+
# Each expansion by name: its 64K banks, and whether it has Csory's
|
|
35
|
+
# mapping.
|
|
36
|
+
KINDS = { hannes256: [4, false], hannes1m: [16, false], csory256: [4, true] }.freeze
|
|
37
|
+
|
|
38
|
+
# Whose design each is, as the pause menu names it.
|
|
39
|
+
NAMES = { hannes256: "HANNES", hannes1m: "HANNES", csory256: "CSORY" }.freeze
|
|
40
|
+
|
|
41
|
+
CONTROL = 0x06
|
|
42
|
+
CSORY_LATCH = 0x05
|
|
43
|
+
|
|
44
|
+
# The register at $FD16, and Csory's latch at $FD15.
|
|
45
|
+
attr_reader :kind, :control, :csory_latch
|
|
46
|
+
|
|
47
|
+
# +ram+ is the machine's own 64K, the standard bank, and +view+ the
|
|
48
|
+
# Bus::MirroredRAM the bus reads it through.
|
|
49
|
+
def initialize(kind, ram, view)
|
|
50
|
+
@kind = kind
|
|
51
|
+
count, csory = KINDS.fetch(kind)
|
|
52
|
+
@csory = csory
|
|
53
|
+
@mask = count - 1
|
|
54
|
+
size = (count - 1) * 0x10000
|
|
55
|
+
@extra = Memory.new(Array.new(size, Bus::RAM_POWER_ON), length: size, start: 0)
|
|
56
|
+
@banks = [view] + Array.new(count - 1) { |index| Bus::MirroredRAM.new(@extra, 0xffff, index * 0x10000) }
|
|
57
|
+
@blocks = csory ? Array.new(16) { |block| block_view(ram, block) } : []
|
|
58
|
+
@control = 0xff
|
|
59
|
+
@csory_latch = 0
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The size in K.
|
|
63
|
+
def size_kb = (@mask + 1) * 64
|
|
64
|
+
|
|
65
|
+
# Whether the TED reads the KERNAL for the whole high ROM.
|
|
66
|
+
def kernal_for_ted? = @csory
|
|
67
|
+
|
|
68
|
+
# A write to $FD11-$FD1F: the control register at $FD16, and on the
|
|
69
|
+
# Csory board, Csory's latch at $FD15.
|
|
70
|
+
def poke(addr, value)
|
|
71
|
+
case addr & 0x0f
|
|
72
|
+
when CONTROL then @control = value
|
|
73
|
+
when CSORY_LATCH then @csory_latch = value if @csory
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# The RESET line sets the register, as power-on does. Csory's latch
|
|
78
|
+
# keeps its value.
|
|
79
|
+
def reset!
|
|
80
|
+
@control = 0xff
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def power_on!
|
|
84
|
+
@extra.clear!(Array.new(@extra.length, Bus::RAM_POWER_ON))
|
|
85
|
+
@csory_latch = 0
|
|
86
|
+
reset!
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Lays the RAM the CPU sees out in +cpu+ and the RAM the TED sees in
|
|
90
|
+
# +ted+, a view for each of the 256 pages.
|
|
91
|
+
def map(cpu, ted)
|
|
92
|
+
control = @control
|
|
93
|
+
bound = control.anybits?(0x80) ? 0x40 : 0x10
|
|
94
|
+
standard = @banks[0]
|
|
95
|
+
cpu.fill(standard, 0x00, bound)
|
|
96
|
+
cpu.fill(@banks[~control & @mask], bound, 0x100 - bound)
|
|
97
|
+
follows = control.anybits?(0x40)
|
|
98
|
+
if follows
|
|
99
|
+
page = 0
|
|
100
|
+
while page < 0x100
|
|
101
|
+
ted[page] = cpu[page]
|
|
102
|
+
page += 1
|
|
103
|
+
end
|
|
104
|
+
else
|
|
105
|
+
ted.fill(standard, 0x00, 0x100)
|
|
106
|
+
end
|
|
107
|
+
return unless csory_on?
|
|
108
|
+
|
|
109
|
+
latch = @csory_latch
|
|
110
|
+
block = @blocks[latch & 0x0f]
|
|
111
|
+
window = (latch >> 6) * 0x40
|
|
112
|
+
cpu.fill(block, window, 0x40)
|
|
113
|
+
ted.fill(block, window, 0x40) if follows
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The banks beyond the standard one, and Csory's latch, in a keyed
|
|
117
|
+
# snapshot module (BL-RAMEXP).
|
|
118
|
+
def save_fields(out)
|
|
119
|
+
out.int("control", @control)
|
|
120
|
+
out.int("csory_latch", @csory_latch) if @csory
|
|
121
|
+
@extra.save_fields(out, "ram")
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def load_fields(input)
|
|
125
|
+
@control = input.byte("control", @control)
|
|
126
|
+
@csory_latch = input.byte("csory_latch", @csory_latch)
|
|
127
|
+
@extra.load_fields(input, "ram")
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
private
|
|
131
|
+
|
|
132
|
+
def csory_on?
|
|
133
|
+
control = @control
|
|
134
|
+
@csory && control.anybits?(0x10) != control.anybits?(0x20)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Block +block+ of the 256K, from the standard bank's four on.
|
|
138
|
+
def block_view(ram, block)
|
|
139
|
+
return Bus::MirroredRAM.new(ram, 0x3fff, block * 0x4000) if block < 4
|
|
140
|
+
|
|
141
|
+
Bus::MirroredRAM.new(@extra, 0x3fff, (block - 4) * 0x4000)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
145
|
+
end
|