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
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/drives/model2031"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
class PET
|
|
7
|
+
# The PET's true drive: a Drives::Model2031 on the IEEE-488 bus
|
|
8
|
+
# (IEEEPorts), clocked after the CPU each cycle and reset with it, as
|
|
9
|
+
# the PET's RES line reaches IFC. The traps on the KERNAL's primitives
|
|
10
|
+
# (IEEETraps) leave device 8 to the drive while it is device 8.
|
|
11
|
+
module IEEEDrive
|
|
12
|
+
# The 2031 on the bus, or nil.
|
|
13
|
+
attr_reader :drive2031
|
|
14
|
+
|
|
15
|
+
# Plugs in a Drives::Model2031, in place of the one there.
|
|
16
|
+
def attach_drive2031(drive)
|
|
17
|
+
detach_drive2031
|
|
18
|
+
drive.host_clock_hz = 1_000_000
|
|
19
|
+
drive.connect(@ieee.bus)
|
|
20
|
+
@drive2031 = drive
|
|
21
|
+
@ieee.lines_written
|
|
22
|
+
@ieee_trap&.device = trap_device
|
|
23
|
+
@init_threshold = [@init_threshold, Badline::Drives::Model2031::BOOT_CYCLES].max if @cycles < @init_threshold
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Unplugs the 2031, leaving it on a bus of its own.
|
|
27
|
+
def detach_drive2031
|
|
28
|
+
drive = @drive2031
|
|
29
|
+
return unless drive
|
|
30
|
+
|
|
31
|
+
drive.connect(Badline::Drives::IEEEBus.new)
|
|
32
|
+
@drive2031 = nil
|
|
33
|
+
@ieee_trap&.device = trap_device
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# The true drive on the bus, or nil.
|
|
37
|
+
def true_drive = @drive2031
|
|
38
|
+
|
|
39
|
+
# Plugs in a 2031 as device 8, in place of the drive there, and
|
|
40
|
+
# returns it.
|
|
41
|
+
def plug_true_drive
|
|
42
|
+
Badline::Drives::Model2031.new.tap { |drive| attach_drive2031(drive) }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
# The 2031 as keyed snapshot modules named from its device number:
|
|
48
|
+
# BL-D8, BL-D8-CPU and so on for device 8.
|
|
49
|
+
def save_drive_modules(sections)
|
|
50
|
+
drive = @drive2031
|
|
51
|
+
drive&.save_modules(sections, "BL-D#{drive.device}")
|
|
52
|
+
sections
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The 2031 a snapshot's modules hold, built afresh unless one is on
|
|
56
|
+
# the bus as the same device, or none when they hold no drive. The
|
|
57
|
+
# bus then carries the PET's lines as its chips left them.
|
|
58
|
+
def load_drive_modules(modules)
|
|
59
|
+
device = 8
|
|
60
|
+
input = modules.optional("BL-D8")
|
|
61
|
+
while input.nil? && device < 11
|
|
62
|
+
device += 1
|
|
63
|
+
input = modules.optional("BL-D#{device}")
|
|
64
|
+
end
|
|
65
|
+
if input
|
|
66
|
+
detach_drive2031 if @drive2031 && @drive2031.device != device
|
|
67
|
+
attach_drive2031(Badline::Drives::Model2031.new(device:)) unless @drive2031
|
|
68
|
+
@drive2031.load_modules(modules, input, "BL-D#{device}")
|
|
69
|
+
else
|
|
70
|
+
detach_drive2031
|
|
71
|
+
end
|
|
72
|
+
@ieee.lines_written
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -15,8 +15,12 @@ module Badline
|
|
|
15
15
|
# The outputs go through inverting drivers onto the bus, so a 0 on a
|
|
16
16
|
# data line's port bit pulls the line low, and the inputs read the
|
|
17
17
|
# lines as they are, a 0 for a low line. It is PIA 2's peripheral, and
|
|
18
|
-
# answers what PIA 1 and the VIA read of the bus.
|
|
18
|
+
# answers what PIA 1 and the VIA read of the bus. It pushes the lines
|
|
19
|
+
# the PET pulls into the bus for the drives on it after every write to
|
|
20
|
+
# the chips (lines_written).
|
|
19
21
|
class IEEEPorts
|
|
22
|
+
attr_reader :bus
|
|
23
|
+
|
|
20
24
|
def initialize(bus)
|
|
21
25
|
@bus = bus
|
|
22
26
|
@pia1 = @pia2 = @via = nil
|
|
@@ -46,6 +50,13 @@ module Badline
|
|
|
46
50
|
value
|
|
47
51
|
end
|
|
48
52
|
|
|
53
|
+
# The PET wrote one of its chips: the drives on the bus see the lines
|
|
54
|
+
# it pulls now.
|
|
55
|
+
def lines_written
|
|
56
|
+
bus = @bus
|
|
57
|
+
bus.host_lines = controller_lines unless bus.drives.empty?
|
|
58
|
+
end
|
|
59
|
+
|
|
49
60
|
private
|
|
50
61
|
|
|
51
62
|
def low_lines = @bus.low_lines(controller_lines)
|
|
@@ -8,12 +8,15 @@ module Badline
|
|
|
8
8
|
# commands, run through the primitives.
|
|
9
9
|
module IEEETraps
|
|
10
10
|
# Puts the storage in device 8. Mounting again swaps the disk while
|
|
11
|
-
# the machine runs, and the drive keeps its RAM and its status.
|
|
11
|
+
# the machine runs, and the drive keeps its RAM and its status. The
|
|
12
|
+
# traps leave device 8 to a true drive with that number.
|
|
12
13
|
def mount(storage)
|
|
13
14
|
return @dos.insert(storage) if @dos
|
|
14
15
|
|
|
15
16
|
@dos = dos = KernalTrap::DOS.new(storage, channel_saves: true)
|
|
16
17
|
@ieee_trap = KernalTrap::IEEE.new(cpu:, bus:, layout: trap_layout, dos:).install
|
|
18
|
+
@ieee_trap.device = trap_device
|
|
19
|
+
@ieee_trap
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
# Takes device 8's storage out, and the traps with it, so the KERNAL
|
|
@@ -33,10 +36,6 @@ module Badline
|
|
|
33
36
|
# The path of the disk or directory device 8 serves, or an empty one.
|
|
34
37
|
def mounted_path = @dos.nil? ? "" : @dos.path
|
|
35
38
|
|
|
36
|
-
# The true drive on the bus, which a PET has none of yet: every disk
|
|
37
|
-
# goes in device 8 through the traps.
|
|
38
|
-
def true_drive = nil
|
|
39
|
-
|
|
40
39
|
# Device 8 through the traps in a keyed snapshot module (BL-TRAPDOS):
|
|
41
40
|
# the storage under storage.*, the DOS, and the IEEE-488 trap under
|
|
42
41
|
# ieee.*. Nothing mounted writes nothing.
|
|
@@ -70,6 +69,12 @@ module Badline
|
|
|
70
69
|
|
|
71
70
|
# The IEEELayout of the KERNAL the model runs.
|
|
72
71
|
def trap_layout = @model.trap_layout
|
|
72
|
+
|
|
73
|
+
# Device 8, unless a true drive is device 8.
|
|
74
|
+
def trap_device
|
|
75
|
+
device = KernalTrap::Routine::DEVICE
|
|
76
|
+
true_drive&.device == device ? nil : device
|
|
77
|
+
end
|
|
73
78
|
end
|
|
74
79
|
end
|
|
75
80
|
end
|
|
@@ -7,7 +7,8 @@ module Badline
|
|
|
7
7
|
# the screen RAM, the last byte on the bus and an 8096's board, both
|
|
8
8
|
# PIAs, the VIA, the CRTC where the model has one, the picture with
|
|
9
9
|
# the video's beam, the CPU, the datasettes at cassette ports #1 and
|
|
10
|
-
# #2, and device 8 through the
|
|
10
|
+
# #2, a 2031 on the IEEE-488 bus (IEEEDrive), and device 8 through the
|
|
11
|
+
# traps.
|
|
11
12
|
#
|
|
12
13
|
# A load lays the pages out before the chips load, and works out what
|
|
13
14
|
# each unit derives from its fields once every unit is loaded.
|
|
@@ -24,6 +25,7 @@ module Badline
|
|
|
24
25
|
cassettes = @cassettes
|
|
25
26
|
sections << Snapshot::Keyed.section("BL-TAPE1") { |out| cassettes.datasette1.save_fields(out) }
|
|
26
27
|
sections << Snapshot::Keyed.section("BL-TAPE2") { |out| cassettes.datasette2.save_fields(out) }
|
|
28
|
+
save_drive_modules(sections)
|
|
27
29
|
sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
|
|
28
30
|
sections
|
|
29
31
|
end
|
|
@@ -43,6 +45,7 @@ module Badline
|
|
|
43
45
|
@cpu.load_fields(modules.required("BL-CPU"))
|
|
44
46
|
@cassettes.datasette1.load_fields(modules.required("BL-TAPE1"))
|
|
45
47
|
@cassettes.datasette2.load_fields(modules.required("BL-TAPE2"))
|
|
48
|
+
load_drive_modules(modules)
|
|
46
49
|
trap_dos = modules.optional("BL-TRAPDOS")
|
|
47
50
|
trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
|
|
48
51
|
@cpu.restored!
|
|
@@ -80,26 +80,30 @@ module Badline
|
|
|
80
80
|
|
|
81
81
|
class Bus
|
|
82
82
|
class Expansion
|
|
83
|
-
# The control register and the board's 64K, under `prefix
|
|
84
|
-
#
|
|
83
|
+
# The control register and the board's 64K, under `prefix`, and
|
|
84
|
+
# on the 8296 the user port lines its RAM last followed. The banks
|
|
85
|
+
# are laid over the pages again once the bus is loaded.
|
|
85
86
|
module KeyedState
|
|
86
87
|
def save_fields(out, prefix)
|
|
87
88
|
out.int("#{prefix}control", @control).bytes("#{prefix}ram", @cells)
|
|
89
|
+
out.int("#{prefix}user_port", @lines) if @board8296
|
|
88
90
|
end
|
|
89
91
|
|
|
90
92
|
def load_fields(input, prefix)
|
|
91
93
|
@control = input.byte("#{prefix}control", @control)
|
|
92
94
|
input.bytes_into("#{prefix}ram", @cells)
|
|
95
|
+
@lines = input.int("#{prefix}user_port", USER_PORT_LINES) & USER_PORT_LINES
|
|
93
96
|
end
|
|
94
97
|
end
|
|
95
98
|
|
|
96
99
|
include KeyedState
|
|
97
100
|
end
|
|
98
101
|
|
|
99
|
-
# The RAM below $8000, the screen RAM,
|
|
100
|
-
# bus.* and an 8096's
|
|
101
|
-
# module (BL-RAM). The pages
|
|
102
|
-
# (#restored!). The ROMs come
|
|
102
|
+
# The RAM below $8000, the screen RAM, all 32K from $8000 on an 8296,
|
|
103
|
+
# the last byte on the bus under bus.* and an 8096's or 8296's board
|
|
104
|
+
# under expansion.*, in a keyed snapshot module (BL-RAM). The pages
|
|
105
|
+
# are laid out again once they are loaded (#restored!). The ROMs come
|
|
106
|
+
# with the model.
|
|
103
107
|
module KeyedState
|
|
104
108
|
def save_fields(out)
|
|
105
109
|
@ram.save_fields(out, "ram")
|
data/lib/badline/pet/model.rb
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
class PET
|
|
5
5
|
# The PETs and CBMs badline builds by name, after xpet's -model names:
|
|
6
|
-
# the board's RAM, its stock ROM set and video, and whether
|
|
7
|
-
#
|
|
6
|
+
# the board's RAM, its stock ROM set and video, and whether a 64K
|
|
7
|
+
# board banked through $FFF0 is fitted, the 8096's or the 8296's own.
|
|
8
8
|
#
|
|
9
9
|
# +video+ is :discrete, the fixed 40-column, 60 Hz generator of the
|
|
10
10
|
# 2001 and 3000 series, :crtc40 or :crtc80, a 6545 the editor ROM
|
|
@@ -14,7 +14,10 @@ module Badline
|
|
|
14
14
|
#
|
|
15
15
|
# The 2001's 1K of screen RAM repeats four times through $8000-$8FFF.
|
|
16
16
|
# Later boards decode $8000-$87FF alone, the 40-column ones with their
|
|
17
|
-
# 1K twice over and the 80-column ones with 2K.
|
|
17
|
+
# 1K twice over and the 80-column ones with 2K. The 8296 has 128K of
|
|
18
|
+
# dynamic RAM, the 32K below $8000, the 32K above it, of which the
|
|
19
|
+
# screen is the start and the rest lies under the ROMs, and the 64K
|
|
20
|
+
# board (Bus::Expansion).
|
|
18
21
|
module Model
|
|
19
22
|
Profile = Data.define(:name, :ram_kb, :basic, :editor_50hz, :editor_60hz, :kernal, :characters, :keyboard,
|
|
20
23
|
:video, :expansion) do
|
|
@@ -22,10 +25,18 @@ module Badline
|
|
|
22
25
|
|
|
23
26
|
def editor(refresh_hz) = refresh_hz == 50 ? editor_50hz : editor_60hz
|
|
24
27
|
|
|
25
|
-
|
|
28
|
+
# The 8296, whose board decodes its memory in a PLA.
|
|
29
|
+
def cbm8296? = name == "8296"
|
|
30
|
+
|
|
31
|
+
# The screen RAM's bytes, and on the 8296 all 32K of RAM from $8000.
|
|
32
|
+
def screen_bytes
|
|
33
|
+
return 0x8000 if cbm8296?
|
|
34
|
+
|
|
35
|
+
video == :crtc80 ? 0x800 : 0x400
|
|
36
|
+
end
|
|
26
37
|
|
|
27
38
|
# How many pages from $8000 on the screen RAM answers in.
|
|
28
|
-
def screen_pages = name == "2001" ? 0x10 : 0x08
|
|
39
|
+
def screen_pages = name == "2001" || cbm8296? ? 0x10 : 0x08
|
|
29
40
|
|
|
30
41
|
# The BASIC 1 KERNAL keeps its keyboard buffer and jiffy clock in
|
|
31
42
|
# page 2; BASIC 2 and 4 keep them where each other does.
|
|
@@ -70,7 +81,12 @@ module Badline
|
|
|
70
81
|
kernal: "pet/kernal4.rom", characters: "pet/characters2.rom", keyboard: :business,
|
|
71
82
|
video: :crtc80, expansion: true)
|
|
72
83
|
|
|
73
|
-
|
|
84
|
+
CBM8296 = Profile.new(name: "8296", ram_kb: 32, basic: "pet/basic4.rom",
|
|
85
|
+
editor_50hz: "pet/editor4-80-b-50hz-8296.rom", editor_60hz: "pet/editor4-80-b-60hz.rom",
|
|
86
|
+
kernal: "pet/kernal4.rom", characters: "pet/characters2.rom", keyboard: :business,
|
|
87
|
+
video: :crtc80, expansion: true)
|
|
88
|
+
|
|
89
|
+
ALL = [PET2001, PET3032, PET3032B, PET4032, CBM8032, CBM8096, CBM8296].freeze
|
|
74
90
|
|
|
75
91
|
DEFAULT = "8032"
|
|
76
92
|
|
|
@@ -85,6 +101,40 @@ module Badline
|
|
|
85
101
|
model
|
|
86
102
|
end
|
|
87
103
|
|
|
104
|
+
# Whether the +profile+'s board takes +ram_kb+ below $8000: the 8296
|
|
105
|
+
# has its 32K soldered on.
|
|
106
|
+
def self.ram_fits?(profile, ram_kb)
|
|
107
|
+
profile.cbm8296? ? ram_kb == 32 : RAM_SIZES.include?(ram_kb)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The +jumpers+ the +profile+'s board has fitted, which only the
|
|
111
|
+
# 8296's takes (ROMSets::JUMPERS), in that order. Raises
|
|
112
|
+
# ArgumentError for any it doesn't have.
|
|
113
|
+
def self.checked_jumpers(profile, jumpers)
|
|
114
|
+
known = ROMSets.jumpers(profile.name)
|
|
115
|
+
unknown = jumpers.reject { |jumper| known.include?(jumper) }
|
|
116
|
+
raise ArgumentError, "the #{profile.name} has no jumper #{unknown.first}" unless unknown.empty?
|
|
117
|
+
|
|
118
|
+
known.select { |jumper| jumpers.include?(jumper) }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# The +profile+ with the character ROM ROMSets::CHARACTERS names in
|
|
122
|
+
# place of its own, nil for its own. Raises ArgumentError for one the
|
|
123
|
+
# model doesn't take.
|
|
124
|
+
def self.fitted(profile, characters)
|
|
125
|
+
return profile if characters.nil?
|
|
126
|
+
|
|
127
|
+
name = profile.name
|
|
128
|
+
unless ROMSets.characters(name).include?(characters)
|
|
129
|
+
raise ArgumentError, "the #{name} takes no character ROM #{characters}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
Profile.new(name:, ram_kb: profile.ram_kb, basic: profile.basic, editor_50hz: profile.editor_50hz,
|
|
133
|
+
editor_60hz: profile.editor_60hz, kernal: profile.kernal,
|
|
134
|
+
characters: ROMSets::CHARACTERS.fetch(characters), keyboard: profile.keyboard,
|
|
135
|
+
video: profile.video, expansion: profile.expansion)
|
|
136
|
+
end
|
|
137
|
+
|
|
88
138
|
# The +profile+ with the BASIC and the editor ROMSets names in place
|
|
89
139
|
# of its own: +basic+ a key of ROMSets::BASICS and +editor+ one of
|
|
90
140
|
# ROMSets::EDITORS, each nil for the one that fits. An editor named
|
data/lib/badline/pet/rom_sets.rb
CHANGED
|
@@ -4,8 +4,9 @@ module Badline
|
|
|
4
4
|
class PET
|
|
5
5
|
# The BASIC and editor ROMs each model's board takes: its own, and the
|
|
6
6
|
# upgrades Commodore sold for it, BASIC 2 for the 2001 and BASIC 4 for
|
|
7
|
-
# the 3032s
|
|
8
|
-
#
|
|
7
|
+
# the 3032s, and the 8296's character ROMs and board jumpers. Options
|
|
8
|
+
# reads it to check --basic, --editor, --characters and --jumpers, so
|
|
9
|
+
# it loads nothing else.
|
|
9
10
|
#
|
|
10
11
|
# An editor ROM goes with one BASIC, scans one keyboard and drives one
|
|
11
12
|
# board's video, so a model and a BASIC fit one editor, and on a CRTC
|
|
@@ -23,26 +24,42 @@ module Badline
|
|
|
23
24
|
"1" => "pet/editor1.rom", "2-n" => "pet/editor2-n.rom", "2-b" => "pet/editor2-b.rom",
|
|
24
25
|
"4-n" => "pet/editor4-n.rom", "4-b" => "pet/editor4-b.rom", "4-40-n-50hz" => "pet/editor4-40-n-50hz.rom",
|
|
25
26
|
"4-40-n-60hz" => "pet/editor4-40-n-60hz.rom", "4-80-b-50hz" => "pet/editor4-80-b-50hz.rom",
|
|
26
|
-
"4-80-b-50hz-0283" => "pet/editor4-80-b-50hz-0283.rom", "4-80-b-60hz" => "pet/editor4-80-b-60hz.rom"
|
|
27
|
+
"4-80-b-50hz-0283" => "pet/editor4-80-b-50hz-0283.rom", "4-80-b-60hz" => "pet/editor4-80-b-60hz.rom",
|
|
28
|
+
"4-80-b-50hz-8296" => "pet/editor4-80-b-50hz-8296.rom"
|
|
27
29
|
}.freeze
|
|
28
30
|
|
|
29
31
|
# The BASICs each model takes, its own first, and the editors that
|
|
30
32
|
# fit each, the 50 Hz one first and the 60 Hz one last on a CRTC
|
|
31
33
|
# board. The 8032's other 50 Hz editor, 901474-04 dated 0283, sits
|
|
32
|
-
# between them.
|
|
34
|
+
# between them, and the 8296 takes the 8032's as well as its own.
|
|
33
35
|
FITS = {
|
|
34
36
|
"2001" => { "1" => %w[1], "2" => %w[2-n] },
|
|
35
37
|
"3032" => { "2" => %w[2-n], "4" => %w[4-n] },
|
|
36
38
|
"3032b" => { "2" => %w[2-b], "4" => %w[4-b] },
|
|
37
39
|
"4032" => { "4" => %w[4-40-n-50hz 4-40-n-60hz] },
|
|
38
40
|
"8032" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
|
|
39
|
-
"8096" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] }
|
|
41
|
+
"8096" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
|
|
42
|
+
"8296" => { "4" => %w[4-80-b-50hz-8296 4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] }
|
|
40
43
|
}.freeze
|
|
41
44
|
|
|
45
|
+
# The character ROMs by the names --characters takes, and the ones
|
|
46
|
+
# each model's board takes, its own first: the 8296's socket takes
|
|
47
|
+
# 901447-10, its ASCII one, or the 4K 324242-01, which holds a DIN set
|
|
48
|
+
# in its first half and 901447-10 in its second.
|
|
49
|
+
CHARACTERS = { "ascii" => "pet/characters2.rom", "din" => "pet/characters-din.rom" }.freeze
|
|
50
|
+
|
|
51
|
+
CHARACTER_FITS = { "8296" => %w[ascii din] }.freeze
|
|
52
|
+
|
|
53
|
+
# The jumpers each model's board takes, by the names --jumpers takes
|
|
54
|
+
# (Bus::Expansion), none on a board not named.
|
|
55
|
+
JUMPERS = { "8296" => %w[ram9 rama userport] }.freeze
|
|
56
|
+
|
|
42
57
|
# The cycles a line and the lines a frame the CRTC editors set up
|
|
43
58
|
# where they differ from 50-cycle lines, 400 a frame at 50 Hz and 333
|
|
44
59
|
# at 60 Hz, by the editor's ROM.
|
|
45
|
-
CRTC_FRAMES = {
|
|
60
|
+
CRTC_FRAMES = {
|
|
61
|
+
"pet/editor4-80-b-50hz-0283.rom" => [64, 313], "pet/editor4-80-b-50hz-8296.rom" => [59, 339]
|
|
62
|
+
}.freeze
|
|
46
63
|
|
|
47
64
|
# The cycles a line and the lines a frame the CRTC editor in the ROM
|
|
48
65
|
# named sets up at +refresh_hz+.
|
|
@@ -55,6 +72,13 @@ module Badline
|
|
|
55
72
|
# a BASIC it doesn't take.
|
|
56
73
|
def self.editors(model, basic) = FITS.fetch(model).fetch(basic, [])
|
|
57
74
|
|
|
75
|
+
# The character ROMs the model takes, by name, none for one that takes
|
|
76
|
+
# only its own.
|
|
77
|
+
def self.characters(model) = CHARACTER_FITS.fetch(model, [])
|
|
78
|
+
|
|
79
|
+
# The jumpers the model's board takes.
|
|
80
|
+
def self.jumpers(model) = JUMPERS.fetch(model, [])
|
|
81
|
+
|
|
58
82
|
# The editor that fits the model with the BASIC at +refresh_hz+.
|
|
59
83
|
def self.editor(model, basic, refresh_hz)
|
|
60
84
|
fitting = editors(model, basic)
|
data/lib/badline/pet.rb
CHANGED
|
@@ -8,6 +8,7 @@ require "badline/pet/via_ports"
|
|
|
8
8
|
require "badline/pet/keyboard_ports"
|
|
9
9
|
require "badline/pet/ieee_ports"
|
|
10
10
|
require "badline/pet/ieee_traps"
|
|
11
|
+
require "badline/pet/ieee_drive"
|
|
11
12
|
require "badline/pet/cassettes"
|
|
12
13
|
require "badline/pet/characters"
|
|
13
14
|
require "badline/pet/canvas"
|
|
@@ -42,13 +43,15 @@ module Badline
|
|
|
42
43
|
# The VIA's CB2 is the sound (Sound).
|
|
43
44
|
#
|
|
44
45
|
# Device 8 answers through traps on the KERNAL's IEEE-488 primitives
|
|
45
|
-
# (IEEETraps), on
|
|
46
|
-
# each cassette port has a datasette
|
|
46
|
+
# (IEEETraps), on the IEEE-488 bus (IEEEPorts), or a true 2031 on the
|
|
47
|
+
# bus answers (IEEEDrive), and each cassette port has a datasette
|
|
48
|
+
# (Cassettes). It runs headless.
|
|
47
49
|
class PET
|
|
48
50
|
include Machine::KeyboardBuffer
|
|
49
51
|
include Machine::Clocking
|
|
50
52
|
include Machine::Settling
|
|
51
53
|
include IEEETraps
|
|
54
|
+
include IEEEDrive
|
|
52
55
|
|
|
53
56
|
attr_reader :model, :bus, :cpu, :pia1, :pia2, :via, :crtc, :keyboard, :ieee, :cassettes, :cycles, :init_threshold
|
|
54
57
|
|
|
@@ -58,6 +61,9 @@ module Badline
|
|
|
58
61
|
# The RAM below $8000, in K.
|
|
59
62
|
attr_reader :ram_kb
|
|
60
63
|
|
|
64
|
+
# The 8296's board jumpers that are fitted, by name.
|
|
65
|
+
attr_reader :jumpers
|
|
66
|
+
|
|
61
67
|
def ram = @bus.ram
|
|
62
68
|
|
|
63
69
|
# Reads and writes memory as the CPU sees it, through the bus.
|
|
@@ -75,14 +81,17 @@ module Badline
|
|
|
75
81
|
# +model+ names one of Model::ALL, with +ram_kb+ of RAM below $8000
|
|
76
82
|
# (Model::RAM_SIZES) in place of the model's own when not nil. A CRTC
|
|
77
83
|
# model runs its 50 Hz editor, or its 60 Hz one for +refresh_hz+ 60.
|
|
78
|
-
# +basic+ and +
|
|
79
|
-
# (Model.upgraded)
|
|
80
|
-
|
|
81
|
-
|
|
84
|
+
# +basic+, +editor+ and +characters+ name ROMs the board takes in place
|
|
85
|
+
# of its own (Model.upgraded, Model.fitted), and +jumpers+ the 8296's
|
|
86
|
+
# board jumpers (ROMSets::JUMPERS).
|
|
87
|
+
def initialize(model: Model::DEFAULT, ram_kb: nil, refresh_hz: 50, debug: false, basic: nil, editor: nil,
|
|
88
|
+
characters: nil, jumpers: [])
|
|
89
|
+
@model = Model.fitted(Model.upgraded(Model.named(model), basic, editor), characters)
|
|
82
90
|
ram_kb ||= @model.ram_kb
|
|
83
|
-
raise ArgumentError, "
|
|
91
|
+
raise ArgumentError, "the #{@model.name} has no #{ram_kb}K RAM board" unless Model.ram_fits?(@model, ram_kb)
|
|
84
92
|
|
|
85
93
|
@ram_kb = ram_kb
|
|
94
|
+
@jumpers = Model.checked_jumpers(@model, jumpers)
|
|
86
95
|
|
|
87
96
|
@refresh_hz = @model.crtc? ? refresh_hz : 60
|
|
88
97
|
@crtc_video = @model.crtc?
|
|
@@ -99,16 +108,21 @@ module Badline
|
|
|
99
108
|
@cassettes = Cassettes.new(@pia1, @via)
|
|
100
109
|
keyboard_ports.connect(@pia1, @ieee, @cassettes)
|
|
101
110
|
characters = Characters.new(@model.characters)
|
|
102
|
-
@crtc_output = CRTCVideo.new(characters, @via, registers, columns80: @model.video == :crtc80
|
|
111
|
+
@crtc_output = CRTCVideo.new(characters, @via, registers, columns80: @model.video == :crtc80,
|
|
112
|
+
board8296: @model.cbm8296?)
|
|
103
113
|
@crtc = Chips::CRTC.new(@crtc_output, registers:)
|
|
104
114
|
@ieee.connect(@pia1, @pia2, @via)
|
|
105
115
|
@bus = Bus.new(model: @model, ram_kb:, editor: @model.editor(@refresh_hz), pia1: @pia1, pia2: @pia2,
|
|
106
|
-
via: @via, crtc: @crtc)
|
|
116
|
+
via: @via, crtc: @crtc, jumpers: @jumpers)
|
|
107
117
|
@crtc_output.cells = @bus.screen.cells
|
|
108
118
|
@discrete = DiscreteVideo.new(@bus.screen.cells, characters, @via)
|
|
109
119
|
@canvas = @crtc_video ? @crtc_output.canvas : @discrete.canvas
|
|
110
120
|
cassettes = @cassettes
|
|
111
|
-
|
|
121
|
+
ieee = @ieee
|
|
122
|
+
@bus.on_io_write do
|
|
123
|
+
cassettes.lines_written
|
|
124
|
+
ieee.lines_written
|
|
125
|
+
end
|
|
112
126
|
@cpu = CPU.new(@bus, debug:)
|
|
113
127
|
@cycles = 0
|
|
114
128
|
@init_handlers = []
|
|
@@ -116,6 +130,7 @@ module Badline
|
|
|
116
130
|
@dos = nil
|
|
117
131
|
@ieee_trap = nil
|
|
118
132
|
@capture_output = nil
|
|
133
|
+
@drive2031 = nil
|
|
119
134
|
@init_threshold = boot_cycles(ram_kb)
|
|
120
135
|
end
|
|
121
136
|
|
|
@@ -123,8 +138,8 @@ module Badline
|
|
|
123
138
|
def vertical_drive? = @vertical_drive
|
|
124
139
|
|
|
125
140
|
# The model, its RAM and its editor's frame rate as the pause menu
|
|
126
|
-
# names them, such as "8032, 32K RAM, 50 HZ".
|
|
127
|
-
def model_label = "#{@model.name.upcase}, #{@ram_kb}K RAM, #{@refresh_hz} HZ"
|
|
141
|
+
# names them, such as "8032, 32K RAM, 50 HZ", all 128K on an 8296.
|
|
142
|
+
def model_label = "#{@model.name.upcase}, #{@model.cbm8296? ? 128 : @ram_kb}K RAM, #{@refresh_hz} HZ"
|
|
128
143
|
|
|
129
144
|
# The Canvas a front end shows.
|
|
130
145
|
def video = @canvas
|
|
@@ -146,7 +161,7 @@ module Badline
|
|
|
146
161
|
end
|
|
147
162
|
|
|
148
163
|
# The video counts and moves the vertical drive line, then the PIAs
|
|
149
|
-
# and the VIA clock, ahead of the CPU.
|
|
164
|
+
# and the VIA clock, ahead of the CPU, and the drive after it.
|
|
150
165
|
def cycle!
|
|
151
166
|
handle_init if @cycles == @init_threshold
|
|
152
167
|
feed_keyboard if @pending_keys
|
|
@@ -165,6 +180,7 @@ module Badline
|
|
|
165
180
|
@cassettes.cycle!
|
|
166
181
|
@cpu.irq = @pia1.irq? || @pia2.irq? || @via.irq?
|
|
167
182
|
@cpu.cycle!
|
|
183
|
+
@drive2031&.host_cycle!
|
|
168
184
|
|
|
169
185
|
@cycles += 1
|
|
170
186
|
end
|
|
@@ -173,14 +189,16 @@ module Badline
|
|
|
173
189
|
# and #drain_samples.
|
|
174
190
|
def sound_source = @sound
|
|
175
191
|
|
|
176
|
-
# The RES line reaches the CPU, the PIAs, the VIA, the CRTC
|
|
177
|
-
# 8096's control register.
|
|
192
|
+
# The RES line reaches the CPU, the PIAs, the VIA, the CRTC, the
|
|
193
|
+
# 8096's control register and, as IFC, the drive.
|
|
178
194
|
def reset!
|
|
179
195
|
@pia1.reset!
|
|
180
196
|
@pia2.reset!
|
|
181
197
|
@via.reset!
|
|
182
198
|
@crtc.reset!
|
|
183
199
|
@bus.reset!
|
|
200
|
+
@drive2031&.reset!
|
|
201
|
+
@ieee.lines_written
|
|
184
202
|
@cpu.reset!
|
|
185
203
|
end
|
|
186
204
|
|
data/lib/badline/roms/README
CHANGED
|
@@ -88,6 +88,18 @@ Downloaded from
|
|
|
88
88
|
https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1551/1551.318008-01.bin
|
|
89
89
|
SHA-1 fae3c788ad9a6cc2dbdfbcf6c0264b2ca921d55e.
|
|
90
90
|
|
|
91
|
+
dos2031.rom
|
|
92
|
+
-----------
|
|
93
|
+
The 2031 disk drive's DOS 2.6, 16 KB for $C000-$FFFF: 901484-03
|
|
94
|
+
($C000-$DFFF, SHA-1 54d608f7f07860f24186749f21c96724dd48bc50)
|
|
95
|
+
followed by 901484-05 ($E000-$FFFF, SHA-1
|
|
96
|
+
ec6b75ecfdd4744e5d57979ef6af990444c11ae1). Drives::Model2031 loads it.
|
|
97
|
+
|
|
98
|
+
Both halves downloaded from
|
|
99
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/old/2031/
|
|
100
|
+
and concatenated.
|
|
101
|
+
SHA-1 c53e180a96649ceb3f421739e8dc66faba7cba44.
|
|
102
|
+
|
|
91
103
|
Commodore VIC-20 ROMs
|
|
92
104
|
=====================
|
|
93
105
|
|
|
@@ -253,7 +265,9 @@ BASIC 4 for $B000-$DFFF, 901465-23, 901465-20 and 901465-21
|
|
|
253
265
|
(basic-4-b000.901465-23.bin, basic-4-c000.901465-20.bin,
|
|
254
266
|
basic-4-d000.901465-21.bin) concatenated. 901465-23 is the patched
|
|
255
267
|
$B000 chip that replaced 901465-19. The 8296's 324746-01 holds the same
|
|
256
|
-
four chips.
|
|
268
|
+
four chips (8296/kernal-basic-324746-01.bin, SHA-1
|
|
269
|
+
e3e1431969bf317c885e47f3790e0bcbdf61fe77, $C000, $D000, $B000 and
|
|
270
|
+
$F000 in that order), so the 8296 runs this file and kernal4.rom.
|
|
257
271
|
SHA-1 d70bec479e1e3a0d0908a4897066e9959bbc11c8.
|
|
258
272
|
|
|
259
273
|
pet/kernal4.rom
|
|
@@ -316,3 +330,21 @@ pet/editor4-80-b-60hz.rom
|
|
|
316
330
|
The 8032's 60 Hz editor, 901474-03 (edit-4-80-b-60Hz.901474-03.bin),
|
|
317
331
|
for $E000-$E7FF.
|
|
318
332
|
SHA-1 c605fa343fd77c73cbe1e0e9567e2f014f6e7e30.
|
|
333
|
+
|
|
334
|
+
pet/editor4-80-b-50hz-8296.rom
|
|
335
|
+
------------------------------
|
|
336
|
+
The 8296's 50 Hz editor, 324243-02B, the EPROM marked "8296 UE8" in
|
|
337
|
+
Andre Fachat's working 8296 (8296/edit-50hz-324243-02b.bin), for
|
|
338
|
+
$E000-$E7FF. The 8296 parts list names it the ASCII board's. It is
|
|
339
|
+
901474-04 with the CRTC's register tables at $E72A changed to lines of
|
|
340
|
+
59 cycles and frames of 339 lines (8296/edit-50hz-324243-02b.txt).
|
|
341
|
+
SHA-1 b715db83fd26458dfd254bef5c4aae636753f7f5.
|
|
342
|
+
|
|
343
|
+
pet/characters-din.rom
|
|
344
|
+
----------------------
|
|
345
|
+
The 8296's 4K character generator for the DIN board, 324242-01
|
|
346
|
+
(8296/characters-324242-01.bin), which --characters din fits. Its
|
|
347
|
+
second half is 901447-10 byte for byte, and its first a DIN set, with
|
|
348
|
+
the German letters and the section sign in place of @ [ \ ] and in
|
|
349
|
+
some of the graphics. The CRTC's MA13, bit 5 of R12, picks the half.
|
|
350
|
+
SHA-1 9616f7f18757cccefb702a945f954b644d5b17d1.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -38,6 +38,9 @@ module Badline
|
|
|
38
38
|
PLUS4 = "PLUS4"
|
|
39
39
|
# xpet names its machine PET, for every model.
|
|
40
40
|
PET = "PET"
|
|
41
|
+
# The file format version each machine's VICE emulator reads, where
|
|
42
|
+
# it isn't FORMAT_MAJOR.0: xpet refuses any other than 1.0.
|
|
43
|
+
FORMAT_MAJORS = { PET => 1 }.freeze
|
|
41
44
|
GZIP_MAGIC = "\x1f\x8b".b
|
|
42
45
|
|
|
43
46
|
# The VICE version block, when VICE wrote the file: four version
|
|
@@ -67,7 +70,7 @@ module Badline
|
|
|
67
70
|
def [](name) = @sections.find { |section| section.name == name }
|
|
68
71
|
|
|
69
72
|
def to_s
|
|
70
|
-
header = MAGIC + [FORMAT_MAJOR, FORMAT_MINOR].pack("CC") + padded(@machine)
|
|
73
|
+
header = MAGIC + [FORMAT_MAJORS.fetch(@machine, FORMAT_MAJOR), FORMAT_MINOR].pack("CC") + padded(@machine)
|
|
71
74
|
header += VERSION_MAGIC + @vice_version if @vice_version
|
|
72
75
|
header + @sections.map { |section| encode(section) }.join
|
|
73
76
|
end
|
|
@@ -37,8 +37,8 @@ module Badline
|
|
|
37
37
|
# The machines the doc is made from: each family at power-on, the C64
|
|
38
38
|
# with each kind of expansion, cartridge and media, and a second SID,
|
|
39
39
|
# the 264 series' models, its TCBM interface, cartridge, tape and
|
|
40
|
-
# true drives, the 1541 and the 1551, and a CRTC PET, a 2001, an 8096
|
|
41
|
-
# and a PET with a disk and tapes.
|
|
40
|
+
# true drives, the 1541 and the 1551, and a CRTC PET, a 2001, an 8096,
|
|
41
|
+
# an 8296 with its user port jumpered and a PET with a disk and tapes.
|
|
42
42
|
def self.machines
|
|
43
43
|
{
|
|
44
44
|
"c64" => [C64.new, C64.new(ram_expansion: :plus256k), C64.new(reu: 512),
|
|
@@ -50,7 +50,8 @@ module Badline
|
|
|
50
50
|
Plus4.new(sid_card: :mos8580, sid_card_address: 0xfe80),
|
|
51
51
|
Plus4.new(ram_expansion: :csory256),
|
|
52
52
|
plus4_with_cartridge, plus4_with_tape, plus4_with_drive, plus4_with_drive("1551")],
|
|
53
|
-
"pet" => [PET.new, PET.new(model: "2001"), PET.new(model: "8096"),
|
|
53
|
+
"pet" => [PET.new, PET.new(model: "2001"), PET.new(model: "8096"),
|
|
54
|
+
PET.new(model: "8296", characters: "din", jumpers: %w[userport]), pet_with_media]
|
|
54
55
|
}
|
|
55
56
|
end
|
|
56
57
|
|