badline 0.17.0 → 0.19.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 +193 -0
- data/CLAUDE.md +19 -7
- data/CONTRIBUTING.md +11 -1
- data/README.md +178 -26
- data/doc/library.md +5 -1
- data/doc/media.md +3 -2
- data/doc/pinned-behaviour.md +169 -1
- data/doc/snapshot-format.md +302 -35
- data/doc/snapshots.md +97 -26
- data/doc/vice.md +344 -0
- data/exe/badline-ruby +5 -0
- data/lib/badline/audio/renderer.rb +4 -8
- data/lib/badline/audio/terminal.rb +34 -2
- data/lib/badline/c128/keyed_state.rb +2 -0
- data/lib/badline/c128.rb +5 -0
- data/lib/badline/c64/bus/fittings.rb +6 -0
- data/lib/badline/c64/keyed_state.rb +2 -0
- data/lib/badline/c64.rb +7 -0
- data/lib/badline/cbm2/keyed_state.rb +4 -0
- data/lib/badline/cbm2/p500.rb +9 -3
- data/lib/badline/cbm2.rb +10 -3
- data/lib/badline/chips/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/core.rb +29 -4
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- data/lib/badline/chips/cpu/interrupts.rb +9 -0
- data/lib/badline/chips/cpu/keyed_state.rb +15 -9
- data/lib/badline/chips/cpu/microcode.rb +10 -0
- data/lib/badline/chips/m6809/arithmetic.rb +140 -0
- data/lib/badline/chips/m6809/bus_steps.rb +223 -0
- data/lib/badline/chips/m6809/core.rb +149 -0
- data/lib/badline/chips/m6809/indexed.rb +82 -0
- data/lib/badline/chips/m6809/interrupts.rb +159 -0
- data/lib/badline/chips/m6809/keyed_state.rb +147 -0
- data/lib/badline/chips/m6809/opcodes.rb +159 -0
- data/lib/badline/chips/m6809/operations.rb +258 -0
- data/lib/badline/chips/m6809/plans.rb +143 -0
- data/lib/badline/chips/m6809/registers.rb +82 -0
- data/lib/badline/chips/m6809/stack.rb +140 -0
- data/lib/badline/chips/m6809.rb +13 -0
- data/lib/badline/chips/riot/timer.rb +14 -3
- data/lib/badline/chips/rriot.rb +8 -3
- data/lib/badline/chips/sid/decimator.rb +1 -1
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/keyed_parts.rb +51 -18
- data/lib/badline/chips/sid/waveform.rb +6 -6
- data/lib/badline/chips/sid.rb +6 -2
- data/lib/badline/chips/vic_i/keyed_parts.rb +12 -2
- data/lib/badline/chips/vic_i/light_pen.rb +89 -0
- data/lib/badline/chips/vic_i.rb +91 -23
- data/lib/badline/data_path.rb +3 -3
- data/lib/badline/drives/ieee_models.rb +54 -0
- data/lib/badline/drives/model1001.rb +37 -0
- data/lib/badline/drives/model1541/sector_reader.rb +20 -3
- data/lib/badline/drives/model1541.rb +1 -0
- data/lib/badline/drives/model1551.rb +1 -0
- data/lib/badline/drives/model1571.rb +1 -0
- data/lib/badline/drives/model1581.rb +1 -0
- data/lib/badline/drives/model2031.rb +7 -0
- data/lib/badline/drives/model4040/disk.rb +62 -0
- data/lib/badline/drives/model4040/ip_bus.rb +46 -0
- data/lib/badline/drives/model4040/keyed_state.rb +57 -0
- data/lib/badline/drives/model4040/mechanism.rb +195 -0
- data/lib/badline/drives/model4040/parts.rb +20 -0
- data/lib/badline/drives/model4040.rb +195 -0
- data/lib/badline/drives/model8050/board.rb +79 -0
- data/lib/badline/drives/model8050/dos_board.rb +134 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
- data/lib/badline/drives/model8050/idle.rb +148 -31
- data/lib/badline/drives/model8050/keyed_state.rb +5 -2
- data/lib/badline/drives/model8050.rb +32 -130
- data/lib/badline/drives/model9060/idle.rb +169 -0
- data/lib/badline/drives/model9060/keyed_state.rb +123 -0
- data/lib/badline/drives/model9060/parts.rb +98 -0
- data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
- data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
- data/lib/badline/drives/model9060/sasi_target.rb +161 -0
- data/lib/badline/drives/model9060.rb +197 -0
- data/lib/badline/frontend/app.rb +8 -0
- data/lib/badline/frontend/boot.rb +3 -2
- data/lib/badline/frontend/controls.rb +62 -6
- data/lib/badline/frontend/keys.rb +36 -0
- data/lib/badline/frontend/kim1_panel.rb +208 -0
- data/lib/badline/frontend/media_slots.rb +8 -1
- data/lib/badline/frontend/menu/drive_types.rb +56 -0
- data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
- data/lib/badline/frontend/menu/menu_media.rb +42 -12
- data/lib/badline/frontend/menu/menu_pages.rb +97 -2
- data/lib/badline/frontend/menu/menu_ports.rb +38 -1
- data/lib/badline/frontend/menu/pause_menu.rb +5 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -1
- data/lib/badline/frontend/screen.rb +1 -1
- data/lib/badline/frontend/screens.rb +40 -2
- data/lib/badline/frontend/teletype_pane.rb +152 -0
- data/lib/badline/frontend/timeline.rb +31 -2
- data/lib/badline/frontend.rb +3 -0
- data/lib/badline/input/joystick.rb +15 -2
- data/lib/badline/kim1/bus.rb +117 -0
- data/lib/badline/kim1/cpu.rb +25 -0
- data/lib/badline/kim1/host_terminal.rb +74 -0
- data/lib/badline/kim1/key_entry.rb +74 -0
- data/lib/badline/kim1/keyed_modules.rb +53 -0
- data/lib/badline/kim1/keyed_state.rb +133 -0
- data/lib/badline/kim1/panel.rb +176 -0
- data/lib/badline/kim1/rriots.rb +28 -0
- data/lib/badline/kim1/sound.rb +29 -0
- data/lib/badline/kim1/tape.rb +118 -0
- data/lib/badline/kim1/teletype.rb +193 -0
- data/lib/badline/kim1.rb +194 -0
- data/lib/badline/machine/ieee_drives.rb +94 -117
- data/lib/badline/machine/keyboard_buffer.rb +20 -3
- data/lib/badline/machine/line_sound.rb +122 -0
- data/lib/badline/machine.rb +49 -15
- data/lib/badline/media/extensions.rb +8 -4
- data/lib/badline/media/kim1_media.rb +150 -0
- data/lib/badline/media/pet_media.rb +3 -1
- data/lib/badline/media/true_drive.rb +52 -45
- data/lib/badline/media/vic20_media.rb +4 -2
- data/lib/badline/media.rb +16 -6
- data/lib/badline/memory.rb +2 -4
- data/lib/badline/options/family.rb +98 -8
- data/lib/badline/options/help.rb +44 -12
- data/lib/badline/options/table.rb +44 -12
- data/lib/badline/options/validation.rb +44 -8
- data/lib/badline/options.rb +27 -8
- data/lib/badline/pet/bus.rb +16 -2
- data/lib/badline/pet/keyboard_ports.rb +9 -2
- data/lib/badline/pet/keyed_state.rb +4 -0
- data/lib/badline/pet/model.rb +11 -2
- data/lib/badline/pet/rom_sets.rb +1 -0
- data/lib/badline/pet/sound.rb +5 -105
- data/lib/badline/pet/superpet/acia.rb +12 -0
- data/lib/badline/pet/superpet/board.rb +221 -0
- data/lib/badline/pet/superpet/dongle.rb +92 -0
- data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
- data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
- data/lib/badline/pet/superpet/m6809.rb +32 -0
- data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
- data/lib/badline/pet/superpet.rb +221 -0
- data/lib/badline/pet.rb +23 -3
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +5 -0
- data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
- data/lib/badline/roms/4040/dos-1.2.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
- data/lib/badline/roms/4040/dos-2.1.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
- data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
- data/lib/badline/roms/9060/dos-3.0.rom +0 -0
- data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
- data/lib/badline/roms/README +176 -0
- data/lib/badline/roms/kim1/6530-002.rom +0 -0
- data/lib/badline/roms/kim1/6530-003.rom +0 -0
- data/lib/badline/roms/pet/characters-superpet.rom +0 -0
- data/lib/badline/roms/pet/superpet-a000.rom +0 -0
- data/lib/badline/roms/pet/superpet-c000.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
- data/lib/badline/roms/pet/superpet-e000.rom +0 -0
- data/lib/badline/snapshot/container.rb +8 -4
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +35 -10
- data/lib/badline/snapshot/image.rb +19 -7
- data/lib/badline/snapshot/kim1_setup.rb +28 -0
- data/lib/badline/snapshot/pet_setup.rb +27 -11
- data/lib/badline/snapshot/storage_setup.rb +4 -1
- data/lib/badline/snapshot/vic20_setup.rb +11 -7
- data/lib/badline/snapshot/vice/pet.rb +14 -7
- data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
- data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
- data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
- data/lib/badline/snapshot/vice/pet_mem.rb +55 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
- data/lib/badline/snapshot/vice/vic20.rb +70 -0
- data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
- data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
- data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
- data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
- data/lib/badline/snapshot/vice.rb +1 -0
- data/lib/badline/snapshot.rb +8 -4
- data/lib/badline/status.rb +30 -30
- data/lib/badline/storage/d67_image.rb +55 -0
- data/lib/badline/storage/d90_image.rb +180 -0
- data/lib/badline/storage/kim_tape.rb +254 -0
- data/lib/badline/storage/papertape.rb +90 -0
- data/lib/badline/storage.rb +4 -0
- data/lib/badline/support/kim1_checkpoint.rb +41 -0
- data/lib/badline/support/superpet_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic20/keyed_modules.rb +6 -3
- data/lib/badline/vic20/keyed_state.rb +6 -4
- data/lib/badline/vic20/port_wiring.rb +11 -0
- data/lib/badline/vic20.rb +30 -12
- data/lib/badline.rb +1 -0
- metadata +85 -1
data/lib/badline/pet/sound.rb
CHANGED
|
@@ -8,121 +8,21 @@ module Badline
|
|
|
8
8
|
# added. CB2 carries the shift register's bits in its shift-out modes,
|
|
9
9
|
# mode 4's free-running square wave under timer 2 for the bell and most
|
|
10
10
|
# PET music, and the PCR's CB2 output otherwise, held high or low by
|
|
11
|
-
# hand or floating high as an input.
|
|
12
|
-
#
|
|
13
|
-
# The machine calls #cycle! once a cycle while #recording?, from #record
|
|
14
|
-
# on, so a sound nobody records costs only that check. Each sample is
|
|
15
|
-
# the share of its cycles CB2 spent high, through the output stage: a
|
|
16
|
-
# one-pole low-pass at 5 kHz and a one-pole high-pass at 100 Hz, so a
|
|
17
|
-
# line left high or low settles to silence.
|
|
11
|
+
# hand or floating high as an input. Machine::LineSound turns it into
|
|
12
|
+
# samples.
|
|
18
13
|
class Sound
|
|
19
|
-
|
|
20
|
-
HIGH_PASS_HZ = 100
|
|
21
|
-
|
|
22
|
-
# A swing from CB2 low to CB2 high, as a signed 16-bit sample.
|
|
23
|
-
SCALE = 24_000.0
|
|
14
|
+
include Machine::LineSound
|
|
24
15
|
|
|
25
16
|
def initialize(via)
|
|
26
17
|
@via = via
|
|
27
|
-
|
|
28
|
-
@high = true
|
|
29
|
-
@run = 0
|
|
30
|
-
@rate = 0
|
|
31
|
-
@clock_hz = 0
|
|
32
|
-
@remainder = 0
|
|
33
|
-
@window = 0
|
|
34
|
-
@left = 0
|
|
35
|
-
@sum = 0
|
|
36
|
-
@low = 0.0
|
|
37
|
-
@drift = 0.0
|
|
38
|
-
@low_pass = 0.0
|
|
39
|
-
@high_pass = 0.0
|
|
40
|
-
@samples = []
|
|
18
|
+
init_line_sound
|
|
41
19
|
end
|
|
42
20
|
|
|
43
21
|
def model = :cb2
|
|
44
22
|
|
|
45
|
-
attr_reader :recording
|
|
46
|
-
alias recording? recording
|
|
47
|
-
|
|
48
|
-
# Starts collecting the output, averaged down to `rate` samples a
|
|
49
|
-
# second at `clock_hz` cycles a second, for #drain_samples. The
|
|
50
|
-
# output stage takes CB2's level now as settled.
|
|
51
|
-
def record(rate:, clock_hz: 1_000_000)
|
|
52
|
-
@rate = rate
|
|
53
|
-
@clock_hz = clock_hz
|
|
54
|
-
@remainder = 0
|
|
55
|
-
@window = next_window
|
|
56
|
-
@left = @window
|
|
57
|
-
@sum = 0
|
|
58
|
-
@high = @via.cb2_output
|
|
59
|
-
@run = 0
|
|
60
|
-
@low = @drift = @high ? 1.0 : 0.0
|
|
61
|
-
@low_pass = 1.0 - Math.exp(-2.0 * Math::PI * LOW_PASS_HZ / rate)
|
|
62
|
-
@high_pass = 1.0 - Math.exp(-2.0 * Math::PI * HIGH_PASS_HZ / rate)
|
|
63
|
-
@samples = []
|
|
64
|
-
@recording = true
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
# Takes CB2's level for this cycle, while recording.
|
|
68
|
-
def cycle!
|
|
69
|
-
high = @via.cb2_output
|
|
70
|
-
if high == @high
|
|
71
|
-
@run += 1
|
|
72
|
-
else
|
|
73
|
-
hold(@high, @run)
|
|
74
|
-
@high = high
|
|
75
|
-
@run = 1
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
|
|
79
|
-
# The samples closed since the last drain.
|
|
80
|
-
def drain_samples
|
|
81
|
-
return [] unless @recording
|
|
82
|
-
|
|
83
|
-
hold(@high, @run)
|
|
84
|
-
@run = 0
|
|
85
|
-
samples = @samples
|
|
86
|
-
@samples = []
|
|
87
|
-
samples
|
|
88
|
-
end
|
|
89
|
-
|
|
90
23
|
private
|
|
91
24
|
|
|
92
|
-
|
|
93
|
-
# they finish.
|
|
94
|
-
def hold(high, cycles)
|
|
95
|
-
left = @left
|
|
96
|
-
while cycles >= left
|
|
97
|
-
@sum += left if high
|
|
98
|
-
cycles -= left
|
|
99
|
-
close
|
|
100
|
-
left = @left
|
|
101
|
-
end
|
|
102
|
-
@sum += cycles if high
|
|
103
|
-
@left = left - cycles
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def close
|
|
107
|
-
@low += @low_pass * (@sum.fdiv(@window) - @low)
|
|
108
|
-
@drift += @high_pass * (@low - @drift)
|
|
109
|
-
sample = ((@low - @drift) * SCALE).round
|
|
110
|
-
sample = 32_767 if sample > 32_767
|
|
111
|
-
sample = -32_768 if sample < -32_768
|
|
112
|
-
@samples << sample
|
|
113
|
-
@sum = 0
|
|
114
|
-
@window = next_window
|
|
115
|
-
@left = @window
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
# Each window is a whole number of cycles, and they add up to the
|
|
119
|
-
# clock's cycles over a second.
|
|
120
|
-
def next_window
|
|
121
|
-
@remainder += @clock_hz
|
|
122
|
-
cycles = @remainder / @rate
|
|
123
|
-
@remainder -= cycles * @rate
|
|
124
|
-
cycles
|
|
125
|
-
end
|
|
25
|
+
def line_high? = @via.cb2_output
|
|
126
26
|
end
|
|
127
27
|
end
|
|
128
28
|
end
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class SuperPET < PET
|
|
6
|
+
# The Waterloo board, after Levak's PET-Interfaces.txt, André
|
|
7
|
+
# Fachat's SuperPET page and Commodore's 324037 schematic: 64K of
|
|
8
|
+
# RAM in 16 banks of 4K, one at a time at $9000-$9FFF, and the I/O
|
|
9
|
+
# page at $EF00 (IOPage), which both CPUs reach.
|
|
10
|
+
#
|
|
11
|
+
# The bank latch at $EFFC-$EFFD picks the bank with bits 0-3, and its
|
|
12
|
+
# bit 7 lets writes reach the system latch at $EFF8-$EFFB:
|
|
13
|
+
#
|
|
14
|
+
# bit 0 the CPU while SW2 is at PROG: 1 the 6502, 0 the 6809
|
|
15
|
+
# bit 1 the bank RAM while SW1 is at PROG: 1 read/write, 0 read only
|
|
16
|
+
# bit 3 pulls the user port's diagnostic sense, PIA 1's PA7, low
|
|
17
|
+
#
|
|
18
|
+
# Bit 0 of $EFFE-$EFFF puts the board's own ROM socket at $9000 in
|
|
19
|
+
# place of the RAM, which badline leaves empty, while the 6502 runs.
|
|
20
|
+
# All three are write-only, and the RES line clears them, so the
|
|
21
|
+
# board starts at bank 0, and with SW2 at PROG with the 6809.
|
|
22
|
+
#
|
|
23
|
+
# SW1 is READ, R/W or PROG, the bank RAM's write protect, and SW2
|
|
24
|
+
# PROG, 6502 or 6809, the CPU. A write to the bank RAM while it is
|
|
25
|
+
# protected goes nowhere.
|
|
26
|
+
#
|
|
27
|
+
# While the 6809 runs, the 8032's ROMs step aside through its NO ROM
|
|
28
|
+
# line, and the Waterloo ROMs answer at $A000-$E7FF and $F000-$FFFF,
|
|
29
|
+
# the $E000 one with the CRTC table for the frame rate.
|
|
30
|
+
class Board
|
|
31
|
+
CPU_SWITCH = %w[6502 6809 prog].freeze
|
|
32
|
+
|
|
33
|
+
RAM_SWITCH = %w[rw read prog].freeze
|
|
34
|
+
|
|
35
|
+
# $EF00-$EFFF: the 6702 at $EFE0-$EFE3, the ACIA at $EFF0-$EFF3
|
|
36
|
+
# and the latches above. A read of a latch finds the last byte on
|
|
37
|
+
# the bus, and below $EFF0, where neither chip answers, $FF.
|
|
38
|
+
class IOPage
|
|
39
|
+
def initialize(board, bus, dongle, acia)
|
|
40
|
+
@board = board
|
|
41
|
+
@bus = bus
|
|
42
|
+
@dongle = dongle
|
|
43
|
+
@acia = acia
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def peek(addr)
|
|
47
|
+
low = addr & 0xff
|
|
48
|
+
return @bus.data if low >= 0xf4
|
|
49
|
+
return @acia.peek(addr) if low >= 0xf0
|
|
50
|
+
return @dongle.peek(addr) if low >= 0xe0 && low < 0xe4
|
|
51
|
+
|
|
52
|
+
0xff
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def poke(addr, value)
|
|
56
|
+
low = addr & 0xff
|
|
57
|
+
if low >= 0xfe
|
|
58
|
+
@board.socket = value
|
|
59
|
+
elsif low >= 0xfc
|
|
60
|
+
@board.bank_latch = value
|
|
61
|
+
elsif low >= 0xf8
|
|
62
|
+
@board.system_latch = value
|
|
63
|
+
elsif low >= 0xf0
|
|
64
|
+
@acia.poke(addr, value) if low < 0xf4
|
|
65
|
+
elsif low >= 0xe0
|
|
66
|
+
@dongle.poke(addr, value) if low < 0xe4
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
attr_reader :cells, :bank_latch, :system_latch, :cpu_switch, :ram_switch
|
|
72
|
+
|
|
73
|
+
# Bit 0 of $EFFE: 1 for the ROM socket at $9000.
|
|
74
|
+
attr_reader :socket
|
|
75
|
+
|
|
76
|
+
# +bus+ is the PET's, whose pages the board maps, and
|
|
77
|
+
# +keyboard_ports+ PIA 1's, whose PA7 the diagnostic sense is.
|
|
78
|
+
# +cpu_switch+ and +ram_switch+ are SW2's and SW1's positions, one
|
|
79
|
+
# of CPU_SWITCH's and RAM_SWITCH's, and +refresh_hz+ picks the
|
|
80
|
+
# Waterloo $E000 ROM, 50 or 60.
|
|
81
|
+
def initialize(bus, dongle:, acia:, keyboard_ports:, cpu_switch: "6502", ram_switch: "rw",
|
|
82
|
+
refresh_hz: 50)
|
|
83
|
+
raise ArgumentError, "the CPU switch has no position #{cpu_switch}" unless CPU_SWITCH.include?(cpu_switch)
|
|
84
|
+
raise ArgumentError, "the RAM switch has no position #{ram_switch}" unless RAM_SWITCH.include?(ram_switch)
|
|
85
|
+
|
|
86
|
+
@bus = bus
|
|
87
|
+
@dongle = dongle
|
|
88
|
+
@keyboard_ports = keyboard_ports
|
|
89
|
+
@cpu_switch = cpu_switch
|
|
90
|
+
@ram_switch = ram_switch
|
|
91
|
+
@cells = Array.new(0x10000, 0)
|
|
92
|
+
@banks = Array.new(16) { |bank| Bus::Expansion::Window.new(@cells, bank << 12, 0x9000) }
|
|
93
|
+
@io = IOPage.new(self, bus, dongle, acia)
|
|
94
|
+
@bank_latch = 0
|
|
95
|
+
@system_latch = 0
|
|
96
|
+
@socket = 0
|
|
97
|
+
@rom_a = ROM.load("pet/superpet-a000.rom", 0xa000)
|
|
98
|
+
@rom_c = ROM.load("pet/superpet-c000.rom", 0xc000)
|
|
99
|
+
@rom_e = ROM.load(refresh_hz == 50 ? "pet/superpet-e000-50hz.rom" : "pet/superpet-e000.rom", 0xe000)
|
|
100
|
+
@m6809 = m6809_selected?
|
|
101
|
+
@select_handler = nil
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# Calls the block when the CPU the switch and the system latch pick
|
|
105
|
+
# changes.
|
|
106
|
+
def on_select(&handler)
|
|
107
|
+
@select_handler = handler
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Whether the 6809 has the bus.
|
|
111
|
+
def m6809? = @m6809
|
|
112
|
+
|
|
113
|
+
def bank = @bank_latch & 0x0f
|
|
114
|
+
|
|
115
|
+
def bank_latch=(value)
|
|
116
|
+
@bank_latch = value
|
|
117
|
+
@bus.map_pages
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def system_latch=(value)
|
|
121
|
+
return if @bank_latch.nobits?(0x80)
|
|
122
|
+
|
|
123
|
+
@system_latch = value
|
|
124
|
+
@keyboard_ports.diagnostic_low = value.anybits?(0x08)
|
|
125
|
+
select
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def socket=(value)
|
|
129
|
+
@socket = @m6809 ? 0 : value & 0x01
|
|
130
|
+
@bus.map_pages
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# Moves SW2, which picks the CPU at the next RES.
|
|
134
|
+
def cpu_switch=(position)
|
|
135
|
+
raise ArgumentError, "the CPU switch has no position #{position}" unless CPU_SWITCH.include?(position)
|
|
136
|
+
|
|
137
|
+
@cpu_switch = position
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Moves SW1, which protects the bank RAM from the next write on.
|
|
141
|
+
def ram_switch=(position)
|
|
142
|
+
raise ArgumentError, "the RAM switch has no position #{position}" unless RAM_SWITCH.include?(position)
|
|
143
|
+
|
|
144
|
+
@ram_switch = position
|
|
145
|
+
@bus.map_pages
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Whether writes to the bank RAM go nowhere.
|
|
149
|
+
def write_protected?
|
|
150
|
+
case @ram_switch
|
|
151
|
+
when "read" then true
|
|
152
|
+
when "rw" then false
|
|
153
|
+
else @system_latch.nobits?(0x02)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Whether the CPU switch, or the system latch with it at PROG,
|
|
158
|
+
# picks the 6809.
|
|
159
|
+
def m6809_selected?
|
|
160
|
+
case @cpu_switch
|
|
161
|
+
when "6502" then false
|
|
162
|
+
when "6809" then true
|
|
163
|
+
else @system_latch.nobits?(0x01)
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# The RES line clears the latches, and the 6702.
|
|
168
|
+
def reset!
|
|
169
|
+
@bank_latch = 0
|
|
170
|
+
@system_latch = 0
|
|
171
|
+
@socket = 0
|
|
172
|
+
@keyboard_ports.diagnostic_low = false
|
|
173
|
+
@dongle.reset!
|
|
174
|
+
select
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def power_on!
|
|
178
|
+
@cells.fill(0)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
# Lays the bank, or the empty socket, over $9000-$9FFF, the I/O
|
|
182
|
+
# page over $EF00, and the Waterloo ROMs over the 8032's while the
|
|
183
|
+
# 6809 runs.
|
|
184
|
+
def map(reads, writes)
|
|
185
|
+
if @socket.zero?
|
|
186
|
+
window = @banks[bank]
|
|
187
|
+
window.read_only = write_protected?
|
|
188
|
+
reads.fill(window, 0x90, 0x10)
|
|
189
|
+
writes.fill(window, 0x90, 0x10)
|
|
190
|
+
else
|
|
191
|
+
reads.fill(@bus.open_bus, 0x90, 0x10)
|
|
192
|
+
writes.fill(@bus.open_bus, 0x90, 0x10)
|
|
193
|
+
end
|
|
194
|
+
reads[0xef] = @io
|
|
195
|
+
writes[0xef] = @io
|
|
196
|
+
map_waterloo(reads) if @m6809
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
private
|
|
200
|
+
|
|
201
|
+
# Picks the CPU again, and hands the bus over if that changed it.
|
|
202
|
+
# The 6809 taking over clears the socket bit.
|
|
203
|
+
def select
|
|
204
|
+
m6809 = m6809_selected?
|
|
205
|
+
changed = m6809 != @m6809
|
|
206
|
+
@m6809 = m6809
|
|
207
|
+
@socket = 0 if m6809
|
|
208
|
+
@bus.map_pages
|
|
209
|
+
@select_handler&.call if changed
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def map_waterloo(reads)
|
|
213
|
+
reads.fill(@rom_a, 0xa0, 0x20)
|
|
214
|
+
reads.fill(@rom_c, 0xc0, 0x20)
|
|
215
|
+
reads.fill(@rom_e, 0xe0, 0x08)
|
|
216
|
+
reads.fill(@rom_e, 0xf0, 0x10)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class SuperPET < PET
|
|
6
|
+
# The 6702 at $EFE0-$EFE3, the protection chip the Waterloo languages
|
|
7
|
+
# check for. Its behaviour comes from David Roberts' Visual 6702, a
|
|
8
|
+
# switch-level simulation of the decapped chip's netlist, driven a
|
|
9
|
+
# bus cycle at a time.
|
|
10
|
+
#
|
|
11
|
+
# Each data line has a shift register of its own, LENGTHS long, whose
|
|
12
|
+
# first stage the line reads, inverted where INVERTED has the bit
|
|
13
|
+
# set. A latch keeps the last byte written, all ones from the RES
|
|
14
|
+
# line. A write that sets D0 where the last one left it clear shifts
|
|
15
|
+
# every register one stage at the end of the next cycle, whichever
|
|
16
|
+
# cycle that is, so a read in it still finds the old byte. Each
|
|
17
|
+
# register takes in its first stage XORed with the latch's bit,
|
|
18
|
+
# inverted where FEEDBACK has the bit clear, the latch as that cycle
|
|
19
|
+
# leaves it. The RES line clears the registers.
|
|
20
|
+
class Dongle
|
|
21
|
+
LENGTHS = [6, 3, 7, 8, 1, 3, 5, 2].freeze
|
|
22
|
+
|
|
23
|
+
INVERTED = 0xd6
|
|
24
|
+
|
|
25
|
+
FEEDBACK = 0x29
|
|
26
|
+
|
|
27
|
+
# No shift waiting.
|
|
28
|
+
NONE = -1
|
|
29
|
+
|
|
30
|
+
# The registers, bit 0 of each the stage its line reads, the latch,
|
|
31
|
+
# and the cycle at whose end the registers shift, or NONE.
|
|
32
|
+
attr_reader :registers, :latch, :shift_at
|
|
33
|
+
|
|
34
|
+
# +clock+ answers #cycles, the cycle the CPU's access falls in.
|
|
35
|
+
def initialize(clock)
|
|
36
|
+
@clock = clock
|
|
37
|
+
@registers = Array.new(8, 0)
|
|
38
|
+
reset!
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reset!
|
|
42
|
+
@registers.fill(0)
|
|
43
|
+
@latch = 0xff
|
|
44
|
+
@shift_at = NONE
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def peek(_addr)
|
|
48
|
+
now = @clock.cycles
|
|
49
|
+
shift if @shift_at != NONE && @shift_at < now
|
|
50
|
+
value = 0
|
|
51
|
+
8.times { |bit| value |= (@registers[bit] & 1) << bit }
|
|
52
|
+
value ^ INVERTED
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def poke(_addr, value)
|
|
56
|
+
now = @clock.cycles
|
|
57
|
+
shift if @shift_at != NONE && @shift_at < now
|
|
58
|
+
raising = value.anybits?(0x01) && @latch.nobits?(0x01)
|
|
59
|
+
@latch = value
|
|
60
|
+
if @shift_at == now
|
|
61
|
+
shift
|
|
62
|
+
elsif raising
|
|
63
|
+
@shift_at = now + 1
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# The registers as they stand once a shift waiting has happened.
|
|
68
|
+
def settled_registers
|
|
69
|
+
registers = @registers.dup
|
|
70
|
+
shift_registers(registers) if @shift_at != NONE
|
|
71
|
+
registers
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
private
|
|
75
|
+
|
|
76
|
+
def shift
|
|
77
|
+
@shift_at = NONE
|
|
78
|
+
shift_registers(@registers)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def shift_registers(registers)
|
|
82
|
+
incoming = @latch ^ FEEDBACK ^ 0xff
|
|
83
|
+
8.times do |bit|
|
|
84
|
+
register = registers[bit]
|
|
85
|
+
stage = (register ^ (incoming >> bit)) & 1
|
|
86
|
+
registers[bit] = (register >> 1) | (stage << (LENGTHS[bit] - 1))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class SuperPET < PET
|
|
6
|
+
# The SuperPET as keyed snapshot modules: the 8032's (PET::KeyedModules),
|
|
7
|
+
# then BL-SUPERPET with the board and the 6702 under dongle.*, BL-ACIA
|
|
8
|
+
# and BL-M6809. The traps on the Waterloo ROM's routines go in
|
|
9
|
+
# BL-TRAPDOS under waterloo.*, beside the KERNAL's. Without BL-ACIA or
|
|
10
|
+
# BL-M6809, as from xpet, the unit stays as it is. The CPU switch and
|
|
11
|
+
# the latches say which CPU has the bus, and the CPU that has it
|
|
12
|
+
# settles on its instruction boundary, or in SYNC or CWAI.
|
|
13
|
+
module KeyedModules
|
|
14
|
+
def settled_for_snapshot?
|
|
15
|
+
return super unless @m6809_running
|
|
16
|
+
|
|
17
|
+
@m6809.boundary? || @m6809.waiting?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def save_modules
|
|
21
|
+
sections = super
|
|
22
|
+
board = @board
|
|
23
|
+
dongle = @dongle
|
|
24
|
+
sections << Snapshot::Keyed.section("BL-SUPERPET") do |out|
|
|
25
|
+
board.save_fields(out)
|
|
26
|
+
dongle.save_fields(out, "dongle.")
|
|
27
|
+
end
|
|
28
|
+
sections << Snapshot::Keyed.section("BL-ACIA") { |out| @acia.save_fields(out) }
|
|
29
|
+
sections << Snapshot::Keyed.section("BL-M6809") { |out| @m6809.save_fields(out) }
|
|
30
|
+
sections
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def load_modules(modules)
|
|
34
|
+
super
|
|
35
|
+
board = modules.required("BL-SUPERPET")
|
|
36
|
+
@board.load_fields(board)
|
|
37
|
+
@dongle.load_fields(board, "dongle.")
|
|
38
|
+
acia = modules.optional("BL-ACIA")
|
|
39
|
+
@acia.load_fields(acia) if acia
|
|
40
|
+
m6809 = modules.optional("BL-M6809")
|
|
41
|
+
@m6809.load_fields(m6809) if m6809
|
|
42
|
+
@m6809_running = @board.m6809?
|
|
43
|
+
@board.restored!
|
|
44
|
+
@acia.restored!
|
|
45
|
+
@m6809.restored!
|
|
46
|
+
self
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def save_trap_dos_fields(out)
|
|
50
|
+
super
|
|
51
|
+
@waterloo_traps&.save_fields(out, "waterloo.")
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def load_trap_dos_fields(input, detached: false)
|
|
55
|
+
super
|
|
56
|
+
@waterloo_traps&.load_fields(input, "waterloo.")
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
include KeyedModules
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class SuperPET < PET
|
|
6
|
+
class Board
|
|
7
|
+
# The latches, the socket bit and the 64K of bank RAM, in a keyed
|
|
8
|
+
# snapshot module (BL-SUPERPET). The switches come with the setup
|
|
9
|
+
# (Snapshot::PETSetup). Which CPU has the bus, and the diagnostic
|
|
10
|
+
# sense line, follow from the latches once loaded, and the pages
|
|
11
|
+
# are laid out again (#restored!).
|
|
12
|
+
module KeyedState
|
|
13
|
+
def save_fields(out)
|
|
14
|
+
out.int("bank_latch", @bank_latch).int("system_latch", @system_latch).int("socket", @socket)
|
|
15
|
+
out.bytes("ram", @cells)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def load_fields(input)
|
|
19
|
+
@bank_latch = input.byte("bank_latch", 0)
|
|
20
|
+
@system_latch = input.byte("system_latch", 0)
|
|
21
|
+
@socket = input.int("socket", 0) & 0x01
|
|
22
|
+
input.bytes_into("ram", @cells)
|
|
23
|
+
@m6809 = m6809_selected?
|
|
24
|
+
@keyboard_ports.diagnostic_low = @system_latch.anybits?(0x08)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def restored! = @bus.map_pages
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
include KeyedState
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
class Dongle
|
|
34
|
+
# The shift registers, the latch and a shift waiting, as the cycles
|
|
35
|
+
# from now to the one it ends, under `prefix`.
|
|
36
|
+
module KeyedState
|
|
37
|
+
def save_fields(out, prefix)
|
|
38
|
+
out.ints("#{prefix}registers", @registers).int("#{prefix}latch", @latch)
|
|
39
|
+
out.optional_int("#{prefix}shift_in", @shift_at == NONE ? nil : @shift_at - @clock.cycles)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def load_fields(input, prefix)
|
|
43
|
+
input.ints_into("#{prefix}registers", @registers)
|
|
44
|
+
@latch = input.byte("#{prefix}latch", 0xff)
|
|
45
|
+
shift_in = input.optional_int("#{prefix}shift_in")
|
|
46
|
+
@shift_at = shift_in.nil? ? NONE : @clock.cycles + shift_in
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
include KeyedState
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class WaterlooTraps
|
|
54
|
+
# Whether a served device listens or talks, the channel and the
|
|
55
|
+
# command it was addressed with, and the bytes sent it so far,
|
|
56
|
+
# under `prefix`.
|
|
57
|
+
module KeyedState
|
|
58
|
+
def save_fields(out, prefix)
|
|
59
|
+
out.bool("#{prefix}listening", @listening).bool("#{prefix}talking", @talking)
|
|
60
|
+
out.int("#{prefix}channel", @channel).int("#{prefix}frame", @frame).ints("#{prefix}buffer", @buffer)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def load_fields(input, prefix)
|
|
64
|
+
@listening = input.bool("#{prefix}listening", false)
|
|
65
|
+
@talking = input.bool("#{prefix}talking", false)
|
|
66
|
+
@channel = input.int("#{prefix}channel", 0) & 0x0f
|
|
67
|
+
@frame = input.byte("#{prefix}frame", 0)
|
|
68
|
+
@buffer = input.ints("#{prefix}buffer", []).map { |byte| byte & 0xff }
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
include KeyedState
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class SuperPET < PET
|
|
6
|
+
# The board's 6809E, on the PET's bus, as the 6502 is. A trap
|
|
7
|
+
# installed on an address runs as the 6809 is about to fetch an
|
|
8
|
+
# opcode there, and may move the PC on.
|
|
9
|
+
class M6809
|
|
10
|
+
include Chips::M6809::Core
|
|
11
|
+
|
|
12
|
+
def initialize(bus)
|
|
13
|
+
super
|
|
14
|
+
@traps = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def install_trap(addr, &handler)
|
|
18
|
+
(@traps ||= {})[addr] = handler
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def remove_trap(addr)
|
|
22
|
+
@traps&.delete(addr)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def cycle!
|
|
26
|
+
@traps[@pc]&.call if @traps && boundary?
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|