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
@@ -11,7 +11,8 @@ module Badline
11
11
  # Both record from VIA PB3. The KERNAL reads each tape as the falling
12
12
  # edges its read line makes, as the C64 and VIC-20 do. The machine
13
13
  # passes the motor and write lines on after each write to the I/O
14
- # chips (#lines_written).
14
+ # chips (#lines_written), and clocks the decks only while a tape runs
15
+ # (#running?).
15
16
  class Cassettes
16
17
  attr_reader :datasette1, :datasette2
17
18
 
@@ -22,6 +23,18 @@ module Badline
22
23
  @datasette2 = Drives::Datasette.new
23
24
  @datasette1.on_flag { pulse1 }
24
25
  @datasette2.on_flag { pulse2 }
26
+ @running = false
27
+ @running_handler = nil
28
+ @datasette1.on_running_change { update_running }
29
+ @datasette2.on_running_change { update_running }
30
+ end
31
+
32
+ # Whether either deck's tape runs.
33
+ def running? = @running
34
+
35
+ # Whether either deck's tape runs, each time that changes.
36
+ def on_running_change(&handler)
37
+ @running_handler = handler
25
38
  end
26
39
 
27
40
  def cycle!
@@ -48,6 +61,14 @@ module Badline
48
61
 
49
62
  private
50
63
 
64
+ def update_running
65
+ running = @datasette1.running? || @datasette2.running?
66
+ return if running == @running
67
+
68
+ @running = running
69
+ @running_handler&.call
70
+ end
71
+
51
72
  # A falling edge on a read line, and the line back up.
52
73
  def pulse1
53
74
  @pia1.ca1 = false
@@ -1,76 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "badline/drives/model2031"
3
+ require "badline/machine/ieee_drives"
4
4
 
5
5
  module Badline
6
6
  class PET
7
- # The PET's true drive: a Drives::Model2031 on the IEEE-488 bus
8
- # (IEEEPorts), clocked after the CPU each cycle and reset with it, as
9
- # the PET's RES line reaches IFC. The traps on the KERNAL's primitives
10
- # (IEEETraps) leave device 8 to the drive while it is device 8.
7
+ # The PET's true drive, a 2031, 8050 or 8250 on its IEEE-488 bus
8
+ # (IEEEPorts), as Machine::IEEEDrives has it, at the PET's 1 MHz.
11
9
  module IEEEDrive
12
- # The 2031 on the bus, or nil.
13
- attr_reader :drive2031
14
-
15
- # Plugs in a Drives::Model2031, in place of the one there.
16
- def attach_drive2031(drive)
17
- detach_drive2031
18
- drive.host_clock_hz = 1_000_000
19
- drive.connect(@ieee.bus)
20
- @drive2031 = drive
21
- @ieee.lines_written
22
- @ieee_trap&.device = trap_device
23
- @init_threshold = [@init_threshold, Badline::Drives::Model2031::BOOT_CYCLES].max if @cycles < @init_threshold
24
- end
25
-
26
- # Unplugs the 2031, leaving it on a bus of its own.
27
- def detach_drive2031
28
- drive = @drive2031
29
- return unless drive
30
-
31
- drive.connect(Badline::Drives::IEEEBus.new)
32
- @drive2031 = nil
33
- @ieee_trap&.device = trap_device
34
- end
35
-
36
- # The true drive on the bus, or nil.
37
- def true_drive = @drive2031
38
-
39
- # Plugs in a 2031 as device 8, in place of the drive there, and
40
- # returns it.
41
- def plug_true_drive
42
- Badline::Drives::Model2031.new.tap { |drive| attach_drive2031(drive) }
43
- end
10
+ include Machine::IEEEDrives
44
11
 
45
12
  private
46
13
 
