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
|
@@ -3,72 +3,16 @@
|
|
|
3
3
|
module Badline
|
|
4
4
|
class Drive1541
|
|
5
5
|
# What VIA 1's ports read from outside the chip. Port B carries the
|
|
6
|
-
# serial bus inputs and the device number jumpers;
|
|
7
|
-
# unused parallel header and floats high.
|
|
8
|
-
#
|
|
9
|
-
# DATA IN (PB0), CLK IN (PB2) and ATN IN (PB7) come through inverters,
|
|
10
|
-
# so a released line reads 0 and a pulled one 1. The jumpers on PB5 and
|
|
11
|
-
# PB6 pull a line low when closed, and both closed make device 8.
|
|
12
|
-
#
|
|
13
|
-
# The drive sees the C64's side of the bus a host cycle late. The C64's
|
|
14
|
-
# CIA changes its pins at the end of the cycle that writes them, and
|
|
15
|
-
# the drive's VIA samples its own late in each of its cycles, so a
|
|
16
|
-
# drive cycle that runs alongside the write reads the lines from
|
|
17
|
-
# before it. The drive runs after the C64 in each host cycle (see
|
|
18
|
-
# Computer#cycle!), so the port reads the C64's lines as latch_host
|
|
19
|
-
# took them at the end of the last one. Fast loaders that time their
|
|
20
|
-
# transfer from an ATN edge, as VICE's drive tests do, depend on it.
|
|
6
|
+
# serial bus inputs and the device number jumpers (Drive::SerialLines);
|
|
7
|
+
# port A goes to the unused parallel header and floats high.
|
|
21
8
|
class SerialPort
|
|
22
|
-
|
|
23
|
-
CLK_IN = 0x04
|
|
24
|
-
ATN_IN = 0x80
|
|
25
|
-
SERIAL_INPUTS = DATA_IN | CLK_IN | ATN_IN
|
|
26
|
-
JUMPERS = 0b0110_0000
|
|
27
|
-
|
|
28
|
-
attr_reader :bus
|
|
9
|
+
include Drive::SerialLines
|
|
29
10
|
|
|
30
11
|
def initialize(device: 8, bus: IECBus.new)
|
|
31
|
-
|
|
32
|
-
@host = 0
|
|
33
|
-
self.bus = bus
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
def bus=(bus)
|
|
37
|
-
@bus = bus
|
|
38
|
-
latch_host
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
# Takes the C64's lines as the machine last pushed them into the bus,
|
|
42
|
-
# for the drive cycles of the next host cycle.
|
|
43
|
-
def latch_host
|
|
44
|
-
@host = @bus.host_lines
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# The C64's lines as last latched. The jumpers and the bus are the
|
|
48
|
-
# drive's wiring.
|
|
49
|
-
def save_state(out)
|
|
50
|
-
out.int(@host)
|
|
12
|
+
init_serial_lines(device, bus)
|
|
51
13
|
end
|
|
52
14
|
|
|
53
|
-
def load_state(input)
|
|
54
|
-
@host = input.int
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
# Whether the C64 has moved ATN since the last latch_host.
|
|
58
|
-
def atn_moved? = (@bus.host_lines ^ @host).anybits?(IECBus::HOST_ATN_OUT)
|
|
59
|
-
|
|
60
|
-
def atn_low? = @bus.atn_low?(@host)
|
|
61
|
-
|
|
62
15
|
def read_a(_lines) = 0xff
|
|
63
|
-
|
|
64
|
-
def read_b(_lines)
|
|
65
|
-
low = @bus.low_lines(@host)
|
|
66
|
-
value = @port_b
|
|
67
|
-
value |= DATA_IN if low.anybits?(IECBus::DATA)
|
|
68
|
-
value |= CLK_IN if low.anybits?(IECBus::CLK)
|
|
69
|
-
value |= ATN_IN if low.anybits?(IECBus::ATN)
|
|
70
|
-
value
|
|
71
|
-
end
|
|
72
16
|
end
|
|
73
17
|
end
|
|
74
18
|
end
|
data/lib/badline/drive1541.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "badline/drive"
|
|
3
4
|
require "badline/drive1541/bus"
|
|
4
5
|
require "badline/drive1541/cpu"
|
|
5
6
|
require "badline/drive1541/serial_port"
|
|
@@ -8,75 +9,20 @@ require "badline/drive1541/track"
|
|
|
8
9
|
require "badline/drive1541/disk"
|
|
9
10
|
require "badline/drive1541/mechanism"
|
|
10
11
|
require "badline/drive1541/disk_via"
|
|
11
|
-
require "badline/drive1541/sleep"
|
|
12
|
-
require "badline/drive1541/idle"
|
|
13
|
-
require "badline/drive1541/orbit"
|
|
14
12
|
|
|
15
13
|
module Badline
|
|
16
14
|
# The 1541 disk drive as a machine of its own: a 6502 running the DOS
|
|
17
|
-
# ROM at 1 MHz, with 2 KB of RAM and two VIAs on its bus.
|
|
18
|
-
# the
|
|
19
|
-
#
|
|
20
|
-
# The drive runs on its own crystal, so it clocks against the host's
|
|
21
|
-
# clock through a fractional accumulator: each host cycle adds 1 MHz
|
|
22
|
-
# worth of phase, and every whole host period in it runs a drive cycle.
|
|
23
|
-
# Against the PAL C64's 985,248 Hz that's one drive cycle per host cycle
|
|
24
|
-
# and a second one about every 67. The host sets its clock on attaching
|
|
25
|
-
# the drive, and until then the drive runs one cycle per host cycle.
|
|
26
|
-
#
|
|
27
|
-
# VIA 2's port B runs the Mechanism, which reads a Disk put in with
|
|
28
|
-
# insert.
|
|
29
|
-
#
|
|
30
|
-
# While the DOS idles, host_cycle! skips the drive's cycles and catches
|
|
31
|
-
# up on them later, exactly (see Idle). The readers of the drive's parts
|
|
32
|
-
# catch up first, so they find the drive as running every cycle would
|
|
33
|
-
# have left it. Once the DOS's timer interrupts come round, the drive
|
|
34
|
-
# sleeps through them too (see Orbit).
|
|
15
|
+
# ROM at 1 MHz, with 2 KB of RAM and two VIAs on its bus. Drive::Core
|
|
16
|
+
# clocks it against the host and sleeps through the DOS's idle loop.
|
|
35
17
|
class Drive1541
|
|
36
|
-
include
|
|
37
|
-
include Idle
|
|
38
|
-
include Orbit
|
|
18
|
+
include Drive::Core
|
|
39
19
|
|
|
40
20
|
CLOCK_HZ = 1_000_000
|
|
41
21
|
|
|
42
|
-
|
|
22
|
+
# Where the DOS 2.6 idle loop starts over (see Drive::Idle).
|
|
23
|
+
IDLE_LOOP = 0xebff
|
|
43
24
|
|
|
44
|
-
def
|
|
45
|
-
settle!
|
|
46
|
-
@cpu
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
def bus
|
|
50
|
-
settle!
|
|
51
|
-
@bus
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
def via1
|
|
55
|
-
settle!
|
|
56
|
-
@via1
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
def via2
|
|
60
|
-
settle!
|
|
61
|
-
@via2
|
|
62
|
-
end
|
|
63
|
-
|
|
64
|
-
def mechanism
|
|
65
|
-
settle!
|
|
66
|
-
@mechanism
|
|
67
|
-
end
|
|
68
|
-
|
|
69
|
-
def cycles
|
|
70
|
-
settle!
|
|
71
|
-
@cycles
|
|
72
|
-
end
|
|
73
|
-
|
|
74
|
-
def ram = bus.ram
|
|
75
|
-
|
|
76
|
-
def host_clock_hz=(clock_hz)
|
|
77
|
-
settle!
|
|
78
|
-
@host_clock_hz = clock_hz
|
|
79
|
-
end
|
|
25
|
+
def model_name = "1541"
|
|
80
26
|
|
|
81
27
|
# +rom+ covers $C000-$FFFF, and defaults to the DOS image in the ROM
|
|
82
28
|
# path. +device+ is the number the jumpers on VIA 1's PB5 and PB6 set,
|
|
@@ -85,14 +31,15 @@ module Badline
|
|
|
85
31
|
@device = device
|
|
86
32
|
@serial_port = SerialPort.new(device:)
|
|
87
33
|
@via1 = VIA.new(start: 0x1800, peripheral: @serial_port)
|
|
88
|
-
@mechanism = Mechanism.new
|
|
34
|
+
@mechanism = Mechanism.new
|
|
89
35
|
@via2 = DiskVIA.new(start: 0x1c00, mechanism: @mechanism)
|
|
90
36
|
@bus = Bus.new(rom: rom || ROM.load("dos1541.rom", 0xc000), via1: @via1, via2: @via2)
|
|
91
37
|
@cpu = CPU.new(@bus, debug:)
|
|
38
|
+
@clock_hz = CLOCK_HZ
|
|
39
|
+
@idle_loop = IDLE_LOOP
|
|
92
40
|
@host_clock_hz = host_clock_hz
|
|
93
41
|
@phase = 0
|
|
94
42
|
@cycles = 0
|
|
95
|
-
@so_pending = false
|
|
96
43
|
@serial_bus = nil
|
|
97
44
|
init_idle(debug)
|
|
98
45
|
# CA1 powers up at the level of a released ATN, without an edge.
|
|
@@ -101,86 +48,6 @@ module Badline
|
|
|
101
48
|
connect(IECBus.new)
|
|
102
49
|
end
|
|
103
50
|
|
|
104
|
-
# Plugs the drive into a serial bus, leaving the one it was on. A drive
|
|
105
|
-
# starts out on a bus of its own, with nothing else on it.
|
|
106
|
-
def connect(serial_bus)
|
|
107
|
-
settle!
|
|
108
|
-
@serial_bus&.detach(self)
|
|
109
|
-
@serial_bus = serial_bus
|
|
110
|
-
@serial_port.bus = serial_bus
|
|
111
|
-
serial_bus.attach(self)
|
|
112
|
-
@via1.ca1 = @serial_port.atn_low?
|
|
113
|
-
end
|
|
114
|
-
|
|
115
|
-
# Puts a Disk in the drive (Disk.from_d64 makes one from an image).
|
|
116
|
-
# Nil takes the disk out.
|
|
117
|
-
def insert(disk)
|
|
118
|
-
settle!
|
|
119
|
-
@mechanism.insert(disk)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def disk = mechanism.disk
|
|
123
|
-
|
|
124
|
-
# Whether the LED is lit. Reading it leaves the drive asleep: the LED
|
|
125
|
-
# is the same at the end of every pass the drive sleeps through.
|
|
126
|
-
def led_on? = @mechanism.led_on?
|
|
127
|
-
|
|
128
|
-
# VIA 1's port B as it drives the serial bus. It holds still while the
|
|
129
|
-
# drive sleeps, so reading it leaves the drive asleep.
|
|
130
|
-
def serial_output = @via1.port_b_output
|
|
131
|
-
|
|
132
|
-
# Stores what the head wrote since the motor last stopped in the
|
|
133
|
-
# disk's image, as the motor stopping does.
|
|
134
|
-
def flush
|
|
135
|
-
settle!
|
|
136
|
-
@mechanism.flush
|
|
137
|
-
end
|
|
138
|
-
|
|
139
|
-
# The serial bus's RESET line reaches the CPU and both VIAs. RAM keeps
|
|
140
|
-
# its contents.
|
|
141
|
-
def reset!
|
|
142
|
-
settle!
|
|
143
|
-
@via1.reset!
|
|
144
|
-
@via2.reset!
|
|
145
|
-
@cpu.reset!
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
# Runs the drive cycles that fall in one host cycle: none, one or two.
|
|
149
|
-
# The host has run this cycle already, and the drive sees what it did
|
|
150
|
-
# from the next one on (see SerialPort). Asleep, the drive owes the
|
|
151
|
-
# cycles instead, until ATN moves or its counters are due (see Idle).
|
|
152
|
-
# It looks at ATN only after the machine pushed its lines into the bus
|
|
153
|
-
# (see host_written!).
|
|
154
|
-
def host_cycle!
|
|
155
|
-
if @asleep
|
|
156
|
-
return if (@slept += 1) != @wake_at && @host_still
|
|
157
|
-
|
|
158
|
-
return doze
|
|
159
|
-
end
|
|
160
|
-
|
|
161
|
-
phase = @phase + CLOCK_HZ
|
|
162
|
-
while phase >= @host_clock_hz
|
|
163
|
-
@asleep ? @owed += 1 : run_cycle
|
|
164
|
-
phase -= @host_clock_hz
|
|
165
|
-
end
|
|
166
|
-
@phase = phase
|
|
167
|
-
settle! if @serial_port.atn_moved?
|
|
168
|
-
@serial_port.latch_host
|
|
169
|
-
plan_wake if @asleep
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
# The machine pushed its lines into the bus, so ATN may have moved.
|
|
173
|
-
def host_written!
|
|
174
|
-
@host_still = false
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# Runs one drive cycle, catching up first on any the drive owes (see
|
|
178
|
-
# step).
|
|
179
|
-
def cycle!
|
|
180
|
-
settle!
|
|
181
|
-
step
|
|
182
|
-
end
|
|
183
|
-
|
|
184
51
|
# The drive's whole state: its clock phase, the CPU, RAM, both VIAs,
|
|
185
52
|
# the serial port's latch of the C64's lines and the mechanism with its
|
|
186
53
|
# disk. The drive settles first, so it is awake and not recording a
|
|
@@ -191,7 +58,7 @@ module Badline
|
|
|
191
58
|
def save_state(out)
|
|
192
59
|
settle!
|
|
193
60
|
out.marker("DRIVE1541")
|
|
194
|
-
out.int(@phase).int(@cycles).boolean(@so_pending)
|
|
61
|
+
out.int(@phase).int(@cycles).boolean(@mechanism.so_pending)
|
|
195
62
|
@serial_port.save_state(out)
|
|
196
63
|
@via1.save_state(out)
|
|
197
64
|
@via2.save_state(out)
|
|
@@ -205,7 +72,7 @@ module Badline
|
|
|
205
72
|
input.marker("DRIVE1541")
|
|
206
73
|
@phase = input.int
|
|
207
74
|
@cycles = input.int
|
|
208
|
-
@so_pending = input.boolean?
|
|
75
|
+
@mechanism.so_pending = input.boolean?
|
|
209
76
|
@serial_port.load_state(input)
|
|
210
77
|
@via1.load_state(input)
|
|
211
78
|
@via2.load_state(input)
|
|
@@ -215,33 +82,8 @@ module Badline
|
|
|
215
82
|
forget_orbits
|
|
216
83
|
end
|
|
217
84
|
|
|
218
|
-
def inspect
|
|
219
|
-
"#<#{self.class.name} cycles=#{cycles} cpu=(#{cpu.inspect})>"
|
|
220
|
-
end
|
|
221
|
-
|
|
222
|
-
# The read electronics signal a whole GCR byte. BYTE READY pulls VIA 2's
|
|
223
|
-
# CA1 low, a falling edge that sets its flag and, with latching on,
|
|
224
|
-
# latches port A. It reaches the CPU's SO pin while VIA 2's CA2 (SOE)
|
|
225
|
-
# is high, which lets the DOS spin on BVC for each byte. The CPU
|
|
226
|
-
# samples SO on the next cycle (see step).
|
|
227
|
-
def byte_ready!
|
|
228
|
-
@via2.ca1 = false
|
|
229
|
-
@so_pending = true if @via2.ca2_output
|
|
230
|
-
end
|
|
231
|
-
|
|
232
|
-
# BYTE READY lets go of CA1 with the next bit.
|
|
233
|
-
def byte_ready_ended!
|
|
234
|
-
@via2.ca1 = true
|
|
235
|
-
end
|
|
236
|
-
|
|
237
85
|
private
|
|
238
86
|
|
|
239
|
-
# A drive cycle from host_cycle!, which may find the idle loop.
|
|
240
|
-
def run_cycle
|
|
241
|
-
step
|
|
242
|
-
idle_loop_reached if @cpu.program_counter == IDLE_LOOP && @idle_skip
|
|
243
|
-
end
|
|
244
|
-
|
|
245
87
|
# One drive cycle. The VIAs clock ahead of the CPU, as the C64's chips
|
|
246
88
|
# do, and either one pulls IRQ. Nothing drives NMI on the 1541. The CPU
|
|
247
89
|
# runs whether or not the motor turns, since it answers ATN.
|
|
@@ -254,8 +96,7 @@ module Badline
|
|
|
254
96
|
# high as the C64 asserts ATN, as the serial port sees it: from the
|
|
255
97
|
# host cycle after the one that asserts it.
|
|
256
98
|
def step
|
|
257
|
-
so = @
|
|
258
|
-
@so_pending = false
|
|
99
|
+
so = @mechanism.take_so
|
|
259
100
|
@mechanism.cycle!
|
|
260
101
|
@via1.ca1 = @serial_port.atn_low?
|
|
261
102
|
@via1.cycle!
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1571
|
|
5
|
+
# The drive CPU's bus:
|
|
6
|
+
#
|
|
7
|
+
# $0000-$07FF - 2 KB of RAM
|
|
8
|
+
# $0800-$17FF - nothing: open bus
|
|
9
|
+
# $1800-$1BFF - VIA 1, its sixteen registers mirrored
|
|
10
|
+
# $1C00-$1FFF - VIA 2, its sixteen registers mirrored
|
|
11
|
+
# $2000-$3FFF - the WD1770, its four registers mirrored
|
|
12
|
+
# $4000-$7FFF - the CIA, its sixteen registers mirrored
|
|
13
|
+
# $8000-$FFFF - the DOS ROM
|
|
14
|
+
#
|
|
15
|
+
# Writes to the ROM and to open bus go nowhere, and a read from open
|
|
16
|
+
# bus finds the last byte on the data bus, as on the 1541. Any access
|
|
17
|
+
# to VIA 2 clears the BYTE READY latch VIA 1's PA7 reads (SerialPort).
|
|
18
|
+
#
|
|
19
|
+
# While Idle records a pass of the DOS's idle loop, the bus watches it,
|
|
20
|
+
# and while Idle looks for an orbit, it guards the drive (see
|
|
21
|
+
# Drive::Watch). Any access to the CIA or the WD1770 makes the pass
|
|
22
|
+
# volatile and taints the drive.
|
|
23
|
+
class Bus
|
|
24
|
+
include Addressable
|
|
25
|
+
include Drive::Watch
|
|
26
|
+
|
|
27
|
+
attr_reader :ram, :rom, :via1, :via2, :cia, :fdc, :data
|
|
28
|
+
|
|
29
|
+
def initialize(rom:, via1:, via2:, cia:, fdc:)
|
|
30
|
+
addressable_at(0, length: 2**16)
|
|
31
|
+
@ram = Memory.new([], length: 0x0800, start: 0)
|
|
32
|
+
@rom = rom
|
|
33
|
+
@via1 = via1
|
|
34
|
+
@via2 = via2
|
|
35
|
+
@cia = cia
|
|
36
|
+
@fdc = fdc
|
|
37
|
+
@mechanism = via2.mechanism
|
|
38
|
+
@data = 0
|
|
39
|
+
init_watch
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# The last byte on the data bus and the RAM. What a watched stretch
|
|
43
|
+
# touched is Idle's, and a restored bus isn't watching.
|
|
44
|
+
def save_state(out)
|
|
45
|
+
out.int(@data)
|
|
46
|
+
@ram.save_state(out)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def load_state(input)
|
|
50
|
+
@data = input.int
|
|
51
|
+
@ram.load_state(input)
|
|
52
|
+
@watching = false
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def peek(addr)
|
|
56
|
+
return @data = @rom.peek(addr) if addr >= 0x8000
|
|
57
|
+
return @data = chip_peek(addr) if addr >= 0x2000
|
|
58
|
+
|
|
59
|
+
watch_read(addr) if @watching
|
|
60
|
+
@data = if addr < 0x0800
|
|
61
|
+
@ram.peek(addr)
|
|
62
|
+
elsif addr < 0x1800
|
|
63
|
+
@data
|
|
64
|
+
elsif addr < 0x1c00
|
|
65
|
+
@via1.peek(addr)
|
|
66
|
+
else
|
|
67
|
+
@mechanism.release_byte_latch
|
|
68
|
+
@via2.peek(addr)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def poke(addr, value)
|
|
73
|
+
@data = value
|
|
74
|
+
return if addr >= 0x8000
|
|
75
|
+
return chip_poke(addr, value) if addr >= 0x2000
|
|
76
|
+
|
|
77
|
+
low_poke(addr, value)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
# A write below $2000: RAM or a VIA.
|
|
83
|
+
def low_poke(addr, value)
|
|
84
|
+
if addr < 0x0800
|
|
85
|
+
touch(addr) if @recording
|
|
86
|
+
@ram.poke(addr, value)
|
|
87
|
+
elsif addr >= 0x1c00
|
|
88
|
+
@mechanism.release_byte_latch
|
|
89
|
+
@via2.poke(addr, value)
|
|
90
|
+
elsif addr >= 0x1800
|
|
91
|
+
@via1.poke(addr, value)
|
|
92
|
+
end
|
|
93
|
+
watch_write(addr) if @watching
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def chip_peek(addr)
|
|
97
|
+
watch_chip if @watching
|
|
98
|
+
addr >= 0x4000 ? @cia.peek(0x4000 | (addr & 0x0f)) : @fdc.peek(addr & 0x03)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def chip_poke(addr, value)
|
|
102
|
+
if addr >= 0x4000
|
|
103
|
+
@cia.poke(0x4000 | (addr & 0x0f), value)
|
|
104
|
+
else
|
|
105
|
+
@fdc.poke(addr & 0x03, value)
|
|
106
|
+
end
|
|
107
|
+
watch_chip if @watching
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1571
|
|
5
|
+
# What VIA 1's ports read from outside the chip. Port B carries the
|
|
6
|
+
# serial bus inputs and the device number jumpers, as on the 1541
|
|
7
|
+
# (Drive::SerialLines). Port A reads the mechanism on two inputs:
|
|
8
|
+
#
|
|
9
|
+
# PA0 track 0 sensor, low while the head sits on track 1
|
|
10
|
+
# PA7 BYTE READY, latched low from a whole byte until the CPU next
|
|
11
|
+
# reads or writes VIA 2
|
|
12
|
+
#
|
|
13
|
+
# The DOS steps the head out until PA0 reads low with the stepper on
|
|
14
|
+
# phase 0, and polls PA7 for each byte at 2 MHz, taking the byte from
|
|
15
|
+
# VIA 2's port A or putting the next one there after each. PA1 (fast serial
|
|
16
|
+
# direction), PA2 (side) and PA5 (1 or 2 MHz) are outputs, which
|
|
17
|
+
# Drive1571 follows (SerialVIA).
|
|
18
|
+
class SerialPort
|
|
19
|
+
include Drive::SerialLines
|
|
20
|
+
|
|
21
|
+
TRACK0 = 0x01
|
|
22
|
+
BYTE_READY = 0x80
|
|
23
|
+
|
|
24
|
+
def initialize(mechanism, device: 8, bus: IECBus.new)
|
|
25
|
+
@mechanism = mechanism
|
|
26
|
+
init_serial_lines(device, bus)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def read_a(_lines)
|
|
30
|
+
value = 0xff
|
|
31
|
+
value &= ~TRACK0 if @mechanism.half_track == Drive1541::Mechanism::MIN_HALF_TRACK
|
|
32
|
+
value &= ~BYTE_READY if @mechanism.byte_latched?
|
|
33
|
+
value
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1571
|
|
5
|
+
# VIA 1, the one facing the serial bus. Its port A also selects the
|
|
6
|
+
# side, the clock rate and the fast serial direction, so the drive
|
|
7
|
+
# hears port A after every write and reset (Drive1571#port_a_written).
|
|
8
|
+
class SerialVIA < VIA
|
|
9
|
+
def initialize(start:, peripheral:, drive:)
|
|
10
|
+
@drive = drive
|
|
11
|
+
super(start:, peripheral:)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def reset!
|
|
15
|
+
super
|
|
16
|
+
@drive.port_a_written(port_a_output)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def poke(addr, value)
|
|
20
|
+
super
|
|
21
|
+
@drive.port_a_written(port_a_output)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class Drive1571
|
|
5
|
+
# The WD1770 floppy controller at $2000, which the DOS needs only for
|
|
6
|
+
# MFM disks. Its four registers hold what's written to them and it
|
|
7
|
+
# runs no commands: the status reads idle, with the motor off.
|
|
8
|
+
#
|
|
9
|
+
# $2000 status (read), command (write)
|
|
10
|
+
# $2001 track
|
|
11
|
+
# $2002 sector
|
|
12
|
+
# $2003 data
|
|
13
|
+
class WD1770
|
|
14
|
+
def initialize
|
|
15
|
+
@registers = [0, 0, 0, 0]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# Register +offset+, 0 to 3.
|
|
19
|
+
def peek(offset) = offset.zero? ? 0 : @registers[offset]
|
|
20
|
+
|
|
21
|
+
def poke(offset, value)
|
|
22
|
+
@registers[offset] = value
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def save_state(out)
|
|
26
|
+
out.ints(@registers)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def load_state(input)
|
|
30
|
+
@registers = input.ints
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|