badline 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/CLAUDE.md +24 -6
  4. data/CONTRIBUTING.md +15 -9
  5. data/README.md +16 -5
  6. data/doc/media.md +28 -1
  7. data/doc/pinned-behaviour.md +31 -1
  8. data/exe/badline-ruby +1 -1
  9. data/lib/badline/c128/bus.rb +4 -17
  10. data/lib/badline/c128/drives.rb +108 -0
  11. data/lib/badline/c128/fast_serial.rb +63 -0
  12. data/lib/badline/c128/io_pages.rb +35 -0
  13. data/lib/badline/c128/mmu.rb +24 -7
  14. data/lib/badline/c128/modes.rb +17 -4
  15. data/lib/badline/c128/saved_state.rb +23 -0
  16. data/lib/badline/c128/z80.rb +49 -0
  17. data/lib/badline/c128/z80_pages.rb +118 -0
  18. data/lib/badline/c128/z80_turns.rb +106 -0
  19. data/lib/badline/c128.rb +25 -26
  20. data/lib/badline/cartridge/action_replay.rb +2 -2
  21. data/lib/badline/cartridge/comal80.rb +1 -7
  22. data/lib/badline/cartridge/dinamic.rb +0 -8
  23. data/lib/badline/cartridge/final_cartridge3.rb +6 -6
  24. data/lib/badline/cartridge/fun_play.rb +0 -8
  25. data/lib/badline/cartridge/game_system.rb +0 -8
  26. data/lib/badline/cartridge/kcs_power.rb +10 -7
  27. data/lib/badline/cartridge/mach5.rb +2 -4
  28. data/lib/badline/cartridge/magic_desk.rb +0 -8
  29. data/lib/badline/cartridge/ocean.rb +0 -8
  30. data/lib/badline/cartridge/pagefox.rb +2 -2
  31. data/lib/badline/cartridge/retro_replay.rb +7 -6
  32. data/lib/badline/cartridge/rex_utility.rb +2 -6
  33. data/lib/badline/cartridge/rgcd.rb +2 -2
  34. data/lib/badline/cartridge/simons_basic.rb +0 -5
  35. data/lib/badline/cartridge/super_games.rb +4 -2
  36. data/lib/badline/cartridge/westermann.rb +2 -6
  37. data/lib/badline/cartridge.rb +37 -9
  38. data/lib/badline/cia/fast_forward.rb +60 -0
  39. data/lib/badline/cia/serial.rb +12 -0
  40. data/lib/badline/cia/timer.rb +39 -0
  41. data/lib/badline/cia.rb +10 -5
  42. data/lib/badline/computer/attachments.rb +4 -50
  43. data/lib/badline/computer/true_drives.rb +80 -0
  44. data/lib/badline/computer.rb +4 -0
  45. data/lib/badline/core.rb +2 -0
  46. data/lib/badline/drive/core.rb +147 -0
  47. data/lib/badline/drive/fast_serial.rb +73 -0
  48. data/lib/badline/{drive1541 → drive}/idle.rb +18 -29
  49. data/lib/badline/drive/idle_cpu.rb +28 -0
  50. data/lib/badline/{drive1541 → drive}/orbit.rb +3 -5
  51. data/lib/badline/drive/serial_lines.rb +73 -0
  52. data/lib/badline/{drive1541 → drive}/sleep.rb +4 -5
  53. data/lib/badline/drive/vias.rb +84 -0
  54. data/lib/badline/drive/watch.rb +150 -0
  55. data/lib/badline/drive.rb +11 -0
  56. data/lib/badline/drive1541/bus.rb +5 -129
  57. data/lib/badline/drive1541/byte_ready.rb +48 -0
  58. data/lib/badline/drive1541/cpu.rb +1 -19
  59. data/lib/badline/drive1541/disk.rb +77 -20
  60. data/lib/badline/drive1541/disk_state.rb +7 -2
  61. data/lib/badline/drive1541/disk_via.rb +3 -0
  62. data/lib/badline/drive1541/mechanism.rb +48 -14
  63. data/lib/badline/drive1541/mechanism_state.rb +5 -4
  64. data/lib/badline/drive1541/rotation.rb +5 -5
  65. data/lib/badline/drive1541/serial_port.rb +4 -60
  66. data/lib/badline/drive1541.rb +14 -172
  67. data/lib/badline/drive1571/bus.rb +111 -0
  68. data/lib/badline/drive1571/cpu.rb +11 -0
  69. data/lib/badline/drive1571/serial_port.rb +37 -0
  70. data/lib/badline/drive1571/serial_via.rb +25 -0
  71. data/lib/badline/drive1571/wd1770.rb +120 -0
  72. data/lib/badline/drive1571.rb +175 -0
  73. data/lib/badline/drive1581/bus.rb +137 -0
  74. data/lib/badline/drive1581/cpu.rb +11 -0
  75. data/lib/badline/drive1581/disk.rb +135 -0
  76. data/lib/badline/drive1581/disk_state.rb +87 -0
  77. data/lib/badline/drive1581/mechanism.rb +117 -0
  78. data/lib/badline/drive1581/serial_port.rb +52 -0
  79. data/lib/badline/drive1581/slot.rb +78 -0
  80. data/lib/badline/drive1581/track.rb +100 -0
  81. data/lib/badline/drive1581/track_bytes.rb +75 -0
  82. data/lib/badline/drive1581/wd1772/registers.rb +89 -0
  83. data/lib/badline/drive1581/wd1772/sector_writes.rb +73 -0
  84. data/lib/badline/drive1581/wd1772/sectors.rb +150 -0
  85. data/lib/badline/drive1581/wd1772/state.rb +66 -0
  86. data/lib/badline/drive1581/wd1772/stepping.rb +109 -0
  87. data/lib/badline/drive1581/wd1772/tracks.rb +125 -0
  88. data/lib/badline/drive1581/wd1772/turn.rb +73 -0
  89. data/lib/badline/drive1581/wd1772.rb +216 -0
  90. data/lib/badline/drive1581.rb +233 -0
  91. data/lib/badline/frontend/app.rb +2 -4
  92. data/lib/badline/frontend/audio_device.rb +67 -0
  93. data/lib/badline/frontend/audio_sink.rb +16 -50
  94. data/lib/badline/frontend/boot.rb +18 -10
  95. data/lib/badline/frontend/buttons.rb +7 -7
  96. data/lib/badline/frontend/drive_led.rb +1 -1
  97. data/lib/badline/frontend/keys.rb +33 -8
  98. data/lib/badline/frontend/media_slots.rb +42 -0
  99. data/lib/badline/frontend/{confirmation.rb → menu/confirmation.rb} +4 -4
  100. data/lib/badline/frontend/{file_browser.rb → menu/file_browser.rb} +18 -34
  101. data/lib/badline/frontend/{menu_dialogs.rb → menu/menu_dialogs.rb} +3 -7
  102. data/lib/badline/frontend/{menu_media.rb → menu/menu_media.rb} +26 -51
  103. data/lib/badline/frontend/{menu_pages.rb → menu/menu_pages.rb} +7 -9
  104. data/lib/badline/frontend/{name_field.rb → menu/name_field.rb} +11 -24
  105. data/lib/badline/frontend/{pause_menu.rb → menu/pause_menu.rb} +13 -28
  106. data/lib/badline/frontend/menu/theme.rb +18 -0
  107. data/lib/badline/frontend/{filter_view.rb → player/filter_view.rb} +4 -4
  108. data/lib/badline/frontend/{info_view.rb → player/info_view.rb} +3 -3
  109. data/lib/badline/frontend/{player_window.rb → player/player_controller.rb} +17 -25
  110. data/lib/badline/frontend/{player_footer.rb → player/player_footer.rb} +8 -8
  111. data/lib/badline/frontend/{player_header.rb → player/player_header.rb} +5 -5
  112. data/lib/badline/frontend/{player_screen.rb → player/player_screen.rb} +4 -5
  113. data/lib/badline/frontend/{scope.rb → player/scope.rb} +1 -1
  114. data/lib/badline/frontend/{sid_view.rb → player/sid_view.rb} +16 -20
  115. data/lib/badline/frontend/player/theme.rb +18 -0
  116. data/lib/badline/frontend/{visualizer_view.rb → player/visualizer_view.rb} +8 -8
  117. data/lib/badline/frontend/sound.rb +19 -38
  118. data/lib/badline/frontend/timeline.rb +3 -14
  119. data/lib/badline/frontend.rb +24 -20
  120. data/lib/badline/iec_bus.rb +68 -6
  121. data/lib/badline/machine.rb +9 -6
  122. data/lib/badline/media/boot_disk.rb +50 -0
  123. data/lib/badline/media/not_disk.rb +8 -0
  124. data/lib/badline/media/true_drive.rb +48 -25
  125. data/lib/badline/media.rb +9 -3
  126. data/lib/badline/options/event.rb +2 -2
  127. data/lib/badline/options/family.rb +3 -9
  128. data/lib/badline/options/help.rb +3 -1
  129. data/lib/badline/options/table.rb +1 -1
  130. data/lib/badline/roms/README +18 -0
  131. data/lib/badline/roms/dos1571.rom +0 -0
  132. data/lib/badline/roms/dos1581.rom +0 -0
  133. data/lib/badline/sdl.rb +37 -20
  134. data/lib/badline/snapshot/state.rb +5 -4
  135. data/lib/badline/storage/d64_image.rb +3 -0
  136. data/lib/badline/storage/d71_image.rb +9 -4
  137. data/lib/badline/storage/g64_image.rb +7 -2
  138. data/lib/badline/storage/p00.rb +8 -0
  139. data/lib/badline/time_of_day.rb +9 -0
  140. data/lib/badline/version.rb +1 -1
  141. data/lib/badline/via/fast_forward.rb +3 -3
  142. data/lib/badline/vic20/attachments.rb +23 -3
  143. data/lib/badline/vic20/saved_state.rb +2 -0
  144. data/lib/badline/vic20.rb +3 -0
  145. data/lib/badline/z80/arithmetic.rb +117 -0
  146. data/lib/badline/z80/bit_page.rb +55 -0
  147. data/lib/badline/z80/block_io.rb +58 -0
  148. data/lib/badline/z80/block_transfers.rb +59 -0
  149. data/lib/badline/z80/bus_cycles.rb +111 -0
  150. data/lib/badline/z80/core.rb +113 -0
  151. data/lib/badline/z80/exchanges.rb +50 -0
  152. data/lib/badline/z80/extended_page.rb +89 -0
  153. data/lib/badline/z80/flags.rb +37 -0
  154. data/lib/badline/z80/interrupt_acceptance.rb +46 -0
  155. data/lib/badline/z80/jumps.rb +82 -0
  156. data/lib/badline/z80/loads.rb +98 -0
  157. data/lib/badline/z80/main_page.rb +134 -0
  158. data/lib/badline/z80/registers.rb +120 -0
  159. data/lib/badline/z80/saved_state.rb +69 -0
  160. data/lib/badline/z80/shifts.rb +64 -0
  161. data/lib/badline/z80.rb +11 -0
  162. metadata +89 -24
  163. /data/lib/badline/frontend/{menu_ports.rb → menu/menu_ports.rb} +0 -0
  164. /data/lib/badline/frontend/{player_commands.rb → player/player_commands.rb} +0 -0
  165. /data/lib/badline/frontend/{player_state.rb → player/player_state.rb} +0 -0
  166. /data/lib/badline/frontend/{sid_history.rb → player/sid_history.rb} +0 -0
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drive
5
+ # What every Commodore disk drive here shares: a 6502 running the DOS
6
+ # ROM, with chips on its bus facing the serial bus and the disk
7
+ # mechanism, on a clock of its own.
8
+ #
9
+ # The drive runs on its own crystal, so it clocks against the host's
10
+ # clock through a fractional accumulator: each host cycle adds the
11
+ # drive's clock rate worth of phase, and every whole host period in it
12
+ # runs a drive cycle. Against the PAL C64's 985,248 Hz a drive at
13
+ # 1 MHz runs one drive cycle per host cycle and a second one about
14
+ # every 67. The host sets its clock on attaching the drive, and until
15
+ # then a 1 MHz drive runs one cycle per host cycle.
16
+ #
17
+ # The Mechanism reads a Disk put in with insert.
18
+ #
19
+ # While the DOS idles, host_cycle! skips the drive's cycles and catches
20
+ # up on them later, exactly (see Idle). The readers of the drive's parts
21
+ # catch up first, so they find the drive as running every cycle would
22
+ # have left it. Once the DOS's timer interrupts come round, the drive
23
+ # sleeps through them too (see Orbit).
24
+ #
25
+ # A model includes Core and builds its parts, sets @clock_hz and
26
+ # @idle_loop, and runs one drive cycle in step. It hears ATN on
27
+ # connecting (hear_atn), drives the serial bus (serial_output), and
28
+ # answers Idle's and Orbit's questions about its chips, as VIAs does
29
+ # for the 1541 and the 1571.
30
+ module Core
31
+ include Sleep
32
+ include Idle
33
+ include Orbit
34
+
35
+ attr_reader :device, :serial_bus
36
+
37
+ def cpu
38
+ settle!
39
+ @cpu
40
+ end
41
+
42
+ def bus
43
+ settle!
44
+ @bus
45
+ end
46
+
47
+ def mechanism
48
+ settle!
49
+ @mechanism
50
+ end
51
+
52
+ def cycles
53
+ settle!
54
+ @cycles
55
+ end
56
+
57
+ def ram = bus.ram
58
+
59
+ def host_clock_hz=(clock_hz)
60
+ settle!
61
+ @host_clock_hz = clock_hz
62
+ end
63
+
64
+ # Plugs the drive into a serial bus, leaving the one it was on. A drive
65
+ # starts out on a bus of its own, with nothing else on it.
66
+ def connect(serial_bus)
67
+ settle!
68
+ @serial_bus&.detach(self)
69
+ @serial_bus = serial_bus
70
+ @serial_port.bus = serial_bus
71
+ serial_bus.attach(self)
72
+ hear_atn
73
+ end
74
+
75
+ # Puts a Disk in the drive (Disk.from_d64 makes one from an image).
76
+ # Nil takes the disk out.
77
+ def insert(disk)
78
+ settle!
79
+ @mechanism.insert(disk)
80
+ end
81
+
82
+ def disk = mechanism.disk
83
+
84
+ # Whether the LED is lit. Reading it leaves the drive asleep: the LED
85
+ # is the same at the end of every pass the drive sleeps through.
86
+ def led_on? = @mechanism.led_on?
87
+
88
+ # Stores what the head wrote since the motor last stopped in the
89
+ # disk's image, as the motor stopping does.
90
+ def flush
91
+ settle!
92
+ @mechanism.flush
93
+ end
94
+
95
+ # Runs the drive cycles that fall in one host cycle: none, one or two.
96
+ # The host has run this cycle already, and the drive sees what it did
97
+ # from the next one on (see SerialPort). Asleep, the drive owes the
98
+ # cycles instead, until ATN moves or its counters are due (see Idle).
99
+ # It looks at ATN only after the machine pushed its lines into the bus
100
+ # (see host_written!).
101
+ def host_cycle!
102
+ if @asleep
103
+ return if (@slept += 1) != @wake_at && @host_still
104
+
105
+ return doze
106
+ end
107
+
108
+ phase = @phase + @clock_hz
109
+ while phase >= @host_clock_hz
110
+ @asleep ? @owed += 1 : run_cycle
111
+ phase -= @host_clock_hz
112
+ end
113
+ @phase = phase
114
+ settle! if @serial_port.atn_moved?
115
+ @serial_port.latch_host
116
+ plan_wake if @asleep
117
+ end
118
+
119
+ # The machine pushed its lines into the bus, so ATN may have moved.
120
+ def host_written!
121
+ @host_still = false
122
+ end
123
+
124
+ # The host moved its fast serial pins, which a 1541 has none to hear.
125
+ def fast_lines_moved = nil
126
+
127
+ # Runs one drive cycle, catching up first on any the drive owes (see
128
+ # step).
129
+ def cycle!
130
+ settle!
131
+ step
132
+ end
133
+
134
+ def inspect
135
+ "#<#{self.class.name} cycles=#{cycles} cpu=(#{cpu.inspect})>"
136
+ end
137
+
138
+ private
139
+
140
+ # A drive cycle from host_cycle!, which may find the idle loop.
141
+ def run_cycle
142
+ step
143
+ idle_loop_reached if @cpu.program_counter == @idle_loop && @idle_skip
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drive
5
+ # Fast serial on a drive with a CIA, the 1571's 6526 or the 1581's
6
+ # 8520: through buffers a port line turns round, the CIA's CNT drives
7
+ # SRQ and its SP drives DATA, or it hears them. The drive pushes its
8
+ # pins into the bus as they move, and hears the host's as the host
9
+ # pushes them, pin by pin, so each byte goes through both shift
10
+ # registers bit by bit. The CIA takes no part in a pass of the idle
11
+ # loop, and any access to it, or the host moving its fast serial pins,
12
+ # wakes the drive.
13
+ #
14
+ # A model includes it beside Core, and calls turn_fast_serial when its
15
+ # direction line moves and drive_fast_serial each cycle the buffers
16
+ # face out. Its serial_output carries @fast_output above its port.
17
+ module FastSerial
18
+ # Whether the buffers face out, the drive's CIA driving DATA and SRQ.
19
+ attr_reader :fast_serial_out
20
+
21
+ # The host moved its fast serial pins: the CIA hears SRQ on CNT and
22
+ # DATA on SP while the buffers face in.
23
+ def fast_lines_moved
24
+ settle!
25
+ hear_fast_serial unless @fast_serial_out
26
+ end
27
+
28
+ private
29
+
30
+ def init_fast_serial
31
+ @fast_serial_out = false
32
+ @fast_output = 0
33
+ end
34
+
35
+ # The buffers turned round: outwards the CIA's pins drive the lines,
36
+ # and inwards they let go and hear them.
37
+ def turn_fast_serial(out)
38
+ @fast_serial_out = out
39
+ return unless @serial_bus
40
+
41
+ push_fast_output(out ? fast_pins : 0)
42
+ hear_fast_serial unless out
43
+ end
44
+
45
+ def drive_fast_serial
46
+ low = fast_pins
47
+ push_fast_output(low) if low != @fast_output
48
+ end
49
+
50
+ # The lines the CIA's CNT and SP pull while its serial port drives
51
+ # them.
52
+ def fast_pins
53
+ serial = @cia.serial
54
+ return 0 unless serial.output?
55
+
56
+ low = serial.cnt ? 0 : IECBus::DRIVE_FAST_SRQ
57
+ serial.sp_out ? low : low | IECBus::DRIVE_FAST_DATA
58
+ end
59
+
60
+ def push_fast_output(low)
61
+ @fast_output = low
62
+ @serial_bus&.drives_fast_moved!
63
+ end
64
+
65
+ def hear_fast_serial
66
+ low = @serial_bus.low_lines
67
+ serial = @cia.serial
68
+ serial.cnt_in = low.nobits?(IECBus::SRQ)
69
+ serial.sp_in = low.nobits?(IECBus::DATA)
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,18 +1,19 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- 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
 
