badline 0.13.0 → 0.14.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 (254) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +49 -1
  3. data/CLAUDE.md +27 -7
  4. data/CONTRIBUTING.md +4 -3
  5. data/README.md +8 -5
  6. data/doc/media.md +2 -1
  7. data/doc/pinned-behaviour.md +29 -0
  8. data/doc/snapshot-format.md +1047 -0
  9. data/doc/snapshots.md +72 -39
  10. data/lib/badline/audio/bare_player.rb +50 -17
  11. data/lib/badline/audio/machine_player.rb +15 -9
  12. data/lib/badline/audio/renderer.rb +32 -21
  13. data/lib/badline/c128/banks.rb +6 -3
  14. data/lib/badline/c128/bus/keyed_state.rb +45 -0
  15. data/lib/badline/c128/bus.rb +17 -3
  16. data/lib/badline/c128/drives.rb +6 -25
  17. data/lib/badline/c128/keyed_modules.rb +117 -0
  18. data/lib/badline/c128/keyed_state.rb +91 -0
  19. data/lib/badline/c128/keys.rb +9 -0
  20. data/lib/badline/c128/mmu.rb +9 -8
  21. data/lib/badline/c128/mmu_pages.rb +20 -0
  22. data/lib/badline/c128/modes.rb +19 -9
  23. data/lib/badline/c128/z80_turns.rb +1 -1
  24. data/lib/badline/c128.rb +8 -8
  25. data/lib/badline/c64/attachments.rb +0 -44
  26. data/lib/badline/c64/banked_ram/plus256k.rb +18 -10
  27. data/lib/badline/c64/banked_ram/plus60k.rb +8 -6
  28. data/lib/badline/c64/banked_ram/unexpanded.rb +2 -2
  29. data/lib/badline/c64/bus/debug_register.rb +32 -0
  30. data/lib/badline/c64/bus/keyed_state.rb +41 -0
  31. data/lib/badline/c64/bus.rb +2 -1
  32. data/lib/badline/c64/cartridge/action_replay.rb +0 -13
  33. data/lib/badline/c64/cartridge/easy_flash.rb +0 -15
  34. data/lib/badline/c64/cartridge/epyx_fastload.rb +0 -8
  35. data/lib/badline/c64/cartridge/final_cartridge3.rb +0 -12
  36. data/lib/badline/c64/cartridge/flash.rb +3 -3
  37. data/lib/badline/c64/cartridge/g_mod2.rb +0 -12
  38. data/lib/badline/c64/cartridge/geo_ram.rb +0 -16
  39. data/lib/badline/c64/cartridge/kcs_power.rb +0 -8
  40. data/lib/badline/c64/cartridge/keyed_state.rb +393 -0
  41. data/lib/badline/c64/cartridge/pagefox.rb +0 -10
  42. data/lib/badline/c64/cartridge/retro_replay.rb +0 -8
  43. data/lib/badline/c64/cartridge/rgcd.rb +0 -10
  44. data/lib/badline/c64/cartridge/super_games.rb +0 -10
  45. data/lib/badline/c64/cartridge/zaxxon.rb +0 -16
  46. data/lib/badline/c64/cartridge.rb +1 -27
  47. data/lib/badline/c64/kernal_traps.rb +28 -25
  48. data/lib/badline/c64/keyed_modules.rb +111 -0
  49. data/lib/badline/c64/keyed_state.rb +68 -0
  50. data/lib/badline/c64/reu/dma.rb +0 -4
  51. data/lib/badline/c64/reu/keyed_state.rb +140 -0
  52. data/lib/badline/c64/reu/ram.rb +0 -15
  53. data/lib/badline/c64/reu.rb +1 -3
  54. data/lib/badline/c64/true_drives.rb +56 -5
  55. data/lib/badline/c64.rb +7 -3
  56. data/lib/badline/chips/cia/interrupt_register.rb +0 -17
  57. data/lib/badline/chips/cia/keyed_parts.rb +213 -0
  58. data/lib/badline/chips/cia/serial.rb +0 -4
  59. data/lib/badline/chips/cia/time_of_day.rb +0 -22
  60. data/lib/badline/chips/cia/timer.rb +0 -20
  61. data/lib/badline/chips/cia.rb +2 -2
  62. data/lib/badline/chips/cpu/core.rb +18 -2
  63. data/lib/badline/chips/cpu/keyed_state.rb +177 -0
  64. data/lib/badline/chips/cpu/microcode.rb +16 -0
  65. data/lib/badline/chips/cpu/port.rb +20 -3
  66. data/lib/badline/chips/cpu.rb +5 -0
  67. data/lib/badline/chips/sid/decimator.rb +10 -8
  68. data/lib/badline/chips/sid/envelope.rb +1 -6
  69. data/lib/badline/chips/sid/filter.rb +0 -14
  70. data/lib/badline/chips/sid/keyed_parts.rb +286 -0
  71. data/lib/badline/chips/sid/voice.rb +0 -10
  72. data/lib/badline/chips/sid/waveform.rb +0 -2
  73. data/lib/badline/chips/sid.rb +1 -2
  74. data/lib/badline/chips/ted/beam.rb +119 -0
  75. data/lib/badline/chips/ted/timers.rb +66 -0
  76. data/lib/badline/chips/ted.rb +143 -0
  77. data/lib/badline/chips/tia.rb +49 -0
  78. data/lib/badline/chips/vdc/keyed_parts.rb +105 -0
  79. data/lib/badline/chips/vdc/painter.rb +0 -12
  80. data/lib/badline/chips/vdc/raster.rb +0 -16
  81. data/lib/badline/chips/vdc/window.rb +0 -9
  82. data/lib/badline/chips/vdc.rb +1 -2
  83. data/lib/badline/chips/via/control_lines.rb +0 -15
  84. data/lib/badline/chips/via/interrupt_register.rb +0 -9
  85. data/lib/badline/chips/via/keyed_parts.rb +163 -0
  86. data/lib/badline/chips/via/shift_register.rb +0 -14
  87. data/lib/badline/chips/via/timer1.rb +0 -13
  88. data/lib/badline/chips/via/timer2.rb +0 -18
  89. data/lib/badline/chips/via.rb +1 -2
  90. data/lib/badline/chips/vic/border_mask.rb +0 -10
  91. data/lib/badline/chips/vic/collisions.rb +0 -11
  92. data/lib/badline/chips/vic/color_patches.rb +0 -11
  93. data/lib/badline/chips/vic/display_state.rb +0 -19
  94. data/lib/badline/chips/vic/graphics_shifter.rb +0 -4
  95. data/lib/badline/chips/vic/keyed_parts.rb +228 -0
  96. data/lib/badline/chips/vic/keyed_sprites.rb +225 -0
  97. data/lib/badline/chips/vic/keyed_state.rb +102 -0
  98. data/lib/badline/chips/vic/register_log.rb +0 -18
  99. data/lib/badline/chips/vic/registers.rb +0 -9
  100. data/lib/badline/chips/vic/sequencer.rb +0 -2
  101. data/lib/badline/chips/vic/sprite/internal_bus.rb +0 -11
  102. data/lib/badline/chips/vic/sprite.rb +0 -2
  103. data/lib/badline/chips/vic/sprites.rb +0 -3
  104. data/lib/badline/chips/vic.rb +6 -2
  105. data/lib/badline/chips/vic_i/keyed_parts.rb +142 -0
  106. data/lib/badline/chips/vic_i/painter.rb +0 -4
  107. data/lib/badline/chips/vic_i/sound.rb +38 -46
  108. data/lib/badline/chips/vic_i.rb +2 -2
  109. data/lib/badline/chips/z80/core.rb +2 -2
  110. data/lib/badline/chips/z80/keyed_state.rb +73 -0
  111. data/lib/badline/chips.rb +10 -0
  112. data/lib/badline/core.rb +4 -21
  113. data/lib/badline/drives/datasette/keyed_state.rb +38 -0
  114. data/lib/badline/drives/datasette.rb +40 -7
  115. data/lib/badline/drives/engine/core.rb +3 -0
  116. data/lib/badline/drives/engine/serial_lines.rb +4 -7
  117. data/lib/badline/drives/model1541/bus.rb +0 -13
  118. data/lib/badline/drives/model1541/disk.rb +9 -8
  119. data/lib/badline/drives/model1541/keyed_state.rb +251 -0
  120. data/lib/badline/drives/model1541/mechanism.rb +0 -2
  121. data/lib/badline/drives/model1541.rb +1 -2
  122. data/lib/badline/drives/model1571/bus.rb +0 -13
  123. data/lib/badline/drives/model1571/keyed_state.rb +108 -0
  124. data/lib/badline/drives/model1571/wd1770.rb +0 -14
  125. data/lib/badline/drives/model1571.rb +1 -2
  126. data/lib/badline/drives/model1581/bus.rb +0 -13
  127. data/lib/badline/drives/model1581/disk.rb +0 -4
  128. data/lib/badline/drives/model1581/keyed_state.rb +280 -0
  129. data/lib/badline/drives/model1581/mechanism.rb +0 -21
  130. data/lib/badline/drives/model1581/slot.rb +0 -18
  131. data/lib/badline/drives/model1581/wd1772.rb +0 -2
  132. data/lib/badline/drives/model1581.rb +2 -2
  133. data/lib/badline/drives.rb +8 -0
  134. data/lib/badline/frontend/app.rb +3 -3
  135. data/lib/badline/frontend/boot.rb +1 -1
  136. data/lib/badline/frontend/controls.rb +19 -3
  137. data/lib/badline/frontend/drive_led.rb +44 -21
  138. data/lib/badline/frontend/menu/menu_dialogs.rb +2 -2
  139. data/lib/badline/frontend/snapshots.rb +12 -1
  140. data/lib/badline/frontend/timeline.rb +28 -10
  141. data/lib/badline/input/matrix_typist.rb +98 -0
  142. data/lib/badline/input.rb +1 -0
  143. data/lib/badline/kernal_trap/chrout.rb +45 -0
  144. data/lib/badline/kernal_trap/dos/channel.rb +0 -27
  145. data/lib/badline/kernal_trap/dos/channels.rb +0 -25
  146. data/lib/badline/kernal_trap/dos/memory.rb +0 -9
  147. data/lib/badline/kernal_trap/dos/status.rb +0 -8
  148. data/lib/badline/kernal_trap/dos/write_file.rb +0 -18
  149. data/lib/badline/kernal_trap/dos.rb +0 -19
  150. data/lib/badline/kernal_trap/file_routine.rb +4 -4
  151. data/lib/badline/kernal_trap/keyed_state.rb +150 -0
  152. data/lib/badline/kernal_trap/layout.rb +75 -11
  153. data/lib/badline/kernal_trap/load.rb +14 -16
  154. data/lib/badline/kernal_trap/routine.rb +12 -0
  155. data/lib/badline/kernal_trap/save.rb +8 -18
  156. data/lib/badline/kernal_trap/serial.rb +1 -17
  157. data/lib/badline/kernal_trap.rb +2 -0
  158. data/lib/badline/machine/keyboard_buffer.rb +49 -0
  159. data/lib/badline/machine/settling.rb +29 -0
  160. data/lib/badline/machine.rb +5 -2
  161. data/lib/badline/media/boot_disk.rb +14 -2
  162. data/lib/badline/media/extensions.rb +10 -0
  163. data/lib/badline/memory.rb +3 -6
  164. data/lib/badline/options/family.rb +5 -2
  165. data/lib/badline/options/help.rb +42 -16
  166. data/lib/badline/options/table.rb +2 -0
  167. data/lib/badline/options/validation.rb +2 -0
  168. data/lib/badline/options.rb +6 -1
  169. data/lib/badline/plus4/acia.rb +45 -0
  170. data/lib/badline/plus4/bus.rb +312 -0
  171. data/lib/badline/plus4/cpu.rb +10 -0
  172. data/lib/badline/plus4/cpu_port.rb +93 -0
  173. data/lib/badline/plus4/keyboard_port.rb +51 -0
  174. data/lib/badline/plus4/model.rb +38 -0
  175. data/lib/badline/plus4/user_port.rb +31 -0
  176. data/lib/badline/plus4.rb +164 -0
  177. data/lib/badline/roms/README +38 -0
  178. data/lib/badline/roms/plus4/basic.rom +0 -0
  179. data/lib/badline/roms/plus4/function-high.rom +0 -0
  180. data/lib/badline/roms/plus4/function-low.rom +0 -0
  181. data/lib/badline/roms/plus4/kernal-ntsc.rom +0 -0
  182. data/lib/badline/roms/plus4/kernal-pal.rom +0 -0
  183. data/lib/badline/snapshot/c128_setup.rb +9 -20
  184. data/lib/badline/snapshot/c64_setup.rb +28 -24
  185. data/lib/badline/snapshot/container.rb +4 -0
  186. data/lib/badline/snapshot/field_doc/markdown.rb +112 -0
  187. data/lib/badline/snapshot/field_doc.rb +109 -0
  188. data/lib/badline/snapshot/image.rb +134 -28
  189. data/lib/badline/snapshot/keyed/cursor.rb +40 -0
  190. data/lib/badline/snapshot/keyed/modules.rb +66 -0
  191. data/lib/badline/snapshot/keyed/reader.rb +188 -0
  192. data/lib/badline/snapshot/keyed/recorder.rb +88 -0
  193. data/lib/badline/snapshot/keyed/report.rb +26 -0
  194. data/lib/badline/snapshot/keyed/writer.rb +55 -0
  195. data/lib/badline/snapshot/keyed.rb +64 -0
  196. data/lib/badline/snapshot/machine_state.rb +17 -64
  197. data/lib/badline/snapshot/storage_setup.rb +29 -34
  198. data/lib/badline/snapshot/vic20_setup.rb +6 -15
  199. data/lib/badline/snapshot/vice/reu1764.rb +2 -2
  200. data/lib/badline/snapshot/vice.rb +7 -31
  201. data/lib/badline/snapshot.rb +43 -20
  202. data/lib/badline/storage/crt_file.rb +3 -1
  203. data/lib/badline/storage/g64_image.rb +8 -3
  204. data/lib/badline/storage/tap.rb +43 -15
  205. data/lib/badline/version.rb +1 -1
  206. data/lib/badline/vic20/attachments.rb +3 -0
  207. data/lib/badline/vic20/bus/keyed_state.rb +52 -0
  208. data/lib/badline/vic20/bus.rb +1 -2
  209. data/lib/badline/vic20/keyed_modules.rb +73 -0
  210. data/lib/badline/vic20/keyed_state.rb +45 -0
  211. data/lib/badline/vic20.rb +5 -5
  212. data/lib/badline.rb +1 -0
  213. metadata +61 -42
  214. data/lib/badline/c128/bus/saved_state.rb +0 -53
  215. data/lib/badline/c128/saved_state.rb +0 -117
  216. data/lib/badline/c64/bus/saved_state.rb +0 -68
  217. data/lib/badline/c64/cartridge/flash/saved_state.rb +0 -40
  218. data/lib/badline/c64/cartridge/retro_replay/saved_state.rb +0 -47
  219. data/lib/badline/c64/cartridge/saved_state.rb +0 -63
  220. data/lib/badline/c64/reu/dma/saved_state.rb +0 -52
  221. data/lib/badline/c64/reu/saved_state.rb +0 -55
  222. data/lib/badline/c64/saved_state.rb +0 -112
  223. data/lib/badline/chips/cia/saved_state.rb +0 -49
  224. data/lib/badline/chips/cia/serial/saved_state.rb +0 -45
  225. data/lib/badline/chips/cpu/saved_state.rb +0 -117
  226. data/lib/badline/chips/sid/envelope/saved_state.rb +0 -70
  227. data/lib/badline/chips/sid/filter/saved_state.rb +0 -42
  228. data/lib/badline/chips/sid/saved_state.rb +0 -52
  229. data/lib/badline/chips/sid/waveform/saved_state.rb +0 -68
  230. data/lib/badline/chips/vdc/saved_state.rb +0 -36
  231. data/lib/badline/chips/via/saved_state.rb +0 -44
  232. data/lib/badline/chips/vic/graphics_shifter/saved_state.rb +0 -40
  233. data/lib/badline/chips/vic/saved_state.rb +0 -82
  234. data/lib/badline/chips/vic/sequencer/saved_state.rb +0 -91
  235. data/lib/badline/chips/vic/sprite/saved_state.rb +0 -66
  236. data/lib/badline/chips/vic/sprites/saved_state.rb +0 -59
  237. data/lib/badline/chips/vic_i/painter/saved_state.rb +0 -40
  238. data/lib/badline/chips/vic_i/saved_state.rb +0 -55
  239. data/lib/badline/chips/z80/saved_state.rb +0 -71
  240. data/lib/badline/chrout_trap.rb +0 -43
  241. data/lib/badline/debug_register.rb +0 -28
  242. data/lib/badline/drives/datasette/saved_state.rb +0 -37
  243. data/lib/badline/drives/model1541/disk/saved_state.rb +0 -88
  244. data/lib/badline/drives/model1541/mechanism/saved_state.rb +0 -72
  245. data/lib/badline/drives/model1541/saved_state.rb +0 -44
  246. data/lib/badline/drives/model1571/saved_state.rb +0 -47
  247. data/lib/badline/drives/model1581/disk/saved_state.rb +0 -89
  248. data/lib/badline/drives/model1581/saved_state.rb +0 -42
  249. data/lib/badline/drives/model1581/wd1772/saved_state.rb +0 -68
  250. data/lib/badline/keyboard_buffer.rb +0 -33
  251. data/lib/badline/snapshot/setup.rb +0 -31
  252. data/lib/badline/snapshot/state.rb +0 -179
  253. data/lib/badline/vic20/bus/saved_state.rb +0 -35
  254. data/lib/badline/vic20/saved_state.rb +0 -110