47
- # The 2031 as keyed snapshot modules named from its device number:
48
- # BL-D8, BL-D8-CPU and so on for device 8.
49
- def save_drive_modules(sections)
50
- drive = @drive2031
51
- drive&.save_modules(sections, "BL-D#{drive.device}")
52
- sections
53
- end
54
-
55
- # The 2031 a snapshot's modules hold, built afresh unless one is on
56
- # the bus as the same device, or none when they hold no drive. The
57
- # bus then carries the PET's lines as its chips left them.
58
- def load_drive_modules(modules)
59
- device = 8
60
- input = modules.optional("BL-D8")
61
- while input.nil? && device < 11
62
- device += 1
63
- input = modules.optional("BL-D#{device}")
64
- end
65
- if input
66
- detach_drive2031 if @drive2031 && @drive2031.device != device
67
- attach_drive2031(Badline::Drives::Model2031.new(device:)) unless @drive2031
68
- @drive2031.load_modules(modules, input, "BL-D#{device}")
69
- else
70
- detach_drive2031
71
- end
72
- @ieee.lines_written
73
- end
14
+ def ieee_host_clock_hz = 1_000_000
74
15
  end
75
16
  end
76
17
  end
@@ -57,6 +57,13 @@ module Badline
57
57
  bus.host_lines = controller_lines unless bus.drives.empty?
58
58
  end
59
59
 
60
+ # The drives on the bus see the lines the PET pulls now.
61
+ def push_lines = lines_written
62
+
63
+ # Nothing to push once a cycle: the PET pushes its lines as its chips
64
+ # are written.
65
+ def cycle_lines = nil
66
+
60
67
  private
61
68
 
62
69
  def low_lines = @bus.low_lines(controller_lines)
@@ -42,8 +42,12 @@ module Badline
42
42
 
43
43
  attr_reader :keyboard
44
44
 
45
+ # Whether the SuperPET's board pulls the diagnostic sense low.
46
+ attr_accessor :diagnostic_low
47
+
45
48
  def initialize(keyboard)
46
49
  @keyboard = keyboard
50
+ @diagnostic_low = false
47
51
  @pia = nil
48
52
  @ieee = nil
49
53
  @cassettes = nil
@@ -60,8 +64,11 @@ module Badline
60
64
 
61
65
  # Port A's other lines: PA4 and PA5 read the cassette sense switches,
62
66
  # PA6 the IEEE bus's EOI, and the diagnostic sense on PA7 floats high
63
- # with nothing on it.
64
- def read_a(_lines) = @ieee.eoi_input & @cassettes.sense_input
67
+ # with nothing on it but the SuperPET's board, which can pull it low.
68
+ def read_a(_lines)
69
+ lines = @ieee.eoi_input & @cassettes.sense_input
70
+ @diagnostic_low ? lines & 0x7f : lines
71
+ end
65
72
 
66
73
  def read_b(lines)
67
74
  row = @pia.port_a_output & 0x0f
@@ -25,7 +25,9 @@ module Badline
25
25
  out.int("cycles", @cycles).bool("vertical_drive", @vertical_drive)
26
26
  pending = @pending_keys
27
27
  out.ints("host.pending_keys", pending) if pending
28
+ save_init_keys(out)
28
29
  out.int("host.init_pending", @cycles <= @init_threshold ? @init_handlers.length : 0)
30
+ save_ieee_drive_fields(out)
29
31
  end
30
32
 
31
33
  def load_machine_fields(input)
@@ -33,7 +35,9 @@ module Badline
33
35
  @vertical_drive = input.bool("vertical_drive", @vertical_drive)
34
36
  pending = input.ints("host.pending_keys", [])
35
37
  @pending_keys = pending.empty? ? nil : pending.dup
38
+ load_init_keys(input)
36
39
  @init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
40
+ load_ieee_drive_fields(input)
37
41
  end
38
42
  end
39
43
 
@@ -17,7 +17,8 @@ module Badline
17
17
  # 1K twice over and the 80-column ones with 2K. The 8296 has 128K of
