badline 0.16.1 → 0.18.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 (256) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +213 -0
  3. data/CLAUDE.md +11 -7
  4. data/CONTRIBUTING.md +7 -1
  5. data/README.md +265 -26
  6. data/doc/media.md +5 -2
  7. data/doc/pinned-behaviour.md +189 -0
  8. data/doc/snapshot-format.md +616 -74
  9. data/doc/snapshots.md +192 -24
  10. data/exe/badline-ruby +5 -0
  11. data/lib/badline/audio/terminal.rb +34 -2
  12. data/lib/badline/c128/keyed_state.rb +2 -0
  13. data/lib/badline/c128.rb +1 -0
  14. data/lib/badline/c64/keyed_state.rb +2 -0
  15. data/lib/badline/c64.rb +1 -0
  16. data/lib/badline/cbm2/acia.rb +10 -0
  17. data/lib/badline/cbm2/bus.rb +263 -0
  18. data/lib/badline/cbm2/cpu.rb +58 -0
  19. data/lib/badline/cbm2/crtc_video.rb +133 -0
  20. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  21. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  22. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  23. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  24. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  25. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  26. data/lib/badline/cbm2/keyed_state.rb +95 -0
  27. data/lib/badline/cbm2/model.rb +44 -0
  28. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  29. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  30. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  31. data/lib/badline/cbm2/p500/model.rb +39 -0
  32. data/lib/badline/cbm2/p500/pages.rb +57 -0
  33. data/lib/badline/cbm2/p500.rb +264 -0
  34. data/lib/badline/cbm2/sid_page.rb +19 -0
  35. data/lib/badline/cbm2.rb +270 -0
  36. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  37. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  38. data/lib/badline/chips/acia.rb +307 -0
  39. data/lib/badline/chips/cia/timer.rb +2 -2
  40. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  41. data/lib/badline/chips/crtc.rb +3 -2
  42. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  43. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  44. data/lib/badline/chips/m6809/core.rb +149 -0
  45. data/lib/badline/chips/m6809/indexed.rb +82 -0
  46. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  47. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  48. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  49. data/lib/badline/chips/m6809/operations.rb +258 -0
  50. data/lib/badline/chips/m6809/plans.rb +143 -0
  51. data/lib/badline/chips/m6809/registers.rb +82 -0
  52. data/lib/badline/chips/m6809/stack.rb +140 -0
  53. data/lib/badline/chips/m6809.rb +13 -0
  54. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  55. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  56. data/lib/badline/chips/riot/timer.rb +91 -0
  57. data/lib/badline/chips/riot.rb +162 -0
  58. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  59. data/lib/badline/chips/rriot.rb +134 -0
  60. data/lib/badline/chips/sid/filter.rb +4 -4
  61. data/lib/badline/chips/sid/waveform.rb +6 -6
  62. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  63. data/lib/badline/chips/tpi.rb +233 -0
  64. data/lib/badline/chips/vic/bank.rb +60 -5
  65. data/lib/badline/chips/vic/sprite.rb +4 -3
  66. data/lib/badline/chips/vic_i/keyed_parts.rb +7 -2
  67. data/lib/badline/chips/vic_i.rb +84 -21
  68. data/lib/badline/data_path.rb +3 -3
  69. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  70. data/lib/badline/drives/datasette.rb +27 -3
  71. data/lib/badline/drives/ieee_models.rb +54 -0
  72. data/lib/badline/drives/model1001.rb +37 -0
  73. data/lib/badline/drives/model2031.rb +6 -0
  74. data/lib/badline/drives/model4040/disk.rb +62 -0
  75. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  76. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  77. data/lib/badline/drives/model4040/mechanism.rb +192 -0
  78. data/lib/badline/drives/model4040/parts.rb +20 -0
  79. data/lib/badline/drives/model4040.rb +179 -0
  80. data/lib/badline/drives/model8050/board.rb +77 -0
  81. data/lib/badline/drives/model8050/disk.rb +163 -0
  82. data/lib/badline/drives/model8050/dos_board.rb +91 -0
  83. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  84. data/lib/badline/drives/model8050/idle.rb +181 -0
  85. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  86. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  87. data/lib/badline/drives/model8050/keyed_state.rb +163 -0
  88. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  89. data/lib/badline/drives/model8050/parts.rb +37 -0
  90. data/lib/badline/drives/model8050.rb +131 -0
  91. data/lib/badline/drives/model9060/idle.rb +149 -0
  92. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  93. data/lib/badline/drives/model9060/parts.rb +98 -0
  94. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  95. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  96. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  97. data/lib/badline/drives/model9060.rb +195 -0
  98. data/lib/badline/frontend/app.rb +18 -3
  99. data/lib/badline/frontend/boot.rb +4 -3
  100. data/lib/badline/frontend/controls.rb +107 -15
  101. data/lib/badline/frontend/gamepads.rb +3 -3
  102. data/lib/badline/frontend/keys.rb +97 -2
  103. data/lib/badline/frontend/kim1_panel.rb +208 -0
  104. data/lib/badline/frontend/media_slots.rb +8 -1
  105. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  106. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  107. data/lib/badline/frontend/menu/menu_media.rb +47 -14
  108. data/lib/badline/frontend/menu/menu_pages.rb +113 -4
  109. data/lib/badline/frontend/menu/menu_ports.rb +77 -4
  110. data/lib/badline/frontend/menu/pause_menu.rb +6 -2
  111. data/lib/badline/frontend/pet_keyboard.rb +45 -7
  112. data/lib/badline/frontend/screen.rb +1 -1
  113. data/lib/badline/frontend/screens.rb +40 -2
  114. data/lib/badline/frontend/teletype_pane.rb +152 -0
  115. data/lib/badline/frontend/timeline.rb +49 -11
  116. data/lib/badline/frontend.rb +3 -0
  117. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  118. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  119. data/lib/badline/kernal_trap/dos.rb +18 -5
  120. data/lib/badline/kernal_trap/ieee.rb +4 -2
  121. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  122. data/lib/badline/kim1/bus.rb +117 -0
  123. data/lib/badline/kim1/cpu.rb +25 -0
  124. data/lib/badline/kim1/host_terminal.rb +74 -0
  125. data/lib/badline/kim1/key_entry.rb +74 -0
  126. data/lib/badline/kim1/keyed_modules.rb +53 -0
  127. data/lib/badline/kim1/keyed_state.rb +133 -0
  128. data/lib/badline/kim1/panel.rb +176 -0
  129. data/lib/badline/kim1/rriots.rb +28 -0
  130. data/lib/badline/kim1/sound.rb +29 -0
  131. data/lib/badline/kim1/tape.rb +116 -0
  132. data/lib/badline/kim1/teletype.rb +193 -0
  133. data/lib/badline/kim1.rb +190 -0
  134. data/lib/badline/machine/ieee_drives.rb +171 -0
  135. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  136. data/lib/badline/machine/line_sound.rb +122 -0
  137. data/lib/badline/machine/settling.rb +4 -0
  138. data/lib/badline/machine.rb +62 -14
  139. data/lib/badline/media/cbm2_media.rb +29 -0
  140. data/lib/badline/media/extensions.rb +8 -4
  141. data/lib/badline/media/kim1_media.rb +150 -0
  142. data/lib/badline/media/pet_media.rb +3 -1
  143. data/lib/badline/media/program.rb +6 -3
  144. data/lib/badline/media/true_drive.rb +51 -25
  145. data/lib/badline/media.rb +23 -8
  146. data/lib/badline/options/family.rb +108 -6
  147. data/lib/badline/options/help.rb +58 -12
  148. data/lib/badline/options/table.rb +60 -14
  149. data/lib/badline/options/validation.rb +71 -11
  150. data/lib/badline/options.rb +46 -13
  151. data/lib/badline/pet/bus.rb +16 -2
  152. data/lib/badline/pet/cassettes.rb +22 -1
  153. data/lib/badline/pet/ieee_drive.rb +5 -64
  154. data/lib/badline/pet/ieee_ports.rb +7 -0
  155. data/lib/badline/pet/keyboard_ports.rb +9 -2
  156. data/lib/badline/pet/keyed_state.rb +4 -0
  157. data/lib/badline/pet/model.rb +11 -2
  158. data/lib/badline/pet/rom_sets.rb +1 -0
  159. data/lib/badline/pet/sound.rb +5 -105
  160. data/lib/badline/pet/superpet/acia.rb +12 -0
  161. data/lib/badline/pet/superpet/board.rb +221 -0
  162. data/lib/badline/pet/superpet/dongle.rb +81 -0
  163. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  164. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  165. data/lib/badline/pet/superpet/m6809.rb +32 -0
  166. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  167. data/lib/badline/pet/superpet.rb +216 -0
  168. data/lib/badline/pet.rb +33 -6
  169. data/lib/badline/plus4/acia.rb +4 -298
  170. data/lib/badline/plus4/keyed_parts.rb +0 -44
  171. data/lib/badline/plus4/keyed_state.rb +2 -0
  172. data/lib/badline/plus4.rb +2 -0
  173. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  174. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  175. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  176. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  177. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  178. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  179. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  180. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  181. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  182. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  183. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  184. data/lib/badline/roms/README +277 -0
  185. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  186. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  187. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  188. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  189. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  190. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  191. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  192. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  193. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  194. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  195. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  196. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  197. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  198. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  199. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  200. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  201. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  202. data/lib/badline/snapshot/container.rb +9 -2
  203. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  204. data/lib/badline/snapshot/field_doc.rb +59 -3
  205. data/lib/badline/snapshot/image.rb +65 -9
  206. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  207. data/lib/badline/snapshot/pet_setup.rb +27 -11
  208. data/lib/badline/snapshot/storage_setup.rb +4 -1
  209. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  210. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  211. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  212. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  213. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  214. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  215. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  216. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  217. data/lib/badline/snapshot/vice/cias.rb +13 -9
  218. data/lib/badline/snapshot/vice/p500.rb +96 -0
  219. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  220. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  221. data/lib/badline/snapshot/vice/pet.rb +20 -10
  222. data/lib/badline/snapshot/vice/pet_cpu.rb +42 -5
  223. data/lib/badline/snapshot/vice/pet_crtc.rb +25 -17
  224. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  225. data/lib/badline/snapshot/vice/pet_drive8050.rb +336 -0
  226. data/lib/badline/snapshot/vice/pet_mem.rb +49 -5
  227. data/lib/badline/snapshot/vice/pet_restore.rb +100 -7
  228. data/lib/badline/snapshot/vice/pet_via.rb +6 -5
  229. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  230. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  231. data/lib/badline/snapshot/vice.rb +3 -0
  232. data/lib/badline/snapshot.rb +15 -3
  233. data/lib/badline/status.rb +30 -30
  234. data/lib/badline/storage/crt_file.rb +7 -1
  235. data/lib/badline/storage/d64_image.rb +7 -0
  236. data/lib/badline/storage/d67_image.rb +55 -0
  237. data/lib/badline/storage/d80_image.rb +17 -1
  238. data/lib/badline/storage/d81_image.rb +14 -0
  239. data/lib/badline/storage/d90_image.rb +180 -0
  240. data/lib/badline/storage/disk_image/bam.rb +18 -3
  241. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  242. data/lib/badline/storage/disk_image/writing.rb +37 -2
  243. data/lib/badline/storage/disk_image.rb +2 -0
  244. data/lib/badline/storage/kim_tape.rb +254 -0
  245. data/lib/badline/storage/papertape.rb +90 -0
  246. data/lib/badline/storage.rb +5 -0
  247. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  248. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  249. data/lib/badline/support/p500_checkpoint.rb +35 -0
  250. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  251. data/lib/badline/version.rb +1 -1
  252. data/lib/badline/vic20/keyed_modules.rb +6 -3
  253. data/lib/badline/vic20/keyed_state.rb +6 -4
  254. data/lib/badline/vic20.rb +26 -12
  255. data/lib/badline.rb +2 -0
  256. metadata +148 -2
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model8050
6
+ # Sleeping through the two processors' idle loops, exactly.
7
+ #
8
+ # With nothing to do, the IP goes round its DOS's idle loop from
9
+ # @ip_loop, and the FDC round its job loop from @fdc_loop (IP_LOOP and
10
+ # FDC_LOOP on the 8050), each pass finding the job queue empty, until
11
+ # ATN interrupts the IP or the 6530's timer the FDC, every 5 ms on the
12
+ # 8050. A pass puts back what it found,
13
+ # so each processor comes back to its loop's start as it left it, and
14
+ # only the chips' counters and the cycle counts move.
15
+ #
16
+ # Each time a processor comes to its loop's start, the unit compares
17
+ # what that processor sees with what it saw last time round: the CPU
18
+ # (Engine::IdleCPU#idle_state), its chips but for their counters, its
19
+ # RAM and the common RAM, and on the FDC's side the drives. The same,
20
+ # with no read of a counter or an interrupt flag between (the buses'
21
+ # volatile), makes the pass repeatable. Once both processors have a
22
+ # repeatable pass, with the motors off, no head stepping, no disk
23
+ # going in or out and no interrupt pulled, the unit sleeps:
24
+ # host_cycle! only counts the cycles it owes, until one of the chips'
25
+ # counters is due to pull IRQ (quiet_cycles), the controller moves a
26
+ # line of the bus, or something outside reads or changes the unit
27
+ # (settle!).
28
+ #
29
+ # Waking runs the counters on in bulk, counts off each processor's
30
+ # whole passes, and runs the rest of a pass on that processor alone.
31
+ # Its passes touch nothing the other one or the counters move, so the
32
+ # order the two run in doesn't matter. Then the unit runs cycle by
33
+ # cycle again. Woken by a counter, it keeps the passes it found, and
34
+ # sleeps again as soon as both repeat: the IP's, which the timer's
35
+ # interrupt doesn't reach, at once, and the FDC's a pass after its
36
+ # interrupt. Woken from outside, it finds both afresh.
37
+ module Idle
38
+ IP_LOOP = 0xc5a1
39
+ FDC_LOOP = 0xfc17
40
+
41
+ # Whether the unit may sleep through its idle loops.
42
+ attr_reader :idle_skip
43
+
44
+ def idle_skip=(on)
45
+ settle!
46
+ @idle_skip = on
47
+ end
48
+
49
+ def asleep? = @asleep
50
+
51
+ # Brings the unit up to the present, cycle for cycle, and drops the
52
+ # passes it found, since whoever called may change it.
53
+ def settle!
54
+ wake! if @asleep
55
+ forget_passes
56
+ end
57
+
58
+ private
59
+
60
+ # The unit's loops start at +ip_loop+ and +fdc_loop+.
61
+ def init_idle(ip_loop, fdc_loop)
62
+ @ip_loop = ip_loop
63
+ @fdc_loop = fdc_loop
64
+ @idle_skip = true
65
+ @asleep = false
66
+ @owed = 0
67
+ @budget = 0
68
+ forget_passes
69
+ end
70
+
71
+ def forget_passes
72
+ @ip_mark = nil
73
+ @fdc_mark = nil
74
+ @ip_pass = nil
75
+ @fdc_pass = nil
76
+ end
77
+
78
+ # A cycle owed while asleep, which wakes the unit once the budget
79
+ # is spent.
80
+ def owe_cycle
81
+ @owed += 1
82
+ wake! if @owed >= @budget
83
+ end
84
+
85
+ # Looks for the loops' starts after a cycle run awake. An interrupt
86
+ # pulled breaks the processor's pass.
87
+ def watch_loops
88
+ ip = @ip_cpu
89
+ @ip_pass = nil if ip.irq
90
+ @fdc_pass = nil if @fdc_cpu.irq
91
+ ip_pass_found if ip.program_counter == @ip_loop && ip.boundary?
92
+ fdc = @fdc_cpu
93
+ fdc_pass_found if fdc.program_counter == @fdc_loop && fdc.boundary?
94
+ end
95
+
96
+ def ip_pass_found
97
+ state = ip_state
98
+ @ip_pass = repeated(@ip_mark, state, @ip_cpu, @ip_bus.volatile)
99
+ @ip_mark = [state, @ip_cpu.cycles, @ip_cpu.instructions]
100
+ @ip_bus.volatile = false
101
+ fall_asleep if @ip_pass && @fdc_pass
102
+ end
103
+
104
+ def fdc_pass_found
105
+ state = fdc_state
106
+ @fdc_pass = repeated(@fdc_mark, state, @fdc_cpu, @fdc_bus.volatile)
107
+ @fdc_mark = [state, @fdc_cpu.cycles, @fdc_cpu.instructions]
108
+ @fdc_bus.volatile = false
109
+ fall_asleep if @ip_pass && @fdc_pass
110
+ end
111
+
112
+ # The pass since +mark+ as [cycles, instructions] if it left the
113
+ # processor as it found it, or nil.
114
+ def repeated(mark, state, cpu, volatile)
115
+ return nil if mark.nil? || volatile || mark[0] != state
116
+
117
+ [cpu.cycles - mark[1], cpu.instructions - mark[2]]
118
+ end
119
+
120
+ def fall_asleep
121
+ return unless @mechanism.quiet? && !@ip_cpu.irq && !@fdc_cpu.irq
122
+
123
+ budget = quiet_cycles
124
+ return if budget < 2
125
+
126
+ @asleep = true
127
+ @owed = 0
128
+ @budget = budget
129
+ end
130
+
131
+ # How many cycles the chips can run with no counter pulling IRQ.
132
+ def quiet_cycles
133
+ [@data_riot.quiet_cycles, @control_riot.quiet_cycles, @rriot.quiet_cycles, @via.quiet_cycles].min
134
+ end
135
+
136
+ # Runs the cycles owed: the counters in bulk, then each processor's
137
+ # whole passes and the rest of a pass.
138
+ def wake!
139
+ owed = @owed
140
+ @asleep = false
141
+ @owed = 0
142
+ return if owed.zero?
143
+
144
+ @data_riot.fast_forward(owed)
145
+ @control_riot.fast_forward(owed)
146
+ @rriot.fast_forward(owed)
147
+ @via.fast_forward(owed)
148
+ skip(@ip_cpu, @ip_pass, @ip_mark, owed)
149
+ skip(@fdc_cpu, @fdc_pass, @fdc_mark, owed)
150
+ @cycles += owed
151
+ end
152
+
153
+ # Counts off the processor's whole passes, moving its mark on with
154
+ # them, and runs the rest on the processor alone.
155
+ def skip(cpu, pass, mark, owed)
156
+ passes = owed / pass[0]
157
+ cpu.fast_forward(passes * pass[0], passes * pass[1])
158
+ mark[1] += passes * pass[0]
159
+ mark[2] += passes * pass[1]
160
+ (owed - (passes * pass[0])).times { cpu.cycle! }
161
+ end
162
+
163
+ # What the IP sees: its CPU, its RIOTs but for their counters, its
164
+ # bus's last byte and the common RAM, as the count of the writes
165
+ # that changed it.
166
+ def ip_state
167
+ [*@ip_cpu.idle_state, *@data_riot.idle_state, *@control_riot.idle_state, @ip_bus.data, common_changes]
168
+ end
169
+
170
+ # What the FDC sees: its CPU, the VIA and the 6530 but for their
171
+ # counters, its bus's last byte, the drives and the common RAM.
172
+ def fdc_state
173
+ [*@fdc_cpu.idle_state, *@via.idle_state, *@rriot.idle_state, @fdc_bus.data, *@mechanism.idle_state,
174
+ common_changes]
175
+ end
176
+
177
+ def common_changes = @ip_bus.common_changes + @fdc_bus.common_changes
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,147 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model8050
6
+ # The interface processor's two 6532s on the IEEE-488 bus
7
+ # (Drives::IEEEBus), through MC3446 transceivers (schematic 8050001,
8
+ # sheet 1). UC1 carries the data lines, its port A reading them and
9
+ # its port B driving them. UE1 carries the rest:
10
+ #
11
+ # PA0 ATN acknowledge (ATNA) PB0-2 the device number jumpers
12
+ # PA1 NDAC out, low while set PB3 drive 1's LED
13
+ # PA2 NRFD out PB4 drive 0's LED
14
+ # PA3 EOI out PB5 the error LED
15
+ # PA4 DAV out PB6 NDAC in
16
+ # PA5 EOI in PB7 NRFD in
17
+ # PA6 DAV in
18
+ # PA7 ATN in, high while ATN is low
19
+ #
20
+ # The ports read and drive the lines' levels, 0 for a low line, but
21
+ # for NDAC out, which pulls NDAC low while set. The DOS sets it as it
22
+ # waits for a byte and clears it once it has taken one ($C627-$C650).
23
+ # Every input reads the bus, the drive's own lines included.
24
+ #
25
+ # ATN in also reaches UE1's PA7 edge detector, which the DOS arms for
26
+ # the rising edge ($C58A), so ATN interrupts the interface processor.
27
+ # While ATN in and ATNA differ, the board pulls NRFD and NDAC low, so
28
+ # the drive answers ATN before the DOS gets to it, as the 2031 does.
29
+ #
30
+ # The device number jumpers ground PB0-2: cutting one adds its weight
31
+ # to 8.
32
+ #
33
+ # The drive takes the controller's lines a host cycle late, as the
34
+ # 2031 does: latch_host takes them at the end of each host cycle.
35
+ class IEEEPort
36
+ ATNA = 0x01
37
+ NDAC_OUT = 0x02
38
+ NRFD_OUT = 0x04
39
+ EOI_OUT = 0x08
40
+ DAV_OUT = 0x10
41
+ EOI_IN = 0x20
42
+ DAV_IN = 0x40
43
+ ATN_IN = 0x80
44
+ NDAC_IN = 0x40
45
+ NRFD_IN = 0x80
46
+
47
+ attr_reader :bus
48
+
49
+ # The lines the controller pulled, as latch_host took them.
50
+ attr_accessor :host
51
+
52
+ # The RIOTs on the data lines and on the control lines.
53
+ attr_accessor :data_riot, :control_riot
54
+
55
+ def initialize(device:, bus:)
56
+ raise ArgumentError, "an 8050 is device 8 to 15, not #{device}" unless device.between?(8, 15)
57
+
58
+ @jumpers = (device - 8) | 0xf8
59
+ @host = 0
60
+ @data_riot = nil
61
+ @control_riot = nil
62
+ self.bus = bus
63
+ end
64
+
65
+ def bus=(bus)
66
+ @bus = bus
67
+ latch_host
68
+ end
69
+
70
+ # Takes the controller's lines for the drive cycles of the next host
71
+ # cycle.
72
+ def latch_host
73
+ @host = @bus.host_lines
74
+ end
75
+
76
+ # Whether the controller has moved ATN since the last latch_host.
77
+ def atn_moved? = (@bus.host_lines ^ @host).anybits?(IEEEBus::ATN)
78
+
79
+ # Whether the controller has moved any line since the last
80
+ # latch_host.
81
+ def lines_moved? = @bus.host_lines != @host
82
+
83
+ def atn_low? = @host.anybits?(IEEEBus::ATN)
84
+
85
+ # UC1's port A: the data lines.
86
+ def data_lines = ~@bus.low_lines(@host) & 0xff
87
+
88
+ # UE1's port A inputs. ATN in comes through the edge detector's own
89
+ # pin (RIOT#pa7=), so bit 7 is left high here.
90
+ def control_a
91
+ low = @bus.low_lines(@host)
92
+ value = 0xff
93
+ value &= ~EOI_IN if low.anybits?(IEEEBus::EOI)
94
+ value &= ~DAV_IN if low.anybits?(IEEEBus::DAV)
95
+ value
96
+ end
97
+
98
+ # UE1's port B inputs: the jumpers, NDAC and NRFD.
99
+ def control_b
100
+ low = @bus.low_lines(@host)
101
+ value = @jumpers
102
+ value &= ~NDAC_IN if low.anybits?(IEEEBus::NDAC)
103
+ value &= ~NRFD_IN if low.anybits?(IEEEBus::NRFD)
104
+ value
105
+ end
106
+
107
+ # The lines the drive pulls low while ATN is low or not, as bus bits.
108
+ def ieee_low_lines(atn)
109
+ control = @control_riot.port_a_output
110
+ low = ~@data_riot.port_b_output & IEEEBus::DIO
111
+ low |= IEEEBus::EOI if control.nobits?(EOI_OUT)
112
+ low |= IEEEBus::DAV if control.nobits?(DAV_OUT)
113
+ low |= IEEEBus::NRFD if control.nobits?(NRFD_OUT)
114
+ low |= IEEEBus::NDAC if control.anybits?(NDAC_OUT)
115
+ low |= IEEEBus::NRFD | IEEEBus::NDAC if atn != control.anybits?(ATNA)
116
+ low
117
+ end
118
+
119
+ # Which LEDs UE1 lights: bit 0 drive 1's, bit 1 drive 0's, bit 2 the
120
+ # error LED.
121
+ def leds = (@control_riot.port_b_output >> 3) & 0x07
122
+ end
123
+
124
+ # UC1's peripheral: the data lines on port A, port B only driving.
125
+ class DataLines
126
+ def initialize(port)
127
+ @port = port
128
+ end
129
+
130
+ def read_a(_lines) = @port.data_lines
131
+
132
+ def read_b(_lines) = 0xff
133
+ end
134
+
135
+ # UE1's peripheral: the control lines and the jumpers.
136
+ class ControlLines
137
+ def initialize(port)
138
+ @port = port
139
+ end
140
+
141
+ def read_a(_lines) = @port.control_a
142
+
143
+ def read_b(_lines) = @port.control_b
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model8050
6
+ # The interface processor's bus (schematic 8050001, sheet 1). A
7
+ # 74LS42 on A12-A15 picks a 4 KB block:
8
+ #
9
+ # $0000-$0FFF - the two 6532s. A9 is their RS pin and A7 picks one:
10
+ # UC1's RAM at $00-$7F and UE1's at $80-$FF, mirrored at
11
+ # $0100 for the stack since A8 isn't decoded, and their
12
+ # registers at $0200 (UC1) and $0280 (UE1)
13
+ # $1000-$4FFF - the common RAM, 1 KB in each block, mirrored through it
14
+ # $5000-$BFFF - nothing: open bus
15
+ # $C000-$FFFF - DOS 2.7, 901887-01 and 901888-01
16
+ #
17
+ # Writes to the ROM and to open bus go nowhere, and a read from open
18
+ # bus finds the last byte on the data bus.
19
+ class IPBus
20
+ include Addressable
21
+
22
+ attr_reader :common, :rom, :data_riot, :control_riot
23
+ attr_accessor :data
24
+
25
+ # How many writes have changed a byte of the common RAM
26
+ # (Model8050::Idle).
27
+ attr_reader :common_changes
28
+
29
+ # Whether a timer or the interrupt flags have been read since it was
30
+ # last cleared (Model8050::Idle).
31
+ attr_accessor :volatile
32
+
33
+ def initialize(rom:, common:, data_riot:, control_riot:)
34
+ addressable_at(0, length: 2**16)
35
+ @rom = rom
36
+ @common = common
37
+ @data_riot = data_riot
38
+ @control_riot = control_riot
39
+ @data = 0
40
+ @volatile = false
41
+ @common_changes = 0
42
+ end
43
+
44
+ def peek(addr)
45
+ return @data = @rom.peek(addr) if addr >= 0xc000
46
+
47
+ @data = if addr < 0x1000
48
+ peek_riots(addr)
49
+ elsif addr < 0x5000
50
+ @common.peek((((addr >> 12) - 1) << 10) | (addr & 0x03ff))
51
+ else
52
+ @data
53
+ end
54
+ end
55
+
56
+ def poke(addr, value)
57
+ @data = value
58
+ return if addr >= 0x5000
59
+
60
+ if addr < 0x1000
61
+ poke_riots(addr, value)
62
+ else
63
+ poke_common((((addr >> 12) - 1) << 10) | (addr & 0x03ff), value)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def poke_common(index, value)
70
+ @common_changes += 1 if @common.peek(index) != value
71
+ @common.poke(index, value)
72
+ end
73
+
74
+ def peek_riots(addr)
75
+ @volatile = true if addr & 0x204 == 0x204
76
+ if addr.anybits?(0x80)
77
+ addr.anybits?(0x200) ? @control_riot.peek(addr & 0x1f) : @control_riot.read_ram(addr)
78
+ else
79
+ addr.anybits?(0x200) ? @data_riot.peek(addr & 0x1f) : @data_riot.read_ram(addr)
80
+ end
81
+ end
82
+
83
+ def poke_riots(addr, value)
84
+ if addr.anybits?(0x80)
85
+ addr.anybits?(0x200) ? @control_riot.poke(addr & 0x1f, value) : @control_riot.write_ram(addr, value)
86
+ else
87
+ addr.anybits?(0x200) ? @data_riot.poke(addr & 0x1f, value) : @data_riot.write_ram(addr, value)
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
93
+ end
@@ -0,0 +1,163 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model8050
6
+ class Disk
7
+ # A disk's fields in a keyed snapshot module (BL-D8-DISK, BL-D8-DISK1
8
+ # for drive 1): the image file's path and how it was opened, the
9
+ # image as last flushed, embedded under image and image.format, each
10
+ # track whose cells differ from what the image lays out, as
11
+ # track<N>, a cell per byte or 256 for a sync, and the tracks
12
+ # written since the last flush.
13
+ module KeyedState
14
+ IMAGE_FORMATS = %w[d80 d82].freeze
15
+
16
+ def save_fields(out)
17
+ image = @image
18
+ out.text("path", image.path.to_s).bool("read_only", image.read_only?)
19
+ out.text("image.format", image_format)
20
+ out.bytes("image", image.contents)
21
+ @tracks.each do |number, cells|
22
+ out.ints("track#{number}", cells) unless cells == build(number)
23
+ end
24
+ out.ints("written", @written.keys)
25
+ end
26
+
27
+ # Puts back the tracks and the writes not yet flushed.
28
+ def load_track_fields(input)
29
+ (1..(side_tracks * sides)).each do |number|
30
+ cells = input.ints("track#{number}", [])
31
+ @tracks[number] = cells.dup unless cells.empty?
32
+ end
33
+ input.ints("written", []).each { |number| @written[number] = true }
34
+ self
35
+ end
36
+ end
37
+
38
+ include KeyedState
39
+
40
+ # The kind of image the disk lays out, as image.format names it.
41
+ def image_format = @image.is_a?(Storage::D82Image) ? "d82" : "d80"
42
+
43
+ # The disk a BL-D8-DISK module holds. A detached reader gets the
44
+ # image write-protected, and it and a host file that is gone leave
45
+ # it no path to write back to.
46
+ def self.from_fields(input, detached: false)
47
+ path = input.text("path", "")
48
+ read_only = input.bool("read_only", false) || detached
49
+ kind = input.choice("image.format", KeyedState::IMAGE_FORMATS, "d80")
50
+ bytes = input.bytes("image", "".b).bytes
51
+ path = "" if detached || !File.file?(path)
52
+ image_class = kind == "d82" ? Storage::D82Image : Storage::D80Image
53
+ new(image_class.new(path, read_only:, bytes:)).load_track_fields(input)
54
+ end
55
+ end
56
+
57
+ class Mechanism
58
+ # The drives' fields in a keyed snapshot module (BL-D8-MECH), a value
59
+ # a drive in each list: the heads' cylinders, the stepper phases,
60
+ # each disk's place in its turn and its bit clock, the disk change
61
+ # under way and the motors, then the byte ready pulse, the byte on
62
+ # VIA port A and SYNC. The disks load from their own modules.
63
+ module KeyedState
64
+ def save_fields(out)
65
+ out.ints("cylinders", @cylinders).ints("phases", @phases).ints("positions", @positions)
66
+ out.ints("bit_clocks", @clocks).ints("changing", @changing)
67
+ out.ints("motors", @motors.map { |on| on ? 1 : 0 }).ints("syncs", @syncs.map { |low| low ? 1 : 0 })
68
+ out.bool("so_pending", @so_pending).bool("ca1_low", @ca1_low).int("data_in", @data_in)
69
+ end
70
+
71
+ def load_fields(input, disks)
72
+ @disks = disks
73
+ input.ints_into("cylinders", @cylinders)
74
+ input.ints_into("phases", @phases)
75
+ input.ints_into("positions", @positions)
76
+ input.ints_into("bit_clocks", @clocks)
77
+ input.ints_into("changing", @changing)
78
+ @motors = input.ints("motors", [0, 0]).map { |on| on == 1 }
79
+ @syncs = input.ints("syncs", [0, 0]).map { |low| low == 1 }
80
+ @so_pending = input.bool("so_pending", @so_pending)
81
+ @ca1_low = input.bool("ca1_low", @ca1_low)
82
+ @data_in = input.byte("data_in", @data_in)
83
+ end
84
+ end
85
+
86
+ include KeyedState
87
+ end
88
+
89
+ # The unit's own fields in a keyed snapshot module (BL-D8): its model,
90
+ # its clock phase and cycle count, each bus's last byte, and the
91
+ # controller's lines as the port latched them, under ieee.*. A save
92
+ # settles the unit first, so it is awake; a load finds its passes
93
+ # afresh.
94
+ module KeyedState
95
+ def save_fields(out)
96
+ settle!
97
+ out.text("model", model_name).int("phase", @phase).int("cycles", @cycles)
98
+ out.int("ip_data", @ip_bus.data).int("fdc_data", @fdc_bus.data)
99
+ out.int("ieee.host", @ieee_port.host)
100
+ out
101
+ end
102
+
103
+ def load_fields(input)
104
+ settle!
105
+ input.choice("model", [model_name], model_name)
106
+ @phase = input.int("phase", @phase)
107
+ @cycles = input.int("cycles", @cycles)
108
+ @ip_bus.data = input.byte("ip_data", @ip_bus.data)
109
+ @fdc_bus.data = input.byte("fdc_data", @fdc_bus.data)
110
+ @ieee_port.host = input.int("ieee.host", @ieee_port.host)
111
+ end
112
+
113
+ # The unit as modules of its own, named from `name` (BL-D8 for
114
+ # device 8): its own fields, the common RAM, UC1 and UE1 with their
115
+ # RAM, each processor, the VIA, the 6530, the drives and each disk
116
+ # in, drive 1's as -DISK1.
117
+ def save_modules(sections, name)
118
+ sections << Snapshot::Keyed.section(name) { |out| save_fields(out) }
119
+ sections << Snapshot::Keyed.section("#{name}-COMMON") { |out| @common.save_fields(out, "ram") }
120
+ sections << Snapshot::Keyed.section("#{name}-RIOT1") { |out| @data_riot.save_fields(out) }
121
+ sections << Snapshot::Keyed.section("#{name}-RIOT2") { |out| @control_riot.save_fields(out) }
122
+ sections << Snapshot::Keyed.section("#{name}-IPCPU") { |out| @ip_cpu.save_fields(out) }
123
+ sections << Snapshot::Keyed.section("#{name}-FDCCPU") { |out| @fdc_cpu.save_fields(out) }
124
+ sections << Snapshot::Keyed.section("#{name}-VIA") { |out| @via.save_fields(out) }
125
+ sections << Snapshot::Keyed.section("#{name}-RRIOT") { |out| @rriot.save_fields(out) }
126
+ sections << Snapshot::Keyed.section("#{name}-MECH") { |out| @mechanism.save_fields(out) }
127
+ [0, 1].each do |drive|
128
+ disk = @mechanism.disk(drive)
129
+ sections << Snapshot::Keyed.section(disk_module(name, drive)) { |out| disk.save_fields(out) } if disk
130
+ end
131
+ sections
132
+ end
133
+
134
+ # The unit from its modules, `input` being its own. No disk module
135
+ # means no disk in that drive.
136
+ def load_modules(modules, input, name)
137
+ load_fields(input)
138
+ @common.load_fields(modules.required("#{name}-COMMON"), "ram")
139
+ @data_riot.load_fields(modules.required("#{name}-RIOT1"))
140
+ @control_riot.load_fields(modules.required("#{name}-RIOT2"))
141
+ @ip_cpu.load_fields(modules.required("#{name}-IPCPU"))
142
+ @fdc_cpu.load_fields(modules.required("#{name}-FDCCPU"))
143
+ @via.load_fields(modules.required("#{name}-VIA"))
144
+ @rriot.load_fields(modules.required("#{name}-RRIOT"))
145
+ disks = [0, 1].map do |drive|
146
+ disk = modules.optional(disk_module(name, drive))
147
+ disk.nil? ? nil : disk_from_fields(disk, modules.detached?)
148
+ end
149
+ @mechanism.load_fields(modules.required("#{name}-MECH"), disks)
150
+ @via.restored!
151
+ @ip_cpu.restored!
152
+ @fdc_cpu.restored!
153
+ end
154
+
155
+ private
156
+
157
+ def disk_module(name, drive) = drive.zero? ? "#{name}-DISK" : "#{name}-DISK1"
158
+ end
159
+
160
+ include KeyedState
161
+ end
162
+ end
163
+ end