@@ -0,0 +1,213 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class CIA
6
+ class Timer
7
+ # A timer's fields in a keyed snapshot module (Snapshot::Keyed),
8
+ # under `prefix`: the counter, the latch and the pipeline between
9
+ # them. The fast-path flags are worked out again on load.
10
+ module KeyedState
11
+ def save_fields(out, prefix)
12
+ out.int("#{prefix}counter", @counter).int("#{prefix}latch", @latch).int("#{prefix}pipe", @pipe)
13
+ out.int("#{prefix}load_delay", @load_delay).bool("#{prefix}reload", @reload)
14
+ out.bool("#{prefix}underflowed", @underflowed).int("#{prefix}oneshot_linger", @oneshot_linger)
15
+ out.bool("#{prefix}toggle", @toggle)
16
+ end
17
+
18
+ def load_fields(input, prefix)
19
+ @counter = input.word("#{prefix}counter", @counter)
20
+ @latch = input.word("#{prefix}latch", @latch)
21
+ @pipe = input.masked("#{prefix}pipe", @pipe, 0b11)
22
+ @load_delay = input.int("#{prefix}load_delay", @load_delay)
23
+ @reload = input.bool("#{prefix}reload", @reload)
24
+ @underflowed = input.bool("#{prefix}underflowed", @underflowed)
25
+ @oneshot_linger = input.int("#{prefix}oneshot_linger", @oneshot_linger)
26
+ @toggle = input.bool("#{prefix}toggle", @toggle)
27
+ end
28
+
29
+ def restored! = settle
30
+ end
31
+
32
+ include KeyedState
33
+ end
34
+
35
+ class InterruptRegister
36
+ # The interrupt register's fields in a keyed snapshot module, under
37
+ # `prefix`: the mask, the flags, an assert on its way and the reads
38
+ # whose acknowledge is still landing.
39
+ module KeyedState
40
+ def save_fields(out, prefix)
41
+ out.int("#{prefix}mask", @mask.value).int("#{prefix}status", @status.value)
42
+ out.int("#{prefix}pending", @pending)
43
+ out.optional_int("#{prefix}read", @read).optional_int("#{prefix}read_last_cycle", @read_last_cycle)
44
+ out.optional_int("#{prefix}read_two_cycles_ago", @read_two_cycles_ago)
45
+ out.bool("#{prefix}timer_b_bug", @timer_b_bug)
46
+ end
47
+
48
+ def load_fields(input, prefix)
49
+ @mask.value = input.byte("#{prefix}mask", @mask.value)
50
+ @status.value = input.byte("#{prefix}status", @status.value)
51
+ @pending = input.int("#{prefix}pending", @pending)
52
+ @read = input.optional_int("#{prefix}read")
53
+ @read_last_cycle = input.optional_int("#{prefix}read_last_cycle")
54
+ @read_two_cycles_ago = input.optional_int("#{prefix}read_two_cycles_ago")
55
+ @timer_b_bug = input.bool("#{prefix}timer_b_bug", @timer_b_bug)
56
+ end
57
+
58
+ # Quiet when nothing is due and no read is recent enough to matter.
59
+ def restored!
60
+ @quiet = @pending.zero? && @read.nil? && @read_last_cycle.nil? && @read_two_cycles_ago.nil?
61
+ end
62
+ end
63
+
64
+ include KeyedState
65
+ end
66
+
67
+ class Serial
68
+ # The serial port's fields in a keyed snapshot module, under
69
+ # `prefix`: the registers, the bit count, the delay lines, the CNT
70
+ # and SP levels, and whether the next cycle can be skipped, which
71
+ # can lag what the delay lines show by a cycle.
72
+ module KeyedState
73
+ def save_fields(out, prefix)
74
+ out.int("#{prefix}data", @data).int("#{prefix}shift", @shift).int("#{prefix}steps", @steps)
75
+ out.optional_int("#{prefix}empty_in", @empty_in).optional_int("#{prefix}busy_in", @busy_in)
76
+ out.bool("#{prefix}busy", @busy).bool("#{prefix}abandoned", @abandoned)
77
+ out.optional_int("#{prefix}pending", @pending).bool("#{prefix}underflow_high", @underflow_high)
78
+ out.bool("#{prefix}in_flight", @in_flight).int("#{prefix}flight_up", @flight_up)
79
+ out.int("#{prefix}flight_down", @flight_down).bool("#{prefix}cnt", @cnt).bool("#{prefix}cnt_in", @cnt_in)
80
+ out.bool("#{prefix}sp_in", @sp_in).bool("#{prefix}sp_out", @sp_out).bool("#{prefix}idle", @idle)
81
+ end
82
+
83
+ def load_fields(input, prefix)
84
+ @data = input.byte("#{prefix}data", @data)
85
+ @shift = input.byte("#{prefix}shift", @shift)
86
+ @steps = input.int("#{prefix}steps", @steps)
87
+ @empty_in = input.optional_int("#{prefix}empty_in")
88
+ @busy_in = input.optional_int("#{prefix}busy_in")
89
+ @busy = input.bool("#{prefix}busy", @busy)
90
+ @abandoned = input.bool("#{prefix}abandoned", @abandoned)
91
+ @pending = input.optional_int("#{prefix}pending")
92
+ load_line_fields(input, prefix)
93
+ end
94
+
95
+ def restored! = nil
96
+
97
+ private
98
+
99
+ def load_line_fields(input, prefix)
100
+ @underflow_high = input.bool("#{prefix}underflow_high", @underflow_high)
101
+ @in_flight = input.bool("#{prefix}in_flight", @in_flight)
102
+ @flight_up = input.int("#{prefix}flight_up", @flight_up)
103
+ @flight_down = input.int("#{prefix}flight_down", @flight_down)
104
+ @cnt = input.bool("#{prefix}cnt", @cnt)
105
+ @cnt_in = input.bool("#{prefix}cnt_in", @cnt_in)
106
+ @sp_in = input.bool("#{prefix}sp_in", @sp_in)
107
+ @sp_out = input.bool("#{prefix}sp_out", @sp_out)
108
+ @idle = input.bool("#{prefix}idle", @idle)
109
+ end
110
+ end
111
+
112
+ include KeyedState
113
+ end
114
+
115
+ class TimeOfDay
116
+ # The TOD clock's fields in a keyed snapshot module, under `prefix`:
117
+ # the clock, the alarm and a latched read field by field, the
118
+ # pulses towards the next tenth, and the mains divider's phase in
119
+ # cycle·Hz (it reaches the clock rate when a pulse arrives). The
120
+ # 50/60 Hz divider comes from CRA bit 7 on load.
121
+ module KeyedState
122
+ def save_fields(out, prefix)
123
+ out.int("#{prefix}mains_phase", @accumulator).int("#{prefix}pulses", @pulses)
124
+ out.bool("#{prefix}stopped", @stopped).bool("#{prefix}alarm_pending", @alarm_pending)
125
+ FIELDS.each do |field|
126
+ out.int("#{prefix}clock.#{field}", @clock[field]).int("#{prefix}alarm.#{field}", @alarm[field])
127
+ end
128
+ latch = @latch
129
+ FIELDS.each { |field| out.int("#{prefix}latch.#{field}", latch[field]) } if latch
130
+ end
131
+
132
+ def load_fields(input, prefix)
133
+ @accumulator = input.int("#{prefix}mains_phase", @accumulator)
134
+ @pulses = input.int("#{prefix}pulses", @pulses)
135
+ @stopped = input.bool("#{prefix}stopped", @stopped)
136
+ @alarm_pending = input.bool("#{prefix}alarm_pending", @alarm_pending)
137
+ FIELDS.each do |field|
138
+ @clock[field] = input.byte("#{prefix}clock.#{field}", @clock[field])
139
+ @alarm[field] = input.byte("#{prefix}alarm.#{field}", @alarm[field])
140
+ end
141
+ load_latch_fields(input, prefix)
142
+ end
143
+
144
+ def restored!(fifty_hz)
145
+ self.fifty_hz = fifty_hz
146
+ end
147
+
148
+ private
149
+
150
+ def load_latch_fields(input, prefix)
151
+ return @latch = nil if input.optional_int("#{prefix}latch.hours").nil?
152
+
153
+ @latch = FIELDS.to_h { |field| [field, input.byte("#{prefix}latch.#{field}", 0)] }
154
+ end
155
+ end
156
+
157
+ include KeyedState
158
+ end
159
+
160
+ # The CIA's fields in a keyed snapshot module (Snapshot::Keyed): the
161
+ # ports, the control registers, the PB4 and CNT levels, and its parts
162
+ # under ta., tb., icr., sdr. and tod.
163
+ module KeyedState
164
+ def save_fields(out)
165
+ out.int("pra", @data_port_a).int("prb", @data_port_b).int("ddra", @data_dir_a).int("ddrb", @data_dir_b)
166
+ out.int("cra", @control_a.value).int("crb", @control_b.value)
167
+ out.bool("pb4_high", @port_b4_high).bool("pb4_driven_high", @port_b4_driven_high)
168
+ out.bool("cnt_high", @cnt_high).bool("cnt_rise", @cnt_rise)
169
+ @ta.save_fields(out, "ta.")
170
+ @tb.save_fields(out, "tb.")
171
+ @icr.save_fields(out, "icr.")
172
+ @serial.save_fields(out, "sdr.")
173
+ @tod.save_fields(out, "tod.")
174
+ out
175
+ end
176
+
177
+ def load_fields(input)
178
+ @data_port_a = input.byte("pra", @data_port_a)
179
+ @data_port_b = input.byte("prb", @data_port_b)
180
+ @data_dir_a = input.byte("ddra", @data_dir_a)
181
+ @data_dir_b = input.byte("ddrb", @data_dir_b)
182
+ @control_a.value = input.byte("cra", @control_a.value)
183
+ @control_b.value = input.byte("crb", @control_b.value)
184
+ @port_b4_high = input.bool("pb4_high", @port_b4_high)
185
+ @port_b4_driven_high = input.bool("pb4_driven_high", @port_b4_driven_high)
186
+ @cnt_high = input.bool("cnt_high", @cnt_high)
187
+ @cnt_rise = input.bool("cnt_rise", @cnt_rise)
188
+ load_part_fields(input)
189
+ end
190
+
191
+ def restored!
192
+ @ta.restored!
193
+ @tb.restored!
194
+ @icr.restored!
195
+ @serial.restored!
196
+ @tod.restored!(@control_a.clock_frequency?)
197
+ end
198
+
199
+ private
200
+
201
+ def load_part_fields(input)
202
+ @ta.load_fields(input, "ta.")
203
+ @tb.load_fields(input, "tb.")
204
+ @icr.load_fields(input, "icr.")
205
+ @serial.load_fields(input, "sdr.")
206
+ @tod.load_fields(input, "tod.")
207
+ end
208
+ end
209
+
210
+ include KeyedState
211
+ end
212
+ end
213
+ end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "badline/chips/cia/serial/saved_state"
4
-
5
3
  module Badline
