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,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # The B series models badline builds by name, after xcbm2's -model
6
+ # names: the segments the RAM board fills, the BASIC that fits it, the
7
+ # character ROM, and whether the board's PC7 jumper tells the editor
8
+ # it drives the 700's monitor.
9
+ module Model
10
+ Profile = Data.define(:name, :ram_segments, :basic, :characters, :high_profile) do
11
+ def ram_kb = ram_segments.length * 64
12
+ end
13
+
14
+ M610 = Profile.new(name: "610", ram_segments: [1, 2].freeze, basic: "cbm2/basic128.rom",
15
+ characters: "cbm2/characters-600.rom", high_profile: false)
16
+ M620 = Profile.new(name: "620", ram_segments: [1, 2, 3, 4].freeze, basic: "cbm2/basic256.rom",
17
+ characters: "cbm2/characters-600.rom", high_profile: false)
18
+ M710 = Profile.new(name: "710", ram_segments: [1, 2].freeze, basic: "cbm2/basic128.rom",
19
+ characters: "cbm2/characters-700.rom", high_profile: true)
20
+ M720 = Profile.new(name: "720", ram_segments: [1, 2, 3, 4].freeze, basic: "cbm2/basic256.rom",
21
+ characters: "cbm2/characters-700.rom", high_profile: true)
22
+
23
+ ALL = [M610, M620, M710, M720].freeze
24
+ DEFAULT = "610"
25
+
26
+ # The KERNALs a B series board takes: 901244-04a, VICE's, and
27
+ # 901244-03b, the 1983 release.
28
+ KERNALS = { "04a" => "cbm2/kernal-04a.rom", "03b" => "cbm2/kernal-03b.rom" }.freeze
29
+
30
+ def self.named(name)
31
+ model = ALL.find { |candidate| candidate.name == name }
32
+ raise ArgumentError, "no CBM-II model named #{name}" if model.nil?
33
+
34
+ model
35
+ end
36
+
37
+ # The BASIC +model+ runs with +kernal+: BASIC 128 of the 1983
38
+ # release, 02b, with 03b.
39
+ def self.basic(model, kernal)
40
+ kernal == "03b" && model.basic == "cbm2/basic128.rom" ? "cbm2/basic128-02b.rom" : model.basic
41
+ end
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ class P500
6
+ # The P500's two control ports on the CIA at $DC00, as its schematic's
7
+ # game port interface wires them (sheets 9 and 10): port 1's
8
+ # directions on PB0-PB3 and its fire button on PA6, which is the VIC's
9
+ # light pen input too, and port 2's on PB4-PB7 and PA7. Every switch
10
+ # pulls its line low. Port A carries the IEEE-488 bus's data lines as
11
+ # well (IEEEPorts::Data), which pull it low too.
12
+ #
13
+ # A device on a port, a pair of paddles or a mouse, adds its buttons
14
+ # to the port's lines, and its pots reach the SID through the analog
15
+ # switches PA0 (port 1) and PA1 (port 2) close, as PA6 and PA7 do on
16
+ # the C64 (Input::ControlPorts).
17
+ class ControlPorts
18
+ DIRECTIONS = 0x0f
19
+ FIRE = 0x10
20
+ PORT1_POTS = 0x01
21
+ PORT2_POTS = 0x02
22
+
23
+ attr_reader :joystick1, :joystick2
24
+ attr_accessor :device1, :device2
25
+
26
+ def initialize(joystick1, joystick2, ieee)
27
+ @joystick1 = joystick1
28
+ @joystick2 = joystick2
29
+ @ieee = ieee
30
+ @device1 = nil
31
+ @device2 = nil
32
+ @cia = nil
33
+ end
34
+
35
+ # The CIA whose PA0 and PA1 close the pots' switches.
36
+ def connect(cia)
37
+ @cia = cia
38
+ end
39
+
40
+ def read_a(port_a, port_b)
41
+ fire1 = port1.anybits?(FIRE) ? 0x40 : 0
42
+ fire2 = port2.anybits?(FIRE) ? 0x80 : 0
43
+ (0x3f | fire1 | fire2) & @ieee.read_a(port_a, port_b)
44
+ end
45
+
46
+ def read_b(_port_a, _port_b)
47
+ (port1 & DIRECTIONS) | ((port2 & DIRECTIONS) << 4)
48
+ end
49
+
50
+ # PB4 is port 2's up line here, not a light pen input.
51
+ def port_b4_high? = true
52
+
53
+ # Whether port 1's fire line is high, the light pen input's.
54
+ def fire1_high? = port1.anybits?(FIRE)
55
+
56
+ def pot_x = selected_devices.map(&:pot_x).min || 0xff
57
+
58
+ def pot_y = selected_devices.map(&:pot_y).min || 0xff
59
+
60
+ private
61
+
62
+ def port1 = port_bits(@joystick1, @device1)
63
+
64
+ def port2 = port_bits(@joystick2, @device2)
65
+
66
+ def port_bits(joystick, device)
67
+ device ? joystick.port_bits & device.port_bits : joystick.port_bits
68
+ end
69
+
70
+ def selected_devices
71
+ lines = @cia&.port_a_lines || 0x00
72
+ devices = []
73
+ devices << @device1 if @device1 && lines.anybits?(PORT1_POTS)
74
+ devices << @device2 if @device2 && lines.anybits?(PORT2_POTS)
75
+ devices
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ class P500
6
+ # Where the P500's KERNAL, 901234-02, keeps the eight IEEE-488
7
+ # primitives: each seven bytes above the B series' (CBM2::IEEELayout),
8
+ # with the same file variables in segment 15's zero page. It drives
9
+ # the bus from the same ports, but leaves TPI1's PA2 an input, so its
10
+ # writes to TPI1's port A and its direction register differ in that
11
+ # bit: TALK and LISTEN set DDRA to $3B and PA to $FE, TKSA and ACPTR
12
+ # keep $3D of PA and set DDRA to $C3, ACPTR keeps $3D of PA and sets
13
+ # PA0, and UNTALK and UNLISTEN leave PA $FD before letting ATN go.
14
+ module IEEELayout
15
+ LineStep = KernalTrap::IEEELineStep
16
+
17
+ def self.write(address, value) = CBM2::IEEELayout.write(address, value)
18
+
19
+ def self.update(address, keep, set) = CBM2::IEEELayout.update(address, keep, set)
20
+
21
+ ATTENTION = [write(0xde03, 0x3b), write(0xdc00, 0xff), write(0xdc02, 0xff), write(0xde00, 0xfe),
22
+ update(0xde00, 0xf7, 0)].freeze
23
+ LISTEN_TO_TALKER = [update(0xde00, 0x3d, 0), write(0xde03, 0xc3), write(0xdc02, 0x00)].freeze
24
+
25
+ LINES = KernalTrap::IEEELines.new(
26
+ timer: CBM2::IEEELayout::TIMER, attention: ATTENTION, byte_sent: CBM2::IEEELayout::BYTE_SENT,
27
+ release_attention: CBM2::IEEELayout::RELEASE_ATTENTION, listen_to_talker: LISTEN_TO_TALKER,
28
+ byte_received: [update(0xde00, 0x3d, 0x01)].freeze,
29
+ release: (ATTENTION + CBM2::IEEELayout::BYTE_SENT + [write(0xde00, 0xfd)] + LISTEN_TO_TALKER.drop(1) +
30
+ CBM2::IEEELayout::RELEASE_ATTENTION).freeze
31
+ )
32
+
33
+ LAYOUT = KernalTrap::IEEELayout.new(
34
+ talk: 0xf237, listen: 0xf23b, second: 0xf27b, tksa: 0xf287, ciout: 0xf29e, untalk: 0xf2b2,
35
+ unlisten: 0xf2b6, acptr: 0xf311, overwrites_device: false, device_in_a: true,
36
+ zero_page: CBM2::IEEELayout::ZERO_PAGE, lines: LINES
37
+ )
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ class P500
6
+ # The machine's own fields as the B series keeps them (CBM2::KeyedState),
7
+ # and the whole machine as keyed snapshot modules (Snapshot::Keyed), in
8
+ # the order a load applies them: BL-MACHINE with the setup, the RAM
9
+ # with the segment registers, the colour RAM, the VIC, the SID, the
10
+ # CIA, the ACIA, both TPIs, the CPU, device 8 through the traps and a
11
+ # true 2031 on the IEEE-488 bus.
12
+ #
13
+ # A load lays the pages out before the chips load, and works out what
14
+ # each unit derives from its fields, the VIC's view of memory among
15
+ # them, once every unit is loaded.
16
+ module KeyedModules
17
+ # The SID's deferred cycles and writes, run up to this cycle, and the
18
+ # VIC's sprite collisions latched up to the beam.
19
+ def settle_chips_for_snapshot
20
+ @sid.catch_up!
21
+ @vic.fold_collisions!
22
+ end
23
+
24
+ def save_modules
25
+ bus = @bus
26
+ sections = [Snapshot::Keyed.section("BL-MACHINE") { |out| save_machine_fields(out) }]
27
+ sections << Snapshot::Keyed.section("BL-CPU") { |out| @cpu.save_fields(out) }
28
+ sections << Snapshot::Keyed.section("BL-RAM") { |out| bus.save_fields(out) }
29
+ sections << Snapshot::Keyed.section("BL-COLORRAM") { |out| @color_ram.save_fields(out, "ram") }
30
+ sections << Snapshot::Keyed.section("BL-VIC") { |out| @vic.save_fields(out) }
31
+ sections << Snapshot::Keyed.section("BL-SID") { |out| @sid.save_fields(out) }
32
+ sections << Snapshot::Keyed.section("BL-CIA") { |out| @cia.save_fields(out) }
33
+ sections << Snapshot::Keyed.section("BL-ACIA") { |out| @acia.save_fields(out) }
34
+ sections << Snapshot::Keyed.section("BL-TPI1") { |out| @tpi1.save_fields(out) }
35
+ sections << Snapshot::Keyed.section("BL-TPI2") { |out| @tpi2.save_fields(out) }
36
+ sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
37
+ save_drive_modules(sections)
38
+ end
39
+
40
+ # Raises Snapshot::FormatError for the modules of a machine built
41
+ # another way.
42
+ def load_modules(modules)
43
+ check_snapshot_setup(Snapshot::CBM2Setup.read_fields(modules.machine))
44
+ load_machine_fields(modules.machine)
45
+ @bus.load_fields(modules.required("BL-RAM"))
46
+ @bus.restored!
47
+ @color_ram.load_fields(modules.required("BL-COLORRAM"), "ram")
48
+ @vic.load_fields(modules.required("BL-VIC"))
49
+ @sid.load_fields(modules.required("BL-SID"))
50
+ @cia.load_fields(modules.required("BL-CIA"))
51
+ @acia.load_fields(modules.required("BL-ACIA"))
52
+ @tpi1.load_fields(modules.required("BL-TPI1"))
53
+ @tpi2.load_fields(modules.required("BL-TPI2"))
54
+ @cpu.load_fields(modules.required("BL-CPU"))
55
+ trap_dos = modules.optional("BL-TRAPDOS")
56
+ trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
57
+ load_drive_modules(modules)
58
+ restored!
59
+ self
60
+ end
61
+
62
+ private
63
+
64
+ def restored!
65
+ @cpu.restored!
66
+ @vic.restored!
67
+ @sid.restored!
68
+ @cia.restored!
69
+ @acia.restored!
70
+ update_video_bus
71
+ end
72
+
73
+ def check_snapshot_setup(setup)
74
+ ours = Snapshot::CBM2Setup.of(self)
75
+ return if setup == ours
76
+
77
+ raise Snapshot::FormatError, "the snapshot is of a #{setup}, not a #{ours}"
78
+ end
79
+ end
80
+
81
+ include CBM2::KeyedState
82
+ include KeyedModules
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ class P500
6
+ # The P500s badline builds by name: the 510 of xcbm5x0's -model, PAL
7
+ # with the 6569 and NTSC with the 6567R8, each with its own region's
8
+ # mains unless told otherwise. Both have 128K of RAM, segments 0 and 1,
9
+ # and the one KERNAL and BASIC.
10
+ module Model
11
+ Profile = Data.define(:name, :region) do
12
+ def ram_kb = RAM_SEGMENTS.length * 64
13
+ end
14
+
15
+ M510 = Profile.new(name: "510", region: Region::PAL)
16
+ M510_NTSC = Profile.new(name: "510ntsc", region: Region::NTSC)
17
+
18
+ ALL = [M510, M510_NTSC].freeze
19
+ DEFAULT = "510"
20
+
21
+ RAM_SEGMENTS = [0, 1].freeze
22
+
23
+ # 901234-02, the KERNAL xcbm5x0 runs, and its BASIC 128, 901236-02
24
+ # and 901235-02.
25
+ KERNALS = { "02" => "cbm2/p500-kernal.rom" }.freeze
26
+ BASIC = "cbm2/p500-basic.rom"
27
+
28
+ def self.named(name)
29
+ model = ALL.find { |candidate| candidate.name == name }
30
+ raise ArgumentError, "no P500 model named #{name}" if model.nil?
31
+
32
+ model
33
+ end
34
+
35
+ def self.include?(name) = ALL.any? { |model| model.name == name }
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ class P500
6
+ # Page $D8 of segment 15: the VIC-II's 64 registers, repeated over the
7
+ # page.
8
+ class VICPage
9
+ def initialize(vic)
10
+ @vic = vic
11
+ end
12
+
13
+ def peek(addr) = @vic.peek(0xd000 | (addr & 0x3f))
14
+
15
+ def poke(addr, value)
16
+ @vic.poke(0xd000 | (addr & 0x3f), value)
17
+ end
18
+ end
19
+
20
+ # The colour RAM's four bits at $D400-$D7FF of segment 15, which the
21
+ # CPU reads with 0 in the high four, as xcbm5x0 does.
22
+ class ColorPages
23
+ def initialize(color_ram)
24
+ @color_ram = color_ram
25
+ end
26
+
27
+ def peek(addr) = @color_ram.nibble(addr)
28
+
29
+ def poke(addr, value)
30
+ @color_ram.poke(addr, value)
31
+ end
32
+ end
33
+
34
+ # A TPI's page, $DE or $DF of segment 15. TPI1's CA and CB are STATVID
35
+ # and VICDOTSEL, and TPI2's PC6 and PC7 the VIC's bank lines, which
36
+ # an access to either chip can move, so the machine takes them again
37
+ # after each one (P500#update_video_bus).
38
+ class TPIPage
39
+ def initialize(tpi, machine)
40
+ @tpi = tpi
41
+ @machine = machine
42
+ end
43
+
44
+ def peek(addr)
45
+ value = @tpi.peek(addr)
46
+ @machine.update_video_bus
47
+ value
48
+ end
49
+
50
+ def poke(addr, value)
51
+ @tpi.poke(addr, value)
52
+ @machine.update_video_bus
53
+ end
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,264 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/cbm2/p500/model"
4
+ require "badline/cbm2/p500/pages"
5
+ require "badline/cbm2/p500/control_ports"
6
+ require "badline/cbm2/p500/ieee_layout"
7
+ require "badline/cbm2/p500/keyed_state"
8
+
9
+ module Badline
10
+ class CBM2
11
+ # The P500, the CBM 510: the B series' 6509, SID, CIA, ACIA and TPIs on
12
+ # the same segment bus (Bus, CPU), at the VIC-II's clock of about
13
+ # 1 MHz, with a VIC-II at $D800 in place of the 6845. RAM fills segments
14
+ # 0 and 1.
15
+ #
16
+ # Segment 15 holds what the B series' does but for $C000-$D7FF: the
17
+ # character ROM at $C000, which the CPU reads too, the 1K static video
18
+ # matrix at $D000 and the colour RAM's nibbles at $D400. What the VIC
19
+ # reads follows the P500's video bus (Chips::VIC::Bank#map_video_bus):
20
+ # TPI1's CA is STATVID and its CB VICDOTSEL, and TPI2's PC6 and PC7
21
+ # drive VA14 and VA15, which pick the 16K of segment 0 the VIC sees.
22
+ # BA halts the CPU as on the C64, and the VIC's IRQ reaches the CPU
23
+ # along with TPI1's.
24
+ #
25
+ # TPI1 is the interrupt controller as on the B series, with the mains
26
+ # on I0, the CIA on I2 and the ACIA on I4. TPI2 scans the B series'
27
+ # key matrix (KeyboardPorts), and the CIA reads the two control ports
28
+ # (ControlPorts), whose paddles reach the SID's pots and whose port 1
29
+ # fire line is the VIC's light pen input. Device 8 answers through traps on the KERNAL's
30
+ # IEEE-488 primitives as on the B series (IEEETraps, with the P500's
31
+ # IEEELayout), or a true 2031, 8050 or 8250 on the bus answers, on the
32
+ # same ports as the B series' (IEEEPorts, IEEEDrive).
33
+ class P500
34
+ include Machine::KeyboardBuffer
35
+ include Machine::Clocking
36
+ include Machine::Settling
37
+ include IEEETraps
38
+ include IEEEDrive
39
+
40
+ # The segment BASIC keeps its program text in.
41
+ TEXT_SEGMENT = 0
42
+
43
+ # The ACIA at $DD00, whose IRQ reaches TPI1's I4.
44
+ class ACIA < Chips::ACIA
45
+ end
46
+
47
+ attr_reader :model, :bus, :cpu, :vic, :sid, :cia, :acia, :tpi1, :tpi2, :keyboard, :joystick1, :joystick2,
48
+ :control_ports, :color_ram, :ieee, :mains_hz, :kernal, :cycles, :init_threshold
49
+
50
+ def ram = @bus.ram
51
+
52
+ # The VIC refreshes the DRAM in its own half of the cycle, so the
53
+ # refresh takes none of the CPU's.
54
+ def refresh? = false
55
+
56
+ def family = :cbm2
57
+
58
+ def peek(addr) = @bus.peek(addr)
59
+
60
+ def poke(addr, value) = @bus.poke(addr, value)
61
+
62
+ def install_debug_register(&) = @bus.install_debug_register(&)
63
+
64
+ def keyboard_buffer = CBM2::KEYBOARD_LAYOUT
65
+
66
+ def sound_source = @sid
67
+
68
+ # The VIC, whose display a front end shows.
69
+ def video = @vic
70
+
71
+ def region = @model.region
72
+
73
+ # +model+ names one of Model::ALL, and +mains_hz+ the mains, 50 or
74
+ # 60, the model's region's unless given. +true_drive_model+ names the
75
+ # drive plug_true_drive plugs in (Drives::IEEEModels).
76
+ def initialize(model: Model::DEFAULT, mains_hz: nil, debug: false, true_drive_model: Drives::IEEEModels::DEFAULT)
77
+ @model = Model.named(model)
78
+ @mains_hz = mains_hz || @model.region.mains_hz
79
+ raise ArgumentError, "the mains is 50 or 60 Hz, not #{@mains_hz}" unless [50, 60].include?(@mains_hz)
80
+
81
+ @kernal = Model::KERNALS.keys.first
82
+ clock_hz = @model.region.clock_hz
83
+ @bus = Bus.new(basic: Model::BASIC, kernal: Model::KERNALS.fetch(@kernal), ram_segments: Model::RAM_SEGMENTS,
84
+ screen_length: 0x400)
85
+ @vic = Chips::VIC.new(model: :mos6569, region: @model.region)
86
+ @color_ram = C64::ColorMemory.new(@vic, start: 0xd400)
87
+ map_video
88
+ @ieee = IEEEPorts.new(Drives::IEEEBus.new)
89
+ @tpi1 = Chips::TPI.new(peripheral: @ieee)
90
+ @joystick1 = Input::Joystick.new
91
+ @joystick2 = Input::Joystick.new
92
+ @control_ports = ControlPorts.new(@joystick1, @joystick2, @ieee.data)
93
+ @sid = Chips::SID.new(model: :mos6581, pots: @control_ports, at: 0xda00)
94
+ @cia = Chips::CIA.new(start: 0xdc00, peripheral: @control_ports, region: @model.region, tod_hz: @mains_hz)
95
+ @control_ports.connect(@cia)
96
+ @ieee.connect(@cia, @tpi1)
97
+ @lightpen_high = true
98
+ @acia = ACIA.new(self, clock_hz)
99
+ @keyboard = Input::Keyboard.new(matrix: KeyboardPorts::MATRIX)
100
+ keyboard_ports = KeyboardPorts.new(@keyboard, 0xc0)
101
+ @tpi2 = Chips::TPI.new(peripheral: keyboard_ports)
102
+ keyboard_ports.connect(@tpi2)
103
+ map_io
104
+ @cpu = CPU.new(@bus, debug:)
105
+ @cycles = 0
106
+ @mains_half_period = clock_hz / (2 * @mains_hz)
107
+ @mains_countdown = @mains_half_period
108
+ @mains_high = true
109
+ # The B series' refresh counter, which CBM2::KeyedState saves: 0
110
+ # here, where the VIC refreshes the DRAM.
111
+ @refresh = 0
112
+ @acia_irq = false
113
+ @acia_due = Chips::ACIA::NEVER
114
+ @init_handlers = []
115
+ @pending_keys = nil
116
+ @init_keys = nil
117
+ @dos = nil
118
+ @ieee_trap = nil
119
+ @capture_output = nil
120
+ init_ieee_drives(true_drive_model)
121
+ @init_threshold = INIT_THRESHOLD
122
+ update_video_bus
123
+ end
124
+
125
+ # The model, its RAM and its mains as the pause menu names them, such
126
+ # as "510, 128K RAM, 50 HZ".
127
+ def model_label = "#{@model.name}, #{@model.ram_kb}K RAM, #{@mains_hz} HZ"
128
+
129
+ def timing
130
+ region = @model.region
131
+ Timing.new(clock_hz: region.clock_hz, cycles_per_line: region.cycles_per_line,
132
+ lines_per_frame: region.lines_per_frame, crop: region.crop, pixel_width: 1, dots: 1)
133
+ end
134
+
135
+ # The VIC and the mains ahead of the chips, then TPI1's inputs from
136
+ # the CIA and the ACIA, and the CPU last, halted while BA is low on a
137
+ # read.
138
+ def cycle!
139
+ handle_init if @cycles == @init_threshold
140
+ feed_keyboard if @pending_keys
141
+
142
+ vic = @vic
143
+ vic.cycle!
144
+ tick_mains
145
+ @cia.cycle!
146
+ sample_lightpen
147
+ @sid.cycle!
148
+ @acia.run!(@cycles) if @cycles >= @acia_due
149
+ tpi1 = @tpi1
150
+ tpi1.interrupt_line(2, !@cia.interrupted?)
151
+ tpi1.interrupt_line(4, !@acia_irq)
152
+ cpu = @cpu
153
+ cpu.irq = tpi1.irq? || vic.interrupted?
154
+ cpu.pending_write? || !vic.ba_low? ? cpu.cycle! : cpu.stall!
155
+ clock_ieee_drives
156
+
157
+ @cycles += 1
158
+ end
159
+
160
+ # The ACIA's IRQ line, and the cycle it next has something to do at.
161
+ def acia_changed(irq, due)
162
+ @acia_irq = irq
163
+ @acia_due = due
164
+ end
165
+
166
+ # STATVID and VICDOTSEL from TPI1's CA and CB, PC6 and PC7 in mode 0,
167
+ # and VA14 and VA15 from TPI2's PC6 and PC7, to the VIC's bank.
168
+ def update_video_bus
169
+ lines = @tpi1.port_c_output
170
+ @vic.vic_bank.video_bus_lines(lines.anybits?(0x40), lines.anybits?(0x80), @tpi2.port_c_output >> 6)
171
+ end
172
+
173
+ # The RES line reaches the CPU and its segment registers, the SID, the
174
+ # CIA, the ACIA and both TPIs. The VIC has no reset pin.
175
+ def reset!
176
+ @sid.reset!
177
+ @cia.reset!
178
+ @acia.reset!
179
+ @tpi1.reset!
180
+ @tpi2.reset!
181
+ update_video_bus
182
+ @bus.reset!
183
+ @cpu.reset!
184
+ reset_ieee_drives
185
+ end
186
+
187
+ # Puts a program file's bytes where its first two say in BASIC's text
188
+ # segment, segment 0, and returns that address.
189
+ def load_prg(data)
190
+ load_addr = data[0] | (data[1] << 8)
191
+ bytes = data[2..] || []
192
+ bytes = bytes[0, 0x10000 - load_addr] if load_addr + bytes.length > 0x10000
193
+ bytes.each_with_index { |byte, i| @bus.poke_bank(TEXT_SEGMENT, load_addr + i, byte) }
194
+ load_addr
195
+ end
196
+
197
+ # The IEEELayout of 901234-02.
198
+ def trap_layout = IEEELayout::LAYOUT
199
+
200
+ # Puts a cartridge's ROM chips in (Bus#attach_cartridge) and switches
201
+ # the machine off and on, for the KERNAL to find it as it starts.
202
+ def attach_cartridge(chips)
203
+ @bus.attach_cartridge(chips)
204
+ power_cycle!
205
+ end
206
+
207
+ # Switches the machine off and on: RAM starts cleared, the VIC starts
208
+ # over, and the RES line resets everything else.
209
+ def power_cycle!
210
+ @bus.power_on!
211
+ @color_ram.clear!
212
+ @vic.power_on!
213
+ reset!
214
+ end
215
+
216
+ # The cycle by which the KERNAL has tested segments 0 and 1 and BASIC
217
+ # printed READY, with a margin.
218
+ INIT_THRESHOLD = 8_600_000
219
+
220
+ private
221
+
222
+ # The character ROM at $C000 and the colour RAM at $D400 of segment
223
+ # 15, and what the VIC reads.
224
+ def map_video
225
+ character_rom = ROM.load("character.rom", 0xc000)
226
+ @bus.map_video(character_rom, 0xc0, 0x10)
227
+ @bus.map_video(ColorPages.new(@color_ram), 0xd4, 0x04, writable: true)
228
+ bank = @vic.vic_bank
229
+ bank.map_character_rom(character_rom, base: 0xc000)
230
+ bank.map_video_bus(@bus.segment_ram[0], @bus.screen, @color_ram, 0xd400)
231
+ end
232
+
233
+ def map_io
234
+ @bus.map_io(0xd8, VICPage.new(@vic))
235
+ @bus.map_io(0xda, SIDPage.new(@sid))
236
+ @bus.map_io(0xdc, @cia)
237
+ @bus.map_io(0xdd, @acia)
238
+ @bus.map_io(0xde, TPIPage.new(@tpi1, self))
239
+ @bus.map_io(0xdf, TPIPage.new(@tpi2, self))
240
+ end
241
+
242
+ # The VIC's light pen input, CIA PA6 as the CIA drives it and control
243
+ # port 1's fire line pulls it, whose falling edge latches the beam.
244
+ def sample_lightpen
245
+ high = @cia.port_a_lines.anybits?(0x40) && @control_ports.fire1_high?
246
+ return if high == @lightpen_high
247
+
248
+ @lightpen_high = high
249
+ @vic.lightpen_level(high)
250
+ end
251
+
252
+ # The mains, low for half its period and high for the other half, on
253
+ # TPI1's I0, whose falling edge latches the tick.
254
+ def tick_mains
255
+ @mains_countdown -= 1
256
+ return unless @mains_countdown.zero?
257
+
258
+ @mains_countdown = @mains_half_period
259
+ @mains_high = !@mains_high
260
+ @tpi1.interrupt_line(0, @mains_high)
261
+ end
262
+ end
263
+ end
264
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # Page $DA of segment 15: the SID's 32 registers, repeated over the
6
+ # page.
7
+ class SIDPage
8
+ def initialize(sid)
9
+ @sid = sid
10
+ end
11
+
12
+ def peek(addr) = @sid.peek(0xda00 | (addr & 0x1f))
13
+
14
+ def poke(addr, value)
15
+ @sid.poke(0xda00 | (addr & 0x1f), value)
16
+ end
17
+ end
18
+ end
19
+ end