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,69 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class RIOT
6
+ class Timer
7
+ # The timer's fields in a keyed snapshot module, under `prefix`, the
8
+ # divider by its A1-A0 code.
9
+ module KeyedState
10
+ def save_fields(out, prefix)
11
+ out.int("#{prefix}counter", @counter).int("#{prefix}interval", DIVIDERS.index(@divider))
12
+ out.int("#{prefix}prescale", @prescale)
13
+ out.bool("#{prefix}expired", @expired).bool("#{prefix}flag", @flag)
14
+ out.bool("#{prefix}irq_enabled", @irq_enabled).bool("#{prefix}fired", @fired)
15
+ end
16
+
17
+ def load_fields(input, prefix)
18
+ @counter = input.byte("#{prefix}counter", @counter)
19
+ @divider = DIVIDERS[input.masked("#{prefix}interval", DIVIDERS.index(@divider), 0x03)]
20
+ @prescale = input.int("#{prefix}prescale", @prescale).clamp(1, @divider)
21
+ @expired = input.bool("#{prefix}expired", @expired)
22
+ @flag = input.bool("#{prefix}flag", @flag)
23
+ @irq_enabled = input.bool("#{prefix}irq_enabled", @irq_enabled)
24
+ @fired = input.bool("#{prefix}fired", @fired)
25
+ end
26
+ end
27
+
28
+ include KeyedState
29
+ end
30
+
31
+ # The RIOT's fields in a keyed snapshot module (Snapshot::Keyed): the
32
+ # RAM, the port and direction registers, the edge detector and the
33
+ # level on PA7, and the timer under timer. The peripheral restores its
34
+ # own state.
35
+ module KeyedState
36
+ def save_fields(out)
37
+ out.bytes("ram", @ram)
38
+ out.int("ora", @ora).int("orb", @orb).int("ddra", @ddra).int("ddrb", @ddrb)
39
+ out.bool("pa7_high", @pa7_high).bool("pa7_active", @pa7_active).bool("pa7_flag", @pa7_flag)
40
+ out.bool("edge_irq_enabled", @edge_irq_enabled).bool("rising_edge", @rising_edge)
41
+ @timer.save_fields(out, "timer.")
42
+ out
43
+ end
44
+
45
+ def load_fields(input)
46
+ input.bytes_into("ram", @ram)
47
+ @ora = input.byte("ora", @ora)
48
+ @orb = input.byte("orb", @orb)
49
+ @ddra = input.byte("ddra", @ddra)
50
+ @ddrb = input.byte("ddrb", @ddrb)
51
+ load_edge_fields(input)
52
+ @timer.load_fields(input, "timer.")
53
+ end
54
+
55
+ private
56
+
57
+ def load_edge_fields(input)
58
+ @pa7_high = input.bool("pa7_high", @pa7_high)
59
+ @pa7_active = input.bool("pa7_active", @pa7_active)
60
+ @pa7_flag = input.bool("pa7_flag", @pa7_flag)
61
+ @edge_irq_enabled = input.bool("edge_irq_enabled", @edge_irq_enabled)
62
+ @rising_edge = input.bool("rising_edge", @rising_edge)
63
+ end
64
+ end
65
+
66
+ include KeyedState
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class RIOT
6
+ # The interval timer of the 6532 and the 6530: an 8-bit counter behind
7
+ # a prescaler that divides φ2 by 1, 8, 64 or 1024, and the timer's
8
+ # interrupt flag.
9
+ #
10
+ # A write loads the counter and counts it once on the write's own
11
+ # cycle, so the counter reads one less than written from the cycle
12
+ # after, and counts again every interval from the write. A write of N
13
+ # reads N - 1 until cycle T, 0 until cycle N x T, and $FF with the flag
14
+ # set on cycle N x T: 52 at ÷8 sets the flag on cycle 416. From there
15
+ # the counter counts every cycle, so it tells how long ago the flag
16
+ # set, and each count through 0 sets the flag again. On the 6532 only
17
+ # a write puts the prescaler back. On the 6530, as the KIM-1 User
18
+ # Manual's appendix H has it, a read of the counter after the flag
19
+ # set does too, and the counter counts on from where it is at the
20
+ # interval written: a 6530 builds its timer with
21
+ # read_restores_divider. No test program has confirmed it.
22
+ #
23
+ # Reading or writing the counter clears the flag, except for a read on
24
+ # the cycle that sets it. Each access takes A3 as the interrupt enable.
25
+ class Timer
26
+ DIVIDERS = [1, 8, 64, 1024].freeze
27
+
28
+ attr_reader :counter, :flag
29
+ attr_accessor :irq_enabled
30
+
31
+ def initialize(read_restores_divider: false)
32
+ @read_restores_divider = read_restores_divider
33
+ @counter = 0
34
+ @divider = 1
35
+ @prescale = 1
36
+ @expired = true
37
+ @flag = false
38
+ @irq_enabled = false
39
+ @fired = false
40
+ end
41
+
42
+ def irq? = @flag && @irq_enabled
43
+
44
+ # +interval+ is A1-A0, picking ÷1, ÷8, ÷64 or ÷1024.
45
+ def write(value, interval, irq_enabled)
46
+ @irq_enabled = irq_enabled
47
+ @flag = false
48
+ @divider = DIVIDERS[interval]
49
+ @prescale = @divider
50
+ @expired = false
51
+ @counter = value
52
+ count
53
+ end
54
+
55
+ def read(irq_enabled)
56
+ @irq_enabled = irq_enabled
57
+ @flag = false unless @fired
58
+ restore_divider if @read_restores_divider && @expired
59
+ @counter
60
+ end
61
+
62
+ def cycle!
63
+ @fired = false
64
+ unless @expired
65
+ @prescale -= 1
66
+ return unless @prescale.zero?
67
+
68
+ @prescale = @divider
69
+ end
70
+ count
71
+ end
72
+
73
+ private
74
+
75
+ def restore_divider
76
+ @expired = false
77
+ @prescale = @divider
78
+ end
79
+
80
+ def count
81
+ @counter = (@counter - 1) & 0xff
82
+ return unless @counter == 0xff
83
+
84
+ @expired = true
85
+ @flag = true
86
+ @fired = true
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/riot/timer"
4
+ require "badline/chips/riot/fast_forward"
5
+ require "badline/chips/riot/keyed_parts"
6
+
7
+ module Badline
8
+ module Chips
9
+ # The MOS 6532 RIOT (RAM, I/O, Timer): 128 bytes of RAM, two 8-bit
10
+ # ports, A and B, each with a direction register, a 1 making the pin an
11
+ # output, the interval timer (Timer) and an edge detector on PA7. The
12
+ # 8050 and 8250's interface processor has two.
13
+ #
14
+ # The RS pin picks the RAM (read_ram and write_ram, A6-A0) or the
15
+ # registers (peek and poke, A4-A0). With A2 low, A1-A0 pick port A, DDRA,
16
+ # port B and DDRB. With A2 high, a read with A0 low reads the timer and
17
+ # one with A0 high the interrupt flags. A write with A4 high loads the
18
+ # timer, A1-A0 picking the divider, and one with A4 low sets the edge
19
+ # detector, A1 the interrupt enable and A0 the active edge, rising when
20
+ # set; its data is ignored. A3 is the timer's interrupt enable on every
21
+ # timer access.
22
+ #
23
+ # Port A reads its pins, whatever drives them. Port B reads its output
24
+ # bits from its output register and its input bits from the pins. An
25
+ # input pin floats high, and a peripheral pulls lines low through read_a
26
+ # and read_b, as a VIA's does. PA7 takes its level from outside through
27
+ # pa7= as well, for the edge detector.
28
+ #
29
+ # The edge detector watches the level on PA7, input or output. Its
30
+ # active edge sets the PA7 flag whether or not its interrupt is enabled,
31
+ # and so can a change of the active edge. A read of the flags clears it.
32
+ class RIOT
33
+ attr_reader :peripheral, :timer, :ram
34
+
35
+ def initialize(peripheral: nil)
36
+ @peripheral = peripheral
37
+ @ram = Array.new(128, 0)
38
+ @timer = Timer.new
39
+ @pa7_high = true
40
+ @pa7_active = false
41
+ @pa7_flag = false
42
+ reset!
43
+ end
44
+
45
+ # The RES line clears the four I/O registers, which leaves every line
46
+ # an input, disables both interrupts and sets the active edge to
47
+ # falling. The timer counts on, and the flags stay as they are.
48
+ def reset!
49
+ @ora = @orb = @ddra = @ddrb = 0x00
50
+ @timer.irq_enabled = false
51
+ @edge_irq_enabled = false
52
+ @rising_edge = false
53
+ sense_pa7
54
+ end
55
+
56
+ def irq? = @timer.irq? || (@pa7_flag && @edge_irq_enabled)
57
+
58
+ def cycle!
59
+ @timer.cycle!
60
+ end
61
+
62
+ # Port A as driven by ORA and DDRA, input lines floating high.
63
+ def port_a_output = driven_lines(@ora, @ddra)
64
+
65
+ def port_b_output = driven_lines(@orb, @ddrb)
66
+
67
+ # The level something outside drives on PA7.
68
+ def pa7=(high)
69
+ @pa7_high = high
70
+ sense_pa7
71
+ end
72
+
73
+ # How many cycles the RIOT can run with no counter pulling IRQ.
74
+ def quiet_cycles = @timer.quiet_cycles
75
+
76
+ # Runs +cycles+ cycles at once, which moves only the timer.
77
+ def fast_forward(cycles)
78
+ @timer.fast_forward(cycles)
79
+ end
80
+
81
+ # Everything the RIOT holds but its timer's counter, for comparing it
82
+ # at two points.
83
+ def idle_state
84
+ [*@ram, @ora, @orb, @ddra, @ddrb, @pa7_high, @pa7_active, @pa7_flag, @edge_irq_enabled, @rising_edge,
85
+ *@timer.state, @timer.flag]
86
+ end
87
+
88
+ def read_ram(addr) = @ram[addr & 0x7f]
89
+
90
+ def write_ram(addr, value)
91
+ @ram[addr & 0x7f] = value
92
+ end
93
+
94
+ def peek(addr)
95
+ return read_timer(addr) if addr.anybits?(0x04)
96
+
97
+ case addr & 0x03
98
+ when 0x00 then pins_a
99
+ when 0x01 then @ddra
100
+ when 0x02 then read_port_b
101
+ else @ddrb
102
+ end
103
+ end
104
+
105
+ def poke(addr, value)
106
+ return write_timer(addr, value) if addr.anybits?(0x04)
107
+
108
+ case addr & 0x03
109
+ when 0x00 then @ora = value
110
+ when 0x01 then @ddra = value
111
+ when 0x02 then @orb = value
112
+ else @ddrb = value
113
+ end
114
+ sense_pa7
115
+ end
116
+
117
+ private
118
+
119
+ def driven_lines(register, direction)
120
+ (register & direction) | (~direction & 0xff)
121
+ end
122
+
123
+ def pins_a
124
+ lines = @pa7_high ? port_a_output : port_a_output & 0x7f
125
+ @peripheral ? lines & @peripheral.read_a(lines) : lines
126
+ end
127
+
128
+ def read_port_b
129
+ lines = port_b_output
130
+ lines &= @peripheral.read_b(lines) if @peripheral
131
+ (@orb & @ddrb) | (lines & ~@ddrb & 0xff)
132
+ end
133
+
134
+ # Bit 7 the timer flag, bit 6 the PA7 flag, which the read clears.
135
+ def read_timer(addr)
136
+ return @timer.read(addr.anybits?(0x08)) if addr.nobits?(0x01)
137
+
138
+ value = (@timer.flag ? 0x80 : 0x00) | (@pa7_flag ? 0x40 : 0x00)
139
+ @pa7_flag = false
140
+ value
141
+ end
142
+
143
+ def write_timer(addr, value)
144
+ if addr.anybits?(0x10)
145
+ @timer.write(value, addr & 0x03, addr.anybits?(0x08))
146
+ else
147
+ @edge_irq_enabled = addr.anybits?(0x02)
148
+ @rising_edge = addr.anybits?(0x01)
149
+ sense_pa7
150
+ end
151
+ end
152
+
153
+ # The detector is active while PA7 sits at the level its active edge
154
+ # leads to, and becoming active sets the flag.
155
+ def sense_pa7
156
+ active = (@pa7_high && port_a_output.anybits?(0x80)) == @rising_edge
157
+ @pa7_flag = true if active && !@pa7_active
158
+ @pa7_active = active
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class RRIOT
6
+ # The RRIOT's fields in a keyed snapshot module (Snapshot::Keyed): the
7
+ # RAM, the port and direction registers, and the timer under timer.
8
+ # The ROM is the mask's, and the peripheral restores its own state.
9
+ module KeyedState
10
+ def save_fields(out)
11
+ out.bytes("ram", @ram)
12
+ out.int("ora", @ora).int("orb", @orb).int("ddra", @ddra).int("ddrb", @ddrb)
13
+ @timer.save_fields(out, "timer.")
14
+ out
15
+ end
16
+
17
+ def load_fields(input)
18
+ input.bytes_into("ram", @ram)
19
+ @ora = input.byte("ora", @ora)
20
+ @orb = input.byte("orb", @orb)
21
+ @ddra = input.byte("ddra", @ddra)
22
+ @ddrb = input.byte("ddrb", @ddrb)
23
+ @timer.load_fields(input, "timer.")
24
+ end
25
+ end
26
+
27
+ include KeyedState
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/riot"
4
+ require "badline/chips/rriot/keyed_state"
5
+
6
+ module Badline
7
+ module Chips
8
+ # The MOS 6530 RRIOT (ROM, RAM, I/O, Timer): 1 KB of mask ROM, 64 bytes
9
+ # of RAM, two 8-bit ports, A and B, each with a direction register, a 1
10
+ # making the pin an output, and the 6532's interval timer (RIOT::Timer).
11
+ # It has no edge detector. The 8050 and 8250's floppy controller has
12
+ # one, its ROM the controller's program, and the KIM-1 two. Its timer
13
+ # reads its counter as the 6532's does, but a read after the flag sets
14
+ # puts the prescaler back (RIOT::Timer).
15
+ #
16
+ # Which addresses select the ROM, the RAM and the registers is part of
17
+ # the mask, so the board decodes them and calls read_rom (A9-A0),
18
+ # read_ram and write_ram (A5-A0), or peek and poke (A3-A0). With A2 low,
19
+ # A1-A0 pick port A, DDRA, port B and DDRB. With A2 high, a write loads
20
+ # the timer, A1-A0 picking the divider, and a read with A0 low reads the
21
+ # timer and one with A0 high the interrupt flag in bit 7, the other bits
22
+ # 0. A3 is the timer's interrupt enable on every timer access.
23
+ #
24
+ # Both ports read their output bits from the output register and their
25
+ # input bits from the pins. An input pin floats high, and a peripheral
26
+ # pulls lines low through read_a and read_b, as a VIA's does.
27
+ #
28
+ # The timer's interrupt comes out on PB7, which the mask can give to it:
29
+ # PB7 then reads low while the flag is up with its interrupt enabled,
30
+ # and the board takes IRQ from irq?. PB5 and PB6 can be chip selects
31
+ # instead of port lines, which the board decodes.
32
+ class RRIOT
33
+ attr_reader :peripheral, :timer, :ram, :rom
34
+
35
+ # +rom+ is the 1024 bytes of the mask ROM.
36
+ def initialize(rom:, peripheral: nil, irq_on_pb7: true)
37
+ @rom = rom
38
+ @peripheral = peripheral
39
+ @irq_on_pb7 = irq_on_pb7
40
+ @ram = Array.new(64, 0)
41
+ @timer = RIOT::Timer.new(read_restores_divider: true)
42
+ reset!
43
+ end
44
+
45
+ # The RES line clears the four I/O registers, which leaves every line
46
+ # an input, and disables the interrupt. The timer counts on.
47
+ def reset!
48
+ @ora = @orb = @ddra = @ddrb = 0x00
49
+ @timer.irq_enabled = false
50
+ end
51
+
52
+ def irq? = @irq_on_pb7 && @timer.irq?
53
+
54
+ def cycle!
55
+ @timer.cycle!
56
+ end
57
+
58
+ # Port A as driven by ORA and DDRA, input lines floating high.
59
+ def port_a_output = driven_lines(@ora, @ddra)
60
+
61
+ # Port B as driven by ORB and DDRB, PB7 pulled low by the interrupt.
62
+ def port_b_output
63
+ lines = driven_lines(@orb, @ddrb)
64
+ irq? ? lines & 0x7f : lines
65
+ end
66
+
67
+ # How many cycles the RRIOT can run with no counter pulling IRQ.
68
+ def quiet_cycles = @timer.quiet_cycles
69
+
70
+ # Runs +cycles+ cycles at once, which moves only the timer.
71
+ def fast_forward(cycles)
72
+ @timer.fast_forward(cycles)
73
+ end
74
+
75
+ # Everything the RRIOT holds but its timer's counter, for comparing
76
+ # it at two points.
77
+ def idle_state = [*@ram, @ora, @orb, @ddra, @ddrb, *@timer.state, @timer.flag]
78
+
79
+ def read_rom(addr) = @rom[addr & 0x3ff]
80
+
81
+ def read_ram(addr) = @ram[addr & 0x3f]
82
+
83
+ def write_ram(addr, value)
84
+ @ram[addr & 0x3f] = value
85
+ end
86
+
87
+ def peek(addr)
88
+ return read_timer(addr) if addr.anybits?(0x04)
89
+
90
+ case addr & 0x03
91
+ when 0x00 then read_port_a
92
+ when 0x01 then @ddra
93
+ when 0x02 then read_port_b
94
+ else @ddrb
95
+ end
96
+ end
97
+
98
+ def poke(addr, value)
99
+ return @timer.write(value, addr & 0x03, addr.anybits?(0x08)) if addr.anybits?(0x04)
100
+
101
+ case addr & 0x03
102
+ when 0x00 then @ora = value
103
+ when 0x01 then @ddra = value
104
+ when 0x02 then @orb = value
105
+ else @ddrb = value
106
+ end
107
+ end
108
+
109
+ private
110
+
111
+ def driven_lines(register, direction)
112
+ (register & direction) | (~direction & 0xff)
113
+ end
114
+
115
+ def read_port_a
116
+ lines = port_a_output
117
+ lines &= @peripheral.read_a(lines) if @peripheral
118
+ (@ora & @ddra) | (lines & ~@ddra & 0xff)
119
+ end
120
+
121
+ def read_port_b
122
+ lines = port_b_output
123
+ lines &= @peripheral.read_b(lines) if @peripheral
124
+ (@orb & @ddrb) | (lines & ~@ddrb & 0xff)
125
+ end
126
+
127
+ def read_timer(addr)
128
+ return @timer.read(addr.anybits?(0x08)) if addr.nobits?(0x01)
129
+
130
+ @timer.flag ? 0x80 : 0x00
131
+ end
132
+ end
133
+ end
134
+ end
@@ -195,7 +195,7 @@ module Badline
195
195
  @unfiltered = 0