6
4
  module Chips
7
5
  class CIA
@@ -18,8 +16,6 @@ module Badline
18
16
  # rather than waiting for the sixteenth underflow, and a byte written
19
17
  # from that point on goes out on the next one.
20
18
  class Serial
21
- include SavedState
22
-
23
19
  BITS = 8
24
20
 
25
21
  # A byte written to the data register reaches the shift register one
@@ -32,28 +32,6 @@ module Badline
32
32
  @match = enabled ? 4 : 5
33
33
  end
34
34
 
35
- # The clock and the alarm, field by field in FIELDS order, the latch,
36
- # and the divider's phase.
37
- def save_state(out)
38
- out.int(@accumulator).int(@pulses).int(@match).boolean(@stopped).boolean(@alarm_pending)
39
- FIELDS.each { |field| out.int(@clock[field]).int(@alarm[field]) }
40
- out.boolean(!@latch.nil?)
41
- FIELDS.each { |field| out.int(@latch[field]) } if @latch
42
- end
43
-
44
- def load_state(input)
45
- @accumulator = input.int
46
- @pulses = input.int
47
- @match = input.int
48
- @stopped = input.boolean?
49
- @alarm_pending = input.boolean?
50
- FIELDS.each do |field|
51
- @clock[field] = input.int
52
- @alarm[field] = input.int
53
- end
54
- @latch = input.boolean? ? FIELDS.to_h { |field| [field, input.int] } : nil
55
- end
56
-
57
35
  # Whether the clock is stopped, or gets no pulses on its pin, with no
