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,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class PET
5
+ class SuperPET < PET
6
+ # The SuperPET as keyed snapshot modules: the 8032's (PET::KeyedModules),
7
+ # then BL-SUPERPET with the board and the 6702 under dongle.*, BL-ACIA
8
+ # and BL-M6809. The traps on the Waterloo ROM's routines go in
9
+ # BL-TRAPDOS under waterloo.*, beside the KERNAL's. Without BL-ACIA or
10
+ # BL-M6809, as from xpet, the unit stays as it is. The CPU switch and
11
+ # the latches say which CPU has the bus, and the CPU that has it
12
+ # settles on its instruction boundary, or in SYNC or CWAI.
13
+ module KeyedModules
14
+ def settled_for_snapshot?
15
+ return super unless @m6809_running
16
+
17
+ @m6809.boundary? || @m6809.waiting?
18
+ end
19
+
20
+ def save_modules
21
+ sections = super
22
+ board = @board
23
+ dongle = @dongle
24
+ sections << Snapshot::Keyed.section("BL-SUPERPET") do |out|
25
+ board.save_fields(out)
26
+ dongle.save_fields(out, "dongle.")
27
+ end
28
+ sections << Snapshot::Keyed.section("BL-ACIA") { |out| @acia.save_fields(out) }
29
+ sections << Snapshot::Keyed.section("BL-M6809") { |out| @m6809.save_fields(out) }
30
+ sections
31
+ end
32
+
33
+ def load_modules(modules)
34
+ super
35
+ board = modules.required("BL-SUPERPET")
36
+ @board.load_fields(board)
37
+ @dongle.load_fields(board, "dongle.")
38
+ acia = modules.optional("BL-ACIA")
39
+ @acia.load_fields(acia) if acia
40
+ m6809 = modules.optional("BL-M6809")
41
+ @m6809.load_fields(m6809) if m6809
42
+ @m6809_running = @board.m6809?
43
+ @board.restored!
44
+ @acia.restored!
45
+ @m6809.restored!
46
+ self
47
+ end
48
+
49
+ def save_trap_dos_fields(out)
50
+ super
51
+ @waterloo_traps&.save_fields(out, "waterloo.")
52
+ end
53
+
54
+ def load_trap_dos_fields(input, detached: false)
55
+ super
56
+ @waterloo_traps&.load_fields(input, "waterloo.")
57
+ end
58
+ end
59
+
60
+ include KeyedModules
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class PET
5
+ class SuperPET < PET
6
+ class Board
7
+ # The latches, the socket bit and the 64K of bank RAM, in a keyed
8
+ # snapshot module (BL-SUPERPET). The switches come with the setup
9
+ # (Snapshot::PETSetup). Which CPU has the bus, and the diagnostic
10
+ # sense line, follow from the latches once loaded, and the pages
11
+ # are laid out again (#restored!).
12
+ module KeyedState
13
+ def save_fields(out)
14
+ out.int("bank_latch", @bank_latch).int("system_latch", @system_latch).int("socket", @socket)
15
+ out.bytes("ram", @cells)
16
+ end
17
+
18
+ def load_fields(input)
19
+ @bank_latch = input.byte("bank_latch", 0)
20
+ @system_latch = input.byte("system_latch", 0)
21
+ @socket = input.int("socket", 0) & 0x01
22
+ input.bytes_into("ram", @cells)
23
+ @m6809 = m6809_selected?
24
+ @keyboard_ports.diagnostic_low = @system_latch.anybits?(0x08)
25
+ end
26
+
27
+ def restored! = @bus.map_pages
28
+ end
29
+
30
+ include KeyedState
31
+ end
32
+
33
+ class Dongle
34
+ # The shift registers, the latch and a shift waiting, as the cycles
35
+ # from now to the one it ends, under `prefix`.
36
+ module KeyedState
37
+ def save_fields(out, prefix)
38
+ out.ints("#{prefix}registers", @registers).int("#{prefix}latch", @latch)
39
+ out.optional_int("#{prefix}shift_in", @shift_at == NONE ? nil : @shift_at - @clock.cycles)
40
+ end
41
+
42
+ def load_fields(input, prefix)
43
+ input.ints_into("#{prefix}registers", @registers)
44
+ @latch = input.byte("#{prefix}latch", 0xff)
45
+ shift_in = input.optional_int("#{prefix}shift_in")
46
+ @shift_at = shift_in.nil? ? NONE : @clock.cycles + shift_in
47
+ end
48
+ end
49
+
50
+ include KeyedState
51
+ end
52
+
53
+ class WaterlooTraps
54
+ # Whether a served device listens or talks, the channel and the
55
+ # command it was addressed with, and the bytes sent it so far,
56
+ # under `prefix`.
57
+ module KeyedState
58
+ def save_fields(out, prefix)
59
+ out.bool("#{prefix}listening", @listening).bool("#{prefix}talking", @talking)
60
+ out.int("#{prefix}channel", @channel).int("#{prefix}frame", @frame).ints("#{prefix}buffer", @buffer)
61
+ end
62
+
63
+ def load_fields(input, prefix)
64
+ @listening = input.bool("#{prefix}listening", false)
65
+ @talking = input.bool("#{prefix}talking", false)
66
+ @channel = input.int("#{prefix}channel", 0) & 0x0f
67
+ @frame = input.byte("#{prefix}frame", 0)
68
+ @buffer = input.ints("#{prefix}buffer", []).map { |byte| byte & 0xff }
69
+ end
70
+ end
71
+
72
+ include KeyedState
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class PET
5
+ class SuperPET < PET
6
+ # The board's 6809E, on the PET's bus, as the 6502 is. A trap
7
+ # installed on an address runs as the 6809 is about to fetch an
8
+ # opcode there, and may move the PC on.
9
+ class M6809
10
+ include Chips::M6809::Core
11
+
12
+ def initialize(bus)
13
+ super
14
+ @traps = nil
15
+ end
16
+
17
+ def install_trap(addr, &handler)
18
+ (@traps ||= {})[addr] = handler
19
+ end
20
+
21
+ def remove_trap(addr)
22
+ @traps&.delete(addr)
23
+ end
24
+
25
+ def cycle!
26
+ @traps[@pc]&.call if @traps && boundary?
27
+ super
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class PET
5
+ class SuperPET < PET
6
+ # The traps on the Waterloo 6809 ROM's IEEE-488 routines, which serve
7
+ # device 8 from the virtual drive (KernalTrap::DOS) the 6502's traps
8
+ # serve it from, after the ROM's source as Mike Naberezny hosts it
9
+ # (opsys-all-clean-20101210-src.txt). Other devices fall through to
10
+ # the ROM, which drives the bus.
11
+ #
12
+ # The routines take their argument in D and return with RTS: SetLstnr
13
+ # and SetTalkr a file control block, whose byte 9 is the device, byte
14
+ # 10 the secondary address and byte 0 the status, 3 after a timeout,
15
+ # SetLstnr with the command's offset pushed before the call ($F0 to
16
+ # open, $E0 to close, $60 for data), ByteOut_ the byte to send and
17
+ # the byte-in routine none, returning the byte in D and setting bit 0
18
+ # of the block's byte 12 at EOI. Each keeps the block's address at
19
+ # $02 and the bus at $77-$7A: $20 listening or $40 talking, the
20
+ # offset, the device and the secondary address. A language that hooks
21
+ # a routine through its flag at $0580 gets the ROM's routine, which
22
+ # calls the hook.
23
+ #
24
+ # The menu loads from "disk/1.", drive 1 of a dual drive. The virtual
25
+ # drive has one, so a name on drive 1 opens the file on drive 0.
26
+ class WaterlooTraps
27
+ BYTE_OUT = 0xbe6f
28
+ BYTE_IN = 0xbefb
29
+ SET_LISTENER = 0xc072
30
+ UNLISTEN = 0xc0d0
31
+ SET_TALKER = 0xc0dd
32
+ UNTALK = 0xc13c
33
+
34
+ # The routines' user hook numbers, whose flags sit at $0580 on.
35
+ HOOKS = { BYTE_OUT => 23, BYTE_IN => 24, SET_LISTENER => 27, UNLISTEN => 28, SET_TALKER => 29,
36
+ UNTALK => 30 }.freeze
37
+
38
+ BLOCK = 0x02
39
+ ACTIVE = 0x77
40
+ LISTENING = 0x20
41
+ TALKING = 0x40
42
+ TIMED_OUT = 3
43
+
44
+ OPEN = 0xf0
45
+ CLOSE = 0xe0
46
+
47
+ # The device the traps serve, or nil to serve none.
48
+ attr_accessor :device
49
+
50
+ def initialize(cpu:, bus:, dos:)
51
+ @cpu = cpu
52
+ @bus = bus
53
+ @dos = dos
54
+ @device = KernalTrap::Routine::DEVICE
55
+ @listening = false
56
+ @talking = false
57
+ @channel = 0
58
+ @frame = 0
59
+ @buffer = []
60
+ end
61
+
62
+ def install
63
+ HOOKS.each_key { |address| @cpu.install_trap(address) { call(address) } }
64
+ self
65
+ end
66
+
67
+ def uninstall
68
+ HOOKS.each_key { |address| @cpu.remove_trap(address) }
69
+ end
70
+
71
+ # Whether a served device listens and talks, the channel and the
72
+ # command, and the bytes sent so far, for a checkpoint.
73
+ def state = [@listening ? 1 : 0, @talking ? 1 : 0, @channel, @frame, *@buffer]
74
+
75
+ private
76
+
77
+ def call(address)
78
+ return unless @bus.peek(0x0580 + HOOKS.fetch(address)).zero?
79
+
80
+ case address
81
+ when SET_LISTENER then set_listener
82
+ when BYTE_OUT then byte_out
83
+ when UNLISTEN then unlisten
84
+ when SET_TALKER then set_talker
85
+ when BYTE_IN then byte_in
86
+ else untalk
87
+ end
88
+ end
89
+
90
+ def served?(block) = !@device.nil? && @bus.peek(block + 9) == @device
91
+
92
+ def set_listener
93
+ block = @cpu.d
94
+ return unless served?(block)
95
+
96
+ offset = @bus.peek(@cpu.s + 3)
97
+ select(block, LISTENING, offset)
98
+ @listening = true
99
+ @frame = offset & 0xf0
100
+ @buffer = []
101
+ @dos.close(@channel) if @frame == CLOSE
102
+ return_from_routine
103
+ end
104
+
105
+ def set_talker
106
+ block = @cpu.d
107
+ return unless served?(block)
108
+
109
+ select(block, TALKING, 0x60)
110
+ @talking = true
111
+ return_from_routine
112
+ end
113
+
114
+ # Ends what the bus was doing, then addresses the block's device as
115
+ # the routine does.
116
+ def select(block, active, offset)
117
+ finish_listening if @listening
118
+ @talking = false
119
+ @bus.poke(BLOCK, block >> 8)
120
+ @bus.poke(BLOCK + 1, block & 0xff)
121
+ @bus.poke(ACTIVE, active)
122
+ @bus.poke(ACTIVE + 1, offset)
123
+ @bus.poke(ACTIVE + 2, @bus.peek(block + 9))
124
+ secondary = @bus.peek(block + 10)
125
+ @bus.poke(ACTIVE + 3, secondary)
126
+ @channel = secondary & 0x0f
127
+ end
128
+
129
+ def byte_out
130
+ return unless @listening
131
+
132
+ block = current_block
133
+ if @bus.peek(block).zero?
134
+ @buffer << (@cpu.d & 0xff)
135
+ @bus.poke(block, TIMED_OUT) unless accepted?
136
+ end
137
+ @cpu.d = 0x00ff
138
+ return_from_routine
139
+ end
140
+
141
+ def accepted? = [OPEN, CLOSE].include?(@frame) || @dos.listening?(@channel)
142
+
143
+ def unlisten
144
+ return unless @listening
145
+
146
+ finish_listening
147
+ @bus.poke(ACTIVE, 0)
148
+ return_from_routine
149
+ end
150
+
151
+ def finish_listening
152
+ @listening = false
153
+ case @frame
154
+ when OPEN then @dos.open(@channel, on_drive_zero(@buffer.pack("C*")))
155
+ when CLOSE then nil
156
+ else @dos.write(@channel, @buffer)
157
+ end
158
+ @buffer = []
159
+ end
160
+
161
+ def on_drive_zero(name) = name.start_with?("1:") ? "0:#{name[2..]}" : name
162
+
163
+ def byte_in
164
+ return unless @talking
165
+
166
+ block = current_block
167
+ byte = 0x0d
168
+ if @bus.peek(block).zero?
169
+ read, eoi = @dos.read(@channel)
170
+ if read.nil?
171
+ @bus.poke(block, TIMED_OUT)
172
+ else
173
+ byte = read
174
+ @bus.poke(block + 12, @bus.peek(block + 12) | 0x01) if eoi
175
+ end
176
+ end
177
+ @cpu.d = byte
178
+ return_from_routine
179
+ end
180
+
181
+ def untalk
182
+ return unless @talking
183
+
184
+ @talking = false
185
+ @bus.poke(ACTIVE, 0)
186
+ return_from_routine
187
+ end
188
+
189
+ def current_block = (@bus.peek(BLOCK) << 8) | @bus.peek(BLOCK + 1)
190
+
191
+ def return_from_routine
192
+ s = @cpu.s
193
+ @cpu.pc = (@bus.peek(s) << 8) | @bus.peek(s + 1)
194
+ @cpu.s = (s + 2) & 0xffff
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,216 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/acia"
4
+ require "badline/chips/m6809"
5
+ require "badline/pet/superpet/dongle"
6
+ require "badline/pet/superpet/acia"
7
+ require "badline/pet/superpet/board"
8
+ require "badline/pet/superpet/m6809"
9
+ require "badline/pet/superpet/waterloo_traps"
10
+ require "badline/pet/superpet/keyed_parts"
11
+ require "badline/pet/superpet/keyed_modules"
12
+
13
+ module Badline
14
+ class PET
15
+ # The SuperPET, the SP9000 or MMF 9000: an 8032 with the University of
16
+ # Waterloo's board in its CPU socket, with 64K of bank RAM, a 6551
17
+ # ACIA, the 6702 and a 6809E (Board). The board's ACIA pulls the PET's
18
+ # IRQ line with the PIAs and the VIA. Its character ROM, 901640-01,
19
+ # holds the 8032's in its first half and an ASCII and an APL set in
20
+ # its second, which the CRTC's MA13 picks.
21
+ #
22
+ # Both CPUs sit on the same bus at the same 1 MHz, and one runs at a
23
+ # time: SW2, the CPU switch, picks it, or with SW2 at PROG bit 0 of the
24
+ # system latch. The one that takes the bus over starts from its RESET
25
+ # (Fachat: "The 6502 does a RESET after being switched on"; on the
26
+ # 324037 schematic one-shots gate the 6809's RES), and the other stops
27
+ # where it is. The PET's IRQ line, the ACIA's with it, reaches both.
28
+ #
29
+ # In 6809 mode device 8 answers through traps on the Waterloo ROM's
30
+ # IEEE-488 routines (WaterlooTraps) as well as the KERNAL's, from the
31
+ # same disk, or a true drive on the bus answers. type_text types
32
+ # through the Waterloo ROM's key buffer, in ASCII, once its menu is up.
33
+ class SuperPET < PET
34
+ # The Waterloo ROM's key buffer: its read and write pointers, and the
35
+ # ring of bytes they run round.
36
+ KEYS_READ = 0x012c
37
+ KEYS_WRITE = 0x012e
38
+ KEYS_FIRST = 0x0130
39
+ KEYS_END = 0x0158
40
+
41
+ # The cycle by which the 6809 has put up the Waterloo menu, with a
42
+ # margin.
43
+ WATERLOO_BOOT = 600_000
44
+
45
+ attr_reader :board, :acia, :dongle, :m6809
46
+
47
+ # The traps on the Waterloo ROM's IEEE-488 routines, while a disk is
48
+ # on device 8 (WaterlooTraps).
49
+ attr_reader :waterloo_traps
50
+
51
+ # +cpu+ is SW2's position and +bank_ram+ SW1's, as Board names them.
52
+ # The other arguments are the PET's for an 8032, its true drive's too.
53
+ def initialize(refresh_hz: 50, debug: false, basic: nil, editor: nil, cpu: "6502", bank_ram: "rw",
54
+ true_drive_model: Drives::IEEEModels::DEFAULT)
55
+ raise ArgumentError, "the CPU switch has no position #{cpu}" unless Board::CPU_SWITCH.include?(cpu)
56
+
57
+ super(model: "superpet", refresh_hz:, debug:, basic:, editor:, true_drive_model:)
58
+ @acia_irq = false
59
+ @acia_due = Chips::ACIA::NEVER
60
+ @acia = ACIA.new(self, 1_000_000)
61
+ @dongle = Dongle.new(self)
62
+ @board = Board.new(@bus, dongle: @dongle, acia: @acia, keyboard_ports: @keyboard_ports, cpu_switch: cpu,
63
+ ram_switch: bank_ram, refresh_hz: @refresh_hz)
64
+ @m6809 = M6809.new(@bus)
65
+ @m6809_running = @board.m6809?
66
+ @waterloo_traps = nil
67
+ @board.on_select { cpu_selected }
68
+ @bus.fit_board(@board)
69
+ @init_threshold = WATERLOO_BOOT if @m6809_running
70
+ end
71
+
72
+ # Whether an autostart finds BASIC: not while the 6809 runs.
73
+ def boots_basic? = !@m6809_running
74
+
75
+ # Device 8 through the traps, for the Waterloo ROM's routines too.
76
+ def mount(storage)
77
+ trap = super
78
+ @waterloo_traps ||= WaterlooTraps.new(cpu: @m6809, bus: @bus, dos: @dos).install
79
+ @waterloo_traps.device = trap_device
80
+ trap
81
+ end
82
+
83
+ def unmount
84
+ super
85
+ @waterloo_traps&.uninstall
86
+ @waterloo_traps = nil
87
+ end
88
+
89
+ def attach_ieee_drive(drive)
90
+ super
91
+ @waterloo_traps&.device = trap_device
92
+ end
93
+
94
+ def detach_ieee_drive
95
+ super
96
+ @waterloo_traps&.device = trap_device
97
+ end
98
+
99
+ # Types into the Waterloo ROM's key buffer, in ASCII, while the 6809
100
+ # runs, and into the editor's otherwise.
101
+ def type_text(text)
102
+ return super unless @m6809_running
103
+
104
+ type_on_init(text.bytes)
105
+ end
106
+
107
+ # Whether the 6809 has the bus, rather than the 6502.
108
+ def m6809_running? = @m6809_running
109
+
110
+ def superpet? = true
111
+
112
+ # Moves the CPU switch to +position+, one of Board::CPU_SWITCH, and
113
+ # resets the machine, so the CPU the switch picks starts from its
114
+ # RESET.
115
+ def switch_cpu(position)
116
+ @board.cpu_switch = position
117
+ reset!
118
+ end
119
+
120
+ # Moves the bank RAM switch to +position+, one of Board::RAM_SWITCH.
121
+ def switch_bank_ram(position)
122
+ @board.ram_switch = position
123
+ end
124
+
125
+ # Whether the picture shows the character ROM's APL set: its second
126
+ # half, which the CRTC's MA13 picks, with VIA CA2 high.
127
+ def apl_characters? = @crtc.registers[12].anybits?(0x20) && @via.ca2_output
128
+
129
+ # Drive 1 while the 6809 runs, where the Waterloo menu's "disk/1."
130
+ # looks, and drive 0 in 6502 mode.
131
+ def disk_drive_number = @m6809_running ? 1 : 0
132
+
133
+ # The 32K below $8000 and the 64K board.
134
+ def model_label = "SUPERPET, 96K RAM, #{@refresh_hz} HZ"
135
+
136
+ # The PET's cycle, with the ACIA run when it falls due and its IRQ
137
+ # on the CPU's.
138
+ def cycle!
139
+ handle_init if @cycles == @init_threshold
140
+ feed_keyboard if @pending_keys
141
+
142
+ @crtc.cycle!
143
+ drive_retrace(!@crtc.vsync?)
144
+ @pia1.cycle!
145
+ @pia2.cycle!
146
+ @via.cycle!
147
+ @sound.cycle! if @sound.recording?
148
+ @cassettes.cycle! if @tapes_running
149
+ @acia.run!(@cycles) if @cycles >= @acia_due
150
+ irq = @pia1.irq? || @pia2.irq? || @via.irq? || @acia_irq
151
+ if @m6809_running
152
+ @m6809.irq = irq
153
+ @m6809.cycle!
154
+ else
155
+ @cpu.irq = irq
156
+ @cpu.cycle!
157
+ end
158
+ clock_ieee_drives
159
+
160
+ @cycles += 1
161
+ end
162
+
163
+ # Whether the ACIA pulls the IRQ line.
164
+ def acia_irq? = @acia_irq
165
+
166
+ # The ACIA's IRQ line, and the cycle it next has something to do at.
167
+ def acia_changed(irq, due)
168
+ @acia_irq = irq
169
+ @acia_due = due
170
+ end
171
+
172
+ # The RES line reaches the board's latches, the 6702, the ACIA and
173
+ # both CPUs too.
174
+ def reset!
175
+ super
176
+ @acia.reset!
177
+ @m6809.reset!
178
+ @board.reset!
179
+ end
180
+
181
+ def power_cycle!
182
+ @board.power_on!
183
+ super
184
+ end
185
+
186
+ private
187
+
188
+ # The Waterloo ROM's ring takes keys until its write pointer would
189
+ # meet its read pointer.
190
+ def feed_keyboard
191
+ return super unless @m6809_running
192
+
193
+ read = word_at(KEYS_READ)
194
+ write = word_at(KEYS_WRITE)
195
+ until @pending_keys.empty?
196
+ following = write + 1 == KEYS_END ? KEYS_FIRST : write + 1
197
+ break if following == read
198
+
199
+ @bus.poke(write, @pending_keys.shift)
200
+ write = following
201
+ end
202
+ @bus.poke(KEYS_WRITE, write >> 8)
203
+ @bus.poke(KEYS_WRITE + 1, write & 0xff)
204
+ @pending_keys = nil if @pending_keys.empty?
205
+ end
206
+
207
+ def word_at(addr) = (@bus.peek(addr) << 8) | @bus.peek(addr + 1)
208
+
209
+ # The CPU the board picks takes the bus over from its RESET.
210
+ def cpu_selected
211
+ @m6809_running = @board.m6809?
212
+ @m6809_running ? @m6809.reset! : @cpu.reset!
213
+ end
214
+ end
215
+ end
216
+ end
data/lib/badline/pet.rb CHANGED
@@ -83,10 +83,13 @@ module Badline
83
83
  # model runs its 50 Hz editor, or its 60 Hz one for +refresh_hz+ 60.