196
196
  route_voice(voice1.output >> 7, 0x1)
197
197
  route_voice(voice2.output >> 7, 0x2)
198
- if @routing & 0x4 != 0
198
+ if @routing.anybits?(0x4)
199
199
  @input += voice3.output >> 7
200
200
  elsif !@voice3_off
201
201
  @unfiltered += voice3.output >> 7
@@ -216,9 +216,9 @@ module Badline
216
216
 
217
217
  def filtered
218
218
  value = 0
219
- value += @lowpass if @mode & 0x1 != 0
220
- value += @bandpass if @mode & 0x2 != 0
221
- value += @highpass if @mode & 0x4 != 0
219
+ value += @lowpass if @mode.anybits?(0x1)
220
+ value += @bandpass if @mode.anybits?(0x2)
221
+ value += @highpass if @mode.anybits?(0x4)
222
222
  value
223
223
  end
224
224
  end
@@ -234,7 +234,7 @@ module Badline
234
234
  def compare_pulse(phase = @accumulator >> 12) = (@pulse = @test || phase >= @pulse_width ? 0xfff : 0x000)
235
235
 
236
236
  # More than one bit set in the selection.
237
- def combined?(selected) = selected & (selected - 1) != 0
237
+ def combined?(selected) = selected.anybits?(selected - 1)
238
238
 