58
36
  # alarm to raise, so its cycles move only the divider's phase.
59
37
  def quiet? = (@stopped || @mains_hz.zero?) && !@alarm_pending
@@ -69,26 +69,6 @@ module Badline
69
69
  settle
70
70
  end
71
71
 
72
- # The counter, the latch and the pipeline between them. The control
73
- # register is the CIA's to save.
74
- def save_state(out)
75
- out.int(@counter).int(@latch).int(@pipe).int(@load_delay).boolean(@reload).boolean(@underflowed)
76
- out.int(@oneshot_linger).boolean(@toggle).boolean(@settled).boolean(@empty)
77
- end
78
-
79
- def load_state(input)
80
- @counter = input.int
81
- @latch = input.int
82
- @pipe = input.int
83
- @load_delay = input.int
84
- @reload = input.boolean?
85
- @underflowed = input.boolean?
86
- @oneshot_linger = input.int
87
- @toggle = input.boolean?
88
- @settled = input.boolean?
89
- @empty = input.boolean?
90
- end
91
-
92
72
  def write_control(value)
93
73
  @toggle = true if value.anybits?(0x01) && !started?
94
74
  @oneshot_linger = 2 if control.run_mode? && value.nobits?(0x08)
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "badline/chips/cia/time_of_day"
3
4
  require "badline/chips/cia/interrupt_register"
