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.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/CLAUDE.md +20 -3
  4. data/CONTRIBUTING.md +8 -3
  5. data/README.md +6 -2
  6. data/lib/badline/c128/drives.rb +95 -0
  7. data/lib/badline/c128/fast_serial.rb +62 -0
  8. data/lib/badline/c128/mmu.rb +15 -0
  9. data/lib/badline/c128/saved_state.rb +1 -0
  10. data/lib/badline/c128.rb +9 -7
  11. data/lib/badline/cia/fast_forward.rb +42 -0
  12. data/lib/badline/cia/serial.rb +12 -0
  13. data/lib/badline/cia/timer.rb +39 -0
  14. data/lib/badline/cia.rb +2 -0
  15. data/lib/badline/computer/attachments.rb +6 -0
  16. data/lib/badline/drive/core.rb +172 -0
  17. data/lib/badline/{drive1541 → drive}/idle.rb +18 -19
  18. data/lib/badline/drive/idle_cpu.rb +28 -0
  19. data/lib/badline/{drive1541 → drive}/orbit.rb +2 -1
  20. data/lib/badline/drive/serial_lines.rb +73 -0
  21. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -3
  22. data/lib/badline/drive/watch.rb +150 -0
  23. data/lib/badline/drive.rb +9 -0
  24. data/lib/badline/drive1541/bus.rb +5 -129
  25. data/lib/badline/drive1541/byte_ready.rb +48 -0
  26. data/lib/badline/drive1541/cpu.rb +1 -19
  27. data/lib/badline/drive1541/disk.rb +77 -20
  28. data/lib/badline/drive1541/disk_state.rb +7 -2
  29. data/lib/badline/drive1541/disk_via.rb +3 -0
  30. data/lib/badline/drive1541/mechanism.rb +48 -14
  31. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  32. data/lib/badline/drive1541/rotation.rb +5 -5
  33. data/lib/badline/drive1541/serial_port.rb +4 -60
  34. data/lib/badline/drive1541.rb +13 -172
  35. data/lib/badline/drive1571/bus.rb +111 -0
  36. data/lib/badline/drive1571/cpu.rb +11 -0
  37. data/lib/badline/drive1571/serial_port.rb +37 -0
  38. data/lib/badline/drive1571/serial_via.rb +25 -0
  39. data/lib/badline/drive1571/wd1770.rb +34 -0
  40. data/lib/badline/drive1571.rb +225 -0
  41. data/lib/badline/frontend/app.rb +1 -1
  42. data/lib/badline/frontend/drive_led.rb +1 -1
  43. data/lib/badline/frontend/menu_media.rb +3 -3
  44. data/lib/badline/frontend/menu_pages.rb +2 -1
  45. data/lib/badline/iec_bus.rb +54 -4
  46. data/lib/badline/machine.rb +7 -5
  47. data/lib/badline/media/true_drive.rb +26 -23
  48. data/lib/badline/options/help.rb +2 -1
  49. data/lib/badline/options/table.rb +1 -1
  50. data/lib/badline/roms/README +9 -0
  51. data/lib/badline/roms/dos1571.rom +0 -0
  52. data/lib/badline/storage/d64_image.rb +3 -0
  53. data/lib/badline/storage/d71_image.rb +9 -4
  54. data/lib/badline/storage/g64_image.rb +7 -2
  55. data/lib/badline/time_of_day.rb +9 -0
  56. data/lib/badline/version.rb +1 -1
  57. data/lib/badline/via/fast_forward.rb +3 -3
  58. data/lib/badline/vic20/attachments.rb +6 -0
  59. data/lib/badline/z80/arithmetic.rb +117 -0
  60. data/lib/badline/z80/bit_page.rb +55 -0
  61. data/lib/badline/z80/block_io.rb +58 -0
  62. data/lib/badline/z80/block_transfers.rb +59 -0
  63. data/lib/badline/z80/bus_cycles.rb +111 -0
  64. data/lib/badline/z80/core.rb +111 -0
  65. data/lib/badline/z80/exchanges.rb +50 -0
  66. data/lib/badline/z80/extended_page.rb +89 -0
  67. data/lib/badline/z80/flags.rb +37 -0
  68. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  69. data/lib/badline/z80/jumps.rb +82 -0
  70. data/lib/badline/z80/loads.rb +98 -0
  71. data/lib/badline/z80/main_page.rb +134 -0
  72. data/lib/badline/z80/registers.rb +120 -0
  73. data/lib/badline/z80/shifts.rb +64 -0
  74. data/lib/badline/z80.rb +11 -0
  75. 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
- class Drive1541
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 $EBFF,
8
- # 446 cycles a pass, until VIA 2's timer 1 interrupts it every 14,850
9
- # cycles for the job loop, or ATN interrupts it through VIA 1's CA1. A
10
- # pass writes RAM and VIA 2's port B, but puts back what it found, so
11
- # the drive comes round to $EBFF as it left it. Only the timers'
12
- # counters and the cycle counts move.
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 $EBFF, the drive records the pass that
15
- # follows. At the next $EBFF it checks that the pass
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 Drive1541 call
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 $EBFF, where the CPU always comes from the loop's
43
- # last instruction, a JMP, so what that leaves in the CPU's working
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 $EBFF. At an instruction boundary, the drive sleeps if
73
- # the pass it recorded can repeat, and otherwise records the next.
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, @so_pending,
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
- class Drive1541
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
- class Drive1541
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 + CLOCK_HZ - 1) / CLOCK_HZ : 1
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 * CLOCK_HZ)
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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/drive/sleep"
4
+ require "badline/drive/idle"
5
+ require "badline/drive/orbit"
6
+ require "badline/drive/core"
7
+ require "badline/drive/idle_cpu"
8
+ require "badline/drive/watch"
9
+ require "badline/drive/serial_lines"