18
18
  # dynamic RAM, the 32K below $8000, the 32K above it, of which the
19
19
  # screen is the start and the rest lies under the ROMs, and the 64K
20
- # board (Bus::Expansion).
20
+ # board (Bus::Expansion). The SuperPET is an 8032 with the 901640-01
21
+ # character ROM, twice the 8032's size, and its own board (SuperPET).
21
22
  module Model
22
23
  Profile = Data.define(:name, :ram_kb, :basic, :editor_50hz, :editor_60hz, :kernal, :characters, :keyboard,
23
24
  :video, :expansion) do
@@ -28,6 +29,9 @@ module Badline
28
29
  # The 8296, whose board decodes its memory in a PLA.
29
30
  def cbm8296? = name == "8296"
30
31
 
32
+ # The SuperPET, an 8032 with the Waterloo board (SuperPET).
33
+ def superpet? = name == "superpet"
34
+
31
35
  # The screen RAM's bytes, and on the 8296 all 32K of RAM from $8000.
32
36
  def screen_bytes
33
37
  return 0x8000 if cbm8296?
@@ -86,7 +90,12 @@ module Badline
86
90
  kernal: "pet/kernal4.rom", characters: "pet/characters2.rom", keyboard: :business,
87
91
  video: :crtc80, expansion: true)
88
92
 
89
- ALL = [PET2001, PET3032, PET3032B, PET4032, CBM8032, CBM8096, CBM8296].freeze
93
+ SUPERPET = Profile.new(name: "superpet", ram_kb: 32, basic: "pet/basic4.rom",
94
+ editor_50hz: "pet/editor4-80-b-50hz.rom", editor_60hz: "pet/editor4-80-b-60hz.rom",
95
+ kernal: "pet/kernal4.rom", characters: "pet/characters-superpet.rom",
96
+ keyboard: :business, video: :crtc80, expansion: false)
97
+
98
+ ALL = [PET2001, PET3032, PET3032B, PET4032, CBM8032, CBM8096, CBM8296, SUPERPET].freeze
90
99
 
91
100
  DEFAULT = "8032"
92
101
 
@@ -39,6 +39,7 @@ module Badline
39
39
  "4032" => { "4" => %w[4-40-n-50hz 4-40-n-60hz] },
40
40
  "8032" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
41
41
  "8096" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
42
+ "superpet" => { "4" => %w[4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] },
42
43
  "8296" => { "4" => %w[4-80-b-50hz-8296 4-80-b-50hz 4-80-b-50hz-0283 4-80-b-60hz] }
43
44
  }.freeze
44
45
 
@@ -8,121 +8,21 @@ module Badline
8
8
  # added. CB2 carries the shift register's bits in its shift-out modes,
9
9
  # mode 4's free-running square wave under timer 2 for the bell and most
10
10
  # PET music, and the PCR's CB2 output otherwise, held high or low by
11
- # hand or floating high as an input.
12
- #
13
- # The machine calls #cycle! once a cycle while #recording?, from #record
14
- # on, so a sound nobody records costs only that check. Each sample is
15
- # the share of its cycles CB2 spent high, through the output stage: a
16
- # one-pole low-pass at 5 kHz and a one-pole high-pass at 100 Hz, so a
17
- # line left high or low settles to silence.
11
+ # hand or floating high as an input. Machine::LineSound turns it into
12
+ # samples.
18
13
  class Sound
19
- LOW_PASS_HZ = 5_000
20
- HIGH_PASS_HZ = 100
21
-
22
- # A swing from CB2 low to CB2 high, as a signed 16-bit sample.
23
- SCALE = 24_000.0
14
+ include Machine::LineSound
24
15
 
25
16
  def initialize(via)
26
17
  @via = via