4
5
  require "badline/chips/cia/serial"
5
6
  require "badline/chips/cia/timer"
6
- require "badline/chips/cia/saved_state"
7
7
  require "badline/chips/cia/fast_forward"
8
+ require "badline/chips/cia/keyed_parts"
8
9
 
9
10
  module Badline
10
11
  module Chips
11
12
  # CIA (Complex Interface Adapter) chip
12
13
  class CIA
13
14
  include Addressable
14
- include SavedState
15
15
  include FastForward
16
16
 
17
17
  # The original NMOS 6526 of the breadbin C64, and the HMOS 6526A of the
@@ -5,7 +5,7 @@ require "badline/chips/cpu/addressing"
5
5
  require "badline/chips/cpu/operations"
6
6
  require "badline/chips/cpu/stack_operations"
7
7
  require "badline/chips/cpu/boundary"
8
- require "badline/chips/cpu/saved_state"
8
+ require "badline/chips/cpu/keyed_state"
9
9
 
10
10
  module Badline
11
11
  module Chips
@@ -23,7 +23,7 @@ module Badline
23
23
  include Operations
24
24
  include StackOperations
25
25
  include Boundary
26
- include SavedState
26
+ include KeyedState
27
27
 
28
28
  attr_reader :memory, :instructions, :boundary_crossed, :cycles, :pending_write
