badline 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +76 -0
- data/CLAUDE.md +1 -1
- data/README.md +130 -17
- data/doc/media.md +14 -2
- data/doc/pinned-behaviour.md +61 -0
- data/doc/snapshot-format.md +379 -70
- data/doc/snapshots.md +185 -10
- data/lib/badline/cbm2/acia.rb +10 -0
- data/lib/badline/cbm2/bus.rb +263 -0
- data/lib/badline/cbm2/cpu.rb +58 -0
- data/lib/badline/cbm2/crtc_video.rb +133 -0
- data/lib/badline/cbm2/ieee_drive.rb +18 -0
- data/lib/badline/cbm2/ieee_layout.rb +69 -0
- data/lib/badline/cbm2/ieee_ports.rb +104 -0
- data/lib/badline/cbm2/ieee_traps.rb +81 -0
- data/lib/badline/cbm2/keyboard_ports.rb +60 -0
- data/lib/badline/cbm2/keyed_modules.rb +67 -0
- data/lib/badline/cbm2/keyed_state.rb +91 -0
- data/lib/badline/cbm2/model.rb +44 -0
- data/lib/badline/cbm2/p500/control_ports.rb +80 -0
- data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
- data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
- data/lib/badline/cbm2/p500/model.rb +39 -0
- data/lib/badline/cbm2/p500/pages.rb +57 -0
- data/lib/badline/cbm2/p500.rb +262 -0
- data/lib/badline/cbm2/sid_page.rb +19 -0
- data/lib/badline/cbm2.rb +267 -0
- data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
- data/lib/badline/chips/acia/keyed_state.rb +47 -0
- data/lib/badline/chips/acia.rb +307 -0
- data/lib/badline/chips/crtc.rb +3 -2
- data/lib/badline/chips/riot/fast_forward.rb +72 -0
- data/lib/badline/chips/riot/keyed_parts.rb +69 -0
- data/lib/badline/chips/riot/timer.rb +80 -0
- data/lib/badline/chips/riot.rb +162 -0
- data/lib/badline/chips/rriot/keyed_state.rb +30 -0
- data/lib/badline/chips/rriot.rb +132 -0
- data/lib/badline/chips/tpi/keyed_state.rb +38 -0
- data/lib/badline/chips/tpi.rb +233 -0
- data/lib/badline/chips/vic/bank.rb +60 -5
- data/lib/badline/chips/vic/sprite.rb +4 -3
- data/lib/badline/drives/datasette/keyed_state.rb +9 -1
- data/lib/badline/drives/datasette.rb +27 -3
- 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/drives/model8050/disk.rb +163 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
- data/lib/badline/drives/model8050/idle.rb +177 -0
- data/lib/badline/drives/model8050/ieee_port.rb +147 -0
- data/lib/badline/drives/model8050/ip_bus.rb +93 -0
- data/lib/badline/drives/model8050/keyed_state.rb +160 -0
- data/lib/badline/drives/model8050/mechanism.rb +230 -0
- data/lib/badline/drives/model8050/parts.rb +37 -0
- data/lib/badline/drives/model8050.rb +243 -0
- data/lib/badline/frontend/app.rb +10 -3
- data/lib/badline/frontend/boot.rb +1 -1
- data/lib/badline/frontend/controls.rb +51 -15
- data/lib/badline/frontend/gamepads.rb +3 -3
- data/lib/badline/frontend/keys.rb +61 -2
- data/lib/badline/frontend/media_slots.rb +1 -1
- data/lib/badline/frontend/menu/menu_media.rb +14 -11
- data/lib/badline/frontend/menu/menu_pages.rb +29 -6
- data/lib/badline/frontend/menu/menu_ports.rb +40 -4
- data/lib/badline/frontend/menu/pause_menu.rb +1 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -7
- data/lib/badline/frontend/timeline.rb +19 -10
- data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
- data/lib/badline/kernal_trap/dos/status.rb +3 -0
- data/lib/badline/kernal_trap/dos.rb +18 -5
- data/lib/badline/kernal_trap/ieee.rb +7 -2
- data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
- data/lib/badline/machine/ieee_drives.rb +194 -0
- data/lib/badline/machine/settling.rb +4 -0
- data/lib/badline/machine.rb +26 -8
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/pet_media.rb +5 -5
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +50 -8
- data/lib/badline/media.rb +21 -8
- data/lib/badline/options/family.rb +15 -3
- data/lib/badline/options/help.rb +24 -6
- data/lib/badline/options/table.rb +25 -5
- data/lib/badline/options/validation.rb +47 -7
- data/lib/badline/options.rb +48 -15
- data/lib/badline/pet/bus/expansion.rb +99 -5
- data/lib/badline/pet/bus.rb +30 -12
- data/lib/badline/pet/cassettes.rb +22 -1
- data/lib/badline/pet/characters.rb +8 -2
- data/lib/badline/pet/crtc_video.rb +20 -7
- data/lib/badline/pet/ieee_drive.rb +17 -0
- data/lib/badline/pet/ieee_ports.rb +19 -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 +45 -16
- data/lib/badline/plus4/acia.rb +4 -298
- data/lib/badline/plus4/keyed_parts.rb +0 -44
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/8050/dos-2.7.rom +0 -0
- data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
- data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
- data/lib/badline/roms/README +144 -1
- data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
- data/lib/badline/roms/cbm2/basic128.rom +0 -0
- data/lib/badline/roms/cbm2/basic256.rom +0 -0
- data/lib/badline/roms/cbm2/characters-600.rom +0 -0
- data/lib/badline/roms/cbm2/characters-700.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
- data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
- data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
- 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/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +9 -1
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +39 -3
- data/lib/badline/snapshot/image.rb +79 -15
- data/lib/badline/snapshot/pet_setup.rb +30 -13
- data/lib/badline/snapshot/vice/cbm2.rb +93 -0
- data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
- data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
- data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
- data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
- data/lib/badline/snapshot/vice/cias.rb +13 -9
- data/lib/badline/snapshot/vice/p500.rb +96 -0
- data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
- data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
- data/lib/badline/snapshot/vice/pet.rb +121 -0
- data/lib/badline/snapshot/vice/pet_cpu.rb +70 -0
- data/lib/badline/snapshot/vice/pet_crtc.rb +241 -0
- data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -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 +214 -0
- data/lib/badline/snapshot/vice/pet_via.rb +165 -0
- data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
- data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
- data/lib/badline/snapshot/vice.rb +4 -0
- data/lib/badline/snapshot.rb +11 -3
- data/lib/badline/storage/crt_file.rb +7 -1
- data/lib/badline/storage/d64_image.rb +7 -0
- data/lib/badline/storage/d80_image.rb +17 -1
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/disk_image/bam.rb +18 -3
- data/lib/badline/storage/disk_image/formatting.rb +69 -0
- data/lib/badline/storage/disk_image/writing.rb +35 -0
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage.rb +1 -0
- data/lib/badline/support/cbm2_checkpoint.rb +48 -0
- data/lib/badline/support/p500_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline.rb +1 -0
- metadata +87 -2
|
@@ -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
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/machine/ieee_drives"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
class PET
|
|
7
|
+
# The PET's true drive, a 2031, 8050 or 8250 on its IEEE-488 bus
|
|
8
|
+
# (IEEEPorts), as Machine::IEEEDrives has it, at the PET's 1 MHz.
|
|
9
|
+
module IEEEDrive
|
|
10
|
+
include Machine::IEEEDrives
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def ieee_host_clock_hz = 1_000_000
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
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,20 @@ 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
|
+
|
|
60
|
+
# The drives on the bus see the lines the PET pulls now.
|
|
61
|
+
def push_lines = lines_written
|
|
62
|
+
|
|
63
|
+
# Nothing to push once a cycle: the PET pushes its lines as its chips
|
|
64
|
+
# are written.
|
|
65
|
+
def cycle_lines = nil
|
|
66
|
+
|
|
49
67
|
private
|
|
50
68
|
|
|
51
69
|
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?
|
|
@@ -97,18 +106,24 @@ module Badline
|
|
|
97
106
|
@via = VIA.new(start: 0xe800, peripheral: VIAPorts.new(self))
|
|
98
107
|
@sound = Sound.new(@via)
|
|
99
108
|
@cassettes = Cassettes.new(@pia1, @via)
|
|
109
|
+
watch_tapes
|
|
100
110
|
keyboard_ports.connect(@pia1, @ieee, @cassettes)
|
|
101
111
|
characters = Characters.new(@model.characters)
|
|
102
|
-
@crtc_output = CRTCVideo.new(characters, @via, registers, columns80: @model.video == :crtc80
|
|
112
|
+
@crtc_output = CRTCVideo.new(characters, @via, registers, columns80: @model.video == :crtc80,
|
|
113
|
+
board8296: @model.cbm8296?)
|
|
103
114
|
@crtc = Chips::CRTC.new(@crtc_output, registers:)
|
|
104
115
|
@ieee.connect(@pia1, @pia2, @via)
|
|
105
116
|
@bus = Bus.new(model: @model, ram_kb:, editor: @model.editor(@refresh_hz), pia1: @pia1, pia2: @pia2,
|
|
106
|
-
via: @via, crtc: @crtc)
|
|
117
|
+
via: @via, crtc: @crtc, jumpers: @jumpers)
|
|
107
118
|
@crtc_output.cells = @bus.screen.cells
|
|
108
119
|
@discrete = DiscreteVideo.new(@bus.screen.cells, characters, @via)
|
|
109
120
|
@canvas = @crtc_video ? @crtc_output.canvas : @discrete.canvas
|
|
110
121
|
cassettes = @cassettes
|
|
111
|
-
|
|
122
|
+
ieee = @ieee
|
|
123
|
+
@bus.on_io_write do
|
|
124
|
+
cassettes.lines_written
|
|
125
|
+
ieee.lines_written
|
|
126
|
+
end
|
|
112
127
|
@cpu = CPU.new(@bus, debug:)
|
|
113
128
|
@cycles = 0
|
|
114
129
|
@init_handlers = []
|
|
@@ -116,6 +131,7 @@ module Badline
|
|
|
116
131
|
@dos = nil
|
|
117
132
|
@ieee_trap = nil
|
|
118
133
|
@capture_output = nil
|
|
134
|
+
init_ieee_drives
|
|
119
135
|
@init_threshold = boot_cycles(ram_kb)
|
|
120
136
|
end
|
|
121
137
|
|
|
@@ -123,8 +139,8 @@ module Badline
|
|
|
123
139
|
def vertical_drive? = @vertical_drive
|
|
124
140
|
|
|
125
141
|
# 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"
|
|
142
|
+
# names them, such as "8032, 32K RAM, 50 HZ", all 128K on an 8296.
|
|
143
|
+
def model_label = "#{@model.name.upcase}, #{@model.cbm8296? ? 128 : @ram_kb}K RAM, #{@refresh_hz} HZ"
|
|
128
144
|
|
|
129
145
|
# The Canvas a front end shows.
|
|
130
146
|
def video = @canvas
|
|
@@ -146,7 +162,7 @@ module Badline
|
|
|
146
162
|
end
|
|
147
163
|
|
|
148
164
|
# The video counts and moves the vertical drive line, then the PIAs
|
|
149
|
-
# and the VIA clock, ahead of the CPU.
|
|
165
|
+
# and the VIA clock, ahead of the CPU, and the drive after it.
|
|
150
166
|
def cycle!
|
|
151
167
|
handle_init if @cycles == @init_threshold
|
|
152
168
|
feed_keyboard if @pending_keys
|
|
@@ -162,9 +178,10 @@ module Badline
|
|
|
162
178
|
@pia2.cycle!
|
|
163
179
|
@via.cycle!
|
|
164
180
|
@sound.cycle! if @sound.recording?
|
|
165
|
-
@cassettes.cycle!
|
|
181
|
+
@cassettes.cycle! if @tapes_running
|
|
166
182
|
@cpu.irq = @pia1.irq? || @pia2.irq? || @via.irq?
|
|
167
183
|
@cpu.cycle!
|
|
184
|
+
clock_ieee_drives
|
|
168
185
|
|
|
169
186
|
@cycles += 1
|
|
170
187
|
end
|
|
@@ -173,14 +190,16 @@ module Badline
|
|
|
173
190
|
# and #drain_samples.
|
|
174
191
|
def sound_source = @sound
|
|
175
192
|
|
|
176
|
-
# The RES line reaches the CPU, the PIAs, the VIA, the CRTC
|
|
177
|
-
# 8096's control register.
|
|
193
|
+
# The RES line reaches the CPU, the PIAs, the VIA, the CRTC, the
|
|
194
|
+
# 8096's control register and, as IFC, the drive.
|
|
178
195
|
def reset!
|
|
179
196
|
@pia1.reset!
|
|
180
197
|
@pia2.reset!
|
|
181
198
|
@via.reset!
|
|
182
199
|
@crtc.reset!
|
|
183
200
|
@bus.reset!
|
|
201
|
+
reset_ieee_drives
|
|
202
|
+
@ieee.lines_written
|
|
184
203
|
@cpu.reset!
|
|
185
204
|
end
|
|
186
205
|
|
|
@@ -216,6 +235,16 @@ module Badline
|
|
|
216
235
|
# on a 3032 and 1,980,000 on an 8032.
|
|
217
236
|
def boot_cycles(ram_kb) = (@model.basic == "pet/basic4.rom" ? 700_000 : 150_000) + (46_000 * ram_kb)
|
|
218
237
|
|
|
238
|
+
# The decks are clocked only while a tape runs.
|
|
239
|
+
def watch_tapes
|
|
240
|
+
@tapes_running = false
|
|
241
|
+
@cassettes.on_running_change { tapes_changed }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def tapes_changed
|
|
245
|
+
@tapes_running = @cassettes.running?
|
|
246
|
+
end
|
|
247
|
+
|
|
219
248
|
def drive_retrace(high)
|
|
220
249
|
return if high == @vertical_drive
|
|
221
250
|
|