badline 0.16.1 → 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 +213 -0
- data/CLAUDE.md +11 -7
- data/CONTRIBUTING.md +7 -1
- data/README.md +265 -26
- data/doc/media.md +5 -2
- data/doc/pinned-behaviour.md +189 -0
- data/doc/snapshot-format.md +616 -74
- data/doc/snapshots.md +192 -24
- 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/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 +95 -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 +264 -0
- data/lib/badline/cbm2/sid_page.rb +19 -0
- data/lib/badline/cbm2.rb +270 -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/cia/timer.rb +2 -2
- data/lib/badline/chips/cpu/instruction_set.rb +1 -1
- data/lib/badline/chips/crtc.rb +3 -2
- 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/fast_forward.rb +72 -0
- data/lib/badline/chips/riot/keyed_parts.rb +69 -0
- data/lib/badline/chips/riot/timer.rb +91 -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 +134 -0
- data/lib/badline/chips/sid/filter.rb +4 -4
- data/lib/badline/chips/sid/waveform.rb +6 -6
- 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/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/datasette/keyed_state.rb +9 -1
- data/lib/badline/drives/datasette.rb +27 -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/disk.rb +163 -0
- data/lib/badline/drives/model8050/dos_board.rb +91 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
- data/lib/badline/drives/model8050/idle.rb +181 -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 +163 -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 +131 -0
- 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 +18 -3
- data/lib/badline/frontend/boot.rb +4 -3
- data/lib/badline/frontend/controls.rb +107 -15
- data/lib/badline/frontend/gamepads.rb +3 -3
- data/lib/badline/frontend/keys.rb +97 -2
- 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 +47 -14
- data/lib/badline/frontend/menu/menu_pages.rb +113 -4
- data/lib/badline/frontend/menu/menu_ports.rb +77 -4
- data/lib/badline/frontend/menu/pause_menu.rb +6 -2
- data/lib/badline/frontend/pet_keyboard.rb +45 -7
- 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 +49 -11
- data/lib/badline/frontend.rb +3 -0
- 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/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 +171 -0
- data/lib/badline/machine/keyboard_buffer.rb +20 -3
- data/lib/badline/machine/line_sound.rb +122 -0
- data/lib/badline/machine/settling.rb +4 -0
- data/lib/badline/machine.rb +62 -14
- data/lib/badline/media/cbm2_media.rb +29 -0
- 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/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +51 -25
- data/lib/badline/media.rb +23 -8
- data/lib/badline/options/family.rb +108 -6
- data/lib/badline/options/help.rb +58 -12
- data/lib/badline/options/table.rb +60 -14
- data/lib/badline/options/validation.rb +71 -11
- data/lib/badline/options.rb +46 -13
- data/lib/badline/pet/bus.rb +16 -2
- 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/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 +33 -6
- data/lib/badline/plus4/acia.rb +4 -298
- data/lib/badline/plus4/keyed_parts.rb +0 -44
- data/lib/badline/plus4/keyed_state.rb +2 -0
- data/lib/badline/plus4.rb +2 -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/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/9060/dos-3.0.rom +0 -0
- data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
- data/lib/badline/roms/README +277 -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/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/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +9 -2
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +59 -3
- data/lib/badline/snapshot/image.rb +65 -9
- 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/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 +20 -10
- data/lib/badline/snapshot/vice/pet_cpu.rb +42 -5
- data/lib/badline/snapshot/vice/pet_crtc.rb +25 -17
- data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +336 -0
- data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
- data/lib/badline/snapshot/vice/pet_restore.rb +100 -7
- 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 +15 -3
- data/lib/badline/status.rb +30 -30
- data/lib/badline/storage/crt_file.rb +7 -1
- data/lib/badline/storage/d64_image.rb +7 -0
- data/lib/badline/storage/d67_image.rb +55 -0
- data/lib/badline/storage/d80_image.rb +17 -1
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/d90_image.rb +180 -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 +37 -2
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage/kim_tape.rb +254 -0
- data/lib/badline/storage/papertape.rb +90 -0
- data/lib/badline/storage.rb +5 -0
- data/lib/badline/support/cbm2_checkpoint.rb +48 -0
- data/lib/badline/support/kim1_checkpoint.rb +41 -0
- data/lib/badline/support/p500_checkpoint.rb +35 -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 +2 -0
- metadata +148 -2
|
@@ -14,7 +14,6 @@ module Badline
|
|
|
14
14
|
# What inserting a tape raises on a machine without a cassette port.
|
|
15
15
|
class Missing < ArgumentError; end
|
|
16
16
|
attr_reader :tape
|
|
17
|
-
attr_writer :motor
|
|
18
17
|
|
|
19
18
|
def initialize(tape = nil, level: false)
|
|
20
19
|
@tape = tape
|
|
@@ -30,6 +29,8 @@ module Badline
|
|
|
30
29
|
@low_recorded = false
|
|
31
30
|
@flag_handler = nil
|
|
32
31
|
@sense_handler = nil
|
|
32
|
+
@running_handler = nil
|
|
33
|
+
@running = false
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
# Falling edges on the tape read line.
|
|
@@ -42,6 +43,17 @@ module Badline
|
|
|
42
43
|
@sense_handler = handler
|
|
43
44
|
end
|
|
44
45
|
|
|
46
|
+
# Whether the tape runs (#running?), each time that changes, a
|
|
47
|
+
# snapshot's load included.
|
|
48
|
+
def on_running_change(&handler)
|
|
49
|
+
@running_handler = handler
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def motor=(on)
|
|
53
|
+
@motor = on
|
|
54
|
+
update_running
|
|
55
|
+
end
|
|
56
|
+
|
|
45
57
|
# Whether the machine has a datasette at its cassette port. The SX-64
|
|
46
58
|
# has none: its keys stay up and it takes no tape.
|
|
47
59
|
def connected? = @connected
|
|
@@ -56,11 +68,13 @@ module Badline
|
|
|
56
68
|
|
|
57
69
|
@tape = tape
|
|
58
70
|
rewind
|
|
71
|
+
update_running
|
|
59
72
|
end
|
|
60
73
|
|
|
61
74
|
def eject
|
|
62
75
|
stop!
|
|
63
76
|
@tape = nil
|
|
77
|
+
update_running
|
|
64
78
|
end
|
|
65
79
|
|
|
66
80
|
def rewind
|
|
@@ -117,7 +131,8 @@ module Badline
|
|
|
117
131
|
|
|
118
132
|
def motor? = @motor
|
|
119
133
|
|
|
120
|
-
|
|
134
|
+
# Whether the tape moves: a key down, the motor on and a tape in.
|
|
135
|
+
def running? = @running
|
|
121
136
|
|
|
122
137
|
# The tape read line's level, on a datasette built with +level: true+.
|
|
123
138
|
# Each pulse holds it low for its first half and high for its second,
|
|
@@ -131,7 +146,7 @@ module Badline
|
|
|
131
146
|
end
|
|
132
147
|
|
|
133
148
|
def cycle!
|
|
134
|
-
return unless running
|
|
149
|
+
return unless @running
|
|
135
150
|
return @since += 1 if @recording
|
|
136
151
|
|
|
137
152
|
next_pulse if @countdown.zero?
|
|
@@ -175,9 +190,18 @@ module Badline
|
|
|
175
190
|
|
|
176
191
|
finish_recording unless down
|
|
177
192
|
@playing = down
|
|
193
|
+
update_running
|
|
178
194
|
@sense_handler&.call
|
|
179
195
|
end
|
|
180
196
|
|
|
197
|
+
def update_running
|
|
198
|
+
running = @playing && @motor && !@tape.nil?
|
|
199
|
+
return if running == @running
|
|
200
|
+
|
|
201
|
+
@running = running
|
|
202
|
+
@running_handler&.call
|
|
203
|
+
end
|
|
204
|
+
|
|
181
205
|
# Releasing the keys ends a recording, and writes the tape out.
|
|
182
206
|
def finish_recording
|
|
183
207
|
return unless @recording
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
# The true drives the PET's and the CBM-II's IEEE-488 bus takes, by the
|
|
6
|
+
# names --drive-type gives them, xpet's drive types, and the disk images
|
|
7
|
+
# each reads. Options reads it to check --drive-type, and
|
|
8
|
+
# Media::TrueDrive to pick the drive an image goes in, so it loads
|
|
9
|
+
# nothing else. Machine::IEEEDrives builds each.
|
|
10
|
+
module IEEEModels
|
|
11
|
+
# The images each model reads, by extension.
|
|
12
|
+
MEDIA = {
|
|
13
|
+
"2031" => %w[.d64 .g64], "8050" => %w[.d80], "8250" => %w[.d82], "1001" => %w[.d82], "4040" => %w[.d64 .d67],
|
|
14
|
+
"3040" => %w[.d64 .d67], "2040" => %w[.d67], "9060" => %w[.d90], "9090" => %w[.d90]
|
|
15
|
+
}.freeze
|
|
16
|
+
|
|
17
|
+
# The model --true-drive plugs in without --drive-type.
|
|
18
|
+
DEFAULT = "2031"
|
|
19
|
+
|
|
20
|
+
# The model an image goes in when neither the drive on device 8 nor
|
|
21
|
+
# the machine's own model reads it. A .d90 goes by its size
|
|
22
|
+
# (hard_disk_model).
|
|
23
|
+
FOR_IMAGES = { ".d64" => "2031", ".g64" => "2031", ".d80" => "8050", ".d82" => "8250", ".d67" => "2040" }.freeze
|
|
24
|
+
|
|
25
|
+
NAMES = MEDIA.keys.freeze
|
|
26
|
+
|
|
27
|
+
module_function
|
|
28
|
+
|
|
29
|
+
# Whether the image at path is a hard disk's, which only a true drive
|
|
30
|
+
# reads.
|
|
31
|
+
def hard_disk?(path) = File.extname(path).casecmp?(".d90")
|
|
32
|
+
|
|
33
|
+
# The hard disk a .d90 image belongs to, by its size: a D9090 for a
|
|
34
|
+
# D9090's, and otherwise a D9060.
|
|
35
|
+
def hard_disk_model(path) = Storage::D90Image.heads_for(File.size?(path).to_i) == 6 ? "9090" : "9060"
|
|
36
|
+
|
|
37
|
+
# Whether the model reads the image at path.
|
|
38
|
+
def reads?(model, path) = MEDIA.fetch(model, []).include?(File.extname(path).downcase)
|
|
39
|
+
|
|
40
|
+
# The model the image at path goes in: the drive's on device 8,
|
|
41
|
+
# `present`, if it reads it, or else the machine's own, `chosen`, or
|
|
42
|
+
# else the image's own, or nil for an image none reads. A .d90 goes
|
|
43
|
+
# in the machine's own model if that is a hard disk, and otherwise in
|
|
44
|
+
# the one its size names.
|
|
45
|
+
def taking(path, present, chosen)
|
|
46
|
+
return reads?(chosen, path) ? chosen : hard_disk_model(path) if hard_disk?(path)
|
|
47
|
+
return present if present && reads?(present, path)
|
|
48
|
+
return chosen if reads?(chosen, path)
|
|
49
|
+
|
|
50
|
+
FOR_IMAGES[File.extname(path).downcase]
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/drives/model8050"
|
|
4
|
+
|
|
5
|
+
module Badline
|
|
6
|
+
module Drives
|
|
7
|
+
# The SFD-1001: the 8250LP's board redrawn as one PCB with one
|
|
8
|
+
# double-sided drive, a Matsushita JU-570-2 (schematic 251406). Both
|
|
9
|
+
# processors see an 8250. The interface processor runs DOS 2.7, which
|
|
10
|
+
# doesn't know there is one drive, and the controller runs 251257-02A
|
|
11
|
+
# from the EPROM on its 6530 adaptor (251252-01): the 8250LP's program
|
|
12
|
+
# with an interrupt every 3 ms for the JU-570's step rate, a longer head
|
|
13
|
+
# settle, and every job for drive 1 ended with an error. Drive 1's
|
|
14
|
+
# stepper, motor and LED aren't wired, so the unit takes no disk there,
|
|
15
|
+
# and its LED never lights.
|
|
16
|
+
class Model1001 < Model8250
|
|
17
|
+
# The LED bit of drive 1, which the board leaves unwired.
|
|
18
|
+
DRIVE1_LED = 0x01
|
|
19
|
+
|
|
20
|
+
def model_name = "1001"
|
|
21
|
+
|
|
22
|
+
# +fdc_rom+ defaults to 251257-02A, the rest as Model8050's.
|
|
23
|
+
def initialize(dos_rom: nil, fdc_rom: nil, host_clock_hz: CLOCK_HZ, device: 8)
|
|
24
|
+
super(dos_rom:, fdc_rom: fdc_rom || ROM.read("1001/fdc-251257-02a.rom"), host_clock_hz:, device:)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def leds = super & ~DRIVE1_LED
|
|
28
|
+
|
|
29
|
+
# Puts a Disk in drive 0, the only one. Drive 1 raises ArgumentError.
|
|
30
|
+
def insert(disk, drive: 0)
|
|
31
|
+
raise ArgumentError, "the 1001 has one drive, not drive #{drive}" unless drive.zero?
|
|
32
|
+
|
|
33
|
+
super
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -34,6 +34,12 @@ module Badline
|
|
|
34
34
|
|
|
35
35
|
def model_name = "2031"
|
|
36
36
|
|
|
37
|
+
def boot_cycles = BOOT_CYCLES
|
|
38
|
+
|
|
39
|
+
# Whether the drive is where VICE saves it: its CPU between
|
|
40
|
+
# instructions.
|
|
41
|
+
def settled_for_vice? = cpu.boundary?
|
|
42
|
+
|
|
37
43
|
# +rom+ covers $C000-$FFFF, and defaults to the DOS image in the ROM
|
|
38
44
|
# path. +device+ is the number the jumpers set, 8 to 11.
|
|
39
45
|
def initialize(rom: nil, host_clock_hz: CLOCK_HZ, device: 8, debug: false)
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model4040
|
|
6
|
+
# A .d64 image as the 4040's controller finds it on the disk, each
|
|
7
|
+
# track a ring of byte cells as on the 8050 (Model8050::Disk), laid
|
|
8
|
+
# out as DOS 2.1's format writes it, sector after sector:
|
|
9
|
+
#
|
|
10
|
+
# 4 sync, $08, checksum, sector, track, ID 2, ID 1,
|
|
11
|
+
# 9 x $00, 4 sync, $07, 256 data bytes, checksum, then $00
|
|
12
|
+
#
|
|
13
|
+
# nine cells being the header gap the interface processor gives the
|
|
14
|
+
# controller ($049D, from DOS 2.1's SECTRK table), past which a write
|
|
15
|
+
# puts its syncs. A .d67, DOS 1's disk, is laid out as DOS 1 writes a
|
|
16
|
+
# sector: 2 sync, the header, one $08, 2 sync, $07, the data and its
|
|
17
|
+
# checksum. DOS 1's format writes headers only, so this is the track as
|
|
18
|
+
# if every sector had been written. The disk's ID is the BAM's, at
|
|
19
|
+
# 18/0 $A2. A turn passes 80,000 / (16 - zone) cells, the 16 MHz bit
|
|
20
|
+
# clock's.
|
|
21
|
+
class Disk < Model8050::Disk
|
|
22
|
+
# How many tracks a disk holds: 35.
|
|
23
|
+
def side_tracks = 35
|
|
24
|
+
|
|
25
|
+
def sides = 1
|
|
26
|
+
|
|
27
|
+
# The cells a track of the zone holds in one turn at 300 rpm: the
|
|
28
|
+
# 200,000 µs of a turn over 2.5 µs times 16 less the zone a byte.
|
|
29
|
+
def self.cells_in(zone) = 80_000 / (16 - zone)
|
|
30
|
+
|
|
31
|
+
# The zone a track is recorded in, 3 outermost to 0 innermost.
|
|
32
|
+
def self.zone_of(track) = Storage::D64Image.zone(track)
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def build(number)
|
|
37
|
+
id1, id2 = disk_id
|
|
38
|
+
cells = []
|
|
39
|
+
sectors = @image.sectors_in(number)
|
|
40
|
+
dos1 = @image.is_a?(Storage::D67Image)
|
|
41
|
+
sectors.times do |sector|
|
|
42
|
+
data = @image.read_block(number, sector)
|
|
43
|
+
header = [0x08, number ^ sector ^ id1 ^ id2, sector, number, id2, id1]
|
|
44
|
+
if dos1
|
|
45
|
+
cells.push(SYNC, SYNC, *header, 0x08, SYNC, SYNC, 0x07)
|
|
46
|
+
else
|
|
47
|
+
cells.push(SYNC, SYNC, SYNC, SYNC, *header, *Array.new(9, 0x00), SYNC, SYNC, SYNC, SYNC, 0x07)
|
|
48
|
+
end
|
|
49
|
+
cells.concat(data)
|
|
50
|
+
cells << checksum(data)
|
|
51
|
+
end
|
|
52
|
+
pad(cells, sectors, self.class.cells_in(self.class.zone_of(number)))
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def disk_id
|
|
56
|
+
bam = @image.read_block(18, 0)
|
|
57
|
+
[bam[0xa2], bam[0xa3]]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model4040
|
|
6
|
+
# The interface processor's bus (schematic 320806, sheet 1). UB3
|
|
7
|
+
# decodes A12-A14 only, so the map repeats every 32 KB:
|
|
8
|
+
#
|
|
9
|
+
# $0000-$0FFF - the two 6532s, as on the 8050 (Model8050::IPBus)
|
|
10
|
+
# $1000-$4FFF - the common RAM, 1 KB in each block, mirrored through it
|
|
11
|
+
# $5000-$7FFF - the ROM sockets UJ1, UL1 and UH1, seen again at
|
|
12
|
+
# $D000-$FFFF, where the processor finds its vectors
|
|
13
|
+
#
|
|
14
|
+
# A socket left empty, as UJ1 is under DOS 1, reads as open bus: the
|
|
15
|
+
# last byte on the data bus.
|
|
16
|
+
class IPBus < Model8050::IPBus
|
|
17
|
+
def peek(addr)
|
|
18
|
+
addr &= 0x7fff
|
|
19
|
+
return @data = rom_peek(addr | 0x8000) if addr >= 0x5000
|
|
20
|
+
|
|
21
|
+
@data = if addr < 0x1000
|
|
22
|
+
peek_riots(addr)
|
|
23
|
+
else
|
|
24
|
+
@common.peek((((addr >> 12) - 1) << 10) | (addr & 0x03ff))
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def poke(addr, value)
|
|
29
|
+
@data = value
|
|
30
|
+
addr &= 0x7fff
|
|
31
|
+
return if addr >= 0x5000
|
|
32
|
+
|
|
33
|
+
if addr < 0x1000
|
|
34
|
+
poke_riots(addr, value)
|
|
35
|
+
else
|
|
36
|
+
poke_common((((addr >> 12) - 1) << 10) | (addr & 0x03ff), value)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def rom_peek(addr) = addr >= @rom.start ? @rom.peek(addr) : @data
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drives
|
|
5
|
+
class Model4040
|
|
6
|
+
class Disk
|
|
7
|
+
def image_format = @image.is_a?(Storage::D67Image) ? "d67" : "d64"
|
|
8
|
+
|
|
9
|
+
# The disk a BL-D8-DISK module holds, as Model8050::Disk's, from a
|
|
10
|
+
# .d64 or a .d67.
|
|
11
|
+
def self.from_fields(input, detached: false)
|
|
12
|
+
path = input.text("path", "")
|
|
13
|
+
read_only = input.bool("read_only", false) || detached
|
|
14
|
+
kind = input.choice("image.format", %w[d64 d67], "d64")
|
|
15
|
+
bytes = input.bytes("image", "".b).bytes
|
|
16
|
+
path = "" if detached || !File.file?(path)
|
|
17
|
+
image = if kind == "d67"
|
|
18
|
+
Storage::D67Image.new(path, read_only:, bytes:)
|
|
19
|
+
else
|
|
20
|
+
Storage::D64Image.new(path, read_only:, bytes:)
|
|
21
|
+
end
|
|
22
|
+
new(image).load_track_fields(input)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
class Mechanism
|
|
27
|
+
# The drives' fields in a keyed snapshot module (BL-D8-MECH), a value
|
|
28
|
+
# a drive in each list: the heads' half tracks, the stepper counts,
|
|
29
|
+
# each disk's place in its turn and its bit clock and the motors,
|
|
30
|
+
# then byte ready, the byte on VIA port A and SYNC. The disks load
|
|
31
|
+
# from their own modules.
|
|
32
|
+
module KeyedState
|
|
33
|
+
def save_fields(out)
|
|
34
|
+
out.ints("half_tracks", @half_tracks).ints("phases", @phases).ints("positions", @positions)
|
|
35
|
+
out.ints("bit_clocks", @clocks)
|
|
36
|
+
out.ints("motors", @motors.map { |on| on ? 1 : 0 }).ints("syncs", @syncs.map { |low| low ? 1 : 0 })
|
|
37
|
+
out.bool("ca1_low", @ca1_low).int("data_in", @data_in)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def load_fields(input, disks)
|
|
41
|
+
@disks = disks
|
|
42
|
+
input.ints_into("half_tracks", @half_tracks)
|
|
43
|
+
input.ints_into("phases", @phases)
|
|
44
|
+
input.ints_into("positions", @positions)
|
|
45
|
+
input.ints_into("bit_clocks", @clocks)
|
|
46
|
+
@motors = input.ints("motors", [0, 0]).map { |on| on == 1 }
|
|
47
|
+
@syncs = input.ints("syncs", [0, 0]).map { |low| low == 1 }
|
|
48
|
+
@ca1_low = input.bool("ca1_low", @ca1_low)
|
|
49
|
+
@data_in = input.byte("data_in", @data_in)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
include KeyedState
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -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
|