239
239
  # The one point in the cycle where the waveform selector drives the DAC:
240
240
  # either it latches a fresh value, or nothing drives the line and the
@@ -274,8 +274,8 @@ module Badline
274
274
  def advance
275
275
  previous = @accumulator
276
276
  @accumulator = (previous + @frequency) & 0xffffff
277
- @msb_rising = @accumulator & ~previous & MSB != 0
278
- if @accumulator & ~previous & 0x080000 != 0
277
+ @msb_rising = (@accumulator & ~previous).anybits?(MSB)
278
+ if (@accumulator & ~previous).anybits?(0x080000)
279
279
  @shift_pipeline = 2
280
280
  elsif @shift_pipeline != 0 && (@shift_pipeline -= 1).zero?
281
281
  write_back_shift
@@ -292,12 +292,12 @@ module Badline
292
292
  # TriXOR = !Saw & ((!V3 & Ring) ^ bit23), where V3 is the modulating
293
293
  # voice's MSB (SID/ringmod).
294
294
  def inverted?(selected)
295
- return false if selected & 0x2 != 0
295
+ return false if selected.anybits?(0x2)
296
296
 
297
- msb = @accumulator & MSB != 0
297
+ msb = @accumulator.anybits?(MSB)
298
298
  return msb unless @ring
