badline 0.11.0 → 0.11.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 +8 -0
- data/CLAUDE.md +20 -3
- data/CONTRIBUTING.md +8 -3
- data/README.md +6 -2
- data/lib/badline/c128/drives.rb +95 -0
- data/lib/badline/c128/fast_serial.rb +62 -0
- data/lib/badline/c128/mmu.rb +15 -0
- data/lib/badline/c128/saved_state.rb +1 -0
- data/lib/badline/c128.rb +9 -7
- data/lib/badline/cia/fast_forward.rb +42 -0
- data/lib/badline/cia/serial.rb +12 -0
- data/lib/badline/cia/timer.rb +39 -0
- data/lib/badline/cia.rb +2 -0
- data/lib/badline/computer/attachments.rb +6 -0
- data/lib/badline/drive/core.rb +172 -0
- data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
- data/lib/badline/drive/idle_cpu.rb +28 -0
- data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
- data/lib/badline/drive/serial_lines.rb +73 -0
- data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
- data/lib/badline/drive/watch.rb +150 -0
- data/lib/badline/drive.rb +9 -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 +13 -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 +34 -0
- data/lib/badline/drive1571.rb +225 -0
- data/lib/badline/frontend/app.rb +1 -1
- data/lib/badline/frontend/drive_led.rb +1 -1
- data/lib/badline/frontend/menu_media.rb +3 -3
- data/lib/badline/frontend/menu_pages.rb +2 -1
- data/lib/badline/iec_bus.rb +54 -4
- data/lib/badline/machine.rb +7 -5
- data/lib/badline/media/true_drive.rb +26 -23
- data/lib/badline/options/help.rb +2 -1
- data/lib/badline/options/table.rb +1 -1
- data/lib/badline/roms/README +9 -0
- data/lib/badline/roms/dos1571.rom +0 -0
- 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/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 +6 -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 +111 -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/shifts.rb +64 -0
- data/lib/badline/z80.rb +11 -0
- metadata +36 -4
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "badline/drive1571/cpu"
|
|
4
|
+
require "badline/drive1571/wd1770"
|
|
5
|
+
require "badline/drive1571/serial_port"
|
|
6
|
+
require "badline/drive1571/serial_via"
|
|
7
|
+
require "badline/drive1571/bus"
|
|
8
|
+
|
|
9
|
+
module Badline
|
|
10
|
+
# The 1571 disk drive, the C128D's built-in one: the 1541's 6502, two
|
|
11
|
+
# VIAs and GCR mechanism, with a second head, a 6526 CIA for fast
|
|
12
|
+
# serial, a WD1770 for MFM disks and DOS 3.0 in 32 KB of ROM. Drive::Core
|
|
13
|
+
# clocks it against the host and sleeps through the DOS's idle loop.
|
|
14
|
+
#
|
|
15
|
+
# VIA 1's port A picks the clock, 1 or 2 MHz on PA5, and the head on
|
|
16
|
+
# PA2, and reads the track 0 sensor and BYTE READY (SerialPort). The
|
|
17
|
+
# DOS resets at 2 MHz and drops to 1 MHz as a 1541 would run, which is
|
|
18
|
+
# how it stays until a C128 asks for fast serial. The disk turns at the
|
|
19
|
+
# same speed either way (Drive1541::Mechanism#clock_hz=).
|
|
20
|
+
#
|
|
21
|
+
# The CIA sits at $4000, on the IRQ line with both VIAs. Only its serial
|
|
22
|
+
# port is wired: through buffers PA1 turns round, its CNT drives SRQ and
|
|
23
|
+
# its SP DATA, or it hears them. The drive pushes its pins into the bus
|
|
24
|
+
# as they move, and hears the host's as the host pushes them, pin by
|
|
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.
|
|
29
|
+
class Drive1571
|
|
30
|
+
include Drive::Core
|
|
31
|
+
|
|
32
|
+
CLOCK_HZ = 1_000_000
|
|
33
|
+
FAST_CLOCK_HZ = 2_000_000
|
|
34
|
+
|
|
35
|
+
# Where the DOS 3.0 idle loop starts over, as DOS 2.6's does.
|
|
36
|
+
IDLE_LOOP = 0xebff
|
|
37
|
+
|
|
38
|
+
# VIA 1's port A outputs.
|
|
39
|
+
FAST_SERIAL_OUT = 0x02
|
|
40
|
+
SIDE = 0x04
|
|
41
|
+
FAST = 0x20
|
|
42
|
+
|
|
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
|
+
def cia
|
|
48
|
+
settle!
|
|
49
|
+
@cia
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def fdc
|
|
53
|
+
settle!
|
|
54
|
+
@fdc
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def model_name = "1571"
|
|
58
|
+
|
|
59
|
+
# +rom+ covers $8000-$FFFF, and defaults to the DOS image in the ROM
|
|
60
|
+
# path. +device+ is the number the jumpers on VIA 1's PB5 and PB6 set,
|
|
61
|
+
# 8 to 11.
|
|
62
|
+
def initialize(rom: nil, host_clock_hz: CLOCK_HZ, device: 8, debug: false)
|
|
63
|
+
@device = device
|
|
64
|
+
@clock_hz = CLOCK_HZ
|
|
65
|
+
@fast_serial_out = false
|
|
66
|
+
@fast_output = 0
|
|
67
|
+
@mechanism = Drive1541::Mechanism.new(slip: 2)
|
|
68
|
+
@serial_port = SerialPort.new(@mechanism, device:)
|
|
69
|
+
@via1 = SerialVIA.new(start: 0x1800, peripheral: @serial_port, drive: self)
|
|
70
|
+
@via2 = Drive1541::DiskVIA.new(start: 0x1c00, mechanism: @mechanism)
|
|
71
|
+
@cia = CIA.new(start: 0x4000)
|
|
72
|
+
@fdc = WD1770.new
|
|
73
|
+
@bus = Bus.new(rom: rom || ROM.load("dos1571.rom", 0x8000), via1: @via1, via2: @via2, cia: @cia, fdc: @fdc)
|
|
74
|
+
@cpu = CPU.new(@bus, debug:)
|
|
75
|
+
@idle_loop = IDLE_LOOP
|
|
76
|
+
@host_clock_hz = host_clock_hz
|
|
77
|
+
@phase = 0
|
|
78
|
+
@cycles = 0
|
|
79
|
+
@serial_bus = nil
|
|
80
|
+
init_idle(debug)
|
|
81
|
+
# CA1 powers up at the level of a released ATN, without an edge.
|
|
82
|
+
@via1.ca1 = false
|
|
83
|
+
@via1.reset!
|
|
84
|
+
connect(IECBus.new)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# The serial bus's RESET line reaches the CPU, both VIAs and the CIA.
|
|
88
|
+
# RAM keeps its contents.
|
|
89
|
+
def reset!
|
|
90
|
+
settle!
|
|
91
|
+
@via1.reset!
|
|
92
|
+
@via2.reset!
|
|
93
|
+
@cia.reset!
|
|
94
|
+
@cpu.reset!
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# VIA 1 drives port A to +lines+: the side, the clock rate and the fast
|
|
98
|
+
# serial direction follow it.
|
|
99
|
+
def port_a_written(lines)
|
|
100
|
+
@mechanism.side = lines.anybits?(SIDE) ? 1 : 0
|
|
101
|
+
fast_serial_out = lines.anybits?(FAST_SERIAL_OUT)
|
|
102
|
+
turn_fast_serial(fast_serial_out) if fast_serial_out != @fast_serial_out
|
|
103
|
+
clock_hz = lines.anybits?(FAST) ? FAST_CLOCK_HZ : CLOCK_HZ
|
|
104
|
+
return if clock_hz == @clock_hz
|
|
105
|
+
|
|
106
|
+
@clock_hz = clock_hz
|
|
107
|
+
@mechanism.clock_hz = clock_hz
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Whether the drive runs at 2 MHz.
|
|
111
|
+
def fast? = @clock_hz == FAST_CLOCK_HZ
|
|
112
|
+
|
|
113
|
+
# VIA 1's port B, with the fast serial pins pulling DATA and SRQ
|
|
114
|
+
# (IECBus::DRIVE_FAST_DATA and DRIVE_FAST_SRQ) above it.
|
|
115
|
+
def serial_output = @via1.port_b_output | @fast_output
|
|
116
|
+
|
|
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
|
+
# The drive's whole state, as the 1541's (Drive1541#save_state), with
|
|
125
|
+
# the CIA and the WD1770's registers. The clock rate, the side and the
|
|
126
|
+
# fast serial direction follow from VIA 1's port A.
|
|
127
|
+
def save_state(out)
|
|
128
|
+
settle!
|
|
129
|
+
out.marker("DRIVE1571")
|
|
130
|
+
out.int(@phase).int(@cycles).boolean(@mechanism.so_pending)
|
|
131
|
+
@serial_port.save_state(out)
|
|
132
|
+
@via1.save_state(out)
|
|
133
|
+
@via2.save_state(out)
|
|
134
|
+
@cia.save_state(out)
|
|
135
|
+
@fdc.save_state(out)
|
|
136
|
+
@bus.save_state(out)
|
|
137
|
+
@cpu.save_state(out)
|
|
138
|
+
@mechanism.save_state(out)
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def load_state(input)
|
|
142
|
+
settle!
|
|
143
|
+
input.marker("DRIVE1571")
|
|
144
|
+
@phase = input.int
|
|
145
|
+
@cycles = input.int
|
|
146
|
+
@mechanism.so_pending = input.boolean?
|
|
147
|
+
@serial_port.load_state(input)
|
|
148
|
+
@via1.load_state(input)
|
|
149
|
+
@via2.load_state(input)
|
|
150
|
+
@cia.load_state(input)
|
|
151
|
+
@fdc.load_state(input)
|
|
152
|
+
@bus.load_state(input)
|
|
153
|
+
@cpu.load_state(input)
|
|
154
|
+
@mechanism.load_state(input)
|
|
155
|
+
@fast_serial_out = @via1.port_a_output.anybits?(FAST_SERIAL_OUT)
|
|
156
|
+
port_a_written(@via1.port_a_output)
|
|
157
|
+
push_fast_output(@fast_serial_out ? fast_pins : 0)
|
|
158
|
+
forget_orbits
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
private
|
|
162
|
+
|
|
163
|
+
# One drive cycle, as the 1541's (Drive1541#step), with the CIA
|
|
164
|
+
# clocked beside the VIAs and pulling IRQ with them.
|
|
165
|
+
def step
|
|
166
|
+
so = @mechanism.take_so
|
|
167
|
+
@mechanism.cycle!
|
|
168
|
+
@via1.ca1 = @serial_port.atn_low?
|
|
169
|
+
@via1.cycle!
|
|
170
|
+
@via2.cycle!
|
|
171
|
+
@cia.cycle!
|
|
172
|
+
drive_fast_serial if @fast_serial_out
|
|
173
|
+
@cpu.irq = @via1.irq? || @via2.irq? || @cia.interrupted?
|
|
174
|
+
@cpu.so! if so
|
|
175
|
+
@cpu.cycle!
|
|
176
|
+
@cycles += 1
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# The VIAs' quiet cycles while the CIA is quiet, and none otherwise.
|
|
180
|
+
def quiet_cycles
|
|
181
|
+
@cia.quiet? ? [@via1.quiet_cycles, @via2.quiet_cycles].min : 0
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def fast_forward_chips(cycles)
|
|
185
|
+
@cia.fast_forward(cycles)
|
|
186
|
+
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
|
+
end
|
|
225
|
+
end
|
data/lib/badline/frontend/app.rb
CHANGED
|
@@ -28,7 +28,7 @@ module Badline
|
|
|
28
28
|
# The expanded path of the disk in device 8, however it went in, or
|
|
29
29
|
# an empty one.
|
|
30
30
|
def disk_path
|
|
31
|
-
drive = @computer.
|
|
31
|
+
drive = @computer.true_drive
|
|
32
32
|
unless drive.nil?
|
|
33
33
|
disk = drive.disk
|
|
34
34
|
return disk.nil? ? "" : disk.path.to_s
|
|
@@ -39,7 +39,7 @@ module Badline
|
|
|
39
39
|
end
|
|
40
40
|
|
|
41
41
|
def inserted?
|
|
42
|
-
drive = @computer.
|
|
42
|
+
drive = @computer.true_drive
|
|
43
43
|
drive.nil? ? @computer.mounted? : !drive.disk.nil?
|
|
44
44
|
end
|
|
45
45
|
|
|
@@ -73,7 +73,7 @@ module Badline
|
|
|
73
73
|
end
|
|
74
74
|
|
|
75
75
|
def eject_disk
|
|
76
|
-
drive = @computer.
|
|
76
|
+
drive = @computer.true_drive
|
|
77
77
|
drive.nil? ? @computer.unmount : drive.insert(nil)
|
|
78
78
|
end
|
|
79
79
|
|
|
@@ -112,7 +112,8 @@ module Badline
|
|
|
112
112
|
end
|
|
113
113
|
toggle("WRITABLE", :writable, [%w[OFF ON], %i[protect unprotect], media.writable? ? 1 : 0])
|
|
114
114
|
skip
|
|
115
|
-
|
|
115
|
+
drive = @computer.true_drive
|
|
116
|
+
info("DEVICE", drive.nil? ? "FAST LOADING" : "#{drive.model_name}, TRUE DRIVE")
|
|
116
117
|
end
|
|
117
118
|
|
|
118
119
|
def draw_datasette
|
data/lib/badline/iec_bus.rb
CHANGED
|
@@ -16,6 +16,14 @@ module Badline
|
|
|
16
16
|
# also pulls DATA while ATN IN differs from its ATN acknowledge on PB4
|
|
17
17
|
# (an XOR gate), which answers ATN in hardware before the DOS gets to it.
|
|
18
18
|
# SerialPort reads the lines back into VIA 1.
|
|
19
|
+
#
|
|
20
|
+
# Fast serial adds SRQ, which the C64 leaves alone. A C128 and a 1571
|
|
21
|
+
# clock bytes over it from a CIA's CNT, with the bits on DATA from its
|
|
22
|
+
# SP, through buffers each side turns towards the bus to send. A pin low
|
|
23
|
+
# pulls its line low. Each side pushes the lines it pulls when they
|
|
24
|
+
# move: the host through host_fast_lines=, which tells the drives
|
|
25
|
+
# (fast_lines_moved), and a drive through drives_fast_moved!, which
|
|
26
|
+
# tells the host (on_fast_change).
|
|
19
27
|
class IECBus
|
|
20
28
|
# CIA 2 port A
|
|
21
29
|
HOST_ATN_OUT = 0x08
|
|
@@ -29,16 +37,56 @@ module Badline
|
|
|
29
37
|
DRIVE_CLK_OUT = 0x08
|
|
30
38
|
DRIVE_ATNA = 0x10
|
|
31
39
|
|
|
32
|
-
# The lines pulled low, as ATN, CLK and
|
|
40
|
+
# The lines pulled low, as ATN, CLK, DATA and SRQ bits
|
|
33
41
|
ATN = 0x01
|
|
34
42
|
CLK = 0x02
|
|
35
43
|
DATA = 0x04
|
|
44
|
+
SRQ = 0x08
|
|
45
|
+
|
|
46
|
+
# A drive's serial_output bits above VIA 1's port B: its fast serial
|
|
47
|
+
# pins pulling DATA and SRQ.
|
|
48
|
+
DRIVE_FAST_DATA = 0x100
|
|
49
|
+
DRIVE_FAST_SRQ = 0x200
|
|
36
50
|
|
|
37
51
|
attr_reader :drives, :host_lines
|
|
38
52
|
|
|
53
|
+
# The lines the host's fast serial pins pull, as DATA and SRQ bits.
|
|
54
|
+
attr_reader :host_fast_lines
|
|
55
|
+
|
|
39
56
|
def initialize
|
|
40
57
|
@drives = []
|
|
41
58
|
@host_lines = 0
|
|
59
|
+
@host_fast_lines = 0
|
|
60
|
+
@drive_fast_lines = 0
|
|
61
|
+
@on_fast_change = nil
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# The host's fast serial pins moved: +low+ is the lines they pull, as
|
|
65
|
+
# DATA and SRQ bits.
|
|
66
|
+
def host_fast_lines=(low)
|
|
67
|
+
return if low == @host_fast_lines
|
|
68
|
+
|
|
69
|
+
@host_fast_lines = low
|
|
70
|
+
@drives.each(&:fast_lines_moved)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# A drive's fast serial pins moved (serial_output's fast bits).
|
|
74
|
+
def drives_fast_moved!
|
|
75
|
+
low = 0
|
|
76
|
+
@drives.each do |drive|
|
|
77
|
+
output = drive.serial_output
|
|
78
|
+
low |= DATA if output.anybits?(DRIVE_FAST_DATA)
|
|
79
|
+
low |= SRQ if output.anybits?(DRIVE_FAST_SRQ)
|
|
80
|
+
end
|
|
81
|
+
return if low == @drive_fast_lines
|
|
82
|
+
|
|
83
|
+
@drive_fast_lines = low
|
|
84
|
+
@on_fast_change&.call
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Calls the block whenever the drives move their fast serial pins.
|
|
88
|
+
def on_fast_change(&block)
|
|
89
|
+
@on_fast_change = block
|
|
42
90
|
end
|
|
43
91
|
|
|
44
92
|
# The machine pushes the lines it pulls, as port A bits, whenever they
|
|
@@ -58,11 +106,13 @@ module Badline
|
|
|
58
106
|
end
|
|
59
107
|
|
|
60
108
|
# One pass over everything on the bus, for a reader that wants more
|
|
61
|
-
# than one line. +host+ is the C64's port A lines as the
|
|
62
|
-
# them, which a drive takes a cycle late (see
|
|
109
|
+
# than one line, SRQ included. +host+ is the C64's port A lines as the
|
|
110
|
+
# reader sees them, which a drive takes a cycle late (see
|
|
111
|
+
# Drive::SerialLines).
|
|
63
112
|
def low_lines(host = @host_lines)
|
|
64
113
|
atn = host.anybits?(HOST_ATN_OUT)
|
|
65
|
-
low =
|
|
114
|
+
low = @host_fast_lines | @drive_fast_lines
|
|
115
|
+
low |= ATN if atn
|
|
66
116
|
low |= CLK if host.anybits?(HOST_CLK_OUT)
|
|
67
117
|
low |= DATA if host.anybits?(HOST_DATA_OUT)
|
|
68
118
|
i = 0
|
data/lib/badline/machine.rb
CHANGED
|
@@ -24,7 +24,8 @@ module Badline
|
|
|
24
24
|
# - Media: +ram+, +load_prg+, +type_text+, +mount+, +unmount+,
|
|
25
25
|
# +mounted?+, +datasette+, +attach_cartridge+,
|
|
26
26
|
# +press_cartridge_button+, +release_cartridge_button+,
|
|
27
|
-
# +attach_drive1541+, +drive1541+, and +
|
|
27
|
+
# +attach_drive1541+, +drive1541+, +true_drive+ and +plug_true_drive+
|
|
28
|
+
# for the drive Media::TrueDrive plugs in, and +address_bus+ for its
|
|
28
29
|
# +cartridge+ and +detach_cartridge+.
|
|
29
30
|
# - Snapshots and tests: +snapshot+, +restore+, +save_snapshot+,
|
|
30
31
|
# +install_debug_register+ and +capture_output+.
|
|
@@ -33,14 +34,15 @@ module Badline
|
|
|
33
34
|
# input parts, with its one joystick as both +joystick1+ and +joystick2+
|
|
34
35
|
# and nil for +control_ports+, and of the rest +ram+, +load_prg+,
|
|
35
36
|
# +type_text+, +mount+, +unmount+, +mounted?+, +datasette+, +attach_cartridge+,
|
|
36
|
-
# +attach_drive1541+, +drive1541+, +
|
|
37
|
-
# +capture_output+.
|
|
37
|
+
# +attach_drive1541+, +drive1541+, +true_drive+, +plug_true_drive+,
|
|
38
|
+
# +install_debug_register+ and +capture_output+.
|
|
38
39
|
#
|
|
39
40
|
# The C128 answers all but the cartridge button, and besides +mode+,
|
|
40
41
|
# :c64, as Machine.build builds it, or :c128, +vdc+, the VDC a front
|
|
41
42
|
# end can show in place of +video+ once +vdc_shown=+ renders it,
|
|
42
|
-
# +press_caps_lock+, +release_caps_lock+, +press_display_key
|
|
43
|
-
# +release_display_key
|
|
43
|
+
# +press_caps_lock+, +release_caps_lock+, +press_display_key+,
|
|
44
|
+
# +release_display_key+, and +attach_drive1571+ and +drive1571+ for the
|
|
45
|
+
# C128D's 1571, its true drive.
|
|
44
46
|
module Machine
|
|
45
47
|
# A new machine of `family`, :c64, :vic20 or :c128, the model of it
|
|
46
48
|
# `model` names. A C64's model is one of Model::ALL, with its SID
|
|
@@ -2,51 +2,54 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Media
|
|
5
|
-
# Device 8 as a true
|
|
6
|
-
# is plugged in as device 8, disk
|
|
7
|
-
# traps, so a LOAD runs the DOS
|
|
8
|
-
# A .g64 holds the disk's raw
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
#
|
|
5
|
+
# Device 8 as a true drive instead of the KERNAL traps: a 1541, or on
|
|
6
|
+
# the C128 its 1571. Once a true drive is plugged in as device 8, disk
|
|
7
|
+
# media go into it rather than into the traps, so a LOAD runs the DOS
|
|
8
|
+
# and every byte crosses the serial bus. A .g64 holds the disk's raw
|
|
9
|
+
# GCR, which only a true drive reads, so it plugs one in when there's
|
|
10
|
+
# none yet. The 1541 reads only .d64 and .g64 images, and the 1571
|
|
11
|
+
# .d71 and .g71 as well, so the traps' other disk media, a .d81 or
|
|
12
|
+
# .t64 or a host directory, raise Error.
|
|
12
13
|
module TrueDrive
|
|
13
14
|
class Error < ArgumentError; end
|
|
14
15
|
|
|
15
16
|
class << self
|
|
16
|
-
# Plugs
|
|
17
|
-
# returns it.
|
|
17
|
+
# Plugs the machine's true drive in as device 8, unless one is there
|
|
18
|
+
# already, and returns it.
|
|
18
19
|
def plug(computer)
|
|
19
|
-
drive(computer) ||
|
|
20
|
+
drive(computer) || computer.plug_true_drive
|
|
20
21
|
end
|
|
21
22
|
|
|
22
|
-
# The
|
|
23
|
+
# The true drive on device 8, or nil.
|
|
23
24
|
def drive(computer)
|
|
24
|
-
drive = computer.
|
|
25
|
+
drive = computer.true_drive
|
|
25
26
|
drive if drive && drive.device == KernalTrap::Routine::DEVICE
|
|
26
27
|
end
|
|
27
28
|
|
|
28
|
-
# Whether the path goes in a true drive: a .g64 always, and
|
|
29
|
-
# disk media, which the traps would mount, when a true drive
|
|
30
|
-
# device 8.
|
|
29
|
+
# Whether the path goes in a true drive: a .g64 or .g71 always, and
|
|
30
|
+
# other disk media, which the traps would mount, when a true drive
|
|
31
|
+
# is device 8.
|
|
31
32
|
def takes?(computer, path)
|
|
32
|
-
return true if File.extname(path).
|
|
33
|
+
return true if %w[.g64 .g71].include?(File.extname(path).downcase)
|
|
33
34
|
|
|
34
35
|
!drive(computer).nil? && (File.directory?(path) || MOUNT_TYPES.key?(File.extname(path).downcase))
|
|
35
36
|
end
|
|
36
37
|
|
|
37
|
-
# Puts the
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
38
|
+
# Puts the image in the drive on device 8, plugging one in when
|
|
39
|
+
# there's none, and takes out what was mounted through the traps,
|
|
40
|
+
# so LOAD and SAVE reach the drive too. `read_only` puts it in
|
|
41
|
+
# write-protected.
|
|
41
42
|
def insert(computer, path, read_only: false)
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
name = drive(computer)&.model_name || (computer.family == :c128 ? "1571" : "1541")
|
|
44
|
+
types = name == "1571" ? %w[.d64 .g64 .d71 .g71] : %w[.d64 .g64]
|
|
45
|
+
unless types.include?(File.extname(path).downcase)
|
|
46
|
+
raise Error, "#{path} is not a #{types[0..-2].join(', ')} or #{types.last} image, which the #{name} reads"
|
|
44
47
|
end
|
|
45
48
|
|
|
46
49
|
disk = Drive1541::Disk.open(path, read_only:)
|
|
47
50
|
computer.unmount
|
|
48
51
|
plug(computer).insert(disk)
|
|
49
|
-
"Inserted #{path} in the
|
|
52
|
+
"Inserted #{path} in the #{name} as device 8"
|
|
50
53
|
end
|
|
51
54
|
end
|
|
52
55
|
end
|
data/lib/badline/options/help.rb
CHANGED
|
@@ -60,7 +60,8 @@ module Badline
|
|
|
60
60
|
window also autosaves every two minutes of play, which only the
|
|
61
61
|
pause menu loads. Device 8 answers through traps on the KERNAL's
|
|
62
62
|
disk routines, unless --true-drive puts an emulated 1541 there,
|
|
63
|
-
which runs its own DOS and reads .d64 and .g64 images only
|
|
63
|
+
which runs its own DOS and reads .d64 and .g64 images only, or on
|
|
64
|
+
the C128 the C128D's 1571, which reads .d71 and .g71 too. A .sid
|
|
64
65
|
tune opens in the SID player, as `sid` plays it, unless an option
|
|
65
66
|
of the emulator's window asks for the machine.
|
|
66
67
|
|
|
@@ -35,7 +35,7 @@ module Badline
|
|
|
35
35
|
Option.new("--sound", "Play the SID through the host's audio device (F10 mutes)",
|
|
36
36
|
section: "Window options", needs: :window),
|
|
37
37
|
Option.new("--no-sound", "Don't play the SID", section: "Window options", needs: :window, build: :native),
|
|
38
|
-
Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps",
|
|
38
|
+
Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps, a 1571 on the C128",
|
|
39
39
|
section: "Window options", needs: :window),
|
|
40
40
|
Option.new("--reu SIZE", "Plug in an REU of SIZE K: 128, 256, 512 (a 1750) or up to 16384",
|
|
41
41
|
section: "Window options", needs: :window),
|
data/lib/badline/roms/README
CHANGED
|
@@ -61,6 +61,15 @@ https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1541/
|
|
|
61
61
|
(1541-c000.325302-01.bin and 1541-e000.901229-05.bin) and concatenated.
|
|
62
62
|
SHA-1 ab16f56989b27d89babe5f89c5a8cb3da71a82f0.
|
|
63
63
|
|
|
64
|
+
dos1571.rom
|
|
65
|
+
-----------
|
|
66
|
+
The 1571 disk drive's DOS 3.0, 310654-05, 32 KB for $8000-$FFFF.
|
|
67
|
+
Drive1571 loads it.
|
|
68
|
+
|
|
69
|
+
Downloaded from
|
|
70
|
+
https://www.zimmers.net/anonftp/pub/cbm/firmware/drives/new/1571/1571-rom.310654-05.bin
|
|
71
|
+
SHA-1 f1be619c106641a685f6609e4d43d6fc9eac1e70.
|
|
72
|
+
|
|
64
73
|
Commodore VIC-20 ROMs
|
|
65
74
|
=====================
|
|
66
75
|
|
|
Binary file
|
|
@@ -7,6 +7,15 @@ module Badline
|
|
|
7
7
|
|
|
8
8
|
def storage_kind = D71
|
|
9
9
|
|
|
10
|
+
# Tracks 1-35 on the first side and 36-70 on the second.
|
|
11
|
+
def track_count = 70
|
|
12
|
+
|
|
13
|
+
def sides = 2
|
|
14
|
+
|
|
15
|
+
def sectors_in(track)
|
|
16
|
+
track > 35 ? super(track - 35) : super
|
|
17
|
+
end
|
|
18
|
+
|
|
10
19
|
private
|
|
11
20
|
|
|
12
21
|
def error_tables = ERROR_TABLES
|
|
@@ -22,10 +31,6 @@ module Badline
|
|
|
22
31
|
def bam_bitmap(track) = track > 35 ? [53, 0, 3 * (track - 36)] : super
|
|
23
32
|
|
|
24
33
|
def double_sided? = @bytes[sector_offset(18, 0) + 3].anybits?(0x80)
|
|
25
|
-
|
|
26
|
-
def sectors_in(track)
|
|
27
|
-
track > 35 ? super(track - 35) : super
|
|
28
|
-
end
|
|
29
34
|
end
|
|
30
35
|
end
|
|
31
36
|
end
|
|
@@ -5,7 +5,9 @@ module Badline
|
|
|
5
5
|
# A .g64 image: a 1541 disk's flux as raw GCR, track by track, for the
|
|
6
6
|
# true drive to read the way it reads a real disk, copy protection and
|
|
7
7
|
# all. It has no file system of its own, so only the true drive
|
|
8
|
-
# (Drive1541::Disk.from_g64) can use it.
|
|
8
|
+
# (Drive1541::Disk.from_g64) can use it. A .g71 holds a 1571 disk the
|
|
9
|
+
# same way, with "GCR-1571" for its signature and 168 half tracks,
|
|
10
|
+
# the first side's 84 and then the second's.
|
|
9
11
|
#
|
|
10
12
|
# The header is the signature "GCR-1541", a version byte ($00), the
|
|
11
13
|
# number of half tracks the tables cover (84, tracks 1 to 42.5) and
|
|
@@ -36,6 +38,7 @@ module Badline
|
|
|
36
38
|
class FormatError < StandardError; end
|
|
37
39
|
|
|
38
40
|
SIGNATURE = "GCR-1541"
|
|
41
|
+
SIGNATURE_1571 = "GCR-1571"
|
|
39
42
|
HEADER_SIZE = 12
|
|
40
43
|
HALF_TRACKS = 84
|
|
41
44
|
MAX_TRACK_SIZE = 7928
|
|
@@ -124,7 +127,9 @@ module Badline
|
|
|
124
127
|
private
|
|
125
128
|
|
|
126
129
|
def parse
|
|
127
|
-
|
|
130
|
+
unless @data.start_with?(SIGNATURE) || @data.start_with?(SIGNATURE_1571)
|
|
131
|
+
raise FormatError, "Missing G64 signature"
|
|
132
|
+
end
|
|
128
133
|
raise FormatError, "G64 header is cut short" if @data.bytesize < HEADER_SIZE
|
|
129
134
|
|
|
130
135
|
version, @half_tracks, @max_track_size = @data.byteslice(8, 4).unpack("CCv")
|
data/lib/badline/time_of_day.rb
CHANGED
|
@@ -52,6 +52,15 @@ module Badline
|
|
|
52
52
|
@latch = input.boolean? ? FIELDS.to_h { |field| [field, input.int] } : nil
|
|
53
53
|
end
|
|
54
54
|
|
|
55
|
+
# Whether the clock is stopped with no alarm to raise, so its cycles
|
|
56
|
+
# move only the divider's phase.
|
|
57
|
+
def quiet? = @stopped && !@alarm_pending
|
|
58
|
+
|
|
59
|
+
# Runs +cycles+ quiet cycles at once.
|
|
60
|
+
def fast_forward(cycles)
|
|
61
|
+
@accumulator = (@accumulator + (cycles * @mains_hz)) % @cycles_per_pulse
|
|
62
|
+
end
|
|
63
|
+
|
|
55
64
|
def cycle!
|
|
56
65
|
@accumulator += @mains_hz
|
|
57
66
|
pulse! if @accumulator >= @cycles_per_pulse
|
data/lib/badline/version.rb
CHANGED
|
@@ -3,14 +3,14 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
class VIA
|
|
5
5
|
# Running a VIA a stretch of cycles at once while nothing but its
|
|
6
|
-
# counters would change, for
|
|
6
|
+
# counters would change, for Drive::Idle.
|
|
7
7
|
module FastForward
|
|
8
8
|
# quiet_cycles for a VIA that will never set a flag of its own.
|
|
9
9
|
QUIET = 1 << 40
|
|
10
10
|
|
|
11
11
|
# How many cycles the VIA can run with nothing but its counters
|
|
12
12
|
# changing: no flag set, no pulse on a control line and no bit
|
|
13
|
-
# shifted. See
|
|
13
|
+
# shifted. See Drive::Idle.
|
|
14
14
|
def quiet_cycles
|
|
15
15
|
return 0 if @sr_uses_t2 || @shift_register.clocking? || @ca.pulsing? || @cb.pulsing?
|
|
16
16
|
|
|
@@ -26,7 +26,7 @@ module Badline
|
|
|
26
26
|
@t2.fast_forward(cycles)
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
# The counters an orbit (see
|
|
29
|
+
# The counters an orbit (see Drive::Idle) has to bring back: those
|
|
30
30
|
# of the armed timers, and of the timers +touched+ names (bit 0 for
|
|
31
31
|
# timer 1, bit 1 for timer 2). The others only count.
|
|
32
32
|
def counter_state(touched)
|
|
@@ -75,6 +75,12 @@ module Badline
|
|
|
75
75
|
@serial_trap&.device = serial_trap_device
|
|
76
76
|
end
|
|
77
77
|
|
|
78
|
+
# The true drive on the serial bus: the Drive1541, or nil.
|
|
79
|
+
def true_drive = @drive1541
|
|
80
|
+
|
|
81
|
+
# Plugs a Drive1541 in as device 8, and returns it.
|
|
82
|
+
def plug_true_drive = Drive1541.new.tap { |drive| attach_drive1541(drive) }
|
|
83
|
+
|
|
78
84
|
# Unplugs the Drive1541, leaving the serial bus with nothing on it.
|
|
79
85
|
def detach_drive1541
|
|
80
86
|
return unless @drive1541
|