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,172 @@
|
|
|
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 two VIAs on its bus, VIA 1 facing the serial bus and VIA 2
|
|
7
|
+
# the disk 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
|
+
# VIA 2's port B runs the Mechanism, which reads a Disk put in with
|
|
18
|
+
# insert.
|
|
19
|
+
#
|
|
20
|
+
# While the DOS idles, host_cycle! skips the drive's cycles and catches
|
|
21
|
+
# up on them later, exactly (see Idle). The readers of the drive's parts
|
|
22
|
+
# catch up first, so they find the drive as running every cycle would
|
|
23
|
+
# have left it. Once the DOS's timer interrupts come round, the drive
|
|
24
|
+
# sleeps through them too (see Orbit).
|
|
25
|
+
#
|
|
26
|
+
# A model includes Core and builds its parts, sets @clock_hz and
|
|
27
|
+
# @idle_loop, and runs one drive cycle in step.
|
|
28
|
+
module Core
|
|
29
|
+
include Sleep
|
|
30
|
+
include Idle
|
|
31
|
+
include Orbit
|
|
32
|
+
|
|
33
|
+
attr_reader :device, :serial_bus
|
|
34
|
+
|
|
35
|
+
def cpu
|
|
36
|
+
settle!
|
|
37
|
+
@cpu
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def bus
|
|
41
|
+
settle!
|
|
42
|
+
@bus
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def via1
|
|
46
|
+
settle!
|
|
47
|
+
@via1
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def via2
|
|
51
|
+
settle!
|
|
52
|
+
@via2
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def mechanism
|
|
56
|
+
settle!
|
|
57
|
+
@mechanism
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def cycles
|
|
61
|
+
settle!
|
|
62
|
+
@cycles
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def ram = bus.ram
|
|
66
|
+
|
|
67
|
+
def host_clock_hz=(clock_hz)
|
|
68
|
+
settle!
|
|
69
|
+
@host_clock_hz = clock_hz
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Plugs the drive into a serial bus, leaving the one it was on. A drive
|
|
73
|
+
# starts out on a bus of its own, with nothing else on it.
|
|
74
|
+
def connect(serial_bus)
|
|
75
|
+
settle!
|
|
76
|
+
@serial_bus&.detach(self)
|
|
77
|
+
@serial_bus = serial_bus
|
|
78
|
+
@serial_port.bus = serial_bus
|
|
79
|
+
serial_bus.attach(self)
|
|
80
|
+
@via1.ca1 = @serial_port.atn_low?
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Puts a Disk in the drive (Disk.from_d64 makes one from an image).
|
|
84
|
+
# Nil takes the disk out.
|
|
85
|
+
def insert(disk)
|
|
86
|
+
settle!
|
|
87
|
+
@mechanism.insert(disk)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def disk = mechanism.disk
|
|
91
|
+
|
|
92
|
+
# Whether the LED is lit. Reading it leaves the drive asleep: the LED
|
|
93
|
+
# is the same at the end of every pass the drive sleeps through.
|
|
94
|
+
def led_on? = @mechanism.led_on?
|
|
95
|
+
|
|
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
|
+
# Stores what the head wrote since the motor last stopped in the
|
|
101
|
+
# disk's image, as the motor stopping does.
|
|
102
|
+
def flush
|
|
103
|
+
settle!
|
|
104
|
+
@mechanism.flush
|
|
105
|
+
end
|
|
106
|
+
|
|
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
|
+
# Runs the drive cycles that fall in one host cycle: none, one or two.
|
|
117
|
+
# The host has run this cycle already, and the drive sees what it did
|
|
118
|
+
# from the next one on (see SerialPort). Asleep, the drive owes the
|
|
119
|
+
# cycles instead, until ATN moves or its counters are due (see Idle).
|
|
120
|
+
# It looks at ATN only after the machine pushed its lines into the bus
|
|
121
|
+
# (see host_written!).
|
|
122
|
+
def host_cycle!
|
|
123
|
+
if @asleep
|
|
124
|
+
return if (@slept += 1) != @wake_at && @host_still
|
|
125
|
+
|
|
126
|
+
return doze
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
phase = @phase + @clock_hz
|
|
130
|
+
while phase >= @host_clock_hz
|
|
131
|
+
@asleep ? @owed += 1 : run_cycle
|
|
132
|
+
phase -= @host_clock_hz
|
|
133
|
+
end
|
|
134
|
+
@phase = phase
|
|
135
|
+
settle! if @serial_port.atn_moved?
|
|
136
|
+
@serial_port.latch_host
|
|
137
|
+
plan_wake if @asleep
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# The machine pushed its lines into the bus, so ATN may have moved.
|
|
141
|
+
def host_written!
|
|
142
|
+
@host_still = false
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# The host moved its fast serial pins, which a 1541 has none to hear.
|
|
146
|
+
def fast_lines_moved = nil
|
|
147
|
+
|
|
148
|
+
# Runs one drive cycle, catching up first on any the drive owes (see
|
|
149
|
+
# step).
|
|
150
|
+
def cycle!
|
|
151
|
+
settle!
|
|
152
|
+
step
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def inspect
|
|
156
|
+
"#<#{self.class.name} cycles=#{cycles} cpu=(#{cpu.inspect})>"
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
private
|
|
160
|
+
|
|
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
|
+
# A drive cycle from host_cycle!, which may find the idle loop.
|
|
166
|
+
def run_cycle
|
|
167
|
+
step
|
|
168
|
+
idle_loop_reached if @cpu.program_counter == @idle_loop && @idle_skip
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
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
|
|
|
@@ -128,7 +127,7 @@ module Badline
|
|
|
128
127
|
|
|
129
128
|
def idle_state
|
|
130
129
|
mechanism = @mechanism
|
|
131
|
-
[*@cpu.idle_state, *@via1.idle_state, *@via2.idle_state, @bus.data,
|
|
130
|
+
[*@cpu.idle_state, *@via1.idle_state, *@via2.idle_state, @bus.data, mechanism.so_pending,
|
|
132
131
|
*mechanism.idle_state, mechanism.read_a(0xff), mechanism.read_b(0xff)]
|
|
133
132
|
end
|
|
134
133
|
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
|
#
|
|
@@ -65,6 +65,7 @@ module Badline
|
|
|
65
65
|
@cpu.fast_forward(cycles, orbits * @orbit_instructions)
|
|
66
66
|
@via1.skip_orbits(cycles, touched & 0x03)
|
|
67
67
|
@via2.skip_orbits(cycles, touched >> 2)
|
|
68
|
+
fast_forward_chips(cycles)
|
|
68
69
|
@cycles += cycles
|
|
69
70
|
cycles
|
|
70
71
|
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
|
|
@@ -104,6 +104,7 @@ module Badline
|
|
|
104
104
|
@cpu.fast_forward(cycles, passes * @pass_instructions)
|
|
105
105
|
@via1.fast_forward(cycles)
|
|
106
106
|
@via2.fast_forward(cycles)
|
|
107
|
+
fast_forward_chips(cycles)
|
|
107
108
|
@cycles += cycles
|
|
108
109
|
@budget -= cycles
|
|
109
110
|
cycles
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
module Drive
|
|
5
|
+
# A drive bus's eye on what the CPU reaches, for Idle and Orbit. While
|
|
6
|
+
# Idle records a pass of the DOS's idle loop, the bus watches it (see
|
|
7
|
+
# watch!). While Idle looks for an orbit, the bus guards the drive
|
|
8
|
+
# (see guard!). The bus calls watch_read and watch_write with the
|
|
9
|
+
# address folded into $0000-$1FFF, RAM and the two VIAs at $1800 and
|
|
10
|
+
# $1C00, while @watching.
|
|
11
|
+
module Watch
|
|
12
|
+
# VIA registers, as bits by offset, whose access makes a watched
|
|
13
|
+
# stretch volatile: reads of either VIA's counters and shift register
|
|
14
|
+
# and of VIA 1's port B, and writes to VIA 2's timers, shift register
|
|
15
|
+
# and ACR. Any write to VIA 1 does too (watch_write).
|
|
16
|
+
VOLATILE_VIA1_READS = 0x0731
|
|
17
|
+
VOLATILE_VIA2_READS = 0x0730
|
|
18
|
+
VOLATILE_VIA2_WRITES = 0x0ff0
|
|
19
|
+
|
|
20
|
+
# The timer each VIA register reaches, by offset, as bits: 1 for
|
|
21
|
+
# timer 1 and 2 for timer 2.
|
|
22
|
+
TIMER_REGISTERS = [0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 0, 0, 0, 0, 0, 0].freeze
|
|
23
|
+
|
|
24
|
+
# Whether a watched stretch did something that makes it unrepeatable.
|
|
25
|
+
attr_reader :volatile
|
|
26
|
+
|
|
27
|
+
# The RAM a watched stretch read or wrote, as each address's value
|
|
28
|
+
# from before the first access.
|
|
29
|
+
attr_reader :touched
|
|
30
|
+
|
|
31
|
+
# Whether the drive did something since guard! that reached outside
|
|
32
|
+
# it, or took something in from outside.
|
|
33
|
+
attr_reader :tainted
|
|
34
|
+
|
|
35
|
+
# The timers the drive read or wrote since guard!, as bits: VIA 1's
|
|
36
|
+
# timer 1 and timer 2 in bits 0 and 1, and VIA 2's in bits 2 and 3.
|
|
37
|
+
attr_reader :touched_timers
|
|
38
|
+
|
|
39
|
+
# Starts watching: see volatile and touched. The bus stops watching
|
|
40
|
+
# once the stretch turns volatile.
|
|
41
|
+
def watch!
|
|
42
|
+
@recording = true
|
|
43
|
+
@volatile = false
|
|
44
|
+
@touched = {}
|
|
45
|
+
watching!
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def unwatch!
|
|
49
|
+
@recording = false
|
|
50
|
+
watching!
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Starts guarding: see tainted and touched_timers. Reading VIA 1's
|
|
54
|
+
# port B, which reads the serial bus, taints the drive, and so do
|
|
55
|
+
# any write to VIA 1, whose port B drives the serial bus, a write to
|
|
56
|
+
# VIA 2's shift register or ACR, and VIA 2 turning the motor on or
|
|
57
|
+
# changing the LED. The bus stops guarding once the drive is tainted.
|
|
58
|
+
def guard!
|
|
59
|
+
@guarding = true
|
|
60
|
+
@tainted = false
|
|
61
|
+
@touched_timers = 0
|
|
62
|
+
@led = @via2.port_b_output & Drive1541::Mechanism::LED
|
|
63
|
+
watching!
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def init_watch
|
|
69
|
+
@recording = @guarding = @watching = false
|
|
70
|
+
@volatile = @tainted = false
|
|
71
|
+
@touched = {}
|
|
72
|
+
@touched_timers = 0
|
|
73
|
+
@led = 0
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def watching!
|
|
77
|
+
@watching = @recording || @guarding
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# An access to a chip beyond the VIAs, as the 1571's CIA and floppy
|
|
81
|
+
# controller are: it makes the stretch volatile and taints the
|
|
82
|
+
# drive.
|
|
83
|
+
def watch_chip
|
|
84
|
+
volatile! if @recording
|
|
85
|
+
taint! if @guarding
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def touch(addr)
|
|
89
|
+
@touched[addr] = @ram.peek(addr) unless @touched.key?(addr)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def watch_read(addr)
|
|
93
|
+
if addr < 0x0800
|
|
94
|
+
touch(addr) if @recording
|
|
95
|
+
elsif addr >= 0x1800
|
|
96
|
+
via1 = addr < 0x1c00
|
|
97
|
+
watch(via1 ? VOLATILE_VIA1_READS : VOLATILE_VIA2_READS, addr) if @recording
|
|
98
|
+
guard_read(via1, addr & 0x0f) if @guarding
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def watch_write(addr)
|
|
103
|
+
if addr >= 0x1c00
|
|
104
|
+
watch_via2_write(addr) if @recording
|
|
105
|
+
guard_via2_write(addr & 0x0f) if @guarding
|
|
106
|
+
elsif addr >= 0x1800
|
|
107
|
+
volatile! if @recording
|
|
108
|
+
taint! if @guarding
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
# Any write to VIA 1 makes the stretch volatile (watch_write), and so
|
|
113
|
+
# does a write to VIA 2 that leaves the motor on.
|
|
114
|
+
def watch_via2_write(addr)
|
|
115
|
+
watch(VOLATILE_VIA2_WRITES, addr)
|
|
116
|
+
volatile! if @recording && @via2.port_b_output.anybits?(Drive1541::Mechanism::MOTOR)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def watch(volatile, addr)
|
|
120
|
+
volatile! if volatile.anybits?(1 << (addr & 0x0f))
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def volatile!
|
|
124
|
+
@volatile = true
|
|
125
|
+
@recording = false
|
|
126
|
+
watching!
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def guard_read(via1, offset)
|
|
130
|
+
return taint! if via1 && offset.zero?
|
|
131
|
+
|
|
132
|
+
@touched_timers |= via1 ? TIMER_REGISTERS[offset] : TIMER_REGISTERS[offset] << 2
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def guard_via2_write(offset)
|
|
136
|
+
return taint! if offset.between?(0x0a, 0x0b)
|
|
137
|
+
|
|
138
|
+
@touched_timers |= TIMER_REGISTERS[offset] << 2
|
|
139
|
+
lines = @via2.port_b_output
|
|
140
|
+
taint! if lines.anybits?(Drive1541::Mechanism::MOTOR) || (lines & Drive1541::Mechanism::LED) != @led
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def taint!
|
|
144
|
+
@tainted = true
|
|
145
|
+
@guarding = false
|
|
146
|
+
watching!
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
end
|