29
29
  attr_accessor :program_counter, :stack_pointer, :status, :a, :x, :y, :nmi, :irq
@@ -67,6 +67,22 @@ module Badline
67
67
  end_sequence
68
68
  end
69
69
 
70
+ # Drops the instruction or interrupt sequence in flight and the
71
+ # interrupt lines' pipeline, so the next cycle fetches an opcode at the
72
+ # program counter, and sets the cycle count. A snapshot from VICE,
73
+ # taken between instructions, restores through here.
74
+ def resume(cycles)
75
+ @irq_sample = @irq_pending = false
76
+ @nmi_sample = @nmi_pending = false
77
+ @skip_poll = false
78
+ @interrupt = nil
79
+ @brk = false
80
+ @stalled_at = nil
81
+ @cycles = cycles
82
+ end_sequence
83
+ @pending_write = false
84
+ end
85
+
70
86
  def p
71
87
  status.value
72
88
  end
@@ -0,0 +1,177 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class CPU
6
+ # Where a CPU caught inside an instruction is, as a snapshot names it:
7
+ # an opcode's instruction, an IRQ or NMI sequence, or jammed.
8
+ POSITIONS = %w[boundary op interrupt jam].freeze
9
+
10
+ # The CPU's fields in a keyed snapshot module (Snapshot::Keyed): the
11
+ # registers, the interrupt lines' pipeline, the counts and, inside an
12
+ # instruction, where it is in hardware terms (pos.*), rather than as
13
+ # the microcode lays it out.
14
+ module KeyedState
15
+ def save_fields(out)
16
+ out.int("pc", @program_counter).int("sp", @stack_pointer).int("a", @a).int("x", @x).int("y", @y)
17
+ out.int("p", @status.value).bool("nmi", @nmi).bool("so_high", @so_high)
18
+ out.bool("irq.sample", @irq_sample).bool("irq.pending", @irq_pending).bool("irq.boundary", @boundary_irq)
19
+ out.bool("nmi.sample", @nmi_sample).bool("nmi.pending", @nmi_pending).bool("nmi.boundary", @boundary_nmi)
20
+ out.bool("skip_poll", @skip_poll).optional_int("stalled", stalled_ago)
21
+ out.int("cycles", @cycles).int("instructions", @instructions)
22
+ save_position(out) unless boundary?
23
+ out
24
+ end
25
+
26
+ def load_fields(input)
27
+ @program_counter = input.word("pc", @program_counter)
28
+ @stack_pointer = input.byte("sp", @stack_pointer)
29
+ @a = input.byte("a", @a)
30
+ @x = input.byte("x", @x)
31
+ @y = input.byte("y", @y)
32
+ @status.value = input.byte("p", @status.value)
33
+ load_lines(input)
34
+ @cycles = input.int("cycles", @cycles)
35
+ @instructions = input.int("instructions", @instructions)
36
+ stalled = input.optional_int("stalled")
37
+ @stalled_at = stalled.nil? ? nil : @cycles - stalled
38
+ load_position(input)
39
+ end
40
+
41
+ # What the fields leave to be worked out: whether the next step
42
+ # writes.
43
+ def restored!
44
+ @pending_write = @writes[@index]
45
+ end
46
+
47
+ private
48
+
49
+ # Cycles since the CPU last stalled, when that was this cycle or the
50
+ # one before, the only ones an instruction looks back at.
51
+ def stalled_ago
52
+ stalled_at = @stalled_at
53
+ return nil if stalled_at.nil?
54
+
55
+ ago = @cycles - stalled_at
56
+ ago.between?(0, 1) ? ago : nil
57
+ end
58
+
59
+ def load_lines(input)
60
+ @nmi = input.bool("nmi", @nmi)
61
+ @so_high = input.bool("so_high", @so_high)
62
+ @irq_sample = input.bool("irq.sample", @irq_sample)
63
+ @irq_pending = input.bool("irq.pending", @irq_pending)
64
+ @boundary_irq = input.bool("irq.boundary", @boundary_irq)
65
+ @nmi_sample = input.bool("nmi.sample", @nmi_sample)
66
+ @nmi_pending = input.bool("nmi.pending", @nmi_pending)
67
+ @boundary_nmi = input.bool("nmi.boundary", @boundary_nmi)
68
+ @skip_poll = input.bool("skip_poll", @skip_poll)
69
+ end
70
+
71
+ def save_position(out)
72
+ source = plan_source
73
+ if source == JAMMED
74
+ out.text("pos.seq", "jam")
75
+ return
76
+ end
77
+
78
+ out.text("pos.seq", source == INTERRUPT ? "interrupt" : "op")
79
+ out.int("pos.op", source) unless source == INTERRUPT
80
+ out.int("pos.t", position_t(source)).bool("pos.crossed", @boundary_crossed)
81
+ out.bool("pos.branch_taken", @branch_taken == true).bool("pos.brk", @brk)
82
+ out.optional_int("pos.vector", @interrupt)
83
+ out.optional_int("pos.address", @address).optional_int("pos.dummy_address", @dummy_address)
84
+ out.optional_int("pos.value", @value).optional_int("pos.rmw", @rmw_result)
85
+ out.optional_int("pos.pointer", @pointer)
86
+ end
87
+
88
+ # The cycles of the instruction or sequence run so far, counting the
89
+ # opcode fetch as 1 and leaving out stalled cycles.
90
+ def position_t(source)
91
+ return @index if source == INTERRUPT
92
+
93
+ MICROCODE.fetch(source).t_at(@index, @boundary_crossed)
94
+ end
95
+
96
+ def load_position(input)
97
+ case input.choice("pos.seq", POSITIONS, "boundary")
98
+ when "op" then load_instruction(input)
99
+ when "interrupt" then load_interrupt(input)
100
+ when "jam" then load_jam
101
+ else load_boundary
102
+ end
103
+ end
104
+
105
+ def load_instruction(input)
106
+ micro = MICROCODE.fetch(input.byte("pos.op", 0xea))
107
+ use_plan(micro.plan, micro.writes)
108
+ @operation = micro.operation
109
+ @optional_dummy = micro.optional_dummy
110
+ @boundary_crossed = input.bool("pos.crossed", false)
111
+ @index = micro.index_at(input.int("pos.t", 1), @boundary_crossed).clamp(1, micro.plan.length - 1)
112
+ load_latches(input)
113
+ end
114
+
115
+ def load_interrupt(input)
116
+ use_plan(INTERRUPT_PLAN, INTERRUPT_WRITES)
117
+ @boundary_crossed = input.bool("pos.crossed", false)
118
+ @index = input.int("pos.t", 1).clamp(1, INTERRUPT_PLAN.length - 1)
119
+ load_latches(input)
120
+ end
121
+
122
+ def load_jam
123
+ use_plan(JAMMED_PLAN, JAMMED_WRITES)
124
+ @index = 0
125
+ clear_latches
126
+ end
127
+
128
+ def load_boundary
129
+ use_plan(FETCH_PLAN, FETCH_WRITES)
130
+ @index = 0
131
+ clear_latches
132
+ end
133
+
134
+ def load_latches(input)
135
+ @branch_taken = input.bool("pos.branch_taken", false)
136
+ @brk = input.bool("pos.brk", false)
137
+ @interrupt = input.optional_int("pos.vector")
138
+ @address = input.optional_int("pos.address")
139
+ @dummy_address = input.optional_int("pos.dummy_address")
140
+ @value = input.optional_int("pos.value")
141
+ @rmw_result = input.optional_int("pos.rmw")
142
+ @pointer = input.optional_int("pos.pointer")
143
+ end
144
+
145
+ # The working registers no step reads before the next instruction
146
+ # sets them.
147
+ def clear_latches
148
+ @boundary_crossed = false
149
+ @branch_taken = false
150
+ @brk = false
151
+ @interrupt = nil
152
+ @address = nil
153
+ @dummy_address = nil
154
+ @value = nil
155
+ @rmw_result = nil
156
+ @pointer = nil
157
+ end
158
+
159
+ # Where the plan running comes from: an opcode's microcode, or FETCH,
160
+ # INTERRUPT or JAMMED below it.
161
+ def plan_source
162
+ return FETCH if @plan.equal?(FETCH_PLAN)
163
+ return INTERRUPT if @plan.equal?(INTERRUPT_PLAN)
164
+ return JAMMED if @plan.equal?(JAMMED_PLAN)
165
+
166
+ MICROCODE.index { |micro| micro.plan.equal?(@plan) && micro.operation == @operation } ||
167
+ MICROCODE.index { |micro| micro.plan.equal?(@plan) }
168
+ end
169
+
170
+ def use_plan(plan, writes)
171
+ @plan = plan
172
+ @writes = writes
173
+ end
174
+ end
175
+ end
176
+ end
177
+ end
@@ -117,6 +117,22 @@ module Badline
117
117
  end.freeze