@@ -82,7 +81,7 @@ module Badline
82
81
  end
83
82
 
84
83
  def start_recording
85
- return if @mechanism.motor_on? || @via1.irq? || @via2.irq? || @cpu.trapped?
84
+ return if busy_for_pass? || @cpu.trapped?
86
85
 
87
86
  @recording = true
88
87
  @record_state = idle_state
@@ -117,20 +116,10 @@ module Badline
117
116
  anchor
118
117
  end
119
118
 
120
- def quiet_cycles
121
- [@via1.quiet_cycles, @via2.quiet_cycles].min
122
- end
123
-
124
119
  def ram_as_found?
125
120
  ram = @bus.ram
126
121
  @bus.touched.all? { |addr, value| ram.peek(addr) == value }
127
122
  end
128
-
129
- def idle_state
130
- mechanism = @mechanism
131
- [*@cpu.idle_state, *@via1.idle_state, *@via2.idle_state, @bus.data, @so_pending,
132
- *mechanism.idle_state, mechanism.read_a(0xff), mechanism.read_b(0xff)]
133
- end
134
123
  end
135
124
  end
136
125
  end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drive
5
+ # What Idle needs of a drive's 6502, which includes it beside
6
+ # CPU::Core.
7
+ module IdleCPU
8
+ # Everything the CPU holds but its cycle and instruction counts: the
9
+ # registers, the interrupt lines and pipeline, and what the last
10
+ # instruction left in its working registers. Drive::Idle compares
11
+ # it at two instruction boundaries.
12
+ def idle_state
13
+ [@program_counter, @stack_pointer, @a, @x, @y, @status.value,
14
+ @irq, @nmi, @irq_sample, @irq_pending, @nmi_sample, @nmi_pending, @skip_poll,
15
+ @boundary_irq, @boundary_nmi, @so_high, @interrupt, @brk, @pending_write, @stalled_at, @traps,
16
+ @operation, @optional_dummy, @boundary_crossed, @branch_taken,
17
+ @address, @pointer, @dummy_address, @value, @rmw_result]
18
+ end
19
+
20
+ # Counts +cycles+ cycles and +instructions+ instructions that ran
21
+ # without changing anything idle_state holds.
22
+ def fast_forward(cycles, instructions)
23
+ @cycles += cycles
24
+ @instructions += instructions
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- class Drive1541
4
+ module Drive
5
5
  # Sleeping through the DOS's timer interrupts, exactly, once they come
