badline 0.11.1 → 0.12.1
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 +26 -0
- data/CLAUDE.md +8 -7
- data/CONTRIBUTING.md +7 -6
- data/README.md +12 -5
- data/doc/media.md +28 -1
- data/doc/pinned-behaviour.md +31 -1
- data/exe/badline-ruby +1 -1
- data/lib/badline/address_bus/saved_state.rb +4 -10
- data/lib/badline/address_bus.rb +8 -27
- data/lib/badline/audio/bare_player.rb +1 -1
- data/lib/badline/c128/bus.rb +17 -52
- data/lib/badline/c128/bus_state.rb +5 -5
- data/lib/badline/c128/drives.rb +35 -22
- data/lib/badline/c128/fast_serial.rb +1 -0
- data/lib/badline/c128/io_pages.rb +35 -0
- data/lib/badline/c128/mmu.rb +9 -7
- data/lib/badline/c128/modes.rb +17 -4
- data/lib/badline/c128/saved_state.rb +22 -0
- data/lib/badline/c128/z80.rb +49 -0
- data/lib/badline/c128/z80_pages.rb +118 -0
- data/lib/badline/c128/z80_turns.rb +106 -0
- data/lib/badline/c128.rb +21 -83
- data/lib/badline/cartridge/action_replay.rb +2 -2
- data/lib/badline/cartridge/comal80.rb +1 -7
- data/lib/badline/cartridge/dinamic.rb +0 -8
- data/lib/badline/cartridge/final_cartridge3.rb +6 -6
- data/lib/badline/cartridge/fun_play.rb +0 -8
- data/lib/badline/cartridge/game_system.rb +0 -8
- data/lib/badline/cartridge/kcs_power.rb +10 -7
- data/lib/badline/cartridge/mach5.rb +2 -4
- data/lib/badline/cartridge/magic_desk.rb +0 -8
- data/lib/badline/cartridge/ocean.rb +0 -8
- data/lib/badline/cartridge/pagefox.rb +2 -2
- data/lib/badline/cartridge/retro_replay.rb +7 -6
- data/lib/badline/cartridge/rex_utility.rb +2 -6
- data/lib/badline/cartridge/rgcd.rb +2 -2
- data/lib/badline/cartridge/simons_basic.rb +0 -5
- data/lib/badline/cartridge/super_games.rb +4 -2
- data/lib/badline/cartridge/westermann.rb +2 -6
- data/lib/badline/cartridge.rb +37 -9
- data/lib/badline/cia/fast_forward.rb +21 -3
- data/lib/badline/cia.rb +8 -5
- data/lib/badline/computer/attachments.rb +14 -95
- data/lib/badline/computer/common.rb +34 -0
- data/lib/badline/computer/kernal_traps.rb +65 -5
- data/lib/badline/computer/true_drives.rb +69 -0
- data/lib/badline/computer.rb +9 -68
- data/lib/badline/core.rb +4 -2
- data/lib/badline/cpu_port.rb +112 -0
- data/lib/badline/drive/core.rb +8 -33
- data/lib/badline/drive/fast_serial.rb +73 -0
- data/lib/badline/drive/idle.rb +1 -11
- data/lib/badline/drive/orbit.rb +2 -5
- data/lib/badline/drive/sleep.rb +0 -2
- data/lib/badline/drive/vias.rb +84 -0
- data/lib/badline/drive.rb +2 -0
- data/lib/badline/drive1541/disk.rb +14 -17
- data/lib/badline/drive1541/disk_state.rb +7 -15
- data/lib/badline/drive1541.rb +1 -0
- data/lib/badline/drive1571/wd1770.rb +94 -8
- data/lib/badline/drive1571.rb +8 -58
- data/lib/badline/drive1581/bus.rb +137 -0
- data/lib/badline/drive1581/cpu.rb +11 -0
- data/lib/badline/drive1581/disk.rb +135 -0
- data/lib/badline/drive1581/disk_state.rb +87 -0
- data/lib/badline/drive1581/mechanism.rb +117 -0
- data/lib/badline/drive1581/serial_port.rb +52 -0
- data/lib/badline/drive1581/slot.rb +78 -0
- data/lib/badline/drive1581/track.rb +100 -0
- data/lib/badline/drive1581/track_bytes.rb +75 -0
- data/lib/badline/drive1581/wd1772/registers.rb +89 -0
- data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
- data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
- data/lib/badline/drive1581/wd1772/state.rb +66 -0
- data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
- data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
- data/lib/badline/drive1581/wd1772/turn.rb +73 -0
- data/lib/badline/drive1581/wd1772.rb +216 -0
- data/lib/badline/drive1581.rb +233 -0
- data/lib/badline/frontend/app.rb +1 -3
- data/lib/badline/frontend/audio_device.rb +67 -0
- data/lib/badline/frontend/audio_sink.rb +16 -50
- data/lib/badline/frontend/boot.rb +24 -67
- data/lib/badline/frontend/buttons.rb +7 -7
- data/lib/badline/frontend/keys.rb +33 -8
- data/lib/badline/frontend/media_slots.rb +42 -0
- data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
- data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
- data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
- data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
- data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +5 -8
- data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
- data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
- data/lib/badline/frontend/menu/theme.rb +18 -0
- data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
- data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
- data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
- data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
- data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
- data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
- data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
- data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
- data/lib/badline/frontend/player/theme.rb +18 -0
- data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
- data/lib/badline/frontend/sound.rb +19 -38
- data/lib/badline/frontend/timeline.rb +3 -14
- data/lib/badline/frontend.rb +24 -20
- data/lib/badline/iec_bus.rb +14 -2
- data/lib/badline/kernal_trap/{file.rb → file_routine.rb} +1 -1
- data/lib/badline/kernal_trap/load.rb +1 -1
- data/lib/badline/kernal_trap/save.rb +1 -1
- data/lib/badline/kernal_trap.rb +1 -1
- data/lib/badline/machine/clocking.rb +50 -0
- data/lib/badline/machine.rb +63 -6
- data/lib/badline/media/boot_disk.rb +50 -0
- data/lib/badline/media/disk_list.rb +3 -3
- data/lib/badline/media/disk_set.rb +1 -1
- data/lib/badline/media/extensions.rb +33 -0
- data/lib/badline/media/not_disk.rb +8 -0
- data/lib/badline/media/program.rb +28 -0
- data/lib/badline/media/true_drive.rb +38 -17
- data/lib/badline/media/tune.rb +35 -0
- data/lib/badline/media/vic20_cartridge.rb +1 -7
- data/lib/badline/media/vic20_media.rb +38 -80
- data/lib/badline/media.rb +90 -81
- data/lib/badline/options/event.rb +4 -2
- data/lib/badline/options/family.rb +3 -9
- data/lib/badline/options/help.rb +2 -1
- data/lib/badline/roms/README +9 -0
- data/lib/badline/roms/dos1581.rom +0 -0
- data/lib/badline/sdl.rb +37 -20
- data/lib/badline/sid.rb +1 -1
- data/lib/badline/snapshot/machine_state.rb +9 -1
- data/lib/badline/snapshot/state.rb +6 -4
- data/lib/badline/snapshot.rb +11 -9
- data/lib/badline/storage/d64_image.rb +15 -7
- data/lib/badline/storage/disk_image/writing.rb +2 -2
- data/lib/badline/storage/disk_image.rb +1 -1
- data/lib/badline/storage/host_directory.rb +1 -1
- data/lib/badline/storage/p00.rb +8 -0
- data/lib/badline/storage/t64.rb +1 -1
- data/lib/badline/time_of_day.rb +3 -3
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +1 -1
- data/lib/badline/vic/border_mask.rb +1 -1
- data/lib/badline/vic/collisions.rb +1 -1
- data/lib/badline/vic/color_patches.rb +1 -1
- data/lib/badline/vic/display_state.rb +1 -1
- data/lib/badline/vic/graphics_mode.rb +1 -1
- data/lib/badline/vic/graphics_shifter.rb +1 -1
- data/lib/badline/vic/register_log.rb +1 -1
- data/lib/badline/vic/registers.rb +1 -1
- data/lib/badline/vic/sequencer.rb +1 -1
- data/lib/badline/vic/sequencer_output.rb +1 -1
- data/lib/badline/vic/sequencer_state.rb +1 -1
- data/lib/badline/vic/sprite/internal_bus.rb +1 -1
- data/lib/badline/vic/sprite/saved_state.rb +1 -1
- data/lib/badline/vic/sprite/shifter.rb +1 -1
- data/lib/badline/vic/sprite/timing.rb +1 -1
- data/lib/badline/vic/sprite.rb +1 -1
- data/lib/badline/vic/sprites.rb +1 -1
- data/lib/badline/vic/sprites_state.rb +1 -1
- data/lib/badline/vic.rb +6 -5
- data/lib/badline/vic20/attachments.rb +15 -71
- data/lib/badline/vic20/saved_state.rb +2 -30
- data/lib/badline/vic20.rb +4 -37
- data/lib/badline/z80/core.rb +2 -0
- data/lib/badline/z80/saved_state.rb +69 -0
- metadata +61 -23
- data/lib/badline/cycleable.rb +0 -38
- /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
- /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
- /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
- /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
data/lib/badline/drive/core.rb
CHANGED
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Drive
|
|
5
5
|
# What every Commodore disk drive here shares: a 6502 running the DOS
|
|
6
|
-
# ROM with
|
|
7
|
-
#
|
|
6
|
+
# ROM, with chips on its bus facing the serial bus and the disk
|
|
7
|
+
# mechanism, on a clock of its own.
|
|
8
8
|
#
|
|
9
9
|
# The drive runs on its own crystal, so it clocks against the host's
|
|
10
10
|
# clock through a fractional accumulator: each host cycle adds the
|
|
@@ -14,8 +14,7 @@ module Badline
|
|
|
14
14
|
# every 67. The host sets its clock on attaching the drive, and until
|
|
15
15
|
# then a 1 MHz drive runs one cycle per host cycle.
|
|
16
16
|
#
|
|
17
|
-
#
|
|
18
|
-
# insert.
|
|
17
|
+
# The Mechanism reads a Disk put in with insert.
|
|
19
18
|
#
|
|
20
19
|
# While the DOS idles, host_cycle! skips the drive's cycles and catches
|
|
21
20
|
# up on them later, exactly (see Idle). The readers of the drive's parts
|
|
@@ -24,7 +23,10 @@ module Badline
|
|
|
24
23
|
# sleeps through them too (see Orbit).
|
|
25
24
|
#
|
|
26
25
|
# A model includes Core and builds its parts, sets @clock_hz and
|
|
27
|
-
# @idle_loop, and runs one drive cycle in step.
|
|
26
|
+
# @idle_loop, and runs one drive cycle in step. It hears ATN on
|
|
27
|
+
# connecting (hear_atn), drives the serial bus (serial_output), and
|
|
28
|
+
# answers Idle's and Orbit's questions about its chips, as VIAs does
|
|
29
|
+
# for the 1541 and the 1571.
|
|
28
30
|
module Core
|
|
29
31
|
include Sleep
|
|
30
32
|
include Idle
|
|
@@ -42,16 +44,6 @@ module Badline
|
|
|
42
44
|
@bus
|
|
43
45
|
end
|
|
44
46
|
|
|
45
|
-
def via1
|
|
46
|
-
settle!
|
|
47
|
-
@via1
|
|
48
|
-
end
|
|
49
|
-
|
|
50
|
-
def via2
|
|
51
|
-
settle!
|
|
52
|
-
@via2
|
|
53
|
-
end
|
|
54
|
-
|
|
55
47
|
def mechanism
|
|
56
48
|
settle!
|
|
57
49
|
@mechanism
|
|
@@ -77,7 +69,7 @@ module Badline
|
|
|
77
69
|
@serial_bus = serial_bus
|
|
78
70
|
@serial_port.bus = serial_bus
|
|
79
71
|
serial_bus.attach(self)
|
|
80
|
-
|
|
72
|
+
hear_atn
|
|
81
73
|
end
|
|
82
74
|
|
|
83
75
|
# Puts a Disk in the drive (Disk.from_d64 makes one from an image).
|
|
@@ -93,10 +85,6 @@ module Badline
|
|
|
93
85
|
# is the same at the end of every pass the drive sleeps through.
|
|
94
86
|
def led_on? = @mechanism.led_on?
|
|
95
87
|
|
|
96
|
-
# VIA 1's port B as it drives the serial bus. It holds still while the
|
|
97
|
-
# drive sleeps, so reading it leaves the drive asleep.
|
|
98
|
-
def serial_output = @via1.port_b_output
|
|
99
|
-
|
|
100
88
|
# Stores what the head wrote since the motor last stopped in the
|
|
101
89
|
# disk's image, as the motor stopping does.
|
|
102
90
|
def flush
|
|
@@ -104,15 +92,6 @@ module Badline
|
|
|
104
92
|
@mechanism.flush
|
|
105
93
|
end
|
|
106
94
|
|
|
107
|
-
# The serial bus's RESET line reaches the CPU and both VIAs. RAM keeps
|
|
108
|
-
# its contents.
|
|
109
|
-
def reset!
|
|
110
|
-
settle!
|
|
111
|
-
@via1.reset!
|
|
112
|
-
@via2.reset!
|
|
113
|
-
@cpu.reset!
|
|
114
|
-
end
|
|
115
|
-
|
|
116
95
|
# Runs the drive cycles that fall in one host cycle: none, one or two.
|
|
117
96
|
# The host has run this cycle already, and the drive sees what it did
|
|
118
97
|
# from the next one on (see SerialPort). Asleep, the drive owes the
|
|
@@ -158,10 +137,6 @@ module Badline
|
|
|
158
137
|
|
|
159
138
|
private
|
|
160
139
|
|
|
161
|
-
# Runs the model's chips beyond the VIAs +cycles+ quiet cycles at once
|
|
162
|
-
# (see quiet_cycles). The 1541 has none.
|
|
163
|
-
def fast_forward_chips(_cycles) = nil
|
|
164
|
-
|
|
165
140
|
# A drive cycle from host_cycle!, which may find the idle loop.
|
|
166
141
|
def run_cycle
|
|
167
142
|
step
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drive
|
|
5
|
+
# Fast serial on a drive with a CIA, the 1571's 6526 or the 1581's
|
|
6
|
+
# 8520: through buffers a port line turns round, the CIA's CNT drives
|
|
7
|
+
# SRQ and its SP drives DATA, or it hears them. The drive pushes its
|
|
8
|
+
# pins into the bus as they move, and hears the host's as the host
|
|
9
|
+
# pushes them, pin by pin, so each byte goes through both shift
|
|
10
|
+
# registers bit by bit. The CIA takes no part in a pass of the idle
|
|
11
|
+
# loop, and any access to it, or the host moving its fast serial pins,
|
|
12
|
+
# wakes the drive.
|
|
13
|
+
#
|
|
14
|
+
# A model includes it beside Core, and calls turn_fast_serial when its
|
|
15
|
+
# direction line moves and drive_fast_serial each cycle the buffers
|
|
16
|
+
# face out. Its serial_output carries @fast_output above its port.
|
|
17
|
+
module FastSerial
|
|
18
|
+
# Whether the buffers face out, the drive's CIA driving DATA and SRQ.
|
|
19
|
+
attr_reader :fast_serial_out
|
|
20
|
+
|
|
21
|
+
# The host moved its fast serial pins: the CIA hears SRQ on CNT and
|
|
22
|
+
# DATA on SP while the buffers face in.
|
|
23
|
+
def fast_lines_moved
|
|
24
|
+
settle!
|
|
25
|
+
hear_fast_serial unless @fast_serial_out
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def init_fast_serial
|
|
31
|
+
@fast_serial_out = false
|
|
32
|
+
@fast_output = 0
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# The buffers turned round: outwards the CIA's pins drive the lines,
|
|
36
|
+
# and inwards they let go and hear them.
|
|
37
|
+
def turn_fast_serial(out)
|
|
38
|
+
@fast_serial_out = out
|
|
39
|
+
return unless @serial_bus
|
|
40
|
+
|
|
41
|
+
push_fast_output(out ? fast_pins : 0)
|
|
42
|
+
hear_fast_serial unless out
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def drive_fast_serial
|
|
46
|
+
low = fast_pins
|
|
47
|
+
push_fast_output(low) if low != @fast_output
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# The lines the CIA's CNT and SP pull while its serial port drives
|
|
51
|
+
# them.
|
|
52
|
+
def fast_pins
|
|
53
|
+
serial = @cia.serial
|
|
54
|
+
return 0 unless serial.output?
|
|
55
|
+
|
|
56
|
+
low = serial.cnt ? 0 : IECBus::DRIVE_FAST_SRQ
|
|
57
|
+
serial.sp_out ? low : low | IECBus::DRIVE_FAST_DATA
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def push_fast_output(low)
|
|
61
|
+
@fast_output = low
|
|
62
|
+
@serial_bus&.drives_fast_moved!
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def hear_fast_serial
|
|
66
|
+
low = @serial_bus.low_lines
|
|
67
|
+
serial = @cia.serial
|
|
68
|
+
serial.cnt_in = low.nobits?(IECBus::SRQ)
|
|
69
|
+
serial.sp_in = low.nobits?(IECBus::DATA)
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
data/lib/badline/drive/idle.rb
CHANGED
|
@@ -81,7 +81,7 @@ module Badline
|
|
|
81
81
|
end
|
|
82
82
|
|
|
83
83
|
def start_recording
|
|
84
|
-
return if
|
|
84
|
+
return if busy_for_pass? || @cpu.trapped?
|
|
85
85
|
|
|
86
86
|
@recording = true
|
|
87
87
|
@record_state = idle_state
|
|
@@ -116,20 +116,10 @@ module Badline
|
|
|
116
116
|
anchor
|
|
117
117
|
end
|
|
118
118
|
|
|
119
|
-
def quiet_cycles
|
|
120
|
-
[@via1.quiet_cycles, @via2.quiet_cycles].min
|
|
121
|
-
end
|
|
122
|
-
|
|
123
119
|
def ram_as_found?
|
|
124
120
|
ram = @bus.ram
|
|
125
121
|
@bus.touched.all? { |addr, value| ram.peek(addr) == value }
|
|
126
122
|
end
|
|
127
|
-
|
|
128
|
-
def idle_state
|
|
129
|
-
mechanism = @mechanism
|
|
130
|
-
[*@cpu.idle_state, *@via1.idle_state, *@via2.idle_state, @bus.data, mechanism.so_pending,
|
|
131
|
-
*mechanism.idle_state, mechanism.read_a(0xff), mechanism.read_b(0xff)]
|
|
132
|
-
end
|
|
133
123
|
end
|
|
134
124
|
end
|
|
135
125
|
end
|
data/lib/badline/drive/orbit.rb
CHANGED
|
@@ -63,9 +63,7 @@ module Badline
|
|
|
63
63
|
cycles = orbits * @orbit_cycles
|
|
64
64
|
touched = @bus.touched_timers
|
|
65
65
|
@cpu.fast_forward(cycles, orbits * @orbit_instructions)
|
|
66
|
-
|
|
67
|
-
@via2.skip_orbits(cycles, touched >> 2)
|
|
68
|
-
fast_forward_chips(cycles)
|
|
66
|
+
skip_chip_orbits(cycles, touched)
|
|
69
67
|
@cycles += cycles
|
|
70
68
|
cycles
|
|
71
69
|
end
|
|
@@ -74,8 +72,7 @@ module Badline
|
|
|
74
72
|
# the unarmed timers it hasn't touched since it was last tainted.
|
|
75
73
|
def anchor_state
|
|
76
74
|
touched = @bus.touched_timers
|
|
77
|
-
[*idle_state,
|
|
78
|
-
@bus.ram.snapshot]
|
|
75
|
+
[*idle_state, *counter_state(touched), @bus.ram.snapshot]
|
|
79
76
|
end
|
|
80
77
|
end
|
|
81
78
|
end
|
data/lib/badline/drive/sleep.rb
CHANGED
|
@@ -102,8 +102,6 @@ module Badline
|
|
|
102
102
|
def skip_passes(passes)
|
|
103
103
|
cycles = passes * @pass_cycles
|
|
104
104
|
@cpu.fast_forward(cycles, passes * @pass_instructions)
|
|
105
|
-
@via1.fast_forward(cycles)
|
|
106
|
-
@via2.fast_forward(cycles)
|
|
107
105
|
fast_forward_chips(cycles)
|
|
108
106
|
@cycles += cycles
|
|
109
107
|
@budget -= cycles
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drive
|
|
5
|
+
# The two VIAs the 1541 and the 1571 share: VIA 1 facing the serial
|
|
6
|
+
# bus, its CA1 on ATN, and VIA 2 running the GCR Mechanism. A model
|
|
7
|
+
# includes it beside Core, and it answers Core's, Idle's and Orbit's
|
|
8
|
+
# questions about the chips: what they hold, how long their counters
|
|
9
|
+
# leave them quiet, and how to run them a stretch at once. A model with
|
|
10
|
+
# more chips on its bus runs them in fast_forward_more.
|
|
11
|
+
module VIAs
|
|
12
|
+
def via1
|
|
13
|
+
settle!
|
|
14
|
+
@via1
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def via2
|
|
18
|
+
settle!
|
|
19
|
+
@via2
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Puts in a Disk from the .d64, .g64, .d71 or .g71 image at +path+
|
|
23
|
+
# (Drive1541::Disk.open), write-protected with `read_only`.
|
|
24
|
+
def insert_image(path, read_only: false)
|
|
25
|
+
insert(Drive1541::Disk.open(path, read_only:))
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# VIA 1's port B as it drives the serial bus. It holds still while the
|
|
29
|
+
# drive sleeps, so reading it leaves the drive asleep.
|
|
30
|
+
def serial_output = @via1.port_b_output
|
|
31
|
+
|
|
32
|
+
# The serial bus's RESET line reaches the CPU and both VIAs. RAM keeps
|
|
33
|
+
# its contents.
|
|
34
|
+
def reset!
|
|
35
|
+
settle!
|
|
36
|
+
@via1.reset!
|
|
37
|
+
@via2.reset!
|
|
38
|
+
@cpu.reset!
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
# ATN reaches VIA 1's CA1 as the serial port latched it.
|
|
44
|
+
def hear_atn
|
|
45
|
+
@via1.ca1 = @serial_port.atn_low?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Whether the drive is doing anything a pass of the idle loop can't
|
|
49
|
+
# repeat: the motor on, or a VIA pulling IRQ.
|
|
50
|
+
def busy_for_pass? = @mechanism.motor_on? || @via1.irq? || @via2.irq?
|
|
51
|
+
|
|
52
|
+
def quiet_cycles
|
|
53
|
+
[@via1.quiet_cycles, @via2.quiet_cycles].min
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def idle_state
|
|
57
|
+
mechanism = @mechanism
|
|
58
|
+
[*@cpu.idle_state, *@via1.idle_state, *@via2.idle_state, @bus.data, mechanism.so_pending,
|
|
59
|
+
*mechanism.idle_state, mechanism.read_a(0xff), mechanism.read_b(0xff)]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# The counters an orbit has to bring back (VIA#counter_state).
|
|
63
|
+
def counter_state(touched)
|
|
64
|
+
[*@via1.counter_state(touched & 0x03), *@via2.counter_state(touched >> 2)]
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def fast_forward_chips(cycles)
|
|
68
|
+
@via1.fast_forward(cycles)
|
|
69
|
+
@via2.fast_forward(cycles)
|
|
70
|
+
fast_forward_more(cycles)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def skip_chip_orbits(cycles, touched)
|
|
74
|
+
@via1.skip_orbits(cycles, touched & 0x03)
|
|
75
|
+
@via2.skip_orbits(cycles, touched >> 2)
|
|
76
|
+
fast_forward_more(cycles)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Runs the model's chips beyond the VIAs +cycles+ quiet cycles at once
|
|
80
|
+
# (see quiet_cycles). The 1541 has none.
|
|
81
|
+
def fast_forward_more(_cycles) = nil
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
data/lib/badline/drive.rb
CHANGED
|
@@ -4,6 +4,8 @@ require "badline/drive/sleep"
|
|
|
4
4
|
require "badline/drive/idle"
|
|
5
5
|
require "badline/drive/orbit"
|
|
6
6
|
require "badline/drive/core"
|
|
7
|
+
require "badline/drive/vias"
|
|
8
|
+
require "badline/drive/fast_serial"
|
|
7
9
|
require "badline/drive/idle_cpu"
|
|
8
10
|
require "badline/drive/watch"
|
|
9
11
|
require "badline/drive/serial_lines"
|
|
@@ -35,20 +35,10 @@ module Badline
|
|
|
35
35
|
# The half track entries a side of a .g71 holds.
|
|
36
36
|
G71_SIDE_ENTRIES = 84
|
|
37
37
|
|
|
38
|
-
# GCR bytes around a track in each speed zone:
|
|
39
|
-
# 300 rpm, in bytes of 32, 30, 28 and 26 µs.
|
|
38
|
+
# GCR bytes around a track in each speed zone (Storage::D64Image.zone):
|
|
39
|
+
# 200 ms of rotation at 300 rpm, in bytes of 32, 30, 28 and 26 µs.
|
|
40
40
|
TRACK_LENGTHS = [6250, 6666, 7142, 7692].freeze
|
|
41
41
|
|
|
42
|
-
# The speed zone each track is written in: 3 for the 21 sectors of
|
|
43
|
-
# tracks 1-17, 2 for 18-24, 1 for 25-30 and 0 from 31 on.
|
|
44
|
-
def self.zone(track)
|
|
45
|
-
if track <= 17 then 3
|
|
46
|
-
elsif track <= 24 then 2
|
|
47
|
-
elsif track <= 30 then 1
|
|
48
|
-
else 0
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
|
|
52
42
|
# How far round the DOS's N: starts each track from the one before,
|
|
53
43
|
# in each zone, in 1/10000 of a turn: it steps in and starts writing
|
|
54
44
|
# the next track that long after it started the last.
|
|
@@ -69,10 +59,17 @@ module Badline
|
|
|
69
59
|
# a .d71 formatted on both sides, and anything else as a .d64,
|
|
70
60
|
# formatted. `read_only` opens the image write-protected.
|
|
71
61
|
def self.open(path, read_only: false)
|
|
72
|
-
|
|
73
|
-
|
|
62
|
+
image = image_for(path, read_only)
|
|
63
|
+
(State.g64?(path) ? from_g64(image) : from_d64(image)).opened(path, read_only)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The image at +path+, by its name: a .g64 or .g71, a .d71, and
|
|
67
|
+
# anything else a .d64.
|
|
68
|
+
def self.image_for(path, read_only)
|
|
69
|
+
return Storage::G64Image.new(path, read_only:) if State.g64?(path)
|
|
70
|
+
return Storage::D71Image.new(path, read_only:) if File.extname(path).casecmp?(".d71")
|
|
74
71
|
|
|
75
|
-
|
|
72
|
+
Storage::D64Image.new(path, read_only:)
|
|
76
73
|
end
|
|
77
74
|
|
|
78
75
|
# A disk from a G64 or G71 image, each half track as the image stores
|
|
@@ -141,7 +138,7 @@ module Badline
|
|
|
141
138
|
angle = 0
|
|
142
139
|
(1..image.track_count).each do |track|
|
|
143
140
|
half_track = d64_half_track(image, track)
|
|
144
|
-
zone = zone((half_track % SIDE) / 2)
|
|
141
|
+
zone = Storage::D64Image.zone((half_track % SIDE) / 2)
|
|
145
142
|
angle = half_track == SIDE + 2 ? 0 : (angle + SKEWS[zone]) % SKEW_TURN if track > 1
|
|
146
143
|
bytes = format_track(image, track, id, zone)
|
|
147
144
|
disk.write(half_track, Track.new(bytes.rotate(-(bytes.length * angle / SKEW_TURN)), zone))
|
|
@@ -149,7 +146,7 @@ module Badline
|
|
|
149
146
|
disk
|
|
150
147
|
end
|
|
151
148
|
|
|
152
|
-
def self.format_track(image, track, id, zone = zone(track))
|
|
149
|
+
def self.format_track(image, track, id, zone = Storage::D64Image.zone(track))
|
|
153
150
|
sectors = image.sectors_in(track)
|
|
154
151
|
length = TRACK_LENGTHS[zone]
|
|
155
152
|
gap = (length - (sectors * SECTOR_LENGTH)) / sectors
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/media/extensions"
|
|
4
|
+
|
|
3
5
|
module Badline
|
|
4
6
|
class Drive1541
|
|
5
7
|
class Disk
|
|
@@ -12,22 +14,12 @@ module Badline
|
|
|
12
14
|
# for a disk from Disk.open. Nil for a disk made another way.
|
|
13
15
|
attr_reader :path, :read_only
|
|
14
16
|
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
def self.g64?(path) = %w[.g64 .g71].include?(File.extname(path).downcase)
|
|
18
|
-
|
|
19
|
-
def self.d71?(path) = File.extname(path).casecmp?(".d71")
|
|
17
|
+
# Whether the image is a .g64 or .g71, by its name.
|
|
18
|
+
def self.g64?(path) = Media::Extensions.kind(path) == :gcr
|
|
20
19
|
|
|
21
|
-
# A disk for the image at the path, its tracks left
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
Disk.new(Storage::G64Image.new(path, read_only:)).opened(path, read_only)
|
|
25
|
-
elsif d71?(path)
|
|
26
|
-
Disk.new(Storage::D71Image.new(path, read_only:)).opened(path, read_only)
|
|
27
|
-
else
|
|
28
|
-
Disk.new(Storage::D64Image.new(path, read_only:)).opened(path, read_only)
|
|
29
|
-
end
|
|
30
|
-
end
|
|
20
|
+
# A disk for the image at the path (Disk.image_for), its tracks left
|
|
21
|
+
# for the state.
|
|
22
|
+
def self.reopen(path, read_only) = Disk.new(Disk.image_for(path, read_only)).opened(path, read_only)
|
|
31
23
|
|
|
32
24
|
# The disk a state from save_state describes: +current+ when it was
|
|
33
25
|
# opened from the same image the same way, and otherwise a disk for
|
data/lib/badline/drive1541.rb
CHANGED
|
@@ -2,32 +2,118 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Drive1571
|
|
5
|
-
# The WD1770 floppy controller at $2000, which
|
|
6
|
-
#
|
|
7
|
-
#
|
|
5
|
+
# The WD1770 floppy controller at $2000, which DOS 3.0 drives for MFM
|
|
6
|
+
# disks. The 1571 here only ever holds GCR disks, so the controller
|
|
7
|
+
# never finds an MFM sector: it runs each command for its time and ends
|
|
8
|
+
# it as the WD177x data sheet does on a disk without one.
|
|
8
9
|
#
|
|
9
10
|
# $2000 status (read), command (write)
|
|
10
11
|
# $2001 track
|
|
11
12
|
# $2002 sector
|
|
12
13
|
# $2003 data
|
|
14
|
+
#
|
|
15
|
+
# BUSY sets as a command is written. A restore, seek or step (type I)
|
|
16
|
+
# moves the track register and ends a few milliseconds later, with
|
|
17
|
+
# TRACK 0 reported at track 0. A sector read or write, a read address
|
|
18
|
+
# or a read track ends with RECORD NOT FOUND after five turns of the
|
|
19
|
+
# disk, and a write track after one, with nothing written. A force
|
|
20
|
+
# interrupt ends the command at once. Time is the drive CPU's cycles.
|
|
13
21
|
class WD1770
|
|
22
|
+
BUSY = 0x01
|
|
23
|
+
TRACK0 = 0x04
|
|
24
|
+
NOT_FOUND = 0x10
|
|
25
|
+
MOTOR_ON = 0x80
|
|
26
|
+
|
|
27
|
+
# Cycles at 2 MHz, the 1571's clock in 1571 mode: a turn of the disk
|
|
28
|
+
# at 300 rpm, and a step with the head settling.
|
|
29
|
+
TURN = 400_000
|
|
30
|
+
STEP = 12_000
|
|
31
|
+
|
|
32
|
+
# The CPU whose cycles time the commands.
|
|
33
|
+
attr_writer :cpu
|
|
34
|
+
|
|
14
35
|
def initialize
|
|
15
36
|
@registers = [0, 0, 0, 0]
|
|
37
|
+
@status = 0
|
|
38
|
+
@due = 0
|
|
39
|
+
@result = 0
|
|
40
|
+
@cpu = nil
|
|
16
41
|
end
|
|
17
42
|
|
|
18
|
-
# Register +offset+, 0 to 3
|
|
19
|
-
|
|
43
|
+
# Register +offset+, 0 to 3: the status, settled to the command's end
|
|
44
|
+
# once its time has run out.
|
|
45
|
+
def peek(offset)
|
|
46
|
+
return @registers[offset] unless offset.zero?
|
|
47
|
+
|
|
48
|
+
finish if @status.anybits?(BUSY) && @cpu.cycles >= @due
|
|
49
|
+
@status
|
|
50
|
+
end
|
|
20
51
|
|
|
21
52
|
def poke(offset, value)
|
|
22
|
-
@registers[offset] = value
|
|
53
|
+
offset.zero? ? command(value) : @registers[offset] = value
|
|
23
54
|
end
|
|
24
55
|
|
|
25
56
|
def save_state(out)
|
|
26
|
-
out.ints(@
|
|
57
|
+
out.ints([*@registers, @status, @due, @result])
|
|
27
58
|
end
|
|
28
59
|
|
|
60
|
+
# A state from before the controller ran commands holds the four
|
|
61
|
+
# registers alone.
|
|
29
62
|
def load_state(input)
|
|
30
|
-
|
|
63
|
+
values = input.ints
|
|
64
|
+
@registers = values[0, 4]
|
|
65
|
+
@status = values[4] || 0
|
|
66
|
+
@due = values[5] || 0
|
|
67
|
+
@result = values[6] || 0
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
private
|
|
71
|
+
|
|
72
|
+
def command(value)
|
|
73
|
+
return interrupt if (value & 0xf0) == 0xd0
|
|
74
|
+
|
|
75
|
+
@registers[0] = value
|
|
76
|
+
value < 0x80 ? step(value) : search(value)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# A restore ($0x), seek ($1x) or step ($2x to $7x), its track register
|
|
80
|
+
# moved at once.
|
|
81
|
+
def step(value)
|
|
82
|
+
track = @registers[1]
|
|
83
|
+
target = case value >> 4
|
|
84
|
+
when 0 then 0
|
|
85
|
+
when 1 then @registers[3]
|
|
86
|
+
else track + step_direction(value)
|
|
87
|
+
end
|
|
88
|
+
@registers[1] = target.clamp(0, 255) if value < 0x20 || value.anybits?(0x10)
|
|
89
|
+
start(STEP * [(target - track).abs, 1].max, @registers[1].zero? ? TRACK0 : 0)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Step in ($4x) moves toward higher tracks, step out ($6x) toward
|
|
93
|
+
# track 0, and step ($2x) the way the last one went.
|
|
94
|
+
def step_direction(value)
|
|
95
|
+
@direction = 1 if (value >> 5) == 2
|
|
96
|
+
@direction = -1 if (value >> 5) == 3
|
|
97
|
+
@direction || 1
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
# A sector or track command that finds no MFM sector.
|
|
101
|
+
def search(value)
|
|
102
|
+
value >= 0xf0 ? start(TURN, 0) : start(TURN * 5, NOT_FOUND)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def start(cycles, result)
|
|
106
|
+
@status = BUSY | MOTOR_ON
|
|
107
|
+
@due = @cpu.cycles + cycles
|
|
108
|
+
@result = result
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def finish
|
|
112
|
+
@status = @result
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def interrupt
|
|
116
|
+
@status &= ~BUSY & 0xff
|
|
31
117
|
end
|
|
32
118
|
end
|
|
33
119
|
end
|
data/lib/badline/drive1571.rb
CHANGED
|
@@ -19,15 +19,13 @@ module Badline
|
|
|
19
19
|
# same speed either way (Drive1541::Mechanism#clock_hz=).
|
|
20
20
|
#
|
|
21
21
|
# The CIA sits at $4000, on the IRQ line with both VIAs. Only its serial
|
|
22
|
-
# port is wired
|
|
23
|
-
#
|
|
24
|
-
#
|
|
25
|
-
# pin, so each byte goes through both shift registers bit by bit. The
|
|
26
|
-
# CIA takes no part in a pass of the idle loop: the drive sleeps only
|
|
27
|
-
# while the CIA is quiet (CIA#quiet?), and any access to it, or the
|
|
28
|
-
# host moving its fast serial pins, wakes the drive.
|
|
22
|
+
# port is wired, for fast serial through buffers PA1 turns round
|
|
23
|
+
# (Drive::FastSerial). The drive sleeps only while the CIA is quiet
|
|
24
|
+
# (CIA#quiet?).
|
|
29
25
|
class Drive1571
|
|
30
26
|
include Drive::Core
|
|
27
|
+
include Drive::VIAs
|
|
28
|
+
include Drive::FastSerial
|
|
31
29
|
|
|
32
30
|
CLOCK_HZ = 1_000_000
|
|
33
31
|
FAST_CLOCK_HZ = 2_000_000
|
|
@@ -40,10 +38,6 @@ module Badline
|
|
|
40
38
|
SIDE = 0x04
|
|
41
39
|
FAST = 0x20
|
|
42
40
|
|
|
43
|
-
# Whether PA1 turns the fast serial buffers outwards, the drive's CIA
|
|
44
|
-
# driving DATA and SRQ.
|
|
45
|
-
attr_reader :fast_serial_out
|
|
46
|
-
|
|
47
41
|
def cia
|
|
48
42
|
settle!
|
|
49
43
|
@cia
|
|
@@ -62,8 +56,7 @@ module Badline
|
|
|
62
56
|
def initialize(rom: nil, host_clock_hz: CLOCK_HZ, device: 8, debug: false)
|
|
63
57
|
@device = device
|
|
64
58
|
@clock_hz = CLOCK_HZ
|
|
65
|
-
|
|
66
|
-
@fast_output = 0
|
|
59
|
+
init_fast_serial
|
|
67
60
|
@mechanism = Drive1541::Mechanism.new(slip: 2)
|
|
68
61
|
@serial_port = SerialPort.new(@mechanism, device:)
|
|
69
62
|
@via1 = SerialVIA.new(start: 0x1800, peripheral: @serial_port, drive: self)
|
|
@@ -72,6 +65,7 @@ module Badline
|
|
|
72
65
|
@fdc = WD1770.new
|
|
73
66
|
@bus = Bus.new(rom: rom || ROM.load("dos1571.rom", 0x8000), via1: @via1, via2: @via2, cia: @cia, fdc: @fdc)
|
|
74
67
|
@cpu = CPU.new(@bus, debug:)
|
|
68
|
+
@fdc.cpu = @cpu
|
|
75
69
|
@idle_loop = IDLE_LOOP
|
|
76
70
|
@host_clock_hz = host_clock_hz
|
|
77
71
|
@phase = 0
|
|
@@ -114,13 +108,6 @@ module Badline
|
|
|
114
108
|
# (IECBus::DRIVE_FAST_DATA and DRIVE_FAST_SRQ) above it.
|
|
115
109
|
def serial_output = @via1.port_b_output | @fast_output
|
|
116
110
|
|
|
117
|
-
# The host moved its fast serial pins: the CIA hears SRQ on CNT and
|
|
118
|
-
# DATA on SP while PA1 turns the buffers inwards.
|
|
119
|
-
def fast_lines_moved
|
|
120
|
-
settle!
|
|
121
|
-
hear_fast_serial unless @fast_serial_out
|
|
122
|
-
end
|
|
123
|
-
|
|
124
111
|
# The drive's whole state, as the 1541's (Drive1541#save_state), with
|
|
125
112
|
# the CIA and the WD1770's registers. The clock rate, the side and the
|
|
126
113
|
# fast serial direction follow from VIA 1's port A.
|
|
@@ -181,45 +168,8 @@ module Badline
|
|
|
181
168
|
@cia.quiet? ? [@via1.quiet_cycles, @via2.quiet_cycles].min : 0
|
|
182
169
|
end
|
|
183
170
|
|
|
184
|
-
def
|
|
171
|
+
def fast_forward_more(cycles)
|
|
185
172
|
@cia.fast_forward(cycles)
|
|
186
173
|
end
|
|
187
|
-
|
|
188
|
-
# PA1 turned the buffers round: outwards the CIA's pins drive the
|
|
189
|
-
# lines, and inwards they let go and hear them.
|
|
190
|
-
def turn_fast_serial(out)
|
|
191
|
-
@fast_serial_out = out
|
|
192
|
-
return unless @serial_bus
|
|
193
|
-
|
|
194
|
-
push_fast_output(out ? fast_pins : 0)
|
|
195
|
-
hear_fast_serial unless out
|
|
196
|
-
end
|
|
197
|
-
|
|
198
|
-
def drive_fast_serial
|
|
199
|
-
low = fast_pins
|
|
200
|
-
push_fast_output(low) if low != @fast_output
|
|
201
|
-
end
|
|
202
|
-
|
|
203
|
-
# The lines the CIA's CNT and SP pull while its serial port drives
|
|
204
|
-
# them.
|
|
205
|
-
def fast_pins
|
|
206
|
-
serial = @cia.serial
|
|
207
|
-
return 0 unless serial.output?
|
|
208
|
-
|
|
209
|
-
low = serial.cnt ? 0 : IECBus::DRIVE_FAST_SRQ
|
|
210
|
-
serial.sp_out ? low : low | IECBus::DRIVE_FAST_DATA
|
|
211
|
-
end
|
|
212
|
-
|
|
213
|
-
def push_fast_output(low)
|
|
214
|
-
@fast_output = low
|
|
215
|
-
@serial_bus&.drives_fast_moved!
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
def hear_fast_serial
|
|
219
|
-
low = @serial_bus.low_lines
|
|
220
|
-
serial = @cia.serial
|
|
221
|
-
serial.cnt_in = low.nobits?(IECBus::SRQ)
|
|
222
|
-
serial.sp_in = low.nobits?(IECBus::DATA)
|
|
223
|
-
end
|
|
224
174
|
end
|
|
225
175
|
end
|