84
84
  # +basic+, +editor+ and +characters+ name ROMs the board takes in place
85
85
  # of its own (Model.upgraded, Model.fitted), and +jumpers+ the 8296's
86
- # board jumpers (ROMSets::JUMPERS).
86
+ # board jumpers (ROMSets::JUMPERS). +true_drive_model+ names the drive
87
+ # plug_true_drive plugs in (Drives::IEEEModels).
87
88
  def initialize(model: Model::DEFAULT, ram_kb: nil, refresh_hz: 50, debug: false, basic: nil, editor: nil,
88
- characters: nil, jumpers: [])
89
+ characters: nil, jumpers: [], true_drive_model: Drives::IEEEModels::DEFAULT)
89
90
  @model = Model.fitted(Model.upgraded(Model.named(model), basic, editor), characters)
91
+ raise ArgumentError, "the SuperPET is built as PET::SuperPET" if @model.superpet? && !superpet?
92
+
90
93
  ram_kb ||= @model.ram_kb
91
94
  raise ArgumentError, "the #{@model.name} has no #{ram_kb}K RAM board" unless Model.ram_fits?(@model, ram_kb)
92
95
 
@@ -99,6 +102,7 @@ module Badline
99
102
  matrix = @model.keyboard == :business ? KeyboardPorts::BUSINESS_MATRIX : KeyboardPorts::GRAPHICS_MATRIX
