badline 0.16.0 → 0.16.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 +16 -0
- data/README.md +32 -9
- data/doc/media.md +9 -0
- data/doc/snapshot-format.md +4 -2
- data/doc/snapshots.md +51 -10
- data/lib/badline/drives/ieee_bus.rb +50 -4
- data/lib/badline/drives/model2031/ieee_port.rb +133 -0
- data/lib/badline/drives/model2031/keyed_state.rb +66 -0
- data/lib/badline/drives/model2031/parts.rb +25 -0
- data/lib/badline/drives/model2031.rb +92 -0
- data/lib/badline/frontend/menu/menu_pages.rb +11 -2
- data/lib/badline/kernal_trap/ieee.rb +3 -0
- data/lib/badline/machine.rb +3 -2
- data/lib/badline/media/pet_media.rb +5 -5
- data/lib/badline/media/true_drive.rb +19 -6
- data/lib/badline/media.rb +6 -2
- data/lib/badline/options/help.rb +8 -4
- data/lib/badline/options/table.rb +9 -3
- data/lib/badline/options/validation.rb +20 -4
- data/lib/badline/options.rb +27 -8
- data/lib/badline/pet/bus/expansion.rb +99 -5
- data/lib/badline/pet/bus.rb +30 -12
- data/lib/badline/pet/characters.rb +8 -2
- data/lib/badline/pet/crtc_video.rb +20 -7
- data/lib/badline/pet/ieee_drive.rb +76 -0
- data/lib/badline/pet/ieee_ports.rb +12 -1
- data/lib/badline/pet/ieee_traps.rb +10 -5
- data/lib/badline/pet/keyed_modules.rb +4 -1
- data/lib/badline/pet/keyed_parts.rb +10 -6
- data/lib/badline/pet/model.rb +56 -6
- data/lib/badline/pet/rom_sets.rb +30 -6
- data/lib/badline/pet.rb +33 -15
- data/lib/badline/roms/README +33 -1
- data/lib/badline/roms/dos2031.rom +0 -0
- data/lib/badline/roms/pet/characters-din.rom +0 -0
- data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
- data/lib/badline/snapshot/container.rb +4 -1
- data/lib/badline/snapshot/field_doc.rb +4 -3
- data/lib/badline/snapshot/image.rb +22 -11
- data/lib/badline/snapshot/pet_setup.rb +30 -13
- data/lib/badline/snapshot/vice/pet.rb +117 -0
- data/lib/badline/snapshot/vice/pet_cpu.rb +67 -0
- data/lib/badline/snapshot/vice/pet_crtc.rb +246 -0
- data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
- data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
- data/lib/badline/snapshot/vice/pet_restore.rb +150 -0
- data/lib/badline/snapshot/vice/pet_via.rb +164 -0
- data/lib/badline/snapshot/vice.rb +1 -0
- data/lib/badline/version.rb +1 -1
- metadata +16 -1
data/lib/badline/machine.rb
CHANGED
|
@@ -153,13 +153,14 @@ module Badline
|
|
|
153
153
|
|
|
154
154
|
# A PET with the RAM --ram names (PET::Model::RAM_SIZES), or with the
|
|
155
155
|
# model's own without it, the editor at the frame rate --60hz or
|
|
156
|
-
# --editor asks for,
|
|
156
|
+
# --editor asks for, the ROMs --basic, --editor and --characters fit it
|
|
157
|
+
# with, and the 8296 board's jumpers --jumpers names.
|
|
157
158
|
def self.pet_for(media, options)
|
|
158
159
|
unsupported = !media.nil? && Media::PETMedia::UNSUPPORTED.include?(File.extname(media).downcase)
|
|
159
160
|
raise Unsupported, "doesn't go in a PET" if unsupported
|
|
160
161
|
|
|
161
162
|
PET.new(model: options.model, ram_kb: options.ram_kb, refresh_hz: options.refresh_hz, basic: options.basic,
|
|
162
|
-
editor: options.editor)
|
|
163
|
+
editor: options.editor, characters: options.characters, jumpers: options.jumpers)
|
|
163
164
|
end
|
|
164
165
|
private_class_method :pet_for
|
|
165
166
|
|
|
@@ -10,7 +10,7 @@ module Badline
|
|
|
10
10
|
# is no cartridge port and no SID, and the true drives are serial ones.
|
|
11
11
|
module PETMedia
|
|
12
12
|
# What a PET doesn't take.
|
|
13
|
-
UNSUPPORTED = Extensions.of(%i[tune cartridge
|
|
13
|
+
UNSUPPORTED = Extensions.of(%i[tune cartridge])
|
|
14
14
|
|
|
15
15
|
# What loads and runs the first program on the disk in device 8.
|
|
16
16
|
AUTOSTART = %(load"*",8\rrun\r)
|
|
@@ -26,12 +26,12 @@ module Badline
|
|
|
26
26
|
|
|
27
27
|
# BASIC 2 and 4 load the first program off the disk and run it.
|
|
28
28
|
# BASIC 1's LOAD finds no device on the IEEE-488 bus, so the first
|
|
29
|
-
# program goes in as a program file does,
|
|
30
|
-
# booted.
|
|
31
|
-
def disk_autostart(machine
|
|
29
|
+
# program, which the block gives, goes in as a program file does,
|
|
30
|
+
# once the KERNAL has booted.
|
|
31
|
+
def disk_autostart(machine)
|
|
32
32
|
return machine.type_text(AUTOSTART) unless machine.model.basic1?
|
|
33
33
|
|
|
34
|
-
data =
|
|
34
|
+
data = yield
|
|
35
35
|
machine.on_init { Program.load(machine, data, autostart: true) } if data
|
|
36
36
|
end
|
|
37
37
|
end
|
|
@@ -4,15 +4,16 @@ module Badline
|
|
|
4
4
|
module Media
|
|
5
5
|
# Device 8 as a true drive instead of the KERNAL traps: a 1541, or on
|
|
6
6
|
# the C128 its 1571, or on the Plus/4 a 1551 if it asks for one, or a
|
|
7
|
-
# 1581. Once a true drive is plugged in as device 8, disk media go into
|
|
7
|
+
# 1581, or on the PET a 2031 on the IEEE-488 bus. Once a true drive is plugged in as device 8, disk media go into
|
|
8
8
|
# it rather than into the traps, so a LOAD runs the DOS and every byte
|
|
9
9
|
# crosses the serial bus or TCBM. A .g64 holds the
|
|
10
10
|
# disk's raw GCR, which only a true drive reads, so it plugs one in
|
|
11
11
|
# when there's none yet. The 1541 reads only .d64 and .g64 images, the
|
|
12
12
|
# 1571 .d71 and .g71 as well, and the 1581 only .d81, so a .d81 swaps
|
|
13
13
|
# the drive on device 8 for a 1581 (Drives::Model1581::Slot), and another disk
|
|
14
|
-
# image swaps a 1581 back for the machine's own drive. The
|
|
15
|
-
#
|
|
14
|
+
# image swaps a 1581 back for the machine's own drive. The PET has no
|
|
15
|
+
# 1581, and its 2031 reads .d64 and .g64 images only. The traps' other
|
|
16
|
+
# disk media, a .t64, a host directory, or a .d80 or .d82, raise Error.
|
|
16
17
|
module TrueDrive
|
|
17
18
|
class Error < ArgumentError; end
|
|
18
19
|
|
|
@@ -36,7 +37,7 @@ module Badline
|
|
|
36
37
|
kind = Extensions.kind(path)
|
|
37
38
|
return true if kind == :gcr
|
|
38
39
|
|
|
39
|
-
!drive(computer).nil? && (File.directory?(path) || %i[disk archive].include?(kind))
|
|
40
|
+
!drive(computer).nil? && (File.directory?(path) || %i[disk archive ieee_disk].include?(kind))
|
|
40
41
|
end
|
|
41
42
|
|
|
42
43
|
# Puts the image in the drive on device 8, plugging one in that
|
|
@@ -44,12 +45,15 @@ module Badline
|
|
|
44
45
|
# mounted through the traps, so LOAD and SAVE reach the drive too.
|
|
45
46
|
# `read_only` puts it in write-protected.
|
|
46
47
|
def insert(computer, path, read_only: false)
|
|
47
|
-
|
|
48
|
+
pet = computer.family == :pet
|
|
49
|
+
return insert_d81(computer, path, read_only) if !pet && File.extname(path).casecmp?(".d81")
|
|
48
50
|
|
|
49
51
|
drive = own_drive(computer)
|
|
50
|
-
name = drive&.model_name || (computer
|
|
52
|
+
name = drive&.model_name || default_model(computer)
|
|
51
53
|
types = name == "1571" ? %w[.d64 .g64 .d71 .g71] : %w[.d64 .g64]
|
|
52
54
|
unless types.include?(File.extname(path).downcase)
|
|
55
|
+
raise Error, "#{path} is not a #{types.join(' or ')} image, which the #{name} reads" if pet
|
|
56
|
+
|
|
53
57
|
raise Error, "#{path} is not a #{types.join(', ')} or .d81 image, which the #{name} and the 1581 read"
|
|
54
58
|
end
|
|
55
59
|
|
|
@@ -61,6 +65,15 @@ module Badline
|
|
|
61
65
|
|
|
62
66
|
private
|
|
63
67
|
|
|
68
|
+
# The model plug_true_drive plugs in.
|
|
69
|
+
def default_model(computer)
|
|
70
|
+
case computer.family
|
|
71
|
+
when :c128 then "1571"
|
|
72
|
+
when :pet then "2031"
|
|
73
|
+
else "1541"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
64
77
|
# The drive on device 8 unless it's a 1581.
|
|
65
78
|
def own_drive(computer)
|
|
66
79
|
drive = drive(computer)
|
data/lib/badline/media.rb
CHANGED
|
@@ -159,7 +159,11 @@ module Badline
|
|
|
159
159
|
|
|
160
160
|
def attach_true_drive(computer, path, disk, autostart:)
|
|
161
161
|
message = TrueDrive.insert(computer, path, read_only: disk.fetch(:read_only, false))
|
|
162
|
-
|
|
162
|
+
if autostart && computer.family == :pet
|
|
163
|
+
PETMedia.disk_autostart(computer) { Vic20Media.first_program(path) }
|
|
164
|
+
elsif autostart
|
|
165
|
+
computer.type_text(disk_autostart(computer) { Vic20Media.first_program(path) })
|
|
166
|
+
end
|
|
163
167
|
message
|
|
164
168
|
end
|
|
165
169
|
|
|
@@ -167,7 +171,7 @@ module Badline
|
|
|
167
171
|
storage = open_storage(path, disk)
|
|
168
172
|
computer.mount(storage)
|
|
169
173
|
if autostart && computer.family == :pet
|
|
170
|
-
PETMedia.disk_autostart(computer
|
|
174
|
+
PETMedia.disk_autostart(computer) { storage.read_file("*") }
|
|
171
175
|
elsif autostart
|
|
172
176
|
computer.type_text(disk_autostart(computer) { storage.read_file("*") })
|
|
173
177
|
end
|
data/lib/badline/options/help.rb
CHANGED
|
@@ -126,10 +126,14 @@ module Badline
|
|
|
126
126
|
and the pause menu's KEYBOARD page says where the rest are. --basic
|
|
127
127
|
and --editor fit the upgrades Commodore sold, as in --model 2001
|
|
128
128
|
--basic 2. BASIC 1 can't LOAD from a disk drive, though a disk still
|
|
129
|
-
starts its first program, and BASIC 2 can.
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
129
|
+
starts its first program, and BASIC 2 can. Their --true-drive makes
|
|
130
|
+
device 8 an emulated 2031 on the IEEE-488 bus, which reads .d64 and
|
|
131
|
+
.g64 images. The 8032 and the 8096 have two 50 Hz editors:
|
|
132
|
+
4-80-b-50hz, the default, and 4-80-b-50hz-0283, another chip's,
|
|
133
|
+
with 64-cycle lines and 313 lines a frame. The 8296 runs its own,
|
|
134
|
+
4-80-b-50hz-8296, and takes the 8032's too. --characters din fits
|
|
135
|
+
it with the DIN character ROM, and --jumpers ram9,rama puts RAM at
|
|
136
|
+
$9000 and $A000, as xpet's -petram9 and -petramA do.
|
|
133
137
|
|
|
134
138
|
--headless plays a .sid tune on the host's audio device without the
|
|
135
139
|
window, and --audio-out renders it to 16-bit PCM instead. The
|
|
@@ -20,7 +20,7 @@ module Badline
|
|
|
20
20
|
|
|
21
21
|
# The PETs and CBMs, as PET::Model names them, the 8032 first, the
|
|
22
22
|
# default.
|
|
23
|
-
PET_MODELS = %w[8032 8096 4032 3032 3032b 2001].freeze
|
|
23
|
+
PET_MODELS = %w[8032 8096 8296 4032 3032 3032b 2001].freeze
|
|
24
24
|
|
|
25
25
|
# The true drives --drive-type names for the Plus/4.
|
|
26
26
|
PLUS4_DRIVE_TYPES = %w[1541 1551].freeze
|
|
@@ -47,7 +47,7 @@ module Badline
|
|
|
47
47
|
section: "Window options", needs: :window),
|
|
48
48
|
Option.new("--no-sound", "Don't play the SID", section: "Window options", needs: :window, build: :native),
|
|
49
49
|
Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps, a 1571 on the C128, " \
|
|
50
|
-
"a 1551 on the Plus/4",
|
|
50
|
+
"a 1551 on the Plus/4, a 2031 on the PET",
|
|
51
51
|
section: "Window options", needs: :window),
|
|
52
52
|
Option.new("--drive-type MODEL", "The Plus/4's true drive: 1541 or 1551 (default: 1551); implies --true-drive",
|
|
53
53
|
section: "Window options", needs: :window),
|
|
@@ -70,6 +70,12 @@ module Badline
|
|
|
70
70
|
Option.new("--editor NAME", "Fit a PET with an editor ROM its board and BASIC take: " \
|
|
71
71
|
"#{PET::ROMSets::EDITORS.keys.join(', ')}",
|
|
72
72
|
section: "Window options", needs: :window),
|
|
73
|
+
Option.new("--characters NAME", "Fit an 8296 with a character ROM: ascii or din (default: ascii)",
|
|
74
|
+
section: "Window options", needs: :window),
|
|
75
|
+
Option.new("--jumpers LIST", "Fit an 8296's board with jumpers, comma-separated: ram9 and rama for RAM at " \
|
|
76
|
+
"$9000 and $A000, userport for the user port's PA0-PA2 to switch in " \
|
|
77
|
+
"the RAM under the ROMs",
|
|
78
|
+
section: "Window options", needs: :window),
|
|
73
79
|
Option.new("--c1lo FILE", "Put a .bin ROM image in the Plus/4's cartridge 1, low slot ($8000)",
|
|
74
80
|
section: "Window options", needs: :window),
|
|
75
81
|
Option.new("--c1hi FILE", "Put a .bin ROM image in cartridge 1's high slot ($C000)",
|
|
@@ -112,7 +118,7 @@ module Badline
|
|
|
112
118
|
section: "Testing options", needs: :window),
|
|
113
119
|
Option.new("--save-snapshot FILE", "Save the machine as a .vsf snapshot after the last frame",
|
|
114
120
|
section: "Testing options", needs: :window),
|
|
115
|
-
Option.new("--vice", "Add VICE's modules to --save-snapshot's C64 snapshot, for x64sc",
|
|
121
|
+
Option.new("--vice", "Add VICE's modules to --save-snapshot's C64 or PET snapshot, for x64sc or xpet",
|
|
116
122
|
section: "Testing options", needs: :window),
|
|
117
123
|
Option.new("--at FRAME:EVENT", "Run EVENT once FRAME frames have run (see above)",
|
|
118
124
|
section: "Testing options", needs: :window),
|
|
@@ -176,18 +176,34 @@ module Badline
|
|
|
176
176
|
raise Error, "--60hz needs pet" unless @refresh_hz.nil?
|
|
177
177
|
raise Error, "--basic needs pet" unless @basic.nil?
|
|
178
178
|
raise Error, "--editor needs pet" unless @editor.nil?
|
|
179
|
+
raise Error, "--characters needs pet" unless @characters.nil?
|
|
180
|
+
raise Error, "--jumpers needs pet" unless @jumpers.empty?
|
|
179
181
|
end
|
|
180
182
|
|
|
181
|
-
# The PET runs in the window, without the C64's SID and REU
|
|
182
|
-
#
|
|
183
|
+
# The PET runs in the window, without the C64's SID and REU, at the
|
|
184
|
+
# frame rate its editor sets.
|
|
183
185
|
def validate_pet
|
|
184
186
|
raise Error, "--ntsc needs the C64; a CRTC PET's 60 Hz editor is --60hz" if @models.include?("ntsc")
|
|
185
187
|
raise Error, "--sid needs the C64" unless @sid_model.nil?
|
|
186
188
|
raise Error, "--reu needs the C64" unless @reu.nil?
|
|
187
|
-
raise Error, "the PET has no true drive yet" if @true_drive
|
|
188
189
|
raise Error, "the PET needs the window" if headless?
|
|
189
190
|
|
|
190
|
-
|
|
191
|
+
return unless PET_MODELS.include?(model)
|
|
192
|
+
|
|
193
|
+
validate_pet_roms
|
|
194
|
+
validate_pet_board
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# --characters names a character ROM the board takes, --jumpers the
|
|
198
|
+
# 8296 board's jumpers, and the 8296 has all of its RAM soldered on.
|
|
199
|
+
def validate_pet_board
|
|
200
|
+
unless @characters.nil? || PET::ROMSets.characters(model).include?(@characters)
|
|
201
|
+
raise Error, "the #{model} takes no character ROM #{@characters}"
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
unknown = @jumpers.find { |jumper| !PET::ROMSets.jumpers(model).include?(jumper) }
|
|
205
|
+
raise Error, "the #{model} has no jumper #{unknown}" unless unknown.nil?
|
|
206
|
+
raise Error, "the 8296 has 32k of RAM" if model == "8296" && ![nil, 32].include?(@ram_kb)
|
|
191
207
|
end
|
|
192
208
|
|
|
193
209
|
# --basic and --editor name ROMs the model's board takes
|
data/lib/badline/options.rb
CHANGED
|
@@ -80,9 +80,13 @@ module Badline
|
|
|
80
80
|
# :plus4 or :pet.
|
|
81
81
|
attr_reader :family
|
|
82
82
|
|
|
83
|
-
# The ROMs --basic and --
|
|
84
|
-
# PET::ROMSets::BASICS
|
|
85
|
-
|
|
83
|
+
# The ROMs --basic, --editor and --characters fit a PET with, keys of
|
|
84
|
+
# PET::ROMSets::BASICS, PET::ROMSets::EDITORS and
|
|
85
|
+
# PET::ROMSets::CHARACTERS, or nil for its own.
|
|
86
|
+
attr_reader :basic, :editor, :characters
|
|
87
|
+
|
|
88
|
+
# The 8296's board jumpers --jumpers names (PET::Bus::Expansion::JUMPERS).
|
|
89
|
+
attr_reader :jumpers
|
|
86
90
|
|
|
87
91
|
# The VIC-20's RAM expansion --ram names, a key of
|
|
88
92
|
# Vic20::Bus::RAM_CONFIGURATIONS, or nil without --ram.
|
|
@@ -132,9 +136,7 @@ module Badline
|
|
|
132
136
|
@ram = nil
|
|
133
137
|
@ram_kb = nil
|
|
134
138
|
@ram_name = nil
|
|
135
|
-
|
|
136
|
-
@basic = nil
|
|
137
|
-
@editor = nil
|
|
139
|
+
initialize_pet
|
|
138
140
|
@cartridges = {}
|
|
139
141
|
@autostart = true
|
|
140
142
|
@writable = false
|
|
@@ -322,8 +324,7 @@ module Badline
|
|
|
322
324
|
when "--model" then @models << value
|
|
323
325
|
when "--reu" then @reu = reu_size(value)
|
|
324
326
|
when "--ram" then @ram_name = value
|
|
325
|
-
when "--basic" then
|
|
326
|
-
when "--editor" then @editor = value
|
|
327
|
+
when "--basic", "--editor", "--characters", "--jumpers" then pet_option(name, value)
|
|
327
328
|
when "--drive-type" then @drive_type = drive_type_for(value)
|
|
328
329
|
when "--c1lo", "--c1hi", "--c2lo", "--c2hi" then @cartridges[CARTRIDGE_SLOTS.fetch(name)] = value
|
|
329
330
|
when "--frames" then @frames = number(flag, value)
|
|
@@ -338,6 +339,24 @@ module Badline
|
|
|
338
339
|
end
|
|
339
340
|
end
|
|
340
341
|
|
|
342
|
+
# The PET's frame rate, ROMs and board jumpers, its own until named.
|
|
343
|
+
def initialize_pet
|
|
344
|
+
@refresh_hz = nil
|
|
345
|
+
@basic = nil
|
|
346
|
+
@editor = nil
|
|
347
|
+
@characters = nil
|
|
348
|
+
@jumpers = []
|
|
349
|
+
end
|
|
350
|
+
|
|
351
|
+
def pet_option(name, value)
|
|
352
|
+
case name
|
|
353
|
+
when "--basic" then @basic = value
|
|
354
|
+
when "--editor" then @editor = value
|
|
355
|
+
when "--characters" then @characters = value
|
|
356
|
+
else @jumpers = value.split(",")
|
|
357
|
+
end
|
|
358
|
+
end
|
|
359
|
+
|
|
341
360
|
def switch(name)
|
|
342
361
|
case name
|
|
343
362
|
when "--no-autostart" then @autostart = false
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
class PET
|
|
5
5
|
class Bus
|
|
6
|
-
# The 8096
|
|
7
|
-
# register at $FFF0 lays over $8000-$FFFF, two at
|
|
6
|
+
# The 64K board of the 8096 and the 8296: four 16K banks, which a
|
|
7
|
+
# write-only control register at $FFF0 lays over $8000-$FFFF, two at
|
|
8
|
+
# a time:
|
|
8
9
|
#
|
|
9
|
-
# bit 7 the board on; when clear,
|
|
10
|
+
# bit 7 the board on; when clear, the rest ignored
|
|
10
11
|
# bit 6 I/O shows through at $E800-$EFFF
|
|
11
12
|
# bit 5 the screen shows through at $8000-$8FFF
|
|
12
13
|
# bit 3 bank 3 at $C000-$FFFF, else bank 1
|
|
@@ -16,6 +17,26 @@ module Badline
|
|
|
16
17
|
#
|
|
17
18
|
# A write to $FFF0 lands where $FFF0 was before it, too. The RES line
|
|
18
19
|
# clears the register, so the 8096 starts as an 8032.
|
|
20
|
+
#
|
|
21
|
+
# The 8296 decodes its memory in a PLA, after the logic equations
|
|
22
|
+
# André Fachat read out of it (8296desc.txt on zimmers.net), with 32K
|
|
23
|
+
# of RAM from $8000 (Bus#screen) besides the board:
|
|
24
|
+
#
|
|
25
|
+
# - With bit 7 clear, every write from $8000 up but to $E8xx goes to
|
|
26
|
+
# that RAM, under the ROMs too. Reads find the screen at $8000,
|
|
27
|
+
# the empty sockets at $9000 and $A000, BASIC, the editor at
|
|
28
|
+
# $E000-$E7FF and again at $E900-$EFFF, and the KERNAL, unless the
|
|
29
|
+
# lines /RAMSEL9, /RAMSELA and /RAMON put RAM there (#ram_lines).
|
|
30
|
+
# RAMSEL9 puts it at $9000, RAMSELA at $A000, RAMON at
|
|
31
|
+
# $8000-$DFFF, RAMON with either of the others at $E000-$EFFF but
|
|
32
|
+
# $E8xx, and RAMON with RAMSELA at $F000 as well.
|
|
33
|
+
# - With bit 7 set, I/O showing through brings back the editor at
|
|
34
|
+
# $E900-$EFFF, which an 8096 leaves empty.
|
|
35
|
+
#
|
|
36
|
+
# Jumpers tie the lines: JU2 RAMSEL9 low and JU1 RAMSELA low
|
|
37
|
+
# (:ram9, :rama), or JU4, JU3 and JU5 RAMSEL9, RAMSELA and RAMON to
|
|
38
|
+
# the user port's PA1, PA0 and PA2 (:userport), each active low.
|
|
39
|
+
# With none fitted, as on xpet's 8296, the lines stay high.
|
|
19
40
|
class Expansion
|
|
20
41
|
# A 16K bank of the board, seen from +start+.
|
|
21
42
|
class Window
|
|
@@ -50,9 +71,16 @@ module Badline
|
|
|
50
71
|
end
|
|
51
72
|
end
|
|
52
73
|
|
|
74
|
+
# The user port lines that RAMSELA, RAMSEL9 and RAMON follow.
|
|
75
|
+
USER_PORT_LINES = 0x07
|
|
76
|
+
|
|
53
77
|
attr_reader :cells, :control
|
|
54
78
|
|
|
55
|
-
|
|
79
|
+
# The user port's PA0-PA2 as the board last saw them.
|
|
80
|
+
attr_reader :lines
|
|
81
|
+
|
|
82
|
+
# +jumpers+ are the 8296's (ROMSets::JUMPERS) when +board8296+ is.
|
|
83
|
+
def initialize(bus, board8296: false, jumpers: [])
|
|
56
84
|
@bus = bus
|
|
57
85
|
@cells = Array.new(0x10000, 0)
|
|
58
86
|
@banks = [0x8000, 0xc000, 0x8000, 0xc000].each_with_index.map do |start, bank|
|
|
@@ -60,13 +88,32 @@ module Badline
|
|
|
60
88
|
end
|
|
61
89
|
@port = ControlPort.new(self, @banks[1])
|
|
62
90
|
@control = 0
|
|
91
|
+
@board8296 = board8296
|
|
92
|
+
@ram9 = jumpers.include?("ram9")
|
|
93
|
+
@rama = jumpers.include?("rama")
|
|
94
|
+
@user_port = jumpers.include?("userport")
|
|
95
|
+
@lines = USER_PORT_LINES
|
|
63
96
|
end
|
|
64
97
|
|
|
98
|
+
def board8296? = @board8296
|
|
99
|
+
|
|
100
|
+
# Whether the board follows the user port's lines.
|
|
101
|
+
def user_port? = @user_port
|
|
102
|
+
|
|
65
103
|
def control=(value)
|
|
66
104
|
@control = value
|
|
67
105
|
@bus.map_pages
|
|
68
106
|
end
|
|
69
107
|
|
|
108
|
+
# Follows the user port's lines, +port_a+ as the VIA drives them.
|
|
109
|
+
def lines=(port_a)
|
|
110
|
+
lines = port_a & USER_PORT_LINES
|
|
111
|
+
return if lines == @lines
|
|
112
|
+
|
|
113
|
+
@lines = lines
|
|
114
|
+
@bus.map_pages
|
|
115
|
+
end
|
|
116
|
+
|
|
70
117
|
def reset!
|
|
71
118
|
self.control = 0
|
|
72
119
|
end
|
|
@@ -75,16 +122,33 @@ module Badline
|
|
|
75
122
|
@cells.fill(0)
|
|
76
123
|
end
|
|
77
124
|
|
|
125
|
+
# Whether the CPU reads the KERNAL at $F000-$FFFF.
|
|
126
|
+
def kernal_mapped?
|
|
127
|
+
return false if @control.anybits?(0x80)
|
|
128
|
+
|
|
129
|
+
!(@board8296 && ram_on? && ram_a?)
|
|
130
|
+
end
|
|
131
|
+
|
|
78
132
|
# Lays the banks the register picks over the pages, and the
|
|
79
133
|
# control register over page $FF's writes.
|
|
80
134
|
def map(reads, writes)
|
|
81
|
-
|
|
135
|
+
if @control.anybits?(0x80)
|
|
136
|
+
map_banks(reads, writes)
|
|
137
|
+
elsif @board8296
|
|
138
|
+
map_ram8296(reads, writes)
|
|
139
|
+
end
|
|
82
140
|
@port.under = writes[0xff]
|
|
83
141
|
writes[0xff] = @port
|
|
84
142
|
end
|
|
85
143
|
|
|
86
144
|
private
|
|
87
145
|
|
|
146
|
+
def ram_on? = @user_port && @lines.nobits?(0x04)
|
|
147
|
+
|
|
148
|
+
def ram9? = @ram9 || (@user_port && @lines.nobits?(0x02))
|
|
149
|
+
|
|
150
|
+
def ram_a? = @rama || (@user_port && @lines.nobits?(0x01))
|
|
151
|
+
|
|
88
152
|
def map_banks(reads, writes)
|
|
89
153
|
low = @banks[@control.anybits?(0x04) ? 2 : 0]
|
|
90
154
|
high = @banks[@control.anybits?(0x08) ? 3 : 1]
|
|
@@ -99,6 +163,36 @@ module Badline
|
|
|
99
163
|
reads.fill(high, page, count)
|
|
100
164
|
writes.fill(high, page, count)
|
|
101
165
|
end
|
|
166
|
+
map_editor_through(reads, writes) if @board8296
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# I/O showing through on the 8296 leaves $E900-$EFFF to the editor,
|
|
170
|
+
# and its writes to nothing.
|
|
171
|
+
def map_editor_through(reads, writes)
|
|
172
|
+
return unless @control.anybits?(0x40)
|
|
173
|
+
|
|
174
|
+
reads.fill(@bus.editor_rom, 0xe9, 0x07)
|
|
175
|
+
writes.fill(@bus.open_bus, 0xe9, 0x07)
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def map_ram8296(reads, writes)
|
|
179
|
+
ram = @bus.screen
|
|
180
|
+
writes.fill(ram, 0x90, 0x58)
|
|
181
|
+
writes.fill(ram, 0xe9, 0x17)
|
|
182
|
+
reads.fill(@bus.editor_rom, 0xe9, 0x07)
|
|
183
|
+
ram_on = ram_on?
|
|
184
|
+
ram9 = ram9?
|
|
185
|
+
ram_a = ram_a?
|
|
186
|
+
reads.fill(ram, 0x90, 0x10) if ram9 || ram_on
|
|
187
|
+
reads.fill(ram, 0xa0, 0x10) if ram_a || ram_on
|
|
188
|
+
return unless ram_on
|
|
189
|
+
|
|
190
|
+
reads.fill(ram, 0xb0, 0x30)
|
|
191
|
+
return unless ram9 || ram_a
|
|
192
|
+
|
|
193
|
+
reads.fill(ram, 0xe0, 0x08)
|
|
194
|
+
reads.fill(ram, 0xe9, 0x07)
|
|
195
|
+
reads.fill(ram, 0xf0, 0x10) if ram_a
|
|
102
196
|
end
|
|
103
197
|
end
|
|
104
198
|
end
|
data/lib/badline/pet/bus.rb
CHANGED
|
@@ -19,7 +19,8 @@ module Badline
|
|
|
19
19
|
# empty spot finds it, mostly the high byte of the address just
|
|
20
20
|
# fetched, as PET/unconnected shows on a real 3016. Writes to ROM and
|
|
21
21
|
# to empty spots go nowhere. An 8096 banks its 64K board over
|
|
22
|
-
# $8000-$FFFF (Expansion)
|
|
22
|
+
# $8000-$FFFF (Expansion), and an 8296 its board and the RAM under its
|
|
23
|
+
# ROMs.
|
|
23
24
|
class Bus
|
|
24
25
|
include Addressable
|
|
25
26
|
|
|
@@ -33,7 +34,8 @@ module Badline
|
|
|
33
34
|
def poke(_addr, _value); end
|
|
34
35
|
end
|
|
35
36
|
|
|
36
|
-
# Screen RAM, repeated over the pages it answers in.
|
|
37
|
+
# Screen RAM, repeated over the pages it answers in. On the 8296 it is
|
|
38
|
+
# all 32K of RAM from $8000, which the board lays under the ROMs.
|
|
37
39
|
class ScreenRAM
|
|
38
40
|
attr_reader :cells
|
|
39
41
|
|
|
@@ -107,29 +109,31 @@ module Badline
|
|
|
107
109
|
end
|
|
108
110
|
end
|
|
109
111
|
|
|
110
|
-
attr_reader :ram, :screen, :expansion
|
|
112
|
+
attr_reader :ram, :screen, :expansion, :editor_rom, :open_bus
|
|
111
113
|
|
|
112
114
|
# The last byte the CPU read or wrote.
|
|
113
115
|
attr_reader :data
|
|
114
116
|
|
|
115
117
|
# +model+ is a Model::Profile, +ram_kb+ the RAM below $8000 and
|
|
116
118
|
# +editor+ the editor ROM's file. The CRTC answers only on a board
|
|
117
|
-
# the model fits one on.
|
|
118
|
-
def initialize(model:, ram_kb:, editor:, pia1:, pia2:, via:, crtc:)
|
|
119
|
+
# the model fits one on. +jumpers+ are the 8296's (ROMSets::JUMPERS).
|
|
120
|
+
def initialize(model:, ram_kb:, editor:, pia1:, pia2:, via:, crtc:, jumpers: [])
|
|
119
121
|
addressable_at(0, length: 2**16)
|
|
120
122
|
@ram = Memory.new([], length: ram_kb * 1024, start: 0)
|
|
121
123
|
@screen = ScreenRAM.new(model.screen_bytes)
|
|
122
124
|
@screen_pages = model.screen_pages
|
|
123
125
|
basic = ROM.read(model.basic)
|
|
124
126
|
@basic_rom = ROM.new(basic, length: basic.length, start: 0xe000 - basic.length)
|
|
125
|
-
@editor_rom = ROM.load(editor, 0xe000)
|
|
127
|
+
@editor_rom = model.cbm8296? ? editor_socket(editor) : ROM.load(editor, 0xe000)
|
|
126
128
|
@kernal_rom = ROM.load(model.kernal, 0xf000)
|
|
127
129
|
@io = IO.new(self, pia1:, pia2:, via:, crtc:, crtc_fitted: model.crtc?)
|
|
128
130
|
@open_bus = OpenBus.new(self)
|
|
129
131
|
@data = 0
|
|
130
132
|
@read_pages = Array.new(256, @open_bus)
|
|
131
133
|
@write_pages = Array.new(256, @open_bus)
|
|
132
|
-
@expansion = model.expansion ? Expansion.new(self) : nil
|
|
134
|
+
@expansion = model.expansion ? Expansion.new(self, board8296: model.cbm8296?, jumpers:) : nil
|
|
135
|
+
@via = via
|
|
136
|
+
@user_port = !@expansion.nil? && @expansion.user_port?
|
|
133
137
|
@debug_register = nil
|
|
134
138
|
@io_write_handler = nil
|
|
135
139
|
map_pages
|
|
@@ -143,11 +147,16 @@ module Badline
|
|
|
143
147
|
|
|
144
148
|
def io_written
|
|
145
149
|
@io_write_handler&.call
|
|
150
|
+
@expansion.lines = @via.port_a_output if @user_port
|
|
146
151
|
end
|
|
147
152
|
|
|
148
|
-
# The RES line reaches the
|
|
153
|
+
# The RES line reaches the board's control register, and the user
|
|
154
|
+
# port's lines float high with the VIA's.
|
|
149
155
|
def reset!
|
|
150
|
-
@expansion
|
|
156
|
+
return if @expansion.nil?
|
|
157
|
+
|
|
158
|
+
@expansion.reset!
|
|
159
|
+
@expansion.lines = @via.port_a_output if @user_port
|
|
151
160
|
end
|
|
152
161
|
|
|
153
162
|
def power_on!
|
|
@@ -166,8 +175,9 @@ module Badline
|
|
|
166
175
|
end
|
|
167
176
|
|
|
168
177
|
# Whether the CPU reads the KERNAL ROM at $F000-$FFFF, which an 8096's
|
|
169
|
-
# board covers while its control register turns it on
|
|
170
|
-
|
|
178
|
+
# board covers while its control register turns it on, and an 8296's
|
|
179
|
+
# RAM too.
|
|
180
|
+
def kernal_mapped? = @expansion.nil? || @expansion.kernal_mapped?
|
|
171
181
|
|
|
172
182
|
# Calls the block with each byte written to $8BFF (DebugRegister).
|
|
173
183
|
def install_debug_register(&)
|
|
@@ -175,7 +185,7 @@ module Badline
|
|
|
175
185
|
map_pages
|
|
176
186
|
end
|
|
177
187
|
|
|
178
|
-
# Lays the pages out again, for the
|
|
188
|
+
# Lays the pages out again, for the board's control register.
|
|
179
189
|
def map_pages
|
|
180
190
|
map(@open_bus, 0x00, 0x100)
|
|
181
191
|
map(@ram, 0x00, @ram.length >> 8)
|
|
@@ -190,6 +200,14 @@ module Badline
|
|
|
190
200
|
|
|
191
201
|
private
|
|
192
202
|
|
|
203
|
+
# The 8296's editor socket, which answers at $E000-$E7FF and
|
|
204
|
+
# $E900-$EFFF: a 4K ROM's halves there, or a 2K one twice over.
|
|
205
|
+
def editor_socket(file)
|
|
206
|
+
bytes = ROM.read(file)
|
|
207
|
+
bytes += bytes if bytes.length == 0x800
|
|
208
|
+
ROM.new(bytes, length: 0x1000, start: 0xe000)
|
|
209
|
+
end
|
|
210
|
+
|
|
193
211
|
def map_debug_register
|
|
194
212
|
@debug_register.under = @write_pages[0x8b]
|
|
195
213
|
@write_pages[0x8b] = @debug_register
|
|
@@ -6,21 +6,27 @@ module Badline
|
|
|
6
6
|
# (upper case and graphics) from $000 and text (lower and upper case)
|
|
7
7
|
# from $400, 128 characters of 8 rows each. Bit 7 of a screen code
|
|
8
8
|
# inverts the character's rows, as the hardware does, so #rows holds
|
|
9
|
-
# 256 codes a set, each row at (set << 11) | (code << 3) | row.
|
|
9
|
+
# 256 codes a set, each row at (set << 11) | (code << 3) | row. A 4K
|
|
10
|
+
# ROM, the 8296's, holds two such halves, the second at HALF.
|
|
10
11
|
class Characters
|
|
11
12
|
TEXT = 0x800
|
|
12
13
|
|
|
14
|
+
HALF = 0x1000
|
|
15
|
+
|
|
13
16
|
attr_reader :rows
|
|
14
17
|
|
|
15
18
|
def initialize(file)
|
|
16
19
|
rom = ROM.read(file)
|
|
17
|
-
@rows = Array.new(
|
|
20
|
+
@rows = Array.new(rom.length << 1) do |index|
|
|
18
21
|
set = index >> 11
|
|
19
22
|
code = (index >> 3) & 0xff
|
|
20
23
|
bits = rom[(set << 10) | ((code & 0x7f) << 3) | (index & 0x07)]
|
|
21
24
|
code >= 0x80 ? bits ^ 0xff : bits
|
|
22
25
|
end
|
|
23
26
|
end
|
|
27
|
+
|
|
28
|
+
# Whether the ROM holds two halves.
|
|
29
|
+
def halves? = @rows.length > HALF
|
|
24
30
|
end
|
|
25
31
|
end
|
|
26
32
|
end
|
|
@@ -7,7 +7,15 @@ module Badline
|
|
|
7
7
|
# and the character ROM's row RA0-RA2 from the half VIA CA2 selects.
|
|
8
8
|
# On the 80-column boards each character reads two bytes, at twice the
|
|
9
9
|
# address and the one after, and shows them side by side, 16 dots to a
|
|
10
|
-
# cycle. MA12 low inverts the picture, and RA3 high blanks the
|
|
10
|
+
# cycle. MA12 low inverts the picture, and RA3 or RA4 high blanks the
|
|
11
|
+
# line (NO ROW on the 8032029 and 8032087 schematics' sheet 10), so a
|
|
12
|
+
# row taller than eight lines shows its first eight alone.
|
|
13
|
+
#
|
|
14
|
+
# The 8296's board (+board8296+) reads the screen from the 32K of RAM
|
|
15
|
+
# from $8000 on: MA0-MA11 count two bytes each from $8000, RA3 alone
|
|
16
|
+
# blanks the line, and RA4 adds $4000, so lines 16 to 23 of a row
|
|
17
|
+
# taller than 16 show the RAM under BASIC at $C000. MA13 picks the half
|
|
18
|
+
# of a 4K character ROM.
|
|
11
19
|
#
|
|
12
20
|
# The picture is a Canvas 273 lines high, 704 dots wide on 80 columns
|
|
13
21
|
# and 384 on 40, framed as xpet frames it. Across, R1's characters sit
|
|
@@ -31,9 +39,13 @@ module Badline
|
|
|
31
39
|
# The screen RAM's bytes.
|
|
32
40
|
attr_writer :cells
|
|
33
41
|
|
|
34
|
-
def initialize(characters, via, crtc_registers, columns80:)
|
|
42
|
+
def initialize(characters, via, crtc_registers, columns80:, board8296: false)
|
|
35
43
|
@cells = []
|
|
36
44
|
@rows = characters.rows
|
|
45
|
+
@cell_mask = board8296 ? 0xfff : 0x3ff
|
|
46
|
+
@ra4 = board8296 ? 0x10 : 0
|
|
47
|
+
@no_row = board8296 ? 0x08 : 0x18
|
|
48
|
+
@half = characters.halves? ? 0x2000 : 0
|
|
37
49
|
@via = via
|
|
38
50
|
@registers = crtc_registers
|
|
39
51
|
@wide = columns80
|
|
@@ -61,7 +73,7 @@ module Badline
|
|
|
61
73
|
x = @origin + (since_hsync << @char_shift)
|
|
62
74
|
@from = x if x < @from
|
|
63
75
|
@to = x if x > @to
|
|
64
|
-
bits = raster.anybits?(
|
|
76
|
+
bits = raster.anybits?(@no_row) ? 0 : row_bits(address, raster)
|
|
65
77
|
if @wide
|
|
66
78
|
@canvas.paint(x, @y, bits >> 8)
|
|
67
79
|
@canvas.paint(x + 8, @y, bits & 0xff)
|
|
@@ -109,11 +121,12 @@ module Badline
|
|
|
109
121
|
def row_bits(address, raster)
|
|
110
122
|
set = @via.ca2_output ? Characters::TEXT : 0
|
|
111
123
|
invert = address.anybits?(0x1000) ? 0 : 0xffff
|
|
112
|
-
line = set | (raster & 0x07)
|
|
113
|
-
cell = address &
|
|
124
|
+
line = set | (raster & 0x07) | ((address & @half) >> 1)
|
|
125
|
+
cell = address & @cell_mask
|
|
114
126
|
if @wide
|
|
115
|
-
|
|
116
|
-
|
|
127
|
+
base = (cell << 1) | ((raster & @ra4) << 10)
|
|
128
|
+
left = @rows[line | (@cells[base] << 3)]
|
|
129
|
+
right = @rows[line | (@cells[base | 1] << 3)]
|
|
117
130
|
((left << 8) | right) ^ invert
|
|
118
131
|
else
|
|
119
132
|
(@rows[line | (@cells[cell] << 3)] ^ invert) & 0xff
|