6
6
  # round.
7
7
  #
@@ -63,8 +63,7 @@ module Badline
63
63
  cycles = orbits * @orbit_cycles
64
64
  touched = @bus.touched_timers
65
65
  @cpu.fast_forward(cycles, orbits * @orbit_instructions)
66
- @via1.skip_orbits(cycles, touched & 0x03)
67
- @via2.skip_orbits(cycles, touched >> 2)
66
+ skip_chip_orbits(cycles, touched)
68
67
  @cycles += cycles
69
68
  cycles
70
69
  end
@@ -73,8 +72,7 @@ module Badline
73
72
  # the unarmed timers it hasn't touched since it was last tainted.
74
73
  def anchor_state
75
74
  touched = @bus.touched_timers
76
- [*idle_state, *@via1.counter_state(touched & 0x03), *@via2.counter_state(touched >> 2),
77
- @bus.ram.snapshot]
75
+ [*idle_state, *counter_state(touched), @bus.ram.snapshot]
78
76
  end
79
77
  end
80
78
  end
@@ -0,0 +1,73 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drive
5
+ # What VIA 1's port B reads from the serial bus. DATA IN (PB0), CLK IN
6
+ # (PB2) and ATN IN (PB7) come through inverters, so a released line
7
+ # reads 0 and a pulled one 1. The jumpers on PB5 and PB6 pull a line
8
+ # low when closed, and both closed make device 8.
9
+ #
10
+ # The drive sees the host's side of the bus a host cycle late. The
11
+ # host's CIA changes its pins at the end of the cycle that writes them,
12
+ # and the drive's VIA samples its own late in each of its cycles, so a
13
+ # drive cycle that runs alongside the write reads the lines from
14
+ # before it. The drive runs after the host in each host cycle (see
15
+ # Computer#cycle!), so the port reads the host's lines as latch_host
16
+ # took them at the end of the last one. Fast loaders that time their
17
+ # transfer from an ATN edge, as VICE's drive tests do, depend on it.
18
+ #
19
+ # A model's serial port includes it, with its own port A.
20
+ module SerialLines
21
+ DATA_IN = 0x01
22
+ CLK_IN = 0x04
23
+ ATN_IN = 0x80
24
+ SERIAL_INPUTS = DATA_IN | CLK_IN | ATN_IN
25
+ JUMPERS = 0b0110_0000
26
+
27
+ attr_reader :bus
28
+
29
+ def bus=(bus)
30
+ @bus = bus
31
+ latch_host
32
+ end
33
+
34
+ # Takes the host's lines as the machine last pushed them into the bus,
35
+ # for the drive cycles of the next host cycle.
36
+ def latch_host
37
+ @host = @bus.host_lines
38
+ end
39
+
40
+ # The host's lines as last latched. The jumpers and the bus are the
41
+ # drive's wiring.
42
+ def save_state(out)
43
+ out.int(@host)
44
+ end
45
+
46
+ def load_state(input)
47
+ @host = input.int
48
+ end
49
+
50
+ # Whether the host has moved ATN since the last latch_host.
51
+ def atn_moved? = (@bus.host_lines ^ @host).anybits?(IECBus::HOST_ATN_OUT)
52
+
53
+ def atn_low? = @bus.atn_low?(@host)
54
+
55
+ def read_b(_lines)
56
+ low = @bus.low_lines(@host)
57
+ value = @port_b
58
+ value |= DATA_IN if low.anybits?(IECBus::DATA)
59
+ value |= CLK_IN if low.anybits?(IECBus::CLK)
60
+ value |= ATN_IN if low.anybits?(IECBus::ATN)
61
+ value
62
+ end
63
+
64
+ private
65
+
66
+ def init_serial_lines(device, bus)
67
+ @port_b = (0xff & ~(SERIAL_INPUTS | JUMPERS)) | (((device - 8) & 0x03) << 5)
68
+ @host = 0
69
+ self.bus = bus
70
+ end
71
+ end
72
+ end
73
+ end
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Badline
4
- 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
@@ -102,8 +102,7 @@ module Badline
102
102
  def skip_passes(passes)