27
- @recording = false
28
- @high = true
29
- @run = 0
30
- @rate = 0
31
- @clock_hz = 0
32
- @remainder = 0
33
- @window = 0
34
- @left = 0
35
- @sum = 0
36
- @low = 0.0
37
- @drift = 0.0
38
- @low_pass = 0.0
39
- @high_pass = 0.0
40
- @samples = []
18
+ init_line_sound
41
19
  end
42
20
 
43
21
  def model = :cb2
44
22
 
45
- attr_reader :recording
46
- alias recording? recording
47
-
48
- # Starts collecting the output, averaged down to `rate` samples a
49
- # second at `clock_hz` cycles a second, for #drain_samples. The
50
- # output stage takes CB2's level now as settled.
51
- def record(rate:, clock_hz: 1_000_000)
52
- @rate = rate
53
- @clock_hz = clock_hz
54
- @remainder = 0
55
- @window = next_window
56
- @left = @window
57
- @sum = 0
58
- @high = @via.cb2_output
59
- @run = 0
60
- @low = @drift = @high ? 1.0 : 0.0
61
- @low_pass = 1.0 - Math.exp(-2.0 * Math::PI * LOW_PASS_HZ / rate)
62
- @high_pass = 1.0 - Math.exp(-2.0 * Math::PI * HIGH_PASS_HZ / rate)
63
- @samples = []
64
- @recording = true
65
- end
66
-
67
- # Takes CB2's level for this cycle, while recording.
68
- def cycle!
69
- high = @via.cb2_output
70
- if high == @high
71
- @run += 1
72
- else
73
- hold(@high, @run)
74
- @high = high
75
- @run = 1
76
- end
77
- end
78
-
79
- # The samples closed since the last drain.
80
- def drain_samples
81
- return [] unless @recording
82
-
83
- hold(@high, @run)
84
- @run = 0
85
- samples = @samples
86
- @samples = []
87
- samples
88
- end
89
-
90
23
  private
91
24
 
92
- # Adds `cycles` cycles with CB2 high or low, closing every sample
93
- # they finish.
94
- def hold(high, cycles)
95
- left = @left
96
- while cycles >= left
97
- @sum += left if high
98
- cycles -= left
99
- close
100
- left = @left
101
- end
102
- @sum += cycles if high
103
- @left = left - cycles
104
- end
105
-
106
- def close
107
- @low += @low_pass * (@sum.fdiv(@window) - @low)
108
- @drift += @high_pass * (@low - @drift)
109
- sample = ((@low - @drift) * SCALE).round
110
- sample = 32_767 if sample > 32_767
111
- sample = -32_768 if sample < -32_768
112
- @samples << sample
113
- @sum = 0
114
- @window = next_window
115
- @left = @window
116
- end
117
-
118
- # Each window is a whole number of cycles, and they add up to the
119
- # clock's cycles over a second.
120
- def next_window
121
- @remainder += @clock_hz
122
- cycles = @remainder / @rate
123
- @remainder -= cycles * @rate
124
- cycles
125
- end
25
+ def line_high? = @via.cb2_output
126
26
  end
127
27
  end
128
28
  end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class PET