299
299
 
300
- msb == (@sync_source.accumulator & MSB != 0)
300
+ msb == @sync_source.accumulator.anybits?(MSB)
301
301
  end
302
302
  end
303
303
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Chips
5
+ class TPI
6
+ # The registers, the interrupt latches and AIR, the priority
7
+ # stack, CA and CB, and the levels on the interrupt inputs, in a
8
+ # keyed snapshot module (BL-TPI1 and BL-TPI2).
9
+ module KeyedState
10
+ def save_fields(out)
11
+ out.ints("ports", [@pra, @prb, @prc]).ints("directions", [@ddra, @ddrb, @ddrc])
12
+ out.int("control", @control).int("latches", @latches).int("air", @air)
13
+ out.ints("stack", @stack).bool("ca", @ca).bool("cb", @cb).int("lines", @lines)
14
+ end
15
+
16
+ def load_fields(input)
17
+ ports = input.ints_into("ports", [@pra, @prb, @prc])
18
+ @pra = ports[0] & 0xff
19
+ @prb = ports[1] & 0xff
20
+ @prc = ports[2] & 0xff
21
+ directions = input.ints_into("directions", [@ddra, @ddrb, @ddrc])
22
+ @ddra = directions[0] & 0xff
23
+ @ddrb = directions[1] & 0xff
24
+ @ddrc = directions[2] & 0xff
25
+ @control = input.byte("control", @control)
26
+ @latches = input.masked("latches", @latches, 0x1f)
27
+ @air = input.masked("air", @air, 0x1f)
28
+ @stack.replace(input.ints("stack", []).last(STACK_DEPTH).map { |value| value & 0x1f })
29
+ @ca = input.bool("ca", @ca)
30
+ @cb = input.bool("cb", @cb)
31
+ @lines = input.masked("lines", @lines, 0x1f)
32
+ end
33
+ end
34
+
35
+ include KeyedState
36
+ end
37
+ end
38
+ end