103
103
  cycles = passes * @pass_cycles
104
104
  @cpu.fast_forward(cycles, passes * @pass_instructions)
105
- @via1.fast_forward(cycles)
106
- @via2.fast_forward(cycles)
105
+ fast_forward_chips(cycles)
107
106
  @cycles += cycles
108
107
  @budget -= cycles
109
108
  cycles
@@ -0,0 +1,84 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drive
5
+ # The two VIAs the 1541 and the 1571 share: VIA 1 facing the serial
6
+ # bus, its CA1 on ATN, and VIA 2 running the GCR Mechanism. A model
7
+ # includes it beside Core, and it answers Core's, Idle's and Orbit's
8
+ # questions about the chips: what they hold, how long their counters
9
+ # leave them quiet, and how to run them a stretch at once. A model with
10
+ # more chips on its bus runs them in fast_forward_more.
11
+ module VIAs
12
+ def via1
13
+ settle!
14
+ @via1
15
+ end
16
+
17
+ def via2
18
+ settle!
19
+ @via2
20
+ end
21
+
22
+ # Puts in a Disk from the .d64, .g64, .d71 or .g71 image at +path+
23
+ # (Drive1541::Disk.open), write-protected with `read_only`.
24
+ def insert_image(path, read_only: false)
25
+ insert(Drive1541::Disk.open(path, read_only:))
26
+ end
27
+
28
+ # VIA 1's port B as it drives the serial bus. It holds still while the
29
+ # drive sleeps, so reading it leaves the drive asleep.
30
+ def serial_output = @via1.port_b_output
31
+
32
+ # The serial bus's RESET line reaches the CPU and both VIAs. RAM keeps
33
+ # its contents.
34
+ def reset!
35
+ settle!
36
+ @via1.reset!
37
+ @via2.reset!
38
+ @cpu.reset!
39
+ end
40
+
41
+ private
42
+
43
+ # ATN reaches VIA 1's CA1 as the serial port latched it.
44
+ def hear_atn
45
+ @via1.ca1 = @serial_port.atn_low?
46
+ end
47
+
48
+ # Whether the drive is doing anything a pass of the idle loop can't
49
+ # repeat: the motor on, or a VIA pulling IRQ.
50
+ def busy_for_pass? = @mechanism.motor_on? || @via1.irq? || @via2.irq?
51
+
52
+ def quiet_cycles
53
+ [@via1.quiet_cycles, @via2.quiet_cycles].min
54
+ end
55
+
56
+ def idle_state
57
+ mechanism = @mechanism
58
+ [*@cpu.idle_state, *@via1.idle_state, *@via2.idle_state, @bus.data, mechanism.so_pending,
59
+ *mechanism.idle_state, mechanism.read_a(0xff), mechanism.read_b(0xff)]
60
+ end
61
+
62
+ # The counters an orbit has to bring back (VIA#counter_state).
63
+ def counter_state(touched)
64
+ [*@via1.counter_state(touched & 0x03), *@via2.counter_state(touched >> 2)]
65
+ end
66
+
67
+ def fast_forward_chips(cycles)
68
+ @via1.fast_forward(cycles)
69
+ @via2.fast_forward(cycles)
70
+ fast_forward_more(cycles)
71
+ end
72
+
73
+ def skip_chip_orbits(cycles, touched)
74
+ @via1.skip_orbits(cycles, touched & 0x03)
75
+ @via2.skip_orbits(cycles, touched >> 2)
76
+ fast_forward_more(cycles)
77
+ end
78
+
79
+ # Runs the model's chips beyond the VIAs +cycles+ quiet cycles at once
80
+ # (see quiet_cycles). The 1541 has none.
81
+ def fast_forward_more(_cycles) = nil
82
+ end
83
+ end
84
+ end