badline 0.16.1 → 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 +60 -0
- data/CLAUDE.md +1 -1
- data/README.md +102 -12
- data/doc/media.md +5 -2
- data/doc/pinned-behaviour.md +61 -0
- data/doc/snapshot-format.md +376 -69
- data/doc/snapshots.md +148 -14
- 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/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 +18 -4
- 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 +4 -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 +23 -6
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +38 -9
- data/lib/badline/media.rb +15 -6
- data/lib/badline/options/family.rb +15 -3
- data/lib/badline/options/help.rb +16 -2
- data/lib/badline/options/table.rb +18 -4
- data/lib/badline/options/validation.rb +27 -3
- data/lib/badline/options.rb +21 -7
- data/lib/badline/pet/cassettes.rb +22 -1
- data/lib/badline/pet/ieee_drive.rb +5 -64
- data/lib/badline/pet/ieee_ports.rb +7 -0
- data/lib/badline/pet.rb +15 -4
- 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 +111 -0
- 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/snapshot/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +7 -2
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +38 -3
- data/lib/badline/snapshot/image.rb +62 -9
- 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 +7 -3
- data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
- data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
- 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_restore.rb +70 -6
- data/lib/badline/snapshot/vice/pet_via.rb +6 -5
- 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 +3 -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 +72 -2
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model8050
|
|
6
|
+
# The unit's two drives as the floppy controller sees them through
|
|
7
|
+
# the VIA and the 6530 (schematic 8050001, sheets 2 and 3).
|
|
8
|
+
#
|
|
9
|
+
# VIA port B runs the drives: PB5 drive 0's motor and PB4 drive 1's,
|
|
10
|
+
# each on while low, PB2-3 drive 0's stepper phases and PB0-1 drive
|
|
11
|
+
# 1's, and PB7 reads SYNC, low while ten one bits sit in the shift
|
|
12
|
+
# register. The 6530's port B picks the drive on PB0, the zone on
|
|
13
|
+
# PB1-2 and the side on PB4, low for the second, and reads write
|
|
14
|
+
# protect on PB3, high while the disk can't be written, and the sides
|
|
15
|
+
# on PB6, low for two, which jumper E3 grounds on the 8250. Its port
|
|
16
|
+
# A holds the byte to write.
|
|
17
|
+
#
|
|
18
|
+
# Each phase change moves the head a track: the controller steps
|
|
19
|
+
# through the Gray code 00, 10, 11, 01 outward and back the other way
|
|
20
|
+
# inward ($FF9C), and bumps the head against track 1 with 92 steps
|
|
21
|
+
# out.
|
|
22
|
+
#
|
|
23
|
+
# A turning disk passes one byte cell under the head every ten bit
|
|
24
|
+
# cells, at 6 MHz divided by 16 less the track's zone, and the byte
|
|
25
|
+
# cells come round 300 times a minute (Disk). Each data cell ends in
|
|
26
|
+
# byte ready, which latches the GCR ROM's byte into VIA port A through
|
|
27
|
+
# CA1 and pulses the controller's SO pin; a sync cell holds SYNC low
|
|
28
|
+
# for the cell after it instead. While the VIA's CB2 is low the drive
|
|
29
|
+
# writes: each cell takes the 6530's port A, or a sync while CA2 is
|
|
30
|
+
# high, and still ends in byte ready.
|
|
31
|
+
#
|
|
32
|
+
# A disk going in or out passes the write-protect sensor, which the
|
|
33
|
+
# controller watches for a disk change: it reads high for a while.
|
|
34
|
+
class Mechanism
|
|
35
|
+
WRITE_PROTECT = 0x08
|
|
36
|
+
ONE_SIDE = 0x40
|
|
37
|
+
|
|
38
|
+
# The cycles the write-protect sensor reads high as a disk goes in
|
|
39
|
+
# or out, ten of the controller's 5 ms interrupts.
|
|
40
|
+
CHANGE_CYCLES = 50_000
|
|
41
|
+
|
|
42
|
+
attr_reader :sides, :data_in, :cylinders, :so_pending
|
|
43
|
+
attr_accessor :via, :rriot
|
|
44
|
+
|
|
45
|
+
def initialize(sides:)
|
|
46
|
+
@sides = sides
|
|
47
|
+
@via = nil
|
|
48
|
+
@rriot = nil
|
|
49
|
+
@disks = [nil, nil]
|
|
50
|
+
@cylinders = [0, 0]
|
|
51
|
+
@phases = [0, 0]
|
|
52
|
+
@positions = [0, 0]
|
|
53
|
+
@clocks = [0, 0]
|
|
54
|
+
@changing = [0, 0]
|
|
55
|
+
@motors = [false, false]
|
|
56
|
+
@so_pending = false
|
|
57
|
+
@data_in = 0xff
|
|
58
|
+
@syncs = [false, false]
|
|
59
|
+
@ca1_low = false
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The Disk in +drive+, 0 or 1, or nil.
|
|
63
|
+
def disk(drive = 0) = @disks[drive]
|
|
64
|
+
|
|
65
|
+
def insert(disk, drive = 0)
|
|
66
|
+
@disks[drive]&.flush
|
|
67
|
+
@disks[drive] = disk
|
|
68
|
+
@changing[drive] = CHANGE_CYCLES
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def flush
|
|
72
|
+
@disks.each { |disk| disk&.flush }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def motor_on?(drive = 0) = @motors[drive]
|
|
76
|
+
|
|
77
|
+
# The track under +drive+'s head on the side the 6530 picks.
|
|
78
|
+
def track(drive = 0)
|
|
79
|
+
side = drive == selected_drive ? selected_side : 0
|
|
80
|
+
@cylinders[drive] + 1 + (side * Storage::D80Image::SIDE_TRACKS)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
def cycle!
|
|
84
|
+
if @ca1_low
|
|
85
|
+
@ca1_low = false
|
|
86
|
+
@via.ca1 = true
|
|
87
|
+
end
|
|
88
|
+
drives = @via.port_b_output
|
|
89
|
+
turn(0, drives.nobits?(0x20), (drives >> 2) & 0x03)
|
|
90
|
+
turn(1, drives.nobits?(0x10), drives & 0x03)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Whether a cycle would change nothing: both motors off, no disk
|
|
94
|
+
# going in or out, and no byte ready under way.
|
|
95
|
+
def quiet?
|
|
96
|
+
!@motors[0] && !@motors[1] && @changing[0].zero? && @changing[1].zero? && !@ca1_low && !@so_pending
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
# What the drives hold, for comparing them at two points.
|
|
100
|
+
def idle_state = [*@cylinders, *@phases, *@motors, *@syncs, @data_in]
|
|
101
|
+
|
|
102
|
+
# Whether byte ready pulses the controller's SO pin this cycle,
|
|
103
|
+
# which takes the pulse.
|
|
104
|
+
def take_so
|
|
105
|
+
so = @so_pending
|
|
106
|
+
@so_pending = false
|
|
107
|
+
so
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Whether ten one bits sit in the shift register, off the selected
|
|
111
|
+
# drive's head.
|
|
112
|
+
def sync? = @syncs[selected_drive]
|
|
113
|
+
|
|
114
|
+
# The 6530's port B inputs.
|
|
115
|
+
def status_lines
|
|
116
|
+
lines = 0xff
|
|
117
|
+
lines &= ~WRITE_PROTECT unless write_protected?(selected_drive)
|
|
118
|
+
@sides == 2 ? lines & ~ONE_SIDE : lines
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
private
|
|
122
|
+
|
|
123
|
+
def selected_drive = @rriot.port_b_output & 0x01
|
|
124
|
+
|
|
125
|
+
def selected_side = @sides == 2 && @rriot.port_b_output.nobits?(0x10) ? 1 : 0
|
|
126
|
+
|
|
127
|
+
def write_protected?(drive)
|
|
128
|
+
return true if @changing[drive].positive?
|
|
129
|
+
|
|
130
|
+
disk = @disks[drive]
|
|
131
|
+
disk ? disk.write_protected? : false
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def turn(drive, motor, phase)
|
|
135
|
+
@changing[drive] -= 1 if @changing[drive].positive?
|
|
136
|
+
step(drive, phase) if phase != @phases[drive]
|
|
137
|
+
motor_off(drive) if @motors[drive] && !motor
|
|
138
|
+
@motors[drive] = motor
|
|
139
|
+
return @syncs[drive] = false unless motor && @disks[drive]
|
|
140
|
+
|
|
141
|
+
clock = @clocks[drive] + 6
|
|
142
|
+
byte = 10 * (16 - Disk.zone_of(track(drive)))
|
|
143
|
+
if clock >= byte
|
|
144
|
+
clock -= byte
|
|
145
|
+
pass_cell(drive)
|
|
146
|
+
end
|
|
147
|
+
@clocks[drive] = clock
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
# Gray code index of a phase pair: 00, 10, 11, 01.
|
|
151
|
+
def phase_index(phase) = [0, 3, 1, 2][phase]
|
|
152
|
+
|
|
153
|
+
def step(drive, phase)
|
|
154
|
+
change = (phase_index(phase) - phase_index(@phases[drive])) & 0x03
|
|
155
|
+
@phases[drive] = phase
|
|
156
|
+
cylinder = @cylinders[drive]
|
|
157
|
+
if change == 1
|
|
158
|
+
cylinder -= 1
|
|
159
|
+
elsif change == 3
|
|
160
|
+
cylinder += 1
|
|
161
|
+
end
|
|
162
|
+
@cylinders[drive] = cylinder.clamp(0, Storage::D80Image::SIDE_TRACKS - 1)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def motor_off(drive)
|
|
166
|
+
@disks[drive]&.flush
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# A cell has passed under +drive+'s head.
|
|
170
|
+
def pass_cell(drive)
|
|
171
|
+
disk = @disks[drive]
|
|
172
|
+
number = track(drive)
|
|
173
|
+
cells = disk.track(number)
|
|
174
|
+
return unless cells
|
|
175
|
+
|
|
176
|
+
position = @positions[drive] % cells.length
|
|
177
|
+
@positions[drive] = position + 1
|
|
178
|
+
return unless drive == selected_drive
|
|
179
|
+
|
|
180
|
+
writing? ? write_cell(drive, disk, number, cells, position) : read_cell(drive, cells, position)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def writing? = (@via.pcr & 0xe0) == 0xc0
|
|
184
|
+
|
|
185
|
+
def read_cell(drive, cells, position)
|
|
186
|
+
cell = cells[position]
|
|
187
|
+
@syncs[drive] = cell == Disk::SYNC
|
|
188
|
+
byte_ready(cell) unless cell == Disk::SYNC
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def write_cell(drive, disk, number, cells, position)
|
|
192
|
+
cells[position] = @via.pcr.allbits?(0x0e) ? Disk::SYNC : @rriot.port_a_output
|
|
193
|
+
disk.written(number)
|
|
194
|
+
@syncs[drive] = false
|
|
195
|
+
byte_ready(@data_in)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def byte_ready(byte)
|
|
199
|
+
@data_in = byte
|
|
200
|
+
@via.ca1 = false
|
|
201
|
+
@ca1_low = true
|
|
202
|
+
@so_pending = true
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# VIA port A reads the decoded byte, and PB7 SYNC, low while the
|
|
207
|
+
# shift register holds a sync.
|
|
208
|
+
class ReadLines
|
|
209
|
+
def initialize(mechanism)
|
|
210
|
+
@mechanism = mechanism
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def read_a(_lines) = @mechanism.data_in
|
|
214
|
+
|
|
215
|
+
def read_b(_lines) = @mechanism.sync? ? 0x7f : 0xff
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# The 6530's port B inputs from the drives.
|
|
219
|
+
class StatusLines
|
|
220
|
+
def initialize(mechanism)
|
|
221
|
+
@mechanism = mechanism
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
def read_a(_lines) = 0xff
|
|
225
|
+
|
|
226
|
+
def read_b(_lines) = @mechanism.status_lines
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model8050
|
|
6
|
+
# Each chip a class of its own, so each keeps one type of peripheral.
|
|
7
|
+
|
|
8
|
+
# UC1, on the IEEE-488 data lines.
|
|
9
|
+
class DataRIOT < Chips::RIOT
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# UE1, on the control lines, the jumpers and the LEDs.
|
|
13
|
+
class ControlRIOT < Chips::RIOT
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# UM3, the controller's VIA, at $0040.
|
|
17
|
+
class ControllerVIA < Chips::VIA
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# UK3, the controller's 6530, its ROM the controller's program.
|
|
21
|
+
class ControllerRRIOT < Chips::RRIOT
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# The interface processor's 6502, UN1, on the IPBus.
|
|
25
|
+
class IPCPU
|
|
26
|
+
include Badline::Chips::CPU::Core
|
|
27
|
+
include Engine::IdleCPU
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# The floppy controller's 6502, UH3, on the FDCBus.
|
|
31
|
+
class FDCCPU
|
|
32
|
+
include Badline::Chips::CPU::Core
|
|
33
|
+
include Engine::IdleCPU
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/chips/riot"
|
|
4
|
+
require "badline/chips/rriot"
|
|
5
|
+
require "badline/drives/ieee_bus"
|
|
6
|
+
require "badline/drives/engine"
|
|
7
|
+
require "badline/drives/model8050/ip_bus"
|
|
8
|
+
require "badline/drives/model8050/fdc_bus"
|
|
9
|
+
require "badline/drives/model8050/parts"
|
|
10
|
+
require "badline/drives/model8050/ieee_port"
|
|
11
|
+
require "badline/drives/model8050/disk"
|
|
12
|
+
require "badline/drives/model8050/mechanism"
|
|
13
|
+
require "badline/drives/model8050/idle"
|
|
14
|
+
require "badline/drives/model8050/keyed_state"
|
|
15
|
+
|
|
16
|
+
module Badline
|
|
17
|
+
module Drives
|
|
18
|
+
# The 8050 dual disk drive of the PET and CBM-II, two drives on one
|
|
19
|
+
# IEEE-488 unit (schematic 8050001; the 8050/8250 service manual).
|
|
20
|
+
#
|
|
21
|
+
# Two 6502s share the board, clocked at 1 MHz from one 12 MHz crystal
|
|
22
|
+
# on opposite phases. The interface processor (IP) runs DOS 2.7 from
|
|
23
|
+
# 16 KB of ROM at $C000, with two 6532s (IPBus): their RAM is its zero
|
|
24
|
+
# page and stack, and their ports face the IEEE-488 bus (IEEEPort). The
|
|
25
|
+
# floppy controller (FDC) runs the 1 KB program in its 6530's ROM, with
|
|
26
|
+
# a VIA and the 6530 facing the drives (FDCBus, Mechanism). The two
|
|
27
|
+
# meet in 4 KB of common RAM, which each reaches in its own half of
|
|
28
|
+
# the cycle: the IP leaves jobs there, and the FDC runs them.
|
|
29
|
+
#
|
|
30
|
+
# Reset starts both. The FDC waits on the common RAM for the IP, which
|
|
31
|
+
# tests it, copies the FDC's start-up code into it ($C370) and waits for
|
|
32
|
+
# the FDC to answer, before both settle into their idle loops.
|
|
33
|
+
#
|
|
34
|
+
# The drive clocks against the host through a phase accumulator, as
|
|
35
|
+
# Drives::Engine::Core does: each host cycle adds 1 MHz of phase, and
|
|
36
|
+
# every whole host period in it runs a cycle of both processors. While
|
|
37
|
+
# both processors idle, it sleeps instead (Idle), and the readers of its
|
|
38
|
+
# parts catch it up first.
|
|
39
|
+
class Model8050
|
|
40
|
+
include Idle
|
|
41
|
+
|
|
42
|
+
CLOCK_HZ = 1_000_000
|
|
43
|
+
|
|
44
|
+
# The host cycles by which a unit switched on with the machine has
|
|
45
|
+
# run the IP's RAM test and the FDC's start-up and reads its jumpers.
|
|
46
|
+
BOOT_CYCLES = 1_500_000
|
|
47
|
+
|
|
48
|
+
attr_reader :device, :ieee_bus
|
|
49
|
+
|
|
50
|
+
def ip_cpu = settled(@ip_cpu)
|
|
51
|
+
|
|
52
|
+
def fdc_cpu = settled(@fdc_cpu)
|
|
53
|
+
|
|
54
|
+
def ip_bus = settled(@ip_bus)
|
|
55
|
+
|
|
56
|
+
def fdc_bus = settled(@fdc_bus)
|
|
57
|
+
|
|
58
|
+
def common = settled(@common)
|
|
59
|
+
|
|
60
|
+
def data_riot = settled(@data_riot)
|
|
61
|
+
|
|
62
|
+
def control_riot = settled(@control_riot)
|
|
63
|
+
|
|
64
|
+
def via = settled(@via)
|
|
65
|
+
|
|
66
|
+
def rriot = settled(@rriot)
|
|
67
|
+
|
|
68
|
+
def mechanism = settled(@mechanism)
|
|
69
|
+
|
|
70
|
+
def cycles
|
|
71
|
+
settle!
|
|
72
|
+
@cycles
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
attr_writer :host_clock_hz
|
|
76
|
+
|
|
77
|
+
def model_name = "8050"
|
|
78
|
+
|
|
79
|
+
# The sides each drive's disk has.
|
|
80
|
+
def sides = 1
|
|
81
|
+
|
|
82
|
+
# +dos_rom+ covers $C000-$FFFF and +fdc_rom+ is the 6530's mask, each
|
|
83
|
+
# defaulting to the image in the ROM path: DOS 2.7 and the mask for
|
|
84
|
+
# MPI mechanisms. +device+ is the number the jumpers set, 8 to 15.
|
|
85
|
+
def initialize(dos_rom: nil, fdc_rom: nil, host_clock_hz: CLOCK_HZ, device: 8)
|
|
86
|
+
@device = device
|
|
87
|
+
@host_clock_hz = host_clock_hz
|
|
88
|
+
@phase = 0
|
|
89
|
+
@cycles = 0
|
|
90
|
+
build_ip(dos_rom || ROM.load("8050/dos-2.7.rom", 0xc000))
|
|
91
|
+
build_fdc(fdc_rom || ROM.read("8050/fdc-901869-01.rom"))
|
|
92
|
+
@ip_cpu = IPCPU.new(@ip_bus)
|
|
93
|
+
@fdc_cpu = FDCCPU.new(@fdc_bus)
|
|
94
|
+
init_idle
|
|
95
|
+
@ieee_bus.attach(self)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Plugs the unit into an IEEE-488 bus, leaving the one it was on.
|
|
99
|
+
def connect(ieee_bus)
|
|
100
|
+
settle!
|
|
101
|
+
@ieee_bus.detach(self)
|
|
102
|
+
@ieee_bus = ieee_bus
|
|
103
|
+
@ieee_port.bus = ieee_bus
|
|
104
|
+
ieee_bus.attach(self)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# The lines the unit pulls on the bus while ATN is low or not
|
|
108
|
+
# (IEEEPort).
|
|
109
|
+
def ieee_low_lines(atn) = @ieee_port.ieee_low_lines(atn)
|
|
110
|
+
|
|
111
|
+
# The controller pushed its lines into the bus.
|
|
112
|
+
def host_written! = nil
|
|
113
|
+
|
|
114
|
+
# Which LEDs are lit: bit 0 drive 1's, bit 1 drive 0's, bit 2 the
|
|
115
|
+
# error LED. They hold still while the unit sleeps, so reading them
|
|
116
|
+
# leaves it asleep.
|
|
117
|
+
def leds = @ieee_port.leds
|
|
118
|
+
|
|
119
|
+
def led_on? = leds.anybits?(0x03)
|
|
120
|
+
|
|
121
|
+
# Puts a Disk in +drive+, 0 or 1. Nil takes the disk out.
|
|
122
|
+
def insert(disk, drive: 0)
|
|
123
|
+
settle!
|
|
124
|
+
@mechanism.insert(disk, drive)
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Puts in the .d80 or .d82 image at +path+, write-protected with
|
|
128
|
+
# `read_only`.
|
|
129
|
+
def insert_image(path, drive: 0, read_only: false)
|
|
130
|
+
image_class = File.extname(path).casecmp?(".d82") ? Storage::D82Image : Storage::D80Image
|
|
131
|
+
insert(Disk.new(image_class.new(path, read_only:)), drive:)
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# The Disk in +drive+, or nil.
|
|
135
|
+
def disk(drive = 0) = @mechanism.disk(drive)
|
|
136
|
+
|
|
137
|
+
# Stores what the heads wrote in the disks' images, as the motors
|
|
138
|
+
# stopping do.
|
|
139
|
+
def flush
|
|
140
|
+
settle!
|
|
141
|
+
@mechanism.flush
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def power_led_on? = true
|
|
145
|
+
|
|
146
|
+
# Runs the drive cycles that fall in one host cycle, then takes the
|
|
147
|
+
# controller's lines for the next. Asleep, the unit owes the cycles
|
|
148
|
+
# instead, and wakes once the controller has moved a line.
|
|
149
|
+
def host_cycle!
|
|
150
|
+
phase = @phase + CLOCK_HZ
|
|
151
|
+
while phase >= @host_clock_hz
|
|
152
|
+
@asleep ? owe_cycle : run_cycle
|
|
153
|
+
phase -= @host_clock_hz
|
|
154
|
+
end
|
|
155
|
+
@phase = phase
|
|
156
|
+
settle! if @asleep && @ieee_port.lines_moved?
|
|
157
|
+
@ieee_port.latch_host
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def cycle!
|
|
161
|
+
settle!
|
|
162
|
+
run_cycle
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# The bus's IFC line resets both processors and every chip. RAM keeps
|
|
166
|
+
# its contents.
|
|
167
|
+
def reset!
|
|
168
|
+
settle!
|
|
169
|
+
@data_riot.reset!
|
|
170
|
+
@control_riot.reset!
|
|
171
|
+
@via.reset!
|
|
172
|
+
@rriot.reset!
|
|
173
|
+
@ip_cpu.reset!
|
|
174
|
+
@fdc_cpu.reset!
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def inspect
|
|
178
|
+
"#<#{self.class.name} cycles=#{@cycles} ip=(#{@ip_cpu.inspect}) fdc=(#{@fdc_cpu.inspect})>"
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
private
|
|
182
|
+
|
|
183
|
+
def settled(part)
|
|
184
|
+
settle!
|
|
185
|
+
part
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
def run_cycle
|
|
189
|
+
step
|
|
190
|
+
watch_loops if @idle_skip
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def build_ip(rom)
|
|
194
|
+
@common = Memory.new([], length: 0x1000)
|
|
195
|
+
@ieee_bus = IEEEBus.new
|
|
196
|
+
@ieee_port = IEEEPort.new(device: @device, bus: @ieee_bus)
|
|
197
|
+
@data_riot = DataRIOT.new(peripheral: DataLines.new(@ieee_port))
|
|
198
|
+
@control_riot = ControlRIOT.new(peripheral: ControlLines.new(@ieee_port))
|
|
199
|
+
@ieee_port.data_riot = @data_riot
|
|
200
|
+
@ieee_port.control_riot = @control_riot
|
|
201
|
+
@ip_bus = IPBus.new(rom:, common: @common, data_riot: @data_riot, control_riot: @control_riot)
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def build_fdc(rom)
|
|
205
|
+
@mechanism = Mechanism.new(sides:)
|
|
206
|
+
@via = ControllerVIA.new(start: 0x0040, peripheral: ReadLines.new(@mechanism))
|
|
207
|
+
@rriot = ControllerRRIOT.new(rom:, peripheral: StatusLines.new(@mechanism))
|
|
208
|
+
@fdc_bus = FDCBus.new(common: @common, via: @via, rriot: @rriot)
|
|
209
|
+
@mechanism.via = @via
|
|
210
|
+
@mechanism.rriot = @rriot
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# One cycle of the board: the drives, then the chips, then the IP and
|
|
214
|
+
# the FDC, each in its own half of the cycle. ATN reaches UE1's PA7 as
|
|
215
|
+
# the port latched it. UE1 pulls the IP's IRQ, and the VIA and the
|
|
216
|
+
# 6530 the FDC's. Byte ready sets the FDC's V flag through SO for the
|
|
217
|
+
# next cycle's instruction step, as on the 1541.
|
|
218
|
+
def step
|
|
219
|
+
so = @mechanism.take_so
|
|
220
|
+
@mechanism.cycle!
|
|
221
|
+
@control_riot.pa7 = @ieee_port.atn_low?
|
|
222
|
+
@data_riot.cycle!
|
|
223
|
+
@control_riot.cycle!
|
|
224
|
+
@via.cycle!
|
|
225
|
+
@rriot.cycle!
|
|
226
|
+
@ip_cpu.irq = @control_riot.irq?
|
|
227
|
+
@ip_cpu.cycle!
|
|
228
|
+
@fdc_cpu.irq = @via.irq? || @rriot.irq?
|
|
229
|
+
@fdc_cpu.so! if so
|
|
230
|
+
@fdc_cpu.cycle!
|
|
231
|
+
@cycles += 1
|
|
232
|
+
end
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
# The 8250: the 8050's board driving double-sided mechanisms, 154 tracks
|
|
236
|
+
# a disk, with jumper E3 grounding the 6530's PB6 to say so.
|
|
237
|
+
class Model8250 < Model8050
|
|
238
|
+
def model_name = "8250"
|
|
239
|
+
|
|
240
|
+
def sides = 2
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
end
|
data/lib/badline/frontend/app.rb
CHANGED
|
@@ -111,16 +111,23 @@ module Badline
|
|
|
111
111
|
end
|
|
112
112
|
end
|
|
113
113
|
|
|
114
|
+
# The window keeps F8, F9, F10, Tab and the snapshot keys for itself,
|
|
115
|
+
# but for the keys the machine claims (Controls#claims?).
|
|
114
116
|
def handle_key(scancode, down)
|
|
115
|
-
|
|
117
|
+
claimed = @controls.claims?(scancode)
|
|
118
|
+
if !claimed && toggle?(scancode)
|
|
116
119
|
handle_toggle(scancode) if down
|
|
117
|
-
elsif down && @snapshots.key(scancode)
|
|
120
|
+
elsif !claimed && down && @snapshots.key(scancode)
|
|
118
121
|
swap(@snapshots.computer)
|
|
119
|
-
elsif !Snapshots::KEYS.include?(scancode)
|
|
122
|
+
elsif claimed || !Snapshots::KEYS.include?(scancode)
|
|
120
123
|
@controls.key(scancode, down)
|
|
121
124
|
end
|
|
122
125
|
end
|
|
123
126
|
|
|
127
|
+
def toggle?(scancode)
|
|
128
|
+
[Keys::F8, Keys::F9, Keys::F10].include?(scancode) || (scancode == Keys::TAB && @controls.tab_toggles?)
|
|
129
|
+
end
|
|
130
|
+
|
|
124
131
|
# Runs the restored machine in place of the one before: the screen,
|
|
125
132
|
# the drive LED, the controls, the gamepads and the sound go over to
|
|
126
133
|
# it.
|
|
@@ -10,7 +10,7 @@ module Badline
|
|
|
10
10
|
# attach, an --at key names nothing or an event fails, which it warns
|
|
11
11
|
# about.
|
|
12
12
|
def self.run(options)
|
|
13
|
-
error = Timeline.error(options.timeline, options.family)
|
|
13
|
+
error = Timeline.error(options.timeline, options.p500? ? :p500 : options.family)
|
|
14
14
|
unless error.empty?
|
|
15
15
|
warn "#{options.program}: #{error}"
|
|
16
16
|
return 1
|
|
@@ -8,10 +8,12 @@ module Badline
|
|
|
8
8
|
# switches, the arrow cluster and space drive one joystick and WASD
|
|
9
9
|
# and left shift the other. The arrows start on joystick 2, and
|
|
10
10
|
# #swap_ports swaps them. The VIC-20's one joystick takes both. The
|
|
11
|
-
# PET
|
|
12
|
-
# (PETKeyboard)
|
|
13
|
-
#
|
|
14
|
-
#
|
|
11
|
+
# PET's and the CBM-II's keys go by the character they type
|
|
12
|
+
# (PETKeyboard), and the PET and the CBM-II's B series have no
|
|
13
|
+
# joystick.
|
|
14
|
+
# - A pot device the pause menu plugs into a control port of a C64, a
|
|
15
|
+
# C128 or a P500: a 1351 mouse or a pair of paddles, which the host
|
|
16
|
+
# mouse drives. Motion
|
|
15
17
|
# turns the paddles or moves the mouse, and the host's left and right
|
|
16
18
|
# buttons go to whichever lines the device puts them on.
|
|
17
19
|
class Controls
|
|
@@ -24,6 +26,21 @@ module Badline
|
|
|
24
26
|
# The pot device plugged in, one of POTS.
|
|
25
27
|
attr_reader :pot, :arrows_port
|
|
26
28
|
|
|
29
|
+
# Whether the machine has joysticks: all but the PET and the CBM-II's
|
|
30
|
+
# B series.
|
|
31
|
+
def self.joysticks?(computer)
|
|
32
|
+
family = computer.family
|
|
33
|
+
family != :pet && (family != :cbm2 || p500?(computer))
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Whether the machine is the CBM-II's P500, which has the B series'
|
|
37
|
+
# keyboard and two joysticks.
|
|
38
|
+
def self.p500?(computer) = computer.is_a?(CBM2::P500)
|
|
39
|
+
|
|
40
|
+
# Whether the machine's keys go by the character they type: the PET's
|
|
41
|
+
# and the CBM-II's.
|
|
42
|
+
def self.symbolic?(computer) = %i[pet cbm2].include?(computer.family)
|
|
43
|
+
|
|
27
44
|
def initialize(computer)
|
|
28
45
|
@computer = computer
|
|
29
46
|
@joystick = false
|
|
@@ -58,9 +75,19 @@ module Badline
|
|
|
58
75
|
self.joystick_mode = !@joystick if tab_toggles?
|
|
59
76
|
end
|
|
60
77
|
|
|
61
|
-
# Whether Tab switches the keys to the joysticks, or on
|
|
62
|
-
#
|
|
63
|
-
def tab_toggles? = @computer
|
|
78
|
+
# Whether Tab switches the keys to the joysticks, or on a machine
|
|
79
|
+
# without them is a key of its own.
|
|
80
|
+
def tab_toggles? = Controls.joysticks?(@computer)
|
|
81
|
+
|
|
82
|
+
# Whether the machine takes a key the window would otherwise keep for
|
|
83
|
+
# itself: on the CBM-II, F8, RUN/STOP on F12, Alt with F9 or F10, and
|
|
84
|
+
# the release of any key it pressed.
|
|
85
|
+
def claims?(scancode)
|
|
86
|
+
return false unless @computer.family == :cbm2
|
|
87
|
+
return true if @pet_keyboard.holds?(scancode) || [Keys::F8, Keys::F12].include?(scancode)
|
|
88
|
+
|
|
89
|
+
@pet_keyboard.alt? && [Keys::F9, Keys::F10].include?(scancode)
|
|
90
|
+
end
|
|
64
91
|
|
|
65
92
|
# Plugs in one of POTS, in place of the one before.
|
|
66
93
|
def plug(pot)
|
|
@@ -116,7 +143,7 @@ module Badline
|
|
|
116
143
|
# A host key lets go of the key it pressed, though the C128 has
|
|
117
144
|
# switched between C64 and C128 mode since and maps it elsewhere.
|
|
118
145
|
def machine_key(scancode, down)
|
|
119
|
-
return @pet_keyboard.key(scancode, down) if @computer
|
|
146
|
+
return @pet_keyboard.key(scancode, down) if Controls.symbolic?(@computer)
|
|
120
147
|
|
|
121
148
|
@held[scancode] = host_key(scancode) if down || !@held.key?(scancode)
|
|
122
149
|
key = @held[scancode]
|
|
@@ -159,7 +186,7 @@ module Badline
|
|
|
159
186
|
# A fresh pot device on its port, and none on the other. A machine
|
|
160
187
|
# without the ports takes none.
|
|
161
188
|
def attach_pot_device
|
|
162
|
-
ports = @computer
|
|
189
|
+
ports = Controls.p500?(@computer) || !Controls.symbolic?(@computer) ? @computer.control_ports : nil
|
|
163
190
|
if ports.nil?
|
|
164
191
|
@pot = :none
|
|
165
192
|
return
|
|
@@ -211,17 +238,26 @@ module Badline
|
|
|
211
238
|
down ? joystick.press(direction) : joystick.release(direction)
|
|
212
239
|
end
|
|
213
240
|
|
|
214
|
-
# The PET's keyboard for a PET, and for any other machine
|
|
215
|
-
# uses.
|
|
241
|
+
# The PET's keyboard for a PET or a CBM-II, and for any other machine
|
|
242
|
+
# one it never uses.
|
|
216
243
|
def pet_keyboard(computer)
|
|
217
|
-
|
|
244
|
+
layout = case computer.family
|
|
245
|
+
when :pet then computer.model.keyboard
|
|
246
|
+
when :cbm2 then :cbm2
|
|
247
|
+
else :graphics
|
|
248
|
+
end
|
|
249
|
+
PETKeyboard.new(computer.keyboard, layout)
|
|
218
250
|
end
|
|
219
251
|
|
|
220
252
|
def release_all
|
|
221
|
-
|
|
253
|
+
if Controls.symbolic?(@computer)
|
|
254
|
+
@pet_keyboard.release_all
|
|
255
|
+
else
|
|
256
|
+
Keys::ARROWS.each_key { |scancode| @computer.keyboard.release(host_key(scancode)) }
|
|
257
|
+
Keys::WASD.each_key { |scancode| @computer.keyboard.release(host_key(scancode)) }
|
|
258
|
+
end
|
|
259
|
+
return unless Controls.joysticks?(@computer)
|
|
222
260
|
|
|
223
|
-
Keys::ARROWS.each_key { |scancode| @computer.keyboard.release(host_key(scancode)) }
|
|
224
|
-
Keys::WASD.each_key { |scancode| @computer.keyboard.release(host_key(scancode)) }
|
|
225
261
|
Keys::ARROWS.each_value do |direction|
|
|
226
262
|
@computer.joystick1.release(direction)
|
|
227
263
|
@computer.joystick2.release(direction)
|
|
@@ -7,8 +7,8 @@ module Badline
|
|
|
7
7
|
# second one joystick 1. PadPort holds the mapping.
|
|
8
8
|
#
|
|
9
9
|
# The controllers are polled every frame, and rescanned whenever SDL
|
|
10
|
-
# reports one added or removed. The PET
|
|
11
|
-
# controllers steer two that nothing reads.
|
|
10
|
+
# reports one added or removed. The PET and the CBM-II's B series have
|
|
11
|
+
# no joysticks, and the controllers steer two that nothing reads.
|
|
12
12
|
class Gamepads
|
|
13
13
|
PORTS = [2, 1].freeze
|
|
14
14
|
|
|
@@ -53,7 +53,7 @@ module Badline
|
|
|
53
53
|
private
|
|
54
54
|
|
|
55
55
|
def joystick(computer, port)
|
|
56
|
-
return Input::Joystick.new
|
|
56
|
+
return Input::Joystick.new unless Controls.joysticks?(computer)
|
|
57
57
|
|
|
58
58
|
port == 1 ? computer.joystick1 : computer.joystick2
|
|
59
59
|
end
|