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,192 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model4040
|
|
6
|
+
# The unit's two SA390 drives as the 6504 sees them through the VIA
|
|
7
|
+
# and the 6530 (schematic 320806, 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 and PB0-1 drive 1's. The
|
|
11
|
+
# 6530's port B picks the drive on PB0 and the zone on PB1-2, and
|
|
12
|
+
# reads write protect on PB3, high while the disk can't be written,
|
|
13
|
+
# and SYNC on PB6, low while ten one bits sit in the shift register.
|
|
14
|
+
# Its port A holds the byte to write.
|
|
15
|
+
#
|
|
16
|
+
# Each stepper's two bits count in binary: one up moves the head half
|
|
17
|
+
# a track in, one down half a track out, and the controller steps
|
|
18
|
+
# once an interrupt, twice a track ($FF82), bumping the head against
|
|
19
|
+
# track 1 with 116 steps out.
|
|
20
|
+
#
|
|
21
|
+
# A turning disk passes one byte cell under the head every ten bit
|
|
22
|
+
# cells, at 16 MHz divided by 16 less the track's zone and then by 4,
|
|
23
|
+
# and the byte cells come round 300 times a minute (Disk). Each data
|
|
24
|
+
# cell ends in byte ready, which latches the byte into VIA port A
|
|
25
|
+
# through CA1; the controller has no SO pin and polls the VIA's
|
|
26
|
+
# interrupt flags for it. A sync cell holds SYNC low instead. While
|
|
27
|
+
# the VIA's CB2 is low the drive writes: each cell takes the 6530's
|
|
28
|
+
# port A, or a sync while CA2 is high, and still ends in byte ready.
|
|
29
|
+
# The format code holds CA2 high in pulse mode, which reading port A
|
|
30
|
+
# pulls low for a cycle.
|
|
31
|
+
class Mechanism
|
|
32
|
+
WRITE_PROTECT = 0x08
|
|
33
|
+
SYNC_LINE = 0x40
|
|
34
|
+
|
|
35
|
+
# The half tracks a head can reach: track 1 to 35.
|
|
36
|
+
HALF_TRACKS = 70
|
|
37
|
+
|
|
38
|
+
attr_reader :data_in, :half_tracks
|
|
39
|
+
attr_accessor :via, :rriot
|
|
40
|
+
|
|
41
|
+
def initialize
|
|
42
|
+
@via = nil
|
|
43
|
+
@rriot = nil
|
|
44
|
+
@disks = [nil, nil]
|
|
45
|
+
@half_tracks = [0, 0]
|
|
46
|
+
@phases = [0, 0]
|
|
47
|
+
@positions = [0, 0]
|
|
48
|
+
@clocks = [0, 0]
|
|
49
|
+
@motors = [false, false]
|
|
50
|
+
@data_in = 0xff
|
|
51
|
+
@syncs = [false, false]
|
|
52
|
+
@ca1_low = false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# The Disk in +drive+, 0 or 1, or nil.
|
|
56
|
+
def disk(drive = 0) = @disks[drive]
|
|
57
|
+
|
|
58
|
+
def insert(disk, drive = 0)
|
|
59
|
+
@disks[drive]&.flush
|
|
60
|
+
@disks[drive] = disk
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def flush
|
|
64
|
+
@disks.each { |disk| disk&.flush }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def motor_on?(drive = 0) = @motors[drive]
|
|
68
|
+
|
|
69
|
+
# The track under +drive+'s head, 1 to 35.
|
|
70
|
+
def track(drive = 0) = (@half_tracks[drive] >> 1) + 1
|
|
71
|
+
|
|
72
|
+
def cycle!
|
|
73
|
+
if @ca1_low
|
|
74
|
+
@ca1_low = false
|
|
75
|
+
@via.ca1 = true
|
|
76
|
+
end
|
|
77
|
+
drives = @via.port_b_output
|
|
78
|
+
turn(0, drives.nobits?(0x20), (drives >> 2) & 0x03)
|
|
79
|
+
turn(1, drives.nobits?(0x10), drives & 0x03)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Whether a cycle would change nothing: both motors off and no byte
|
|
83
|
+
# ready under way.
|
|
84
|
+
def quiet? = !@motors[0] && !@motors[1] && !@ca1_low
|
|
85
|
+
|
|
86
|
+
# What the drives hold, for comparing them at two points.
|
|
87
|
+
def idle_state = [*@half_tracks, *@phases, *@motors, *@syncs, @data_in]
|
|
88
|
+
|
|
89
|
+
# The 6530's port B inputs.
|
|
90
|
+
def status_lines
|
|
91
|
+
drive = selected_drive
|
|
92
|
+
lines = 0xff
|
|
93
|
+
lines &= ~WRITE_PROTECT unless @disks[drive]&.write_protected?
|
|
94
|
+
@syncs[drive] ? lines & ~SYNC_LINE : lines
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def selected_drive = @rriot.port_b_output & 0x01
|
|
100
|
+
|
|
101
|
+
def turn(drive, motor, phase)
|
|
102
|
+
step(drive, phase) if phase != @phases[drive]
|
|
103
|
+
motor_off(drive) if @motors[drive] && !motor
|
|
104
|
+
@motors[drive] = motor
|
|
105
|
+
return @syncs[drive] = false unless motor && @disks[drive]
|
|
106
|
+
|
|
107
|
+
clock = @clocks[drive] + 2
|
|
108
|
+
byte = 5 * (16 - Disk.zone_of(track(drive)))
|
|
109
|
+
if clock >= byte
|
|
110
|
+
clock -= byte
|
|
111
|
+
pass_cell(drive)
|
|
112
|
+
end
|
|
113
|
+
@clocks[drive] = clock
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def step(drive, phase)
|
|
117
|
+
change = (phase - @phases[drive]) & 0x03
|
|
118
|
+
@phases[drive] = phase
|
|
119
|
+
half_track = @half_tracks[drive]
|
|
120
|
+
if change == 1
|
|
121
|
+
half_track += 1
|
|
122
|
+
elsif change == 3
|
|
123
|
+
half_track -= 1
|
|
124
|
+
end
|
|
125
|
+
@half_tracks[drive] = half_track.clamp(0, HALF_TRACKS - 1)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def motor_off(drive)
|
|
129
|
+
@disks[drive]&.flush
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# A cell has passed under +drive+'s head.
|
|
133
|
+
def pass_cell(drive)
|
|
134
|
+
disk = @disks[drive]
|
|
135
|
+
number = track(drive)
|
|
136
|
+
cells = disk.track(number)
|
|
137
|
+
return unless cells
|
|
138
|
+
|
|
139
|
+
position = @positions[drive] % cells.length
|
|
140
|
+
@positions[drive] = position + 1
|
|
141
|
+
return unless drive == selected_drive
|
|
142
|
+
|
|
143
|
+
writing? ? write_cell(drive, disk, number, cells, position) : read_cell(drive, cells, position)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def writing? = !@via.cb2_output
|
|
147
|
+
|
|
148
|
+
def read_cell(drive, cells, position)
|
|
149
|
+
cell = cells[position]
|
|
150
|
+
@syncs[drive] = cell == Disk::SYNC
|
|
151
|
+
byte_ready(cell) unless cell == Disk::SYNC
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def write_cell(drive, disk, number, cells, position)
|
|
155
|
+
cells[position] = @via.ca2_output ? Disk::SYNC : @rriot.port_a_output
|
|
156
|
+
disk.written(number)
|
|
157
|
+
@syncs[drive] = false
|
|
158
|
+
byte_ready(@data_in)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def byte_ready(byte)
|
|
162
|
+
@data_in = byte
|
|
163
|
+
@via.ca1 = false
|
|
164
|
+
@ca1_low = true
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
# VIA port A reads the byte the last cell latched. Port B drives the
|
|
169
|
+
# drives only.
|
|
170
|
+
class ReadLines
|
|
171
|
+
def initialize(mechanism)
|
|
172
|
+
@mechanism = mechanism
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def read_a(_lines) = @mechanism.data_in
|
|
176
|
+
|
|
177
|
+
def read_b(_lines) = 0xff
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# The 6530's port B inputs from the drives.
|
|
181
|
+
class StatusLines
|
|
182
|
+
def initialize(mechanism)
|
|
183
|
+
@mechanism = mechanism
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def read_a(_lines) = 0xff
|
|
187
|
+
|
|
188
|
+
def read_b(_lines) = @mechanism.status_lines
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model4040
|
|
6
|
+
# The interface processor's 6502, UN1, on the 4040's IPBus.
|
|
7
|
+
class IPCPU
|
|
8
|
+
include Badline::Chips::CPU::Core
|
|
9
|
+
include Engine::IdleCPU
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# The floppy controller's 6504, UH3, on the 8050's FDCBus. It has no
|
|
13
|
+
# SO pin.
|
|
14
|
+
class FDCCPU
|
|
15
|
+
include Badline::Chips::CPU::Core
|
|
16
|
+
include Engine::IdleCPU
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/drives/model8050"
|
|
4
|
+
require "badline/drives/model4040/ip_bus"
|
|
5
|
+
require "badline/drives/model4040/parts"
|
|
6
|
+
require "badline/drives/model4040/disk"
|
|
7
|
+
require "badline/drives/model4040/mechanism"
|
|
8
|
+
require "badline/drives/model4040/keyed_state"
|
|
9
|
+
|
|
10
|
+
module Badline
|
|
11
|
+
module Drives
|
|
12
|
+
# The 4040 dual disk drive of the PET and CBM-II, two Shugart SA390
|
|
13
|
+
# drives on one IEEE-488 unit (schematic 320806, the 8050's parent).
|
|
14
|
+
#
|
|
15
|
+
# The board is the 8050's (Model8050::Board) with an older floppy side.
|
|
16
|
+
# A 16 MHz crystal clocks both processors at 1 MHz on opposite phases.
|
|
17
|
+
# The interface processor runs DOS 2.1 from 12 KB of ROM at $D000, with
|
|
18
|
+
# the 8050's 6532s on the bus and the same common RAM, on a bus that
|
|
19
|
+
# doesn't decode A15 (IPBus). The floppy controller is a 6504, with no
|
|
20
|
+
# SO pin, on the 8050's map (Model8050::FDCBus): it runs the program in
|
|
21
|
+
# its 6530 (901466-04) and polls the VIA for byte ready, whose own
|
|
22
|
+
# interrupt line goes nowhere, so only the 6530's timer interrupts it
|
|
23
|
+
# (Mechanism). The disks are the 1541's, 35 tracks of 17 to 21
|
|
24
|
+
# sectors, a .d64 each (Disk).
|
|
25
|
+
#
|
|
26
|
+
# At reset the controller waits for the common RAM's $0400 to read 0,
|
|
27
|
+
# starts, and writes its timer's period there. The interface processor
|
|
28
|
+
# tests its RAM and ROMs, has the controller reset its drives, and
|
|
29
|
+
# bumps both heads to track 1.
|
|
30
|
+
class Model4040
|
|
31
|
+
include Model8050::Board
|
|
32
|
+
include Model8050::Idle
|
|
33
|
+
include Model8050::KeyedState
|
|
34
|
+
|
|
35
|
+
CLOCK_HZ = Model8050::CLOCK_HZ
|
|
36
|
+
|
|
37
|
+
# The host cycles by which a unit switched on with the machine has
|
|
38
|
+
# run the IP's tests and bumped both heads, and reads its jumpers.
|
|
39
|
+
BOOT_CYCLES = 2_500_000
|
|
40
|
+
|
|
41
|
+
# Where DOS 2.1's idle loop and the controller's job loop start over
|
|
42
|
+
# (Model8050::Idle).
|
|
43
|
+
IP_LOOP = 0xd4b7
|
|
44
|
+
FDC_LOOP = 0xfc54
|
|
45
|
+
|
|
46
|
+
def model_name = "4040"
|
|
47
|
+
|
|
48
|
+
def boot_cycles = BOOT_CYCLES
|
|
49
|
+
|
|
50
|
+
def sides = 1
|
|
51
|
+
|
|
52
|
+
# +dos_rom+ covers $D000-$FFFF and +fdc_rom+ is the 6530's mask, each
|
|
53
|
+
# defaulting to the image in the ROM path: the revised DOS 2.1 that
|
|
54
|
+
# xpet's 4040 runs, and 901466-04. +device+ is the number the jumpers
|
|
55
|
+
# set, 8 to 15.
|
|
56
|
+
def initialize(dos_rom: nil, fdc_rom: nil, host_clock_hz: CLOCK_HZ, device: 8)
|
|
57
|
+
@device = device
|
|
58
|
+
@host_clock_hz = host_clock_hz
|
|
59
|
+
@phase = 0
|
|
60
|
+
@cycles = 0
|
|
61
|
+
build_ip(dos_rom || ROM.load(dos_file, dos_start))
|
|
62
|
+
build_fdc(fdc_rom || ROM.read(fdc_file))
|
|
63
|
+
@ip_cpu = IPCPU.new(@ip_bus)
|
|
64
|
+
@fdc_cpu = FDCCPU.new(@fdc_bus)
|
|
65
|
+
init_idle(ip_loop, fdc_loop)
|
|
66
|
+
@ieee_bus.attach(self)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
# Puts in the .d64 image at +path+, or the DOS 1 disk a .d67 holds,
|
|
70
|
+
# write-protected with `read_only`.
|
|
71
|
+
def insert_image(path, drive: 0, read_only: false)
|
|
72
|
+
image = if File.extname(path).casecmp?(".d67")
|
|
73
|
+
Storage::D67Image.new(path, read_only:)
|
|
74
|
+
else
|
|
75
|
+
Storage::D64Image.new(path, read_only:)
|
|
76
|
+
end
|
|
77
|
+
insert(Disk.new(image), drive:)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def dos_file = "4040/dos-2.1-rev.rom"
|
|
83
|
+
|
|
84
|
+
def dos_start = 0xd000
|
|
85
|
+
|
|
86
|
+
def fdc_file = "4040/fdc-901466-04.rom"
|
|
87
|
+
|
|
88
|
+
def ip_loop = IP_LOOP
|
|
89
|
+
|
|
90
|
+
def fdc_loop = FDC_LOOP
|
|
91
|
+
|
|
92
|
+
def build_ip(rom)
|
|
93
|
+
@common = Memory.new([], length: 0x1000)
|
|
94
|
+
@ieee_bus = IEEEBus.new
|
|
95
|
+
@ieee_port = Model8050::IEEEPort.new(device: @device, bus: @ieee_bus)
|
|
96
|
+
@data_riot = Model8050::DataRIOT.new(peripheral: Model8050::DataLines.new(@ieee_port))
|
|
97
|
+
@control_riot = Model8050::ControlRIOT.new(peripheral: Model8050::ControlLines.new(@ieee_port))
|
|
98
|
+
@ieee_port.data_riot = @data_riot
|
|
99
|
+
@ieee_port.control_riot = @control_riot
|
|
100
|
+
@ip_bus = IPBus.new(rom:, common: @common, data_riot: @data_riot, control_riot: @control_riot)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def build_fdc(rom)
|
|
104
|
+
@mechanism = Mechanism.new
|
|
105
|
+
@via = Model8050::ControllerVIA.new(start: 0x0040, peripheral: ReadLines.new(@mechanism))
|
|
106
|
+
@rriot = Model8050::ControllerRRIOT.new(rom:, peripheral: StatusLines.new(@mechanism))
|
|
107
|
+
@fdc_bus = Model8050::FDCBus.new(common: @common, via: @via, rriot: @rriot)
|
|
108
|
+
@mechanism.via = @via
|
|
109
|
+
@mechanism.rriot = @rriot
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# The disk a BL-D8-DISK module holds (Model8050::KeyedState).
|
|
113
|
+
def disk_from_fields(input, detached) = Disk.from_fields(input, detached:)
|
|
114
|
+
|
|
115
|
+
# One cycle of the board: the drives, then the chips, then the IP and
|
|
116
|
+
# the FDC, each in its own half of the cycle, as on the 8050. Only the
|
|
117
|
+
# 6530 pulls the FDC's IRQ, and the FDC finds byte ready by polling.
|
|
118
|
+
def step
|
|
119
|
+
@mechanism.cycle!
|
|
120
|
+
@control_riot.pa7 = @ieee_port.atn_low?
|
|
121
|
+
@data_riot.cycle!
|
|
122
|
+
@control_riot.cycle!
|
|
123
|
+
@via.cycle!
|
|
124
|
+
@rriot.cycle!
|
|
125
|
+
@ip_cpu.irq = @control_riot.irq?
|
|
126
|
+
@ip_cpu.cycle!
|
|
127
|
+
@fdc_cpu.irq = @rriot.irq?
|
|
128
|
+
@fdc_cpu.cycle!
|
|
129
|
+
@cycles += 1
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The 3040: the 4040's board with the first DOS 2.1, as xpet's 3040
|
|
134
|
+
# runs it, 901468-11 to -13.
|
|
135
|
+
class Model3040 < Model4040
|
|
136
|
+
# Where the first DOS 2.1's idle loop starts over.
|
|
137
|
+
IP_LOOP = 0xd4b8
|
|
138
|
+
|
|
139
|
+
def model_name = "3040"
|
|
140
|
+
|
|
141
|
+
private
|
|
142
|
+
|
|
143
|
+
def dos_file = "4040/dos-2.1.rom"
|
|
144
|
+
|
|
145
|
+
def ip_loop = IP_LOOP
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# The 2040: the 4040's board with DOS 1.2 in the sockets at $E000 and
|
|
149
|
+
# $F000, UJ1 at $D000 left empty, and 901466-02 in the controller's
|
|
150
|
+
# 6530, as the 3040 with DOS 1 has it too. Its disks are DOS 1's .d67,
|
|
151
|
+
# with 20 sectors on tracks 18 to 24 (Storage::D67Image). The
|
|
152
|
+
# controller starts at once, with no handshake on the common RAM, and
|
|
153
|
+
# the interface processor leaves the common RAM alone at boot.
|
|
154
|
+
class Model2040 < Model4040
|
|
155
|
+
# Where DOS 1.2's idle loop and its controller's job loop start over.
|
|
156
|
+
IP_LOOP = 0xe27e
|
|
157
|
+
FDC_LOOP = 0xfc47
|
|
158
|
+
|
|
159
|
+
def model_name = "2040"
|
|
160
|
+
|
|
161
|
+
# Puts in the .d67 image at +path+, write-protected with `read_only`.
|
|
162
|
+
def insert_image(path, drive: 0, read_only: false)
|
|
163
|
+
insert(Disk.new(Storage::D67Image.new(path, read_only:)), drive:)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def dos_file = "4040/dos-1.2.rom"
|
|
169
|
+
|
|
170
|
+
def dos_start = 0xe000
|
|
171
|
+
|
|
172
|
+
def fdc_file = "4040/fdc-901466-02.rom"
|
|
173
|
+
|
|
174
|
+
def ip_loop = IP_LOOP
|
|
175
|
+
|
|
176
|
+
def fdc_loop = FDC_LOOP
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model8050
|
|
6
|
+
# The board of a unit with two processors on the IEEE-488 bus, as the
|
|
7
|
+
# 8050 family and the 4040 family share it: the DOS board's interface
|
|
8
|
+
# processor side (DOSBoard), and the floppy controller with its VIA
|
|
9
|
+
# and 6530 facing the drives. Each unit's class includes it, with
|
|
10
|
+
# Idle and KeyedState, and builds its own chips, so no call it makes
|
|
11
|
+
# on them is a union: the class sets DOSBoard's fields and @fdc_cpu,
|
|
12
|
+
# @fdc_bus, @rriot and @mechanism, and runs one cycle of the board in
|
|
13
|
+
# its own step. While both processors idle, it sleeps (Idle).
|
|
14
|
+
module Board
|
|
15
|
+
include DOSBoard
|
|
16
|
+
|
|
17
|
+
def fdc_cpu = settled(@fdc_cpu)
|
|
18
|
+
|
|
19
|
+
def fdc_bus = settled(@fdc_bus)
|
|
20
|
+
|
|
21
|
+
def rriot = settled(@rriot)
|
|
22
|
+
|
|
23
|
+
def mechanism = settled(@mechanism)
|
|
24
|
+
|
|
25
|
+
# Whether the unit is where VICE saves it: both processors between
|
|
26
|
+
# instructions, the FDC at the start of its idle loop.
|
|
27
|
+
def settled_for_vice?
|
|
28
|
+
fdc = fdc_cpu
|
|
29
|
+
ip_cpu.boundary? && fdc.boundary? && fdc.program_counter == @fdc_loop
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Whether a drive's LED is lit: of the LEDs, bit 0 is drive 1's,
|
|
33
|
+
# bit 1 drive 0's, and bit 2 the error LED.
|
|
34
|
+
def led_on? = leds.anybits?(0x03)
|
|
35
|
+
|
|
36
|
+
# Puts a Disk in +drive+, 0 or 1. Nil takes the disk out.
|
|
37
|
+
def insert(disk, drive: 0)
|
|
38
|
+
settle!
|
|
39
|
+
@mechanism.insert(disk, drive)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The Disk in +drive+, or nil.
|
|
43
|
+
def disk(drive = 0) = @mechanism.disk(drive)
|
|
44
|
+
|
|
45
|
+
# Stores what the heads wrote in the disks' images, as the motors
|
|
46
|
+
# stopping do.
|
|
47
|
+
def flush
|
|
48
|
+
settle!
|
|
49
|
+
@mechanism.flush
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# The bus's IFC line resets both processors and every chip. RAM keeps
|
|
53
|
+
# its contents.
|
|
54
|
+
def reset!
|
|
55
|
+
settle!
|
|
56
|
+
@data_riot.reset!
|
|
57
|
+
@control_riot.reset!
|
|
58
|
+
@via.reset!
|
|
59
|
+
@rriot.reset!
|
|
60
|
+
@ip_cpu.reset!
|
|
61
|
+
@fdc_cpu.reset!
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def inspect
|
|
65
|
+
"#<#{self.class.name} cycles=#{@cycles} ip=(#{@ip_cpu.inspect}) fdc=(#{@fdc_cpu.inspect})>"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def run_cycle
|
|
71
|
+
step
|
|
72
|
+
watch_loops if @idle_skip
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model8050
|
|
6
|
+
# The half of the DOS board every unit with an interface processor
|
|
7
|
+
# shares: the interface processor with its two 6532s on the IEEE-488
|
|
8
|
+
# bus, the common RAM, and the second processor's VIA, whichever side
|
|
9
|
+
# the second processor runs. Board adds the floppy controller's side
|
|
10
|
+
# to it, and Model9060 the SASI host's. Each unit's class sets
|
|
11
|
+
# @ip_cpu, @ip_bus, @common, @data_riot, @control_riot, @via,
|
|
12
|
+
# @ieee_bus and @ieee_port, and has its own run_cycle and settle!.
|
|
13
|
+
#
|
|
14
|
+
# The unit clocks against the host through a phase accumulator, as
|
|
15
|
+
# Drives::Engine::Core does: each host cycle adds 1 MHz of phase, and
|
|
16
|
+
# every whole host period in it runs a cycle of both processors. While
|
|
17
|
+
# both processors idle, it sleeps instead, and the readers of its
|
|
18
|
+
# parts catch it up first.
|
|
19
|
+
module DOSBoard
|
|
20
|
+
attr_reader :device, :ieee_bus
|
|
21
|
+
|
|
22
|
+
def ip_cpu = settled(@ip_cpu)
|
|
23
|
+
|
|
24
|
+
def ip_bus = settled(@ip_bus)
|
|
25
|
+
|
|
26
|
+
def common = settled(@common)
|
|
27
|
+
|
|
28
|
+
def data_riot = settled(@data_riot)
|
|
29
|
+
|
|
30
|
+
def control_riot = settled(@control_riot)
|
|
31
|
+
|
|
32
|
+
def via = settled(@via)
|
|
33
|
+
|
|
34
|
+
def cycles
|
|
35
|
+
settle!
|
|
36
|
+
@cycles
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
attr_writer :host_clock_hz
|
|
40
|
+
|
|
41
|
+
# Plugs the unit into an IEEE-488 bus, leaving the one it was on.
|
|
42
|
+
def connect(ieee_bus)
|
|
43
|
+
settle!
|
|
44
|
+
@ieee_bus.detach(self)
|
|
45
|
+
@ieee_bus = ieee_bus
|
|
46
|
+
@ieee_port.bus = ieee_bus
|
|
47
|
+
ieee_bus.attach(self)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The lines the unit pulls on the bus while ATN is low or not
|
|
51
|
+
# (IEEEPort).
|
|
52
|
+
def ieee_low_lines(atn) = @ieee_port.ieee_low_lines(atn)
|
|
53
|
+
|
|
54
|
+
# The controller pushed its lines into the bus.
|
|
55
|
+
def host_written! = nil
|
|
56
|
+
|
|
57
|
+
# Which LEDs are lit, as the unit's port drives them. They hold
|
|
58
|
+
# still while the unit sleeps, so reading them leaves it asleep.
|
|
59
|
+
def leds = @ieee_port.leds
|
|
60
|
+
|
|
61
|
+
def power_led_on? = true
|
|
62
|
+
|
|
63
|
+
# Runs the drive cycles that fall in one host cycle, then takes the
|
|
64
|
+
# controller's lines for the next. Asleep, the unit owes the cycles
|
|
65
|
+
# instead, and wakes once the controller has moved a line.
|
|
66
|
+
def host_cycle!
|
|
67
|
+
phase = @phase + CLOCK_HZ
|
|
68
|
+
while phase >= @host_clock_hz
|
|
69
|
+
@asleep ? owe_cycle : run_cycle
|
|
70
|
+
phase -= @host_clock_hz
|
|
71
|
+
end
|
|
72
|
+
@phase = phase
|
|
73
|
+
settle! if @asleep && @ieee_port.lines_moved?
|
|
74
|
+
@ieee_port.latch_host
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def cycle!
|
|
78
|
+
settle!
|
|
79
|
+
run_cycle
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
private
|
|
83
|
+
|
|
84
|
+
def settled(part)
|
|
85
|
+
settle!
|
|
86
|
+
part
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -5,10 +5,11 @@ module Badline
|
|
|
5
5
|
class Model8050
|
|
6
6
|
# Sleeping through the two processors' idle loops, exactly.
|
|
7
7
|
#
|
|
8
|
-
# With nothing to do, the IP goes round DOS
|
|
9
|
-
#
|
|
10
|
-
# finding the job queue empty, until
|
|
11
|
-
# 6530's timer the FDC, every 5 ms
|
|
8
|
+
# With nothing to do, the IP goes round its DOS's idle loop from
|
|
9
|
+
# @ip_loop, and the FDC round its job loop from @fdc_loop (IP_LOOP and
|
|
10
|
+
# FDC_LOOP on the 8050), each pass finding the job queue empty, until
|
|
11
|
+
# ATN interrupts the IP or the 6530's timer the FDC, every 5 ms on the
|
|
12
|
+
# 8050. A pass puts back what it found,
|
|
12
13
|
# so each processor comes back to its loop's start as it left it, and
|
|
13
14
|
# only the chips' counters and the cycle counts move.
|
|
14
15
|
#
|
|
@@ -56,7 +57,10 @@ module Badline
|
|
|
56
57
|
|
|
57
58
|
private
|
|
58
59
|
|
|
59
|
-
|
|
60
|
+
# The unit's loops start at +ip_loop+ and +fdc_loop+.
|
|
61
|
+
def init_idle(ip_loop, fdc_loop)
|
|
62
|
+
@ip_loop = ip_loop
|
|
63
|
+
@fdc_loop = fdc_loop
|
|
60
64
|
@idle_skip = true
|
|
61
65
|
@asleep = false
|
|
62
66
|
@owed = 0
|
|
@@ -84,9 +88,9 @@ module Badline
|
|
|
84
88
|
ip = @ip_cpu
|
|
85
89
|
@ip_pass = nil if ip.irq
|
|
86
90
|
@fdc_pass = nil if @fdc_cpu.irq
|
|
87
|
-
ip_pass_found if ip.program_counter ==
|
|
91
|
+
ip_pass_found if ip.program_counter == @ip_loop && ip.boundary?
|
|
88
92
|
fdc = @fdc_cpu
|
|
89
|
-
fdc_pass_found if fdc.program_counter ==
|
|
93
|
+
fdc_pass_found if fdc.program_counter == @fdc_loop && fdc.boundary?
|
|
90
94
|
end
|
|
91
95
|
|
|
92
96
|
def ip_pass_found
|
|
@@ -16,7 +16,7 @@ module Badline
|
|
|
16
16
|
def save_fields(out)
|
|
17
17
|
image = @image
|
|
18
18
|
out.text("path", image.path.to_s).bool("read_only", image.read_only?)
|
|
19
|
-
out.text("image.format",
|
|
19
|
+
out.text("image.format", image_format)
|
|
20
20
|
out.bytes("image", image.contents)
|
|
21
21
|
@tracks.each do |number, cells|
|
|
22
22
|
out.ints("track#{number}", cells) unless cells == build(number)
|
|
@@ -37,6 +37,9 @@ module Badline
|
|
|
37
37
|
|
|
38
38
|
include KeyedState
|
|
39
39
|
|
|
40
|
+
# The kind of image the disk lays out, as image.format names it.
|
|
41
|
+
def image_format = @image.is_a?(Storage::D82Image) ? "d82" : "d80"
|
|
42
|
+
|
|
40
43
|
# The disk a BL-D8-DISK module holds. A detached reader gets the
|
|
41
44
|
# image write-protected, and it and a host file that is gone leave
|
|
42
45
|
# it no path to write back to.
|
|
@@ -141,7 +144,7 @@ module Badline
|
|
|
141
144
|
@rriot.load_fields(modules.required("#{name}-RRIOT"))
|
|
142
145
|
disks = [0, 1].map do |drive|
|
|
143
146
|
disk = modules.optional(disk_module(name, drive))
|
|
144
|
-
disk.nil? ? nil :
|
|
147
|
+
disk.nil? ? nil : disk_from_fields(disk, modules.detached?)
|
|
145
148
|
end
|
|
146
149
|
@mechanism.load_fields(modules.required("#{name}-MECH"), disks)
|
|
147
150
|
@via.restored!
|