badline 0.17.0 → 0.18.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 +153 -0
- data/CLAUDE.md +11 -7
- data/CONTRIBUTING.md +7 -1
- data/README.md +169 -20
- data/doc/pinned-behaviour.md +128 -0
- data/doc/snapshot-format.md +253 -18
- data/doc/snapshots.md +57 -23
- data/exe/badline-ruby +5 -0
- data/lib/badline/audio/terminal.rb +34 -2
- data/lib/badline/c128/keyed_state.rb +2 -0
- data/lib/badline/c128.rb +1 -0
- data/lib/badline/c64/keyed_state.rb +2 -0
- data/lib/badline/c64.rb +1 -0
- data/lib/badline/cbm2/keyed_state.rb +4 -0
- data/lib/badline/cbm2/p500.rb +5 -3
- data/lib/badline/cbm2.rb +6 -3
- data/lib/badline/chips/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- 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 +4 -2
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/waveform.rb +6 -6
- data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
- data/lib/badline/chips/vic_i.rb +84 -21
- 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/model2031.rb +6 -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 +192 -0
- data/lib/badline/drives/model4040/parts.rb +20 -0
- data/lib/badline/drives/model4040.rb +179 -0
- data/lib/badline/drives/model8050/board.rb +77 -0
- data/lib/badline/drives/model8050/dos_board.rb +91 -0
- data/lib/badline/drives/model8050/idle.rb +11 -7
- data/lib/badline/drives/model8050/keyed_state.rb +5 -2
- data/lib/badline/drives/model8050.rb +18 -130
- data/lib/badline/drives/model9060/idle.rb +149 -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 +195 -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/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 +116 -0
- data/lib/badline/kim1/teletype.rb +193 -0
- data/lib/badline/kim1.rb +190 -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 +46 -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 +42 -45
- data/lib/badline/media.rb +12 -6
- data/lib/badline/options/family.rb +98 -8
- data/lib/badline/options/help.rb +44 -12
- data/lib/badline/options/table.rb +43 -11
- 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 +81 -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 +216 -0
- data/lib/badline/pet.rb +19 -3
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +1 -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 +166 -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 +2 -0
- data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
- data/lib/badline/snapshot/field_doc.rb +27 -6
- data/lib/badline/snapshot/image.rb +4 -1
- 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 +13 -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_drive8050.rb +188 -68
- data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
- data/lib/badline/snapshot.rb +4 -0
- 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/disk_image/writing.rb +2 -2
- 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.rb +26 -12
- data/lib/badline.rb +1 -0
- metadata +77 -1
|
@@ -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,81 @@
|
|
|
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
|
+
private
|
|
68
|
+
|
|
69
|
+
def shift
|
|
70
|
+
@shift_at = NONE
|
|
71
|
+
incoming = @latch ^ FEEDBACK ^ 0xff
|
|
72
|
+
8.times do |bit|
|
|
73
|
+
register = @registers[bit]
|
|
74
|
+
stage = (register ^ (incoming >> bit)) & 1
|
|
75
|
+
@registers[bit] = (register >> 1) | (stage << (LENGTHS[bit] - 1))
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
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
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class PET
|
|
5
|
+
class SuperPET < PET
|
|
6
|
+
# The traps on the Waterloo 6809 ROM's IEEE-488 routines, which serve
|
|
7
|
+
# device 8 from the virtual drive (KernalTrap::DOS) the 6502's traps
|
|
8
|
+
# serve it from, after the ROM's source as Mike Naberezny hosts it
|
|
9
|
+
# (opsys-all-clean-20101210-src.txt). Other devices fall through to
|
|
10
|
+
# the ROM, which drives the bus.
|
|
11
|
+
#
|
|
12
|
+
# The routines take their argument in D and return with RTS: SetLstnr
|
|
13
|
+
# and SetTalkr a file control block, whose byte 9 is the device, byte
|
|
14
|
+
# 10 the secondary address and byte 0 the status, 3 after a timeout,
|
|
15
|
+
# SetLstnr with the command's offset pushed before the call ($F0 to
|
|
16
|
+
# open, $E0 to close, $60 for data), ByteOut_ the byte to send and
|
|
17
|
+
# the byte-in routine none, returning the byte in D and setting bit 0
|
|
18
|
+
# of the block's byte 12 at EOI. Each keeps the block's address at
|
|
19
|
+
# $02 and the bus at $77-$7A: $20 listening or $40 talking, the
|
|
20
|
+
# offset, the device and the secondary address. A language that hooks
|
|
21
|
+
# a routine through its flag at $0580 gets the ROM's routine, which
|
|
22
|
+
# calls the hook.
|
|
23
|
+
#
|
|
24
|
+
# The menu loads from "disk/1.", drive 1 of a dual drive. The virtual
|
|
25
|
+
# drive has one, so a name on drive 1 opens the file on drive 0.
|
|
26
|
+
class WaterlooTraps
|
|
27
|
+
BYTE_OUT = 0xbe6f
|
|
28
|
+
BYTE_IN = 0xbefb
|
|
29
|
+
SET_LISTENER = 0xc072
|
|
30
|
+
UNLISTEN = 0xc0d0
|
|
31
|
+
SET_TALKER = 0xc0dd
|
|
32
|
+
UNTALK = 0xc13c
|
|
33
|
+
|
|
34
|
+
# The routines' user hook numbers, whose flags sit at $0580 on.
|
|
35
|
+
HOOKS = { BYTE_OUT => 23, BYTE_IN => 24, SET_LISTENER => 27, UNLISTEN => 28, SET_TALKER => 29,
|
|
36
|
+
UNTALK => 30 }.freeze
|
|
37
|
+
|
|
38
|
+
BLOCK = 0x02
|
|
39
|
+
ACTIVE = 0x77
|
|
40
|
+
LISTENING = 0x20
|
|
41
|
+
TALKING = 0x40
|
|
42
|
+
TIMED_OUT = 3
|
|
43
|
+
|
|
44
|
+
OPEN = 0xf0
|
|
45
|
+
CLOSE = 0xe0
|
|
46
|
+
|
|
47
|
+
# The device the traps serve, or nil to serve none.
|
|
48
|
+
attr_accessor :device
|
|
49
|
+
|
|
50
|
+
def initialize(cpu:, bus:, dos:)
|
|
51
|
+
@cpu = cpu
|
|
52
|
+
@bus = bus
|
|
53
|
+
@dos = dos
|
|
54
|
+
@device = KernalTrap::Routine::DEVICE
|
|
55
|
+
@listening = false
|
|
56
|
+
@talking = false
|
|
57
|
+
@channel = 0
|
|
58
|
+
@frame = 0
|
|
59
|
+
@buffer = []
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def install
|
|
63
|
+
HOOKS.each_key { |address| @cpu.install_trap(address) { call(address) } }
|
|
64
|
+
self
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def uninstall
|
|
68
|
+
HOOKS.each_key { |address| @cpu.remove_trap(address) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Whether a served device listens and talks, the channel and the
|
|
72
|
+
# command, and the bytes sent so far, for a checkpoint.
|
|
73
|
+
def state = [@listening ? 1 : 0, @talking ? 1 : 0, @channel, @frame, *@buffer]
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
def call(address)
|
|
78
|
+
return unless @bus.peek(0x0580 + HOOKS.fetch(address)).zero?
|
|
79
|
+
|
|
80
|
+
case address
|
|
81
|
+
when SET_LISTENER then set_listener
|
|
82
|
+
when BYTE_OUT then byte_out
|
|
83
|
+
when UNLISTEN then unlisten
|
|
84
|
+
when SET_TALKER then set_talker
|
|
85
|
+
when BYTE_IN then byte_in
|
|
86
|
+
else untalk
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def served?(block) = !@device.nil? && @bus.peek(block + 9) == @device
|
|
91
|
+
|
|
92
|
+
def set_listener
|
|
93
|
+
block = @cpu.d
|
|
94
|
+
return unless served?(block)
|
|
95
|
+
|
|
96
|
+
offset = @bus.peek(@cpu.s + 3)
|
|
97
|
+
select(block, LISTENING, offset)
|
|
98
|
+
@listening = true
|
|
99
|
+
@frame = offset & 0xf0
|
|
100
|
+
@buffer = []
|
|
101
|
+
@dos.close(@channel) if @frame == CLOSE
|
|
102
|
+
return_from_routine
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def set_talker
|
|
106
|
+
block = @cpu.d
|
|
107
|
+
return unless served?(block)
|
|
108
|
+
|
|
109
|
+
select(block, TALKING, 0x60)
|
|
110
|
+
@talking = true
|
|
111
|
+
return_from_routine
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Ends what the bus was doing, then addresses the block's device as
|
|
115
|
+
# the routine does.
|
|
116
|
+
def select(block, active, offset)
|
|
117
|
+
finish_listening if @listening
|
|
118
|
+
@talking = false
|
|
119
|
+
@bus.poke(BLOCK, block >> 8)
|
|
120
|
+
@bus.poke(BLOCK + 1, block & 0xff)
|
|
121
|
+
@bus.poke(ACTIVE, active)
|
|
122
|
+
@bus.poke(ACTIVE + 1, offset)
|
|
123
|
+
@bus.poke(ACTIVE + 2, @bus.peek(block + 9))
|
|
124
|
+
secondary = @bus.peek(block + 10)
|
|
125
|
+
@bus.poke(ACTIVE + 3, secondary)
|
|
126
|
+
@channel = secondary & 0x0f
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def byte_out
|
|
130
|
+
return unless @listening
|
|
131
|
+
|
|
132
|
+
block = current_block
|
|
133
|
+
if @bus.peek(block).zero?
|
|
134
|
+
@buffer << (@cpu.d & 0xff)
|
|
135
|
+
@bus.poke(block, TIMED_OUT) unless accepted?
|
|
136
|
+
end
|
|
137
|
+
@cpu.d = 0x00ff
|
|
138
|
+
return_from_routine
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def accepted? = [OPEN, CLOSE].include?(@frame) || @dos.listening?(@channel)
|
|
142
|
+
|
|
143
|
+
def unlisten
|
|
144
|
+
return unless @listening
|
|
145
|
+
|
|
146
|
+
finish_listening
|
|
147
|
+
@bus.poke(ACTIVE, 0)
|
|
148
|
+
return_from_routine
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def finish_listening
|
|
152
|
+
@listening = false
|
|
153
|
+
case @frame
|
|
154
|
+
when OPEN then @dos.open(@channel, on_drive_zero(@buffer.pack("C*")))
|
|
155
|
+
when CLOSE then nil
|
|
156
|
+
else @dos.write(@channel, @buffer)
|
|
157
|
+
end
|
|
158
|
+
@buffer = []
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def on_drive_zero(name) = name.start_with?("1:") ? "0:#{name[2..]}" : name
|
|
162
|
+
|
|
163
|
+
def byte_in
|
|
164
|
+
return unless @talking
|
|
165
|
+
|
|
166
|
+
block = current_block
|
|
167
|
+
byte = 0x0d
|
|
168
|
+
if @bus.peek(block).zero?
|
|
169
|
+
read, eoi = @dos.read(@channel)
|
|
170
|
+
if read.nil?
|
|
171
|
+
@bus.poke(block, TIMED_OUT)
|
|
172
|
+
else
|
|
173
|
+
byte = read
|
|
174
|
+
@bus.poke(block + 12, @bus.peek(block + 12) | 0x01) if eoi
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
@cpu.d = byte
|
|
178
|
+
return_from_routine
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def untalk
|
|
182
|
+
return unless @talking
|
|
183
|
+
|
|
184
|
+
@talking = false
|
|
185
|
+
@bus.poke(ACTIVE, 0)
|
|
186
|
+
return_from_routine
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def current_block = (@bus.peek(BLOCK) << 8) | @bus.peek(BLOCK + 1)
|
|
190
|
+
|
|
191
|
+
def return_from_routine
|
|
192
|
+
s = @cpu.s
|
|
193
|
+
@cpu.pc = (@bus.peek(s) << 8) | @bus.peek(s + 1)
|
|
194
|
+
@cpu.s = (s + 2) & 0xffff
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
198
|
+
end
|
|
199
|
+
end
|