badline 0.11.0 → 0.12.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 +26 -0
- data/CLAUDE.md +24 -6
- data/CONTRIBUTING.md +15 -9
- data/README.md +16 -5
- data/doc/media.md +28 -1
- data/doc/pinned-behaviour.md +31 -1
- data/exe/badline-ruby +1 -1
- data/lib/badline/c128/bus.rb +4 -17
- data/lib/badline/c128/drives.rb +108 -0
- data/lib/badline/c128/fast_serial.rb +63 -0
- data/lib/badline/c128/io_pages.rb +35 -0
- data/lib/badline/c128/mmu.rb +24 -7
- data/lib/badline/c128/modes.rb +17 -4
- data/lib/badline/c128/saved_state.rb +23 -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 +25 -26
- 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 +60 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +10 -5
- data/lib/badline/computer/attachments.rb +4 -50
- data/lib/badline/computer/true_drives.rb +80 -0
- data/lib/badline/computer.rb +4 -0
- data/lib/badline/core.rb +2 -0
- data/lib/badline/drive/core.rb +147 -0
- data/lib/badline/drive/fast_serial.rb +73 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -29
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +3 -5
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -5
- data/lib/badline/drive/vias.rb +84 -0
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +11 -0
- data/lib/badline/drive1541/bus.rb +5 -129
- data/lib/badline/drive1541/byte_ready.rb +48 -0
- data/lib/badline/drive1541/cpu.rb +1 -19
- data/lib/badline/drive1541/disk.rb +77 -20
- data/lib/badline/drive1541/disk_state.rb +7 -2
- data/lib/badline/drive1541/disk_via.rb +3 -0
- data/lib/badline/drive1541/mechanism.rb +48 -14
- data/lib/badline/drive1541/mechanism_state.rb +5 -4
- data/lib/badline/drive1541/rotation.rb +5 -5
- data/lib/badline/drive1541/serial_port.rb +4 -60
- data/lib/badline/drive1541.rb +14 -172
- data/lib/badline/drive1571/bus.rb +111 -0
- data/lib/badline/drive1571/cpu.rb +11 -0
- data/lib/badline/drive1571/serial_port.rb +37 -0
- data/lib/badline/drive1571/serial_via.rb +25 -0
- data/lib/badline/drive1571/wd1770.rb +120 -0
- data/lib/badline/drive1571.rb +175 -0
- 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 +2 -4
- data/lib/badline/frontend/audio_device.rb +67 -0
- data/lib/badline/frontend/audio_sink.rb +16 -50
- data/lib/badline/frontend/boot.rb +18 -10
- data/lib/badline/frontend/buttons.rb +7 -7
- data/lib/badline/frontend/drive_led.rb +1 -1
- 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} +7 -9
- 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 +68 -6
- data/lib/badline/machine.rb +9 -6
- data/lib/badline/media/boot_disk.rb +50 -0
- data/lib/badline/media/not_disk.rb +8 -0
- data/lib/badline/media/true_drive.rb +48 -25
- data/lib/badline/media.rb +9 -3
- data/lib/badline/options/event.rb +2 -2
- data/lib/badline/options/family.rb +3 -9
- data/lib/badline/options/help.rb +3 -1
- data/lib/badline/options/table.rb +1 -1
- data/lib/badline/roms/README +18 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- data/lib/badline/roms/dos1581.rom +0 -0
- data/lib/badline/sdl.rb +37 -20
- data/lib/badline/snapshot/state.rb +5 -4
- data/lib/badline/storage/d64_image.rb +3 -0
- data/lib/badline/storage/d71_image.rb +9 -4
- data/lib/badline/storage/g64_image.rb +7 -2
- data/lib/badline/storage/p00.rb +8 -0
- data/lib/badline/time_of_day.rb +9 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline/via/fast_forward.rb +3 -3
- data/lib/badline/vic20/attachments.rb +23 -3
- data/lib/badline/vic20/saved_state.rb +2 -0
- data/lib/badline/vic20.rb +3 -0
- data/lib/badline/z80/arithmetic.rb +117 -0
- data/lib/badline/z80/bit_page.rb +55 -0
- data/lib/badline/z80/block_io.rb +58 -0
- data/lib/badline/z80/block_transfers.rb +59 -0
- data/lib/badline/z80/bus_cycles.rb +111 -0
- data/lib/badline/z80/core.rb +113 -0
- data/lib/badline/z80/exchanges.rb +50 -0
- data/lib/badline/z80/extended_page.rb +89 -0
- data/lib/badline/z80/flags.rb +37 -0
- data/lib/badline/z80/interrupt_acceptance.rb +46 -0
- data/lib/badline/z80/jumps.rb +82 -0
- data/lib/badline/z80/loads.rb +98 -0
- data/lib/badline/z80/main_page.rb +134 -0
- data/lib/badline/z80/registers.rb +120 -0
- data/lib/badline/z80/saved_state.rb +69 -0
- data/lib/badline/z80/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- metadata +89 -24
- /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
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drive
|
|
5
|
+
# What every Commodore disk drive here shares: a 6502 running the DOS
|
|
6
|
+
# ROM, with chips on its bus facing the serial bus and the disk
|
|
7
|
+
# mechanism, on a clock of its own.
|
|
8
|
+
#
|
|
9
|
+
# The drive runs on its own crystal, so it clocks against the host's
|
|
10
|
+
# clock through a fractional accumulator: each host cycle adds the
|
|
11
|
+
# drive's clock rate worth of phase, and every whole host period in it
|
|
12
|
+
# runs a drive cycle. Against the PAL C64's 985,248 Hz a drive at
|
|
13
|
+
# 1 MHz runs one drive cycle per host cycle and a second one about
|
|
14
|
+
# every 67. The host sets its clock on attaching the drive, and until
|
|
15
|
+
# then a 1 MHz drive runs one cycle per host cycle.
|
|
16
|
+
#
|
|
17
|
+
# The Mechanism reads a Disk put in with insert.
|
|
18
|
+
#
|
|
19
|
+
# While the DOS idles, host_cycle! skips the drive's cycles and catches
|
|
20
|
+
# up on them later, exactly (see Idle). The readers of the drive's parts
|
|
21
|
+
# catch up first, so they find the drive as running every cycle would
|
|
22
|
+
# have left it. Once the DOS's timer interrupts come round, the drive
|
|
23
|
+
# sleeps through them too (see Orbit).
|
|
24
|
+
#
|
|
25
|
+
# A model includes Core and builds its parts, sets @clock_hz and
|
|
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.
|
|
30
|
+
module Core
|
|
31
|
+
include Sleep
|
|
32
|
+
include Idle
|
|
33
|
+
include Orbit
|
|
34
|
+
|
|
35
|
+
attr_reader :device, :serial_bus
|
|
36
|
+
|
|
37
|
+
def cpu
|
|
38
|
+
settle!
|
|
39
|
+
@cpu
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def bus
|
|
43
|
+
settle!
|
|
44
|
+
@bus
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def mechanism
|
|
48
|
+
settle!
|
|
49
|
+
@mechanism
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def cycles
|
|
53
|
+
settle!
|
|
54
|
+
@cycles
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def ram = bus.ram
|
|
58
|
+
|
|
59
|
+
def host_clock_hz=(clock_hz)
|
|
60
|
+
settle!
|
|
61
|
+
@host_clock_hz = clock_hz
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Plugs the drive into a serial bus, leaving the one it was on. A drive
|
|
65
|
+
# starts out on a bus of its own, with nothing else on it.
|
|
66
|
+
def connect(serial_bus)
|
|
67
|
+
settle!
|
|
68
|
+
@serial_bus&.detach(self)
|
|
69
|
+
@serial_bus = serial_bus
|
|
70
|
+
@serial_port.bus = serial_bus
|
|
71
|
+
serial_bus.attach(self)
|
|
72
|
+
hear_atn
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Puts a Disk in the drive (Disk.from_d64 makes one from an image).
|
|
76
|
+
# Nil takes the disk out.
|
|
77
|
+
def insert(disk)
|
|
78
|
+
settle!
|
|
79
|
+
@mechanism.insert(disk)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def disk = mechanism.disk
|
|
83
|
+
|
|
84
|
+
# Whether the LED is lit. Reading it leaves the drive asleep: the LED
|
|
85
|
+
# is the same at the end of every pass the drive sleeps through.
|
|
86
|
+
def led_on? = @mechanism.led_on?
|
|
87
|
+
|
|
88
|
+
# Stores what the head wrote since the motor last stopped in the
|
|
89
|
+
# disk's image, as the motor stopping does.
|
|
90
|
+
def flush
|
|
91
|
+
settle!
|
|
92
|
+
@mechanism.flush
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Runs the drive cycles that fall in one host cycle: none, one or two.
|
|
96
|
+
# The host has run this cycle already, and the drive sees what it did
|
|
97
|
+
# from the next one on (see SerialPort). Asleep, the drive owes the
|
|
98
|
+
# cycles instead, until ATN moves or its counters are due (see Idle).
|
|
99
|
+
# It looks at ATN only after the machine pushed its lines into the bus
|
|
100
|
+
# (see host_written!).
|
|
101
|
+
def host_cycle!
|
|
102
|
+
if @asleep
|
|
103
|
+
return if (@slept += 1) != @wake_at && @host_still
|
|
104
|
+
|
|
105
|
+
return doze
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
phase = @phase + @clock_hz
|
|
109
|
+
while phase >= @host_clock_hz
|
|
110
|
+
@asleep ? @owed += 1 : run_cycle
|
|
111
|
+
phase -= @host_clock_hz
|
|
112
|
+
end
|
|
113
|
+
@phase = phase
|
|
114
|
+
settle! if @serial_port.atn_moved?
|
|
115
|
+
@serial_port.latch_host
|
|
116
|
+
plan_wake if @asleep
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# The machine pushed its lines into the bus, so ATN may have moved.
|
|
120
|
+
def host_written!
|
|
121
|
+
@host_still = false
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
# The host moved its fast serial pins, which a 1541 has none to hear.
|
|
125
|
+
def fast_lines_moved = nil
|
|
126
|
+
|
|
127
|
+
# Runs one drive cycle, catching up first on any the drive owes (see
|
|
128
|
+
# step).
|
|
129
|
+
def cycle!
|
|
130
|
+
settle!
|
|
131
|
+
step
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def inspect
|
|
135
|
+
"#<#{self.class.name} cycles=#{cycles} cpu=(#{cpu.inspect})>"
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
private
|
|
139
|
+
|
|
140
|
+
# A drive cycle from host_cycle!, which may find the idle loop.
|
|
141
|
+
def run_cycle
|
|
142
|
+
step
|
|
143
|
+
idle_loop_reached if @cpu.program_counter == @idle_loop && @idle_skip
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -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
|
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
|
-
|
|
4
|
+
module Drive
|
|
5
5
|
# Skipping the drive's cycles while the DOS idles, exactly.
|
|
6
6
|
#
|
|
7
|
-
# With nothing to do, the DOS goes round its idle loop from
|
|
8
|
-
# 446 cycles a pass, until VIA
|
|
9
|
-
# cycles for the job loop, or
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# counters and the cycle
|
|
7
|
+
# With nothing to do, the DOS goes round its idle loop from the
|
|
8
|
+
# model's @idle_loop ($EBFF in the 1541, 446 cycles a pass), until VIA
|
|
9
|
+
# 2's timer 1 interrupts it every 14,850 cycles for the job loop, or
|
|
10
|
+
# ATN interrupts it through VIA 1's CA1. A pass writes RAM and VIA 2's
|
|
11
|
+
# port B, but puts back what it found, so the drive comes round to the
|
|
12
|
+
# loop's start as it left it. Only the timers' counters and the cycle
|
|
13
|
+
# counts move.
|
|
13
14
|
#
|
|
14
|
-
# Each time the CPU comes to
|
|
15
|
-
# follows. At the next
|
|
15
|
+
# Each time the CPU comes to the loop's start, the drive records the
|
|
16
|
+
# pass that follows. At the next it checks that the pass
|
|
16
17
|
#
|
|
17
18
|
# - left the CPU, both VIAs but for their counters, the bus's last
|
|
18
19
|
# byte, the SO pin, the mechanism (Mechanism#idle_state) and what
|
|
@@ -30,7 +31,7 @@ module Badline
|
|
|
30
31
|
# sleeps: host_cycle! only counts the host cycles that go by (see
|
|
31
32
|
# Sleep). It wakes
|
|
32
33
|
# when ATN moves, when a counter is due to set a flag, or when something
|
|
33
|
-
# outside reads or changes the drive (the readers in
|
|
34
|
+
# outside reads or changes the drive (the readers in Core call
|
|
34
35
|
# settle!). Waking counts off the whole passes it owes in bulk,
|
|
35
36
|
# moving the counters on, and runs the rest cycle by cycle.
|
|
36
37
|
#
|
|
@@ -39,16 +40,13 @@ module Badline
|
|
|
39
40
|
# own lines hold still while it sleeps, so the C64 reads them without
|
|
40
41
|
# waking it (serial_output).
|
|
41
42
|
#
|
|
42
|
-
# A pass starts at
|
|
43
|
-
# last instruction, a JMP, so what that leaves in the CPU's
|
|
44
|
-
# registers is the same each time round.
|
|
43
|
+
# A pass starts at the loop's start, where the CPU always comes from
|
|
44
|
+
# the loop's last instruction, a JMP, so what that leaves in the CPU's
|
|
45
|
+
# working registers is the same each time round.
|
|
45
46
|
#
|
|
46
47
|
# The timer's interrupts come round too, and once they do, the drive
|
|
47
48
|
# sleeps on through them (see Orbit).
|
|
48
49
|
module Idle
|
|
49
|
-
# Where the DOS 2.6 idle loop starts over.
|
|
50
|
-
IDLE_LOOP = 0xebff
|
|
51
|
-
|
|
52
50
|
# Whether the drive may sleep through its idle loop. On by default,
|
|
53
51
|
# but for a drive whose CPU logs each instruction.
|
|
54
52
|
attr_reader :idle_skip
|
|
@@ -69,8 +67,9 @@ module Badline
|
|
|
69
67
|
init_orbits
|
|
70
68
|
end
|
|
71
69
|
|
|
72
|
-
# The CPU is at
|
|
73
|
-
# the pass it recorded can repeat, and otherwise
|
|
70
|
+
# The CPU is at the loop's start. At an instruction boundary, the
|
|
71
|
+
# drive sleeps if the pass it recorded can repeat, and otherwise
|
|
72
|
+
# records the next.
|
|
74
73
|
def idle_loop_reached
|
|
75
74
|
return unless @cpu.boundary?
|
|
76
75
|
|
|
@@ -82,7 +81,7 @@ module Badline
|
|
|
82
81
|
end
|
|
83
82
|
|
|
84
83
|
def start_recording
|
|
85
|
-
return if
|
|
84
|
+
return if busy_for_pass? || @cpu.trapped?
|
|
86
85
|
|
|
87
86
|
@recording = true
|
|
88
87
|
@record_state = idle_state
|
|
@@ -117,20 +116,10 @@ module Badline
|
|
|
117
116
|
anchor
|
|
118
117
|
end
|
|
119
118
|
|
|
120
|
-
def quiet_cycles
|
|
121
|
-
[@via1.quiet_cycles, @via2.quiet_cycles].min
|
|
122
|
-
end
|
|
123
|
-
|
|
124
119
|
def ram_as_found?
|
|
125
120
|
ram = @bus.ram
|
|
126
121
|
@bus.touched.all? { |addr, value| ram.peek(addr) == value }
|
|
127
122
|
end
|
|
128
|
-
|
|
129
|
-
def idle_state
|
|
130
|
-
mechanism = @mechanism
|
|
131
|
-
[*@cpu.idle_state, *@via1.idle_state, *@via2.idle_state, @bus.data, @so_pending,
|
|
132
|
-
*mechanism.idle_state, mechanism.read_a(0xff), mechanism.read_b(0xff)]
|
|
133
|
-
end
|
|
134
123
|
end
|
|
135
124
|
end
|
|
136
125
|
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drive
|
|
5
|
+
# What Idle needs of a drive's 6502, which includes it beside
|
|
6
|
+
# CPU::Core.
|
|
7
|
+
module IdleCPU
|
|
8
|
+
# Everything the CPU holds but its cycle and instruction counts: the
|
|
9
|
+
# registers, the interrupt lines and pipeline, and what the last
|
|
10
|
+
# instruction left in its working registers. Drive::Idle compares
|
|
11
|
+
# it at two instruction boundaries.
|
|
12
|
+
def idle_state
|
|
13
|
+
[@program_counter, @stack_pointer, @a, @x, @y, @status.value,
|
|
14
|
+
@irq, @nmi, @irq_sample, @irq_pending, @nmi_sample, @nmi_pending, @skip_poll,
|
|
15
|
+
@boundary_irq, @boundary_nmi, @so_high, @interrupt, @brk, @pending_write, @stalled_at, @traps,
|
|
16
|
+
@operation, @optional_dummy, @boundary_crossed, @branch_taken,
|
|
17
|
+
@address, @pointer, @dummy_address, @value, @rmw_result]
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Counts +cycles+ cycles and +instructions+ instructions that ran
|
|
21
|
+
# without changing anything idle_state holds.
|
|
22
|
+
def fast_forward(cycles, instructions)
|
|
23
|
+
@cycles += cycles
|
|
24
|
+
@instructions += instructions
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
|
-
|
|
4
|
+
module Drive
|
|
5
5
|
# Sleeping through the DOS's timer interrupts, exactly, once they come
|
|
6
6
|
# round.
|
|
7
7
|
#
|
|
@@ -63,8 +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)
|
|
66
|
+
skip_chip_orbits(cycles, touched)
|
|
68
67
|
@cycles += cycles
|
|
69
68
|
cycles
|
|
70
69
|
end
|
|
@@ -73,8 +72,7 @@ module Badline
|
|
|
73
72
|
# the unarmed timers it hasn't touched since it was last tainted.
|
|
74
73
|
def anchor_state
|
|
75
74
|
touched = @bus.touched_timers
|
|
76
|
-
[*idle_state,
|
|
77
|
-
@bus.ram.snapshot]
|
|
75
|
+
[*idle_state, *counter_state(touched), @bus.ram.snapshot]
|
|
78
76
|
end
|
|
79
77
|
end
|
|
80
78
|
end
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drive
|
|
5
|
+
# What VIA 1's port B reads from the serial bus. DATA IN (PB0), CLK IN
|
|
6
|
+
# (PB2) and ATN IN (PB7) come through inverters, so a released line
|
|
7
|
+
# reads 0 and a pulled one 1. The jumpers on PB5 and PB6 pull a line
|
|
8
|
+
# low when closed, and both closed make device 8.
|
|
9
|
+
#
|
|
10
|
+
# The drive sees the host's side of the bus a host cycle late. The
|
|
11
|
+
# host's CIA changes its pins at the end of the cycle that writes them,
|
|
12
|
+
# and the drive's VIA samples its own late in each of its cycles, so a
|
|
13
|
+
# drive cycle that runs alongside the write reads the lines from
|
|
14
|
+
# before it. The drive runs after the host in each host cycle (see
|
|
15
|
+
# Computer#cycle!), so the port reads the host's lines as latch_host
|
|
16
|
+
# took them at the end of the last one. Fast loaders that time their
|
|
17
|
+
# transfer from an ATN edge, as VICE's drive tests do, depend on it.
|
|
18
|
+
#
|
|
19
|
+
# A model's serial port includes it, with its own port A.
|
|
20
|
+
module SerialLines
|
|
21
|
+
DATA_IN = 0x01
|
|
22
|
+
CLK_IN = 0x04
|
|
23
|
+
ATN_IN = 0x80
|
|
24
|
+
SERIAL_INPUTS = DATA_IN | CLK_IN | ATN_IN
|
|
25
|
+
JUMPERS = 0b0110_0000
|
|
26
|
+
|
|
27
|
+
attr_reader :bus
|
|
28
|
+
|
|
29
|
+
def bus=(bus)
|
|
30
|
+
@bus = bus
|
|
31
|
+
latch_host
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Takes the host's lines as the machine last pushed them into the bus,
|
|
35
|
+
# for the drive cycles of the next host cycle.
|
|
36
|
+
def latch_host
|
|
37
|
+
@host = @bus.host_lines
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# The host's lines as last latched. The jumpers and the bus are the
|
|
41
|
+
# drive's wiring.
|
|
42
|
+
def save_state(out)
|
|
43
|
+
out.int(@host)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def load_state(input)
|
|
47
|
+
@host = input.int
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Whether the host has moved ATN since the last latch_host.
|
|
51
|
+
def atn_moved? = (@bus.host_lines ^ @host).anybits?(IECBus::HOST_ATN_OUT)
|
|
52
|
+
|
|
53
|
+
def atn_low? = @bus.atn_low?(@host)
|
|
54
|
+
|
|
55
|
+
def read_b(_lines)
|
|
56
|
+
low = @bus.low_lines(@host)
|
|
57
|
+
value = @port_b
|
|
58
|
+
value |= DATA_IN if low.anybits?(IECBus::DATA)
|
|
59
|
+
value |= CLK_IN if low.anybits?(IECBus::CLK)
|
|
60
|
+
value |= ATN_IN if low.anybits?(IECBus::ATN)
|
|
61
|
+
value
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
private
|
|
65
|
+
|
|
66
|
+
def init_serial_lines(device, bus)
|
|
67
|
+
@port_b = (0xff & ~(SERIAL_INPUTS | JUMPERS)) | (((device - 8) & 0x03) << 5)
|
|
68
|
+
@host = 0
|
|
69
|
+
self.bus = bus
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
|
-
|
|
4
|
+
module Drive
|
|
5
5
|
# The drive asleep, as Idle and Orbit let it: host_cycle! counts the
|
|
6
6
|
# host cycles that go by, and waking turns them into drive cycles
|
|
7
7
|
# through the phase accumulator and runs them. Whole orbits and whole
|
|
@@ -62,12 +62,12 @@ module Badline
|
|
|
62
62
|
return @wake_at = 0 if @orbit_cycles
|
|
63
63
|
|
|
64
64
|
needed = ((@budget - @owed) * @host_clock_hz) - @phase
|
|
65
|
-
@wake_at = needed.positive? ? (needed +
|
|
65
|
+
@wake_at = needed.positive? ? (needed + @clock_hz - 1) / @clock_hz : 1
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
# Runs the cycles owed.
|
|
69
69
|
def wake!
|
|
70
|
-
phase = @phase + (@slept *
|
|
70
|
+
phase = @phase + (@slept * @clock_hz)
|
|
71
71
|
@phase = phase % @host_clock_hz
|
|
72
72
|
owed = @owed + (phase / @host_clock_hz)
|
|
73
73
|
@owed = 0
|
|
@@ -102,8 +102,7 @@ module Badline
|
|
|
102
102
|
def skip_passes(passes)
|
|
103
103
|
cycles = passes * @pass_cycles
|
|
104
104
|
@cpu.fast_forward(cycles, passes * @pass_instructions)
|
|
105
|
-
|
|
106
|
-
@via2.fast_forward(cycles)
|
|
105
|
+
fast_forward_chips(cycles)
|
|
107
106
|
@cycles += cycles
|
|
108
107
|
@budget -= cycles
|
|
109
108
|
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
|