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,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # The 6509: a 6502 core with four more address lines, which pick one of
6
+ # the Bus's sixteen segments. Every access goes to the execution
7
+ # segment, but the ones at the address LDA (zp),Y and STA (zp),Y index,
8
+ # their data and the dummy read before it, which go to the indirection
9
+ # segment: the opcode and the zero-page pointer stay in the execution
10
+ # segment. Interrupts leave both segments alone.
11
+ class CPU
12
+ include Badline::Chips::CPU::Core
13
+
14
+ def initialize(memory, debug: false)
15
+ super
16
+ @indirect = false
17
+ end
18
+
19
+ def reset!
20
+ super
21
+ @indirect = false
22
+ end
23
+
24
+ private
25
+
26
+ def op_pointer_high_y
27
+ super
28
+ @indirect = @operation == :lda || @operation == :sta
29
+ end
30
+
31
+ def op_idx_dummy
32
+ return super unless @indirect
33
+
34
+ @memory.select_indirect!
35
+ super
36
+ @memory.select_execute!
37
+ end
38
+
39
+ def op_mem_read
40
+ return super unless @indirect
41
+
42
+ @indirect = false
43
+ @memory.select_indirect!
44
+ super
45
+ @memory.select_execute!
46
+ end
47
+
48
+ def op_mem_write
49
+ return super unless @indirect
50
+
51
+ @indirect = false
52
+ @memory.select_indirect!
53
+ super
54
+ @memory.select_execute!
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # What the B series makes of its 6845's outputs. Each character the
6
+ # CRTC displays reads the 2K screen RAM at its memory address, MA0-MA10,
7
+ # and the character ROM's 16-line glyph for the screen code's low seven
8
+ # bits, from the set TPI1's CA picks: the first 128 glyphs while it is
9
+ # low, the second while it is high. Bit 7 of the screen code reverses
10
+ # the glyph. The ROM's row is the CRTC's raster line, RA0-RA3, so the
11
+ # 600's eight lines show the 8x8 glyphs in the first half of each and
12
+ # the 700's fourteen the 8x16 ones.
13
+ #
14
+ # The cursor is the 6845's: on the character at R14:R15, from line
15
+ # R10's low five bits to line R11, it reverses the dots. R10's bits 5
16
+ # and 6 pick its blink: 00 steady, 01 no cursor, 10 on for 8 fields and
17
+ # off for 8, 11 on for 16 and off for 16.
18
+ #
19
+ # The picture is a PET::Canvas framed as xcbm2 frames its screenshots:
20
+ # 704 dots wide, the first displayed character 32 dots in, and 266
21
+ # lines high with the first row 33 lines down on a 600, or 367 lines
22
+ # with it 8 down on a 700. Each frame paints its displayed lines from
23
+ # there down. A frame laid out otherwise than the last, its lines,
24
+ # rows or sync moved, starts on a black canvas, so nothing the old
25
+ # layout painted stays.
26
+ class CRTCVideo
27
+ LEFT = 32
28
+ WIDTH = 704
29
+
30
+ # The lines of the picture and the line its first row starts on, for
31
+ # a 600 and a 700.
32
+ FRAMES = { false => [266, 33], true => [367, 8] }.freeze
33
+
34
+ attr_reader :canvas, :frames
35
+
36
+ # Whether it paints the characters: a run that never shows the
37
+ # picture can leave it alone.
38
+ attr_accessor :render
39
+
40
+ # +characters+ is the character ROM's bytes, +screen+ the screen RAM,
41
+ # +tpi+ TPI1, whose CA picks the set, and +registers+ the CRTC's.
42
+ def initialize(characters, screen, tpi, registers, high_profile:)
43
+ @glyphs = characters
44
+ @screen = screen
45
+ @tpi = tpi
46
+ @registers = registers
47
+ height, @top = FRAMES.fetch(high_profile)
48
+ @canvas = PET::Canvas.new(WIDTH, height)
49
+ @frames = 0
50
+ @line = 0
51
+ @line_shown = false
52
+ @origin = LEFT
53
+ @layout = []
54
+ @render = true
55
+ frame_start
56
+ end
57
+
58
+ def character(address, raster, since_hsync)
59
+ @line_shown = true
60
+ y = @top + @line
61
+ return if y >= @canvas.height || !@render
62
+
63
+ @canvas.paint(@origin + (since_hsync << 3), y, row_bits(address, raster))
64
+ end
65
+
66
+ def hsync; end
67
+
68
+ def line_end
69
+ @line += 1 if @line_shown
70
+ @line_shown = false
71
+ end
72
+
73
+ def rows_end; end
74
+
75
+ # A frame starts painting from the first row's line again, its
76
+ # characters placed so the first one the line displays sits LEFT
77
+ # dots in.
78
+ def frame_start
79
+ regs = @registers
80
+ @frames += 1
81
+ @line = 0
82
+ @origin = LEFT - ((regs[0] + 1 - regs[2]) << 3)
83
+ layout = regs.values_at(0, 1, 2, 4, 5, 6, 9, 12, 13)
84
+ return if layout == @layout
85
+
86
+ @layout = layout
87
+ @canvas.clear!
88
+ end
89
+
90
+ # The picture under canvas.*, the fields counted, the line the frame
91
+ # paints next and the layout it was framed by, in a keyed snapshot
92
+ # module (BL-VIDEO).
93
+ def save_fields(out)
94
+ out.int("frames", @frames).int("line", @line).bool("line_shown", @line_shown).int("origin", @origin)
95
+ out.ints("layout", @layout)
96
+ @canvas.save_fields(out, "canvas.")
97
+ end
98
+
99
+ def load_fields(input)
100
+ @frames = input.int("frames", @frames)
101
+ @line = input.int("line", @line)
102
+ @line_shown = input.bool("line_shown", @line_shown)
103
+ @origin = input.int("origin", @origin)
104
+ @layout = input.ints("layout", @layout).dup
105
+ @canvas.load_fields(input, "canvas.")
106
+ end
107
+
108
+ private
109
+
110
+ def row_bits(address, raster)
111
+ code = @screen.peek(0xd000 | (address & 0x7ff))
112
+ glyph = (code & 0x7f) | (@tpi.ca? ? 0x80 : 0)
113
+ bits = @glyphs[(glyph << 4) | (raster & 0x0f)]
114
+ bits ^= 0xff if code >= 0x80
115
+ bits ^= 0xff if cursor?(address, raster)
116
+ bits
117
+ end
118
+
119
+ def cursor?(address, raster)
120
+ regs = @registers
121
+ return false unless address == ((regs[14] << 8) | regs[15])
122
+ return false if raster < (regs[10] & 0x1f) || raster > regs[11]
123
+
124
+ case regs[10] & 0x60
125
+ when 0x00 then true
126
+ when 0x20 then false
127
+ when 0x40 then @frames.nobits?(0x08)
128
+ else @frames.nobits?(0x10)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/machine/ieee_drives"
4
+
5
+ module Badline
6
+ class CBM2
7
+ # The CBM-II's true drive, a 2031, 8050 or 8250 on its IEEE-488 bus
8
+ # (IEEEPorts), as Machine::IEEEDrives has it, at the machine's clock:
9
+ # 2 MHz on the B series, the VIC-II's on the P500. Both include it.
10
+ module IEEEDrive
11
+ include Machine::IEEEDrives
12
+
13
+ private
14
+
15
+ def ieee_host_clock_hz = timing.clock_hz
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # Where the B series KERNALs, 901244-04a and -03b, keep the eight
6
+ # IEEE-488 primitives, their file variables in segment 15, and the
7
+ # steps they take on the bus's lines (KernalTrap::IEEELayout). TALK and
8
+ # LISTEN take the device in A.
9
+ #
10
+ # The KERNAL drives the bus from TPI1's port A at $DE00 (PA3 ATN, PA4
11
+ # DAV, PA5 EOI, PA6 NDAC, PA7 NRFD, PA0-PA2 the transceivers'
12
+ # directions) and the CIA's port A at $DC00, the data lines inverted.
13
+ # TALK and LISTEN set both ports' directions for sending and pull ATN
14
+ # low, TKSA and ACPTR turn NRFD, NDAC and the data lines round for
15
+ # listening, and each handshake times out on CIA timer B, which the
16
+ # ROM starts with $FF to $DC07 and $11 to $DC0F and acknowledges by
17
+ # reading $DC0D. ACPTR leaves port A as TKSA does, without touching
18
+ # the directions. 901244-03b differs from -04a in setting PA0 where -04a
19
+ # clears it, at the end of UNTALK, UNLISTEN and ACPTR.
20
+ module IEEELayout
21
+ LineStep = KernalTrap::IEEELineStep
22
+
23
+ # A write of +value+ to +address+.
24
+ def self.write(address, value) = LineStep.new(address:, keep: 0, set: value)
25
+
26
+ # +address+'s bits in +keep+, ORed with +set+.
27
+ def self.update(address, keep, set) = LineStep.new(address:, keep:, set:)
28
+
29
+ TIMER = [write(0xdc07, 0xff), write(0xdc0f, 0x11),
30
+ LineStep.new(address: 0xdc0d, keep: KernalTrap::IEEE_LINE_READ, set: 0)].freeze
31
+ ATTENTION = [write(0xde03, 0x3f), write(0xdc00, 0xff), write(0xdc02, 0xff), write(0xde00, 0xfa),
32
+ update(0xde00, 0xf7, 0)].freeze
33
+ BYTE_SENT = [update(0xde00, 0xff, 0x12), write(0xdc00, 0xff)].freeze
34
+ RELEASE_ATTENTION = [update(0xde00, 0xff, 0x08)].freeze
35
+ LISTEN_TO_TALKER = [update(0xde00, 0x39, 0), write(0xde03, 0xc7), write(0xdc02, 0x00)].freeze
36
+
37
+ # The end of UNTALK and UNLISTEN, which leaves PA $F8, or $F9 on
38
+ # 901244-03b, before letting ATN go, and the end of ACPTR, which
39
+ # leaves PA0 clear, or set on 901244-03b.
40
+ def self.lines(release)
41
+ KernalTrap::IEEELines.new(
42
+ timer: TIMER, attention: ATTENTION, byte_sent: BYTE_SENT, release_attention: RELEASE_ATTENTION,
43
+ listen_to_talker: LISTEN_TO_TALKER, byte_received: [update(0xde00, 0x39, release & 0x01)].freeze,
44
+ release: (ATTENTION + BYTE_SENT + [write(0xde00, release)] + LISTEN_TO_TALKER.drop(1) +
45
+ RELEASE_ATTENTION).freeze
46
+ )
47
+ end
48
+
49
+ # The file variables in segment 15's zero page: the file name's
50
+ # pointer at $90, SAVE's start at $99 and its end at $96, ST at $9C,
51
+ # FNLEN at $9D, FA at $9F, SA at $A0 and VERIFY at $035F.
52
+ ZERO_PAGE = KernalTrap::ZeroPage.new(
53
+ status: 0x9c, verify: 0x035f, start_address: 0x99, end_address: 0x96, filename_length: 0x9d,
54
+ secondary_address: 0xa0, device: 0x9f, filename_address: 0x90, save_start: 0x99, load_address: 0x99
55
+ )
56
+
57
+ def self.layout(release)
58
+ KernalTrap::IEEELayout.new(
59
+ talk: 0xf230, listen: 0xf234, second: 0xf274, tksa: 0xf280, ciout: 0xf297, untalk: 0xf2ab,
60
+ unlisten: 0xf2af, acptr: 0xf30a, overwrites_device: false, device_in_a: true, zero_page: ZERO_PAGE,
61
+ lines: lines(release)
62
+ )
63
+ end
64
+
65
+ # By the KERNAL's name in Model::KERNALS.
66
+ LAYOUTS = { "04a" => layout(0xf8), "03b" => layout(0xf9) }.freeze
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # The CBM-II's side of the IEEE-488 bus (Drives::IEEEBus): the data
6
+ # lines DIO1-8 on the CIA's port A at $DC00, and the handshake on
7
+ # TPI1's port A at $DE00:
8
+ #
9
+ # PA3 ATN PA4 DAV PA5 EOI PA6 NDAC PA7 NRFD
10
+ #
11
+ # PA0-PA2 set the transceivers' directions, which follow the ports'
12
+ # direction registers as the KERNAL sets them. Each line goes through
13
+ # its transceiver as it is: an output bit at 0 pulls its line low, and
14
+ # an input bit reads its line, 0 while it is low. It is TPI1's
15
+ # peripheral, and Data the CIA's.
16
+ #
17
+ # The machine pushes the lines it pulls into the bus for the drives on
18
+ # it once a cycle while a drive is on the bus (#cycle_lines), and only
19
+ # when they moved.
20
+ class IEEEPorts
21
+ ATN = 0x08
22
+ DAV = 0x10
23
+ EOI = 0x20
24
+ NDAC = 0x40
25
+ NRFD = 0x80
26
+
27
+ # The CIA's port A: the data lines.
28
+ class Data
29
+ def initialize(ports)
30
+ @ports = ports
31
+ end
32
+
33
+ def read_a(_port_a, _port_b) = ~@ports.low_lines & 0xff
34
+
35
+ def read_b(_port_a, _port_b) = 0xff
36
+
37
+ def port_b4_high? = true
38
+ end
39
+
40
+ attr_reader :bus, :data
41
+
42
+ def initialize(bus)
43
+ @bus = bus
44
+ @data = Data.new(self)
45
+ @cia = nil
46
+ @tpi = nil
47
+ @pushed = 0
48
+ end
49
+
50
+ def connect(cia, tpi)
51
+ @cia = cia
52
+ @tpi = tpi
53
+ end
54
+
55
+ # TPI1's port A: the handshake lines and ATN.
56
+ def read_a(_lines)
57
+ low = low_lines
58
+ value = 0xff
59
+ value &= ~ATN if low.anybits?(Drives::IEEEBus::ATN)
60
+ value &= ~DAV if low.anybits?(Drives::IEEEBus::DAV)
61
+ value &= ~EOI if low.anybits?(Drives::IEEEBus::EOI)
62
+ value &= ~NDAC if low.anybits?(Drives::IEEEBus::NDAC)
63
+ value &= ~NRFD if low.anybits?(Drives::IEEEBus::NRFD)
64
+ value & 0xff
65
+ end
66
+
67
+ def read_b(_lines) = 0xff
68
+
69
+ def read_c(_lines) = 0xff
70
+
71
+ # The lines low on the bus.
72
+ def low_lines = @bus.low_lines(controller_lines)
73
+
74
+ # The drives on the bus see the lines the machine pulls now, if they
75
+ # moved.
76
+ def cycle_lines
77
+ lines = controller_lines
78
+ push(lines) unless lines == @pushed
79
+ end
80
+
81
+ # The drives on the bus see the lines the machine pulls now.
82
+ def push_lines = push(controller_lines)
83
+
84
+ private
85
+
86
+ def push(lines)
87
+ @pushed = lines
88
+ @bus.host_lines = lines
89
+ end
90
+
91
+ # The lines the machine pulls low.
92
+ def controller_lines
93
+ low = ~@cia.port_a_lines & Drives::IEEEBus::DIO
94
+ port_a = @tpi.port_a_output
95
+ low |= Drives::IEEEBus::ATN if port_a.nobits?(ATN)
96
+ low |= Drives::IEEEBus::DAV if port_a.nobits?(DAV)
97
+ low |= Drives::IEEEBus::EOI if port_a.nobits?(EOI)
98
+ low |= Drives::IEEEBus::NDAC if port_a.nobits?(NDAC)
99
+ low |= Drives::IEEEBus::NRFD if port_a.nobits?(NRFD)
100
+ low
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # The disk device 8 serves through the traps on the KERNAL's IEEE-488
6
+ # primitives (KernalTrap::IEEE, IEEELayout), and CHROUT capture. There
7
+ # is no LOAD or SAVE trap: the ROM's own LOAD and SAVE, which move the
8
+ # bytes into the segment they name, and BASIC's disk commands run
9
+ # through the primitives. Other devices find only a true drive on the
10
+ # bus (IEEEDrive), and the KERNAL reports the rest not present.
11
+ module IEEETraps
12
+ # Puts the storage in device 8. Mounting again swaps the disk while
13
+ # the machine runs, and the drive keeps its RAM and its status. The
14
+ # traps leave device 8 to a true drive with that number.
15
+ def mount(storage)
16
+ return @dos.insert(storage) if @dos
17
+
18
+ @dos = dos = KernalTrap::DOS.new(storage, channel_saves: true)
19
+ @ieee_trap = KernalTrap::IEEE.new(cpu:, bus:, layout: trap_layout, dos:).install
20
+ @ieee_trap.device = trap_device
21
+ @ieee_trap
22
+ end
23
+
24
+ # Takes device 8's storage out, and the traps with it.
25
+ def unmount
26
+ trap = @ieee_trap
27
+ return unless trap
28
+
29
+ trap.uninstall
30
+ @ieee_trap = nil
31
+ @dos = nil
32
+ end
33
+
34
+ # Whether device 8 serves a disk or directory through the traps.
35
+ def mounted? = !@dos.nil?
36
+
37
+ # The path of the disk or directory device 8 serves, or an empty one.
38
+ def mounted_path = @dos.nil? ? "" : @dos.path
39
+
40
+ # Device 8 through the traps in a keyed snapshot module (BL-TRAPDOS):
41
+ # the storage under storage.*, the DOS, and the IEEE-488 trap under
42
+ # ieee.*. Nothing mounted writes nothing.
43
+ def save_trap_dos_fields(out)
44
+ dos = @dos
45
+ return unless dos
46
+
47
+ Snapshot::StorageSetup.write_fields(dos.storage, out)
48
+ dos.save_fields(out)
49
+ @ieee_trap&.save_fields(out, "ieee.")
50
+ end
51
+
52
+ # `detached` mounts a disk image write-protected and no directory.
53
+ def load_trap_dos_fields(input, detached: false)
54
+ unmount
55
+ return if input.missing?
56
+
57
+ mount(Snapshot::StorageSetup.read_fields(input, detached:))
58
+ @dos&.load_fields(input)
59
+ @ieee_trap&.load_fields(input, "ieee.")
60
+ end
61
+
62
+ # Records what the KERNAL prints through CHROUT (KernalTrap::Chrout).
63
+ def capture_output
64
+ @capture_output ||= KernalTrap::Chrout.new(cpu:, bus:, layout: trap_layout).tap do |trap|
65
+ cpu.install_trap(KernalTrap::Chrout::ADDRESS) { trap.call }
66
+ end
67
+ end
68
+
69
+ # The IEEELayout of the KERNAL the machine runs.
70
+ def trap_layout = IEEELayout::LAYOUTS.fetch(@kernal)
71
+
72
+ private
73
+
74
+ # Device 8, unless a true drive is device 8.
75
+ def trap_device
76
+ device = KernalTrap::Routine::DEVICE
77
+ true_drive&.device == device ? nil : device
78
+ end
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,60 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # What TPI2's ports read from outside the chip: the key matrix and the
6
+ # board's jumpers. The KERNAL drives the matrix's sixteen column lines
7
+ # low one at a time from PB0-PB7 and PA0-PA7 and reads its six row
8
+ # lines on PC0-PC5, a 0 for each key down. PC6 and PC7 read the
9
+ # jumpers: PC7 high on a 700, whose editor then loads its own CRTC
10
+ # table, and PC6 high on a 600 at 60 Hz.
11
+ #
12
+ # MATRIX lists the keys on each column line, PB0 first, in row order,
13
+ # :none where no key sits, as the KERNAL's key table at $EA29 places
14
+ # them, named as the B series' keyboard matrix sheet labels them.
15
+ class KeyboardPorts
16
+ MATRIX = [
17
+ %i[f1 escape tab none shift control],
18
+ %i[f2 1 q a z none],
19
+ %i[f3 2 w s x c],
20
+ %i[f4 3 e d f v],
21
+ %i[f5 4 r t g b],
22
+ %i[f6 5 6 y h n],
23
+ %i[f7 7 u j m space],
24
+ %i[f8 8 i k , .],
25
+ %i[f9 9 o l ; /],
26
+ [:f10, :"0", :-, :p, :"[", :"'"],
27
+ [:cursor_down, :"=", :left_arrow, :"]", :return, :pi],
28
+ %i[cursor_up cursor_left cursor_right delete cbm none],
29
+ %i[clr_home kp_question kp7 kp4 kp1 kp0],
30
+ %i[off_rvs ce kp8 kp5 kp2 kp_period],
31
+ %i[norm_graph kp_multiply kp9 kp6 kp3 kp00],
32
+ %i[run_stop kp_divide kp_minus kp_plus kp_enter none]
33
+ ].freeze
34
+
35
+ attr_reader :keyboard
36
+
37
+ # +jumpers+ is PC6 and PC7 as the board's jumpers drive them.
38
+ def initialize(keyboard, jumpers)
39
+ @keyboard = keyboard
40
+ @jumpers = jumpers
41
+ @tpi = nil
42
+ end
43
+
44
+ # The TPI whose ports drive the column lines.
45
+ def connect(tpi)
46
+ @tpi = tpi
47
+ end
48
+
49
+ def read_a(lines) = lines
50
+
51
+ def read_b(lines) = lines
52
+
53
+ def read_c(lines)
54
+ columns = (@tpi.port_a_output << 8) | @tpi.port_b_output
55
+ rows = @keyboard.scan(columns, lines | 0x3f).last
56
+ (rows & 0x3f) | (@jumpers & lines & 0xc0)
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # The whole machine as keyed snapshot modules (Snapshot::Keyed), in the
6
+ # order a load applies them: BL-MACHINE with the setup, the RAM with
7
+ # the segment registers, the CRTC, the picture, the SID, the CIA, the ACIA, both
8
+ # TPIs, the CPU, device 8 through the traps and a true 2031 on the
9
+ # IEEE-488 bus.
10
+ #
11
+ # A load lays the pages out before the chips load, and works out what
12
+ # each unit derives from its fields once every unit is loaded.
13
+ module KeyedModules
14
+ def save_modules
15
+ bus = @bus
16
+ sections = [Snapshot::Keyed.section("BL-MACHINE") { |out| save_machine_fields(out) }]
17
+ sections << Snapshot::Keyed.section("BL-CPU") { |out| @cpu.save_fields(out) }
18
+ sections << Snapshot::Keyed.section("BL-RAM") { |out| bus.save_fields(out) }
19
+ sections << Snapshot::Keyed.section("BL-CRTC") { |out| @crtc.save_fields(out) }
20
+ sections << Snapshot::Keyed.section("BL-VIDEO") { |out| @video.save_fields(out) }
21
+ sections << Snapshot::Keyed.section("BL-SID") { |out| @sid.save_fields(out) }
22
+ sections << Snapshot::Keyed.section("BL-CIA") { |out| @cia.save_fields(out) }
23
+ sections << Snapshot::Keyed.section("BL-ACIA") { |out| @acia.save_fields(out) }
24
+ sections << Snapshot::Keyed.section("BL-TPI1") { |out| @tpi1.save_fields(out) }
25
+ sections << Snapshot::Keyed.section("BL-TPI2") { |out| @tpi2.save_fields(out) }
26
+ sections << Snapshot::Keyed.section("BL-TRAPDOS") { |out| save_trap_dos_fields(out) } if mounted?
27
+ save_drive_modules(sections)
28
+ end
29
+
30
+ # Raises Snapshot::FormatError for the modules of a machine built
31
+ # another way.
32
+ def load_modules(modules)
33
+ check_snapshot_setup(Snapshot::CBM2Setup.read_fields(modules.machine))
34
+ load_machine_fields(modules.machine)
35
+ @bus.load_fields(modules.required("BL-RAM"))
36
+ @bus.restored!
37
+ @crtc.load_fields(modules.required("BL-CRTC"))
38
+ @video.load_fields(modules.required("BL-VIDEO"))
39
+ @sid.load_fields(modules.required("BL-SID"))
40
+ @cia.load_fields(modules.required("BL-CIA"))
41
+ @acia.load_fields(modules.required("BL-ACIA"))
42
+ @tpi1.load_fields(modules.required("BL-TPI1"))
43
+ @tpi2.load_fields(modules.required("BL-TPI2"))
44
+ @cpu.load_fields(modules.required("BL-CPU"))
45
+ trap_dos = modules.optional("BL-TRAPDOS")
46
+ trap_dos ? load_trap_dos_fields(trap_dos, detached: modules.detached?) : unmount
47
+ load_drive_modules(modules)
48
+ @cpu.restored!
49
+ @sid.restored!
50
+ @cia.restored!
51
+ @acia.restored!
52
+ self
53
+ end
54
+
55
+ private
56
+
57
+ def check_snapshot_setup(setup)
58
+ ours = Snapshot::CBM2Setup.of(self)
59
+ return if setup == ours
60
+
61
+ raise Snapshot::FormatError, "the snapshot is of a #{setup}, not a #{ours}"
62
+ end
63
+ end
64
+
65
+ include KeyedModules
66
+ end
67
+ end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ class Bus
6
+ # Segment 15's RAM, the screen RAM, the RAM board's segments, both
7
+ # segment registers, the last byte on the bus and a cartridge's ROMs
8
+ # by load address, in a keyed snapshot module (BL-RAM). The pages are
9
+ # laid out again once loaded (#restored!). The BASIC and KERNAL ROMs
10
+ # come with the machine.
11
+ module KeyedState
12
+ def save_fields(out)
13
+ @ram.save_fields(out, "system")
14
+ @screen.save_fields(out, "screen")
15
+ @segment_ram.each_with_index { |memory, segment| memory&.save_fields(out, "segment#{segment}") }
16
+ out.int("execution_segment", @execution_segment).int("indirect_segment", @indirect_segment)
17
+ out.int("data", @data)
18
+ out.ints("cartridge.addresses", @cartridge.map(&:start))
19
+ @cartridge.each { |rom| out.bytes("cartridge.#{rom.start}", rom.snapshot) }
20
+ end
21
+
22
+ def load_fields(input)
23
+ @ram.load_fields(input, "system")
24
+ @screen.load_fields(input, "screen")
25
+ @segment_ram.each_with_index { |memory, segment| memory&.load_fields(input, "segment#{segment}") }
26
+ @execution_segment = input.masked("execution_segment", @execution_segment, 0x0f)
27
+ @indirect_segment = input.masked("indirect_segment", @indirect_segment, 0x0f)
28
+ @data = input.byte("data", @data)
29
+ @cartridge = input.ints("cartridge.addresses", []).filter_map { |address| cartridge_rom(input, address) }
30
+ end
31
+
32
+ def restored! = map_pages
33
+
34
+ private
35
+
36
+ # The cartridge ROM saved for +address+, in $1000-$7FFF of segment
37
+ # 15, or nil.
38
+ def cartridge_rom(input, address)
39
+ data = input.bytes("cartridge.#{address}", "".b).bytes
40
+ return nil if data.empty? || address < 0x1000 || address + data.length > 0x8000
41
+
42
+ ROM.new(data, length: data.length, start: address)
43
+ end
44
+ end
45
+
46
+ include KeyedState
47
+ end
48
+
49
+ # The machine's own fields in a keyed snapshot module (Snapshot::Keyed,
50
+ # BL-MACHINE): the family, its setup by name (Snapshot::CBM2Setup), the
51
+ # cycle count, where the mains is in its period, the refresh counter,
52
+ # and the host's typing
53
+ # still to feed and on_init handlers still to run under host.*. The
54
+ # badline version that wrote it is written for reports and never read.
55
+ module KeyedState
56
+ # How many more on_init handlers the machine a snapshot was taken of had
57
+ # yet to run than this one has. Nil until the machine is restored.
58
+ attr_reader :init_handlers_lost
59
+
60
+ # Whether a snapshot saves now (Machine::Settling): the CPU between
61
+ # instructions or jammed.
62
+ def settled_for_snapshot? = @cpu.boundary? || @cpu.jammed?
63
+
64
+ # The SID's deferred cycles and writes, run up to this cycle.
65
+ def settle_chips_for_snapshot = @sid.catch_up!
66
+
67
+ def save_machine_fields(out)
68
+ out.text("family", "cbm2").text("written_by", VERSION)
69
+ Snapshot::CBM2Setup.of(self).write_fields(out)
70
+ out.int("cycles", @cycles).int("mains_countdown", @mains_countdown).bool("mains_high", @mains_high)
71
+ out.int("refresh", @refresh)
72
+ pending = @pending_keys
73
+ out.ints("host.pending_keys", pending) if pending
74
+ save_init_keys(out)
75
+ out.int("host.init_pending", @cycles <= @init_threshold ? @init_handlers.length : 0)
76
+ save_ieee_drive_fields(out)
77
+ end
78
+
79
+ def load_machine_fields(input)
80
+ @cycles = input.int("cycles", @cycles)
81
+ countdown = input.int("mains_countdown", @mains_countdown)
82
+ @mains_countdown = countdown.between?(1, @mains_half_period) ? countdown : @mains_half_period
83
+ @mains_high = input.bool("mains_high", @mains_high)
84
+ @refresh = input.int("refresh", 0).clamp(0, REFRESH_COUNT)
85
+ pending = input.ints("host.pending_keys", [])
86
+ @pending_keys = pending.empty? ? nil : pending.dup
87
+ load_init_keys(input)
88
+ @init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
89
+ load_ieee_drive_fields(input)
90
+ end
91
+ end
92
+
93
+ include KeyedState
94
+ end
95
+ end