118
118
  end
119
119
 
120
+ # The plan step that follows `cycles` cycles of the instruction, counting
121
+ # the opcode fetch as 1: one further on once an indexed read has
122
+ # skipped its dummy read, which it does when the index add didn't
123
+ # cross a page.
124
+ def index_at(cycles, crossed) = skips_dummy?(cycles, crossed) ? cycles + 1 : cycles
125
+
126
+ # The cycles run before plan step `index`, as index_at counts them.
127
+ def t_at(index, crossed) = skips_dummy?(index - 1, crossed) ? index - 1 : index
128
+
129
+ def skips_dummy?(cycles, crossed)
130
+ return false unless optional_dummy && !crossed
131
+
132
+ dummy = plan.index(:op_idx_dummy)
133
+ !dummy.nil? && cycles >= dummy
134
+ end
135
+
120
136
  def self.write_mask(plan)
121
137
  plan.map { |step| WRITE_STEPS.include?(step) }.freeze
122
138
  end
@@ -6,7 +6,8 @@ module Badline
6
6
  # The I/O port of the 6510 and the 8502 at $00 (direction) and $01
7
7
  # (data). An input pin reads its pull-up, P4 reads the datasette's sense
8
8
  # line, and a pin without a pull-up holds the charge last driven onto
9
- # it. P5 drives the datasette motor through an inverter: low runs it.
9
+ # it. P5 drives the datasette motor through an inverter: low runs it,
10
+ # and P3 drives its write line.
10
11
  #