5
+ class SuperPET < PET
6
+ # The board's 6551 at $EFF0, the RS-232 port's, on a 1.8432 MHz
7
+ # crystal, whose IRQ joins the PET's.
8
+ class ACIA < Chips::ACIA
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class PET
5
+ class SuperPET < PET
6
+ # The Waterloo board, after Levak's PET-Interfaces.txt, André
7
+ # Fachat's SuperPET page and Commodore's 324037 schematic: 64K of
8
+ # RAM in 16 banks of 4K, one at a time at $9000-$9FFF, and the I/O
9
+ # page at $EF00 (IOPage), which both CPUs reach.
10
+ #
11
+ # The bank latch at $EFFC-$EFFD picks the bank with bits 0-3, and its
12
+ # bit 7 lets writes reach the system latch at $EFF8-$EFFB:
13
+ #
14
+ # bit 0 the CPU while SW2 is at PROG: 1 the 6502, 0 the 6809
15
+ # bit 1 the bank RAM while SW1 is at PROG: 1 read/write, 0 read only
16
+ # bit 3 pulls the user port's diagnostic sense, PIA 1's PA7, low
17
+ #
18
+ # Bit 0 of $EFFE-$EFFF puts the board's own ROM socket at $9000 in
19
+ # place of the RAM, which badline leaves empty, while the 6502 runs.
20
+ # All three are write-only, and the RES line clears them, so the
21
+ # board starts at bank 0, and with SW2 at PROG with the 6809.
22
+ #
23
+ # SW1 is READ, R/W or PROG, the bank RAM's write protect, and SW2
24
+ # PROG, 6502 or 6809, the CPU. A write to the bank RAM while it is
25
+ # protected goes nowhere.
26
+ #
27
+ # While the 6809 runs, the 8032's ROMs step aside through its NO ROM
28
+ # line, and the Waterloo ROMs answer at $A000-$E7FF and $F000-$FFFF,
29
+ # the $E000 one with the CRTC table for the frame rate.
30
+ class Board
31
+ CPU_SWITCH = %w[6502 6809 prog].freeze
32
+
33
+ RAM_SWITCH = %w[rw read prog].freeze
34
+
35
+ # $EF00-$EFFF: the 6702 at $EFE0-$EFE3, the ACIA at $EFF0-$EFF3
36
+ # and the latches above. A read of a latch finds the last byte on
37
+ # the bus, and below $EFF0, where neither chip answers, $FF.
38
+ class IOPage
39
+ def initialize(board, bus, dongle, acia)
40
+ @board = board
41
+ @bus = bus
42
+ @dongle = dongle
43
+ @acia = acia
44
+ end
45
+
46
+ def peek(addr)
47
+ low = addr & 0xff
48
+ return @bus.data if low >= 0xf4
49
+ return @acia.peek(addr) if low >= 0xf0
50
+ return @dongle.peek(addr) if low >= 0xe0 && low < 0xe4
51
+
52
+ 0xff
53
+ end
54
+
55
+ def poke(addr, value)
56
+ low = addr & 0xff
57
+ if low >= 0xfe
58
+ @board.socket = value
59
+ elsif low >= 0xfc
60
+ @board.bank_latch = value
61
+ elsif low >= 0xf8
62
+ @board.system_latch = value
63
+ elsif low >= 0xf0
64
+ @acia.poke(addr, value) if low < 0xf4
65
+ elsif low >= 0xe0
66
+ @dongle.poke(addr, value) if low < 0xe4
67
+ end
68
+ end
69
+ end
70
+
71
+ attr_reader :cells, :bank_latch, :system_latch, :cpu_switch, :ram_switch
72
+
73
+ # Bit 0 of $EFFE: 1 for the ROM socket at $9000.
74
+ attr_reader :socket
75
+
76
+ # +bus+ is the PET's, whose pages the board maps, and
77
+ # +keyboard_ports+ PIA 1's, whose PA7 the diagnostic sense is.
78
+ # +cpu_switch+ and +ram_switch+ are SW2's and SW1's positions, one
79
+ # of CPU_SWITCH's and RAM_SWITCH's, and +refresh_hz+ picks the
80
+ # Waterloo $E000 ROM, 50 or 60.
81
+ def initialize(bus, dongle:, acia:, keyboard_ports:, cpu_switch: "6502", ram_switch: "rw",
82
+ refresh_hz: 50)
83
+ raise ArgumentError, "the CPU switch has no position #{cpu_switch}" unless CPU_SWITCH.include?(cpu_switch)
84
+ raise ArgumentError, "the RAM switch has no position #{ram_switch}" unless RAM_SWITCH.include?(ram_switch)
85
+
86
+ @bus = bus
87
+ @dongle = dongle
88
+ @keyboard_ports = keyboard_ports
89
+ @cpu_switch = cpu_switch
90
+ @ram_switch = ram_switch
91
+ @cells = Array.new(0x10000, 0)
92
+ @banks = Array.new(16) { |bank| Bus::Expansion::Window.new(@cells, bank << 12, 0x9000) }
93
+ @io = IOPage.new(self, bus, dongle, acia)
94
+ @bank_latch = 0
95
+ @system_latch = 0
96
+ @socket = 0
97
+ @rom_a = ROM.load("pet/superpet-a000.rom", 0xa000)
98
+ @rom_c = ROM.load("pet/superpet-c000.rom", 0xc000)
99
+ @rom_e = ROM.load(refresh_hz == 50 ? "pet/superpet-e000-50hz.rom" : "pet/superpet-e000.rom", 0xe000)
100
+ @m6809 = m6809_selected?
101
+ @select_handler = nil
102
+ end
103
+
104
+ # Calls the block when the CPU the switch and the system latch pick
105
+ # changes.
106
+ def on_select(&handler)
107
+ @select_handler = handler
108
+ end
109
+
110
+ # Whether the 6809 has the bus.
111
+ def m6809? = @m6809
112
+
113
+ def bank = @bank_latch & 0x0f
114
+
115
+ def bank_latch=(value)
116
+ @bank_latch = value
117
+ @bus.map_pages
118
+ end
119
+
120
+ def system_latch=(value)
121
+ return if @bank_latch.nobits?(0x80)
122
+
123
+ @system_latch = value
124
+ @keyboard_ports.diagnostic_low = value.anybits?(0x08)
125
+ select
126
+ end
127
+
128
+ def socket=(value)
129
+ @socket = @m6809 ? 0 : value & 0x01
130
+ @bus.map_pages
131
+ end
132
+
133
+ # Moves SW2, which picks the CPU at the next RES.
134
+ def cpu_switch=(position)
135
+ raise ArgumentError, "the CPU switch has no position #{position}" unless CPU_SWITCH.include?(position)
136
+
137
+ @cpu_switch = position
138
+ end
139
+
140
+ # Moves SW1, which protects the bank RAM from the next write on.
141
+ def ram_switch=(position)
142
+ raise ArgumentError, "the RAM switch has no position #{position}" unless RAM_SWITCH.include?(position)
143
+
144
+ @ram_switch = position
145
+ @bus.map_pages
146
+ end
147
+
148
+ # Whether writes to the bank RAM go nowhere.
149
+ def write_protected?
150
+ case @ram_switch
151
+ when "read" then true
152
+ when "rw" then false
153
+ else @system_latch.nobits?(0x02)
154
+ end
155
+ end
156
+
157
+ # Whether the CPU switch, or the system latch with it at PROG,
158
+ # picks the 6809.
159
+ def m6809_selected?
160
+ case @cpu_switch
161
+ when "6502" then false
162
+ when "6809" then true
163
+ else @system_latch.nobits?(0x01)
164
+ end
165
+ end
166
+
167
+ # The RES line clears the latches, and the 6702.
168
+ def reset!
169
+ @bank_latch = 0
170
+ @system_latch = 0
171
+ @socket = 0
172
+ @keyboard_ports.diagnostic_low = false
173
+ @dongle.reset!
174
+ select
175
+ end
176
+
177
+ def power_on!
178
+ @cells.fill(0)
179
+ end
180
+
181
+ # Lays the bank, or the empty socket, over $9000-$9FFF, the I/O
182
+ # page over $EF00, and the Waterloo ROMs over the 8032's while the
183
+ # 6809 runs.
184
+ def map(reads, writes)
185
+ if @socket.zero?
186
+ window = @banks[bank]
187
+ window.read_only = write_protected?
188
+ reads.fill(window, 0x90, 0x10)
189
+ writes.fill(window, 0x90, 0x10)
190
+ else
191
+ reads.fill(@bus.open_bus, 0x90, 0x10)
192
+ writes.fill(@bus.open_bus, 0x90, 0x10)
193
+ end
194
+ reads[0xef] = @io
195
+ writes[0xef] = @io
196
+ map_waterloo(reads) if @m6809
197
+ end
198
+
199
+ private
200
+
201
+ # Picks the CPU again, and hands the bus over if that changed it.
202
+ # The 6809 taking over clears the socket bit.
203
+ def select
204
+ m6809 = m6809_selected?
205
+ changed = m6809 != @m6809
206
+ @m6809 = m6809
207
+ @socket = 0 if m6809
208
+ @bus.map_pages
209
+ @select_handler&.call if changed
210
+ end
211
+
212
+ def map_waterloo(reads)
213
+ reads.fill(@rom_a, 0xa0, 0x20)
214
+ reads.fill(@rom_c, 0xc0, 0x20)
215
+ reads.fill(@rom_e, 0xe0, 0x08)
216
+ reads.fill(@rom_e, 0xf0, 0x10)
217
+ end
218
+ end
219
+ end
220
+ end
221
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class PET
5
+ class SuperPET < PET
6
+ # The 6702 at $EFE0-$EFE3, the protection chip the Waterloo languages
7
+ # check for. Its behaviour comes from David Roberts' Visual 6702, a
8
+ # switch-level simulation of the decapped chip's netlist, driven a
9
+ # bus cycle at a time.
10
+ #
11
+ # Each data line has a shift register of its own, LENGTHS long, whose
12
+ # first stage the line reads, inverted where INVERTED has the bit
13
+ # set. A latch keeps the last byte written, all ones from the RES
14
+ # line. A write that sets D0 where the last one left it clear shifts
15
+ # every register one stage at the end of the next cycle, whichever
16
+ # cycle that is, so a read in it still finds the old byte. Each
17
+ # register takes in its first stage XORed with the latch's bit,
18
+ # inverted where FEEDBACK has the bit clear, the latch as that cycle
19
+ # leaves it. The RES line clears the registers.
20
+ class Dongle
21
+ LENGTHS = [6, 3, 7, 8, 1, 3, 5, 2].freeze
22
+
23
+ INVERTED = 0xd6
24
+
25
+ FEEDBACK = 0x29
26
+
27
+ # No shift waiting.
28
+ NONE = -1
29
+
30
+ # The registers, bit 0 of each the stage its line reads, the latch,
31
+ # and the cycle at whose end the registers shift, or NONE.
32
+ attr_reader :registers, :latch, :shift_at
33
+
34
+ # +clock+ answers #cycles, the cycle the CPU's access falls in.
35
+ def initialize(clock)
36
+ @clock = clock
37
+ @registers = Array.new(8, 0)
38
+ reset!
39
+ end
40
+
41
+ def reset!
42
+ @registers.fill(0)
43
+ @latch = 0xff
44
+ @shift_at = NONE
45
+ end
46
+
47
+ def peek(_addr)
48
+ now = @clock.cycles
49
+ shift if @shift_at != NONE && @shift_at < now
50
+ value = 0
51
+ 8.times { |bit| value |= (@registers[bit] & 1) << bit }
52
+ value ^ INVERTED
53
+ end
54
+
55
+ def poke(_addr, value)
56
+ now = @clock.cycles
57
+ shift if @shift_at != NONE && @shift_at < now
58
+ raising = value.anybits?(0x01) && @latch.nobits?(0x01)
59
+ @latch = value
60
+ if @shift_at == now
61
+ shift
62
+ elsif raising
63
+ @shift_at = now + 1
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ def shift
70
+ @shift_at = NONE
71
+ incoming = @latch ^ FEEDBACK ^ 0xff
72
+ 8.times do |bit|
73
+ register = @registers[bit]
74
+ stage = (register ^ (incoming >> bit)) & 1
75
+ @registers[bit] = (register >> 1) | (stage << (LENGTHS[bit] - 1))
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end