100
103
  @keyboard = Input::Keyboard.new(matrix:)
101
104
  keyboard_ports = KeyboardPorts.new(@keyboard)
105
+ @keyboard_ports = keyboard_ports
102
106
  registers = Array.new(Chips::CRTC::MASKS.length, 0)
103
107
  @pia1 = Chips::PIA.new(peripheral: keyboard_ports)
104
108
  @ieee = IEEEPorts.new(Drives::IEEEBus.new)
@@ -106,6 +110,7 @@ module Badline
106
110
  @via = VIA.new(start: 0xe800, peripheral: VIAPorts.new(self))
107
111
  @sound = Sound.new(@via)
108
112
  @cassettes = Cassettes.new(@pia1, @via)
113
+ watch_tapes
109
114
  keyboard_ports.connect(@pia1, @ieee, @cassettes)
110
115
  characters = Characters.new(@model.characters)
111
116
  @crtc_output = CRTCVideo.new(characters, @via, registers, columns80: @model.video == :crtc80,
@@ -127,13 +132,23 @@ module Badline
127
132
  @cycles = 0
128
133
  @init_handlers = []
129
134
  @pending_keys = nil
135
+ @init_keys = nil
130
136
  @dos = nil
131
137
  @ieee_trap = nil
132
138
  @capture_output = nil
133
- @drive2031 = nil
139
+ init_ieee_drives(true_drive_model)
134
140
  @init_threshold = boot_cycles(ram_kb)
135
141
  end
136
142
 
143
+ # Whether this is the SuperPET's board (SuperPET).
144
+ def superpet? = false
145
+
146
+ # Whether the picture shows the SuperPET's APL characters.
147
+ def apl_characters? = false
148
+
149
+ # Whether an autostart finds BASIC to type into.
150
+ def boots_basic? = true
151
+
137
152
  # The vertical drive line, high but in the retrace.
138
153
  def vertical_drive? = @vertical_drive
139
154
 
@@ -177,10 +192,10 @@ module Badline
177
192
  @pia2.cycle!
178
193
  @via.cycle!
179
194
  @sound.cycle! if @sound.recording?
180
- @cassettes.cycle!
195
+ @cassettes.cycle! if @tapes_running
181
196
  @cpu.irq = @pia1.irq? || @pia2.irq? || @via.irq?
182
197
  @cpu.cycle!
183
- @drive2031&.host_cycle!
198
+ clock_ieee_drives
184
199
 
185
200
  @cycles += 1
186
201
  end
@@ -197,7 +212,7 @@ module Badline
197
212
  @via.reset!
198
213
  @crtc.reset!
199
214
  @bus.reset!
200
- @drive2031&.reset!
215
+ reset_ieee_drives
201
216
  @ieee.lines_written
202
217
  @cpu.reset!
203
218
  end
@@ -234,6 +249,16 @@ module Badline
234
249
  # on a 3032 and 1,980,000 on an 8032.
235
250
  def boot_cycles(ram_kb) = (@model.basic == "pet/basic4.rom" ? 700_000 : 150_000) + (46_000 * ram_kb)
236
251
 
252
+ # The decks are clocked only while a tape runs.
253
+ def watch_tapes
254
+ @tapes_running = false
255
+ @cassettes.on_running_change { tapes_changed }
256
+ end
257
+
258
+ def tapes_changed
259
+ @tapes_running = @cassettes.running?
260
+ end
261
+
237
262
  def drive_retrace(high)
238
263
  return if high == @vertical_drive
239
264
 
@@ -242,3 +267,5 @@ module Badline
242
267
  end
243
268
  end
244
269
  end
270
+
271
+ require "badline/pet/superpet"