11
12
  # The 6510 has pull-ups on P0-P2 and P4 and leaves P3, P6 and P7
12
13
  # floating. The 8502 adds a CAPS LOCK pin on P6, high while the key is
@@ -38,6 +39,7 @@ module Badline
38
39
  @floating = 0x00
39
40
  @change_handler = nil
40
41
  @status = PortStatus.new(%i[basic kernal io tape_out tape_switch tape_motor], value: input_value)
42
+ @datasette.write_level = @status.tape_out?
41
43
  end
42
44
 
43
45
  # Calls the block after every write to the port, for the bus to remap
@@ -68,19 +70,33 @@ module Badline
68
70
  end
69
71
 
70
72
  # Sets the registers and the charge, as a snapshot restores them, and
71
- # drives the motor and calls the change handler as a write does.
73
+ # drives the datasette and calls the change handler as a write does.
72
74
  def restore(ddr, data, floating)
73
75
  load(ddr, data, floating)
74
76
  update
75
77
  end
76
78
 
77
79
  # Sets the registers and the charge, as a snapshot loads them, without
78
- # driving the motor or calling the change handler.
80
+ # driving the datasette or calling the change handler. The write
81
+ # line takes P3's level without recording an edge.
79
82
  def load(ddr, data, floating)
80
83
  @ddr = ddr
81
84
  @data = data
82
85
  @floating = floating
83
86
  refresh
87
+ @datasette.write_level = @status.tape_out?
88
+ end
89
+
90
+ # The registers and the charge in a keyed snapshot module
91
+ # (Snapshot::Keyed), under `prefix`. What a read sees is worked out
92
+ # again on load, and the bus maps its banks afterwards.
93
+ def save_fields(out, prefix)
94
+ out.int("#{prefix}ddr", @ddr).int("#{prefix}data", @data).int("#{prefix}floating", @floating)
95
+ end
96
+
97
+ def load_fields(input, prefix)
98
+ load(input.byte("#{prefix}ddr", @ddr), input.byte("#{prefix}data", @data),
99
+ input.byte("#{prefix}floating", @floating))
84
100
  end
85
101
 
86
102
  # Holds CAPS LOCK down, or lets it up.
@@ -101,6 +117,7 @@ module Badline
101
117
  @floating = (@floating & ~driven) | (@data & driven)
102
118
  refresh
103
119
  @datasette.motor = !@status.tape_motor?
120
+ @datasette.write_line = @status.tape_out?
104
121
  @change_handler&.call
105
122
  end
106
123
 
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "badline/chips/cpu/port"
4
+ require "badline/chips/cpu/instruction"
5
+ require "badline/chips/cpu/instruction_set"
6
+ require "badline/chips/cpu/interrupts"
7
+ require "badline/chips/cpu/traps"
3
8
  require "badline/chips/cpu/core"
4
9
 
5
10
  module Badline
@@ -23,17 +23,19 @@ module Badline
23
23
  # Cycles until the current window closes, counting the next one as 1.
24
24
  attr_reader :cycles_to_close
25
25
 
26
- # How far the current window has got. The clock and the rate are the
26
+ # How far the current window has got, in a keyed snapshot module
27
+ # (Snapshot::Keyed) under `prefix`. The clock and the rate are the
27
28
  # host's.
28
- def save_state(out)
29
- out.int(@phase).int(@sum).int(@count).int(@cycles_to_close)
29
+ def save_fields(out, prefix)
30
+ out.int("#{prefix}phase", @phase).int("#{prefix}sum", @sum).int("#{prefix}count", @count)
31
+ out.int("#{prefix}cycles_to_close", @cycles_to_close)
30
32
  end
31
33
 
32
- def load_state(input)
33
- @phase = input.int
34
- @sum = input.int
35
- @count = input.int
36
- @cycles_to_close = input.int
34
+ def load_fields(input, prefix)
35
+ @phase = input.int("#{prefix}phase", @phase)
36
+ @sum = input.int("#{prefix}sum", @sum)
37
+ @count = input.int("#{prefix}count", @count)
38
+ @cycles_to_close = input.int("#{prefix}cycles_to_close", @cycles_to_close)
37
39
  end
38
40
 
39
41
  # Returns the averaged sample when these cycles close a window, and
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "badline/chips/sid/envelope/saved_state"
4
-
5
3
  module Badline
6
4
  module Chips
7
5
  class SID
@@ -28,11 +26,8 @@ module Badline
28
26
  EXPONENTIAL_PERIODS = { 0xff => 1, 0x5d => 2, 0x36 => 4, 0x1a => 8,
29
27
  0x0e => 16, 0x06 => 30, 0x00 => 1 }.freeze
30
28
 
31
- # The states a snapshot names by index.
29
+ # The states, which VICE's SIDEXTENDED numbers by index.
32
30
  STATES = %i[attack decay_sustain release].freeze
33
-
34
- include SavedState
35
-
36
31
  attr_reader :state, :counter, :env3
37
32
 
38
33
  def initialize