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,233 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/tpi/keyed_state"
4
+
5
+ module Badline
6
+ module Chips
7
+ # The MOS 6525 TPI (tri-port interface): three 8-bit ports, A, B and C,
8
+ # each with a direction register, a 1 making the pin an output. Its
9
+ # three address lines repeat the eight registers every 8 bytes:
10
+ #
11
+ # 0 - PRA
12
+ # 1 - PRB
13
+ # 2 - PRC, or in mode 1 the interrupt latches (bits 0-4) and the levels
14
+ # of IRQ, CA and CB (bits 5-7)
15
+ # 3 - DDRA
16
+ # 4 - DDRB
17
+ # 5 - DDRC, which in mode 1 is the interrupt mask, its bits 0-4 enabling
18
+ # I0-I4 with a 1, and reads back all eight bits written
19
+ # 6 - CR: bit 0 MC, the mode, bit 1 IP, the priority, bits 2-3 IE3 and
20
+ # IE4, the active edges of I3 and I4 (1 rising), bits 4-5 CA's mode
21
+ # and bits 6-7 CB's
22
+ # 7 - AIR, the active interrupt register, in mode 1
23
+ #
24
+ # A port reads its pins: an output's level, and what the peripheral
25
+ # lets through of an input, which floats high (read_a, read_b and
26
+ # read_c pull lines low, as a VIA's peripheral does).
27
+ #
28
+ # In mode 1 PC0-PC4 are the interrupt inputs I0-I4 (#interrupt_line).
29
+ # A falling edge on I0-I2, or the edge IE3 and IE4 pick on I3 and I4,
30
+ # sets the input's latch whether or not its mask bit is set. A write of
31
+ # 0 to a latch's bit of PRC clears it, and a 1 does nothing. PC5 is
32
+ # the IRQ output, open drain, low (#irq?) while the AIR holds a bit;
33
+ # PC6 is CA and PC7 CB (#ca?, #cb?).
34
+ #
35
+ # Without priority (IP 0), the AIR holds every unmasked latch that is
36
+ # set, and a read of it clears them and it. With priority (IP 1) the
37
+ # inputs rank I4 > I3 > I2 > I1 > I0, and the AIR holds the one highest
38
+ # unmasked latch that outranks the interrupt in service, the top of a
39
+ # five-level stack. A read of the AIR pushes it on the stack, clears it
40
+ # and its latch, and lets IRQ go; a write pops the stack, letting lower
41
+ # interrupts through again.
42
+ #
43
+ # CA: 00 set high by I3's active edge and low by a read of PRA, 01
44
+ # pulsed low by a read of PRA, 10 low, 11 high. CB: 00 set low by a
45
+ # write of PRB and high by I4's active edge, 01 pulsed low by a write of
46
+ # PRB, 10 low, 11 high. A pulse is shorter than a cycle, so CA and CB
47
+ # read high in mode 01.
48
+ #
49
+ # The rules are the MPS 6525 datasheet's (Commodore Semiconductor
50
+ # Group), pages 2-81 to 2-83.
51
+ class TPI
52
+ STACK_DEPTH = 5
53
+
54
+ attr_reader :peripheral, :latches, :control, :stack
55
+
56
+ def initialize(peripheral: nil)
57
+ @peripheral = peripheral
58
+ @lines = 0x1f
59
+ @stack = []
60
+ reset!
61
+ end
62
+
63
+ # The RES pin clears every register: mode 0, and every pin an input.
64
+ def reset!
65
+ @pra = @prb = @prc = 0
66
+ @ddra = @ddrb = @ddrc = 0
67
+ @control = 0
68
+ @latches = 0
69
+ @air = 0
70
+ @stack.clear
71
+ @ca = @cb = false
72
+ end
73
+
74
+ # The interrupt mask in mode 1: DDRC's bits 0-4.
75
+ def mask = @ddrc & 0x1f
76
+
77
+ # Whether IRQ is pulled low.
78
+ def irq? = @air != 0
79
+
80
+ # The levels of CA and CB in mode 1.
81
+ def ca? = @ca
82
+
83
+ def cb? = @cb
84
+
85
+ # The port lines as the chip drives them, inputs floating high.
86
+ def port_a_output = driven(@pra, @ddra)
87
+
88
+ def port_b_output = driven(@prb, @ddrb)
89
+
90
+ def port_c_output = mode1? ? pins_c_mode1 : driven(@prc, @ddrc)
91
+
92
+ # Something outside drives interrupt input +index+, I0 to I4, +high+
93
+ # or low. Its active edge sets its latch in mode 1.
94
+ def interrupt_line(index, high)
95
+ bit = 1 << index
96
+ was_high = @lines.anybits?(bit)
97
+ return if was_high == high
98
+
99
+ @lines = high ? @lines | bit : @lines & ~bit
100
+ return unless mode1? && high == rising_active?(index)
101
+
102
+ @latches |= bit
103
+ @ca = true if index == 3 && ca_mode.zero?
104
+ @cb = true if index == 4 && cb_mode.zero?
105
+ update_air
106
+ end
107
+
108
+ def peek(addr)
109
+ case addr & 0x07
110
+ when 0 then read_pra
111
+ when 1 then pins(@prb, @ddrb, @peripheral ? @peripheral.read_b(port_b_output) : 0xff)
112
+ when 2 then mode1? ? pins_c_mode1 : read_pins_c
113
+ when 3 then @ddra
114
+ when 4 then @ddrb
115
+ when 5 then @ddrc
116
+ when 6 then @control
117
+ else read_air
118
+ end
119
+ end
120
+
121
+ def poke(addr, value)
122
+ case addr & 0x07
123
+ when 0 then @pra = value
124
+ when 1 then write_prb(value)
125
+ when 2 then write_prc(value)
126
+ when 3 then @ddra = value
127
+ when 4 then @ddrb = value
128
+ when 5 then write_ddrc(value)
129
+ when 6 then write_control(value)
130
+ else pop_air
131
+ end
132
+ end
133
+
134
+ private
135
+
136
+ def mode1? = @control.anybits?(0x01)
137
+
138
+ def priority? = @control.anybits?(0x02)
139
+
140
+ def ca_mode = (@control >> 4) & 0x03
141
+
142
+ def cb_mode = (@control >> 6) & 0x03
143
+
144
+ # I3 and I4 latch on the edge IE3 and IE4 pick, the others falling.
145
+ def rising_active?(index)
146
+ case index
147
+ when 3 then @control.anybits?(0x04)
148
+ when 4 then @control.anybits?(0x08)
149
+ else false
150
+ end
151
+ end
152
+
153
+ def driven(register, direction) = (register & direction) | (~direction & 0xff)
154
+
155
+ def pins(register, direction, inputs) = (register & direction) | (inputs & ~direction & 0xff)
156
+
157
+ def read_pra
158
+ @ca = false if mode1? && ca_mode.zero?
159
+ pins(@pra, @ddra, @peripheral ? @peripheral.read_a(port_a_output) : 0xff)
160
+ end
161
+
162
+ def read_pins_c
163
+ pins(@prc, @ddrc, @peripheral ? @peripheral.read_c(port_c_output) : 0xff)
164
+ end
165
+
166
+ # The latches, IRQ's level, CA's and CB's.
167
+ def pins_c_mode1
168
+ @latches | (irq? ? 0 : 0x20) | (@ca ? 0x40 : 0) | (@cb ? 0x80 : 0)
169
+ end
170
+
171
+ def write_prb(value)
172
+ @prb = value
173
+ @cb = false if mode1? && cb_mode.zero?
174
+ end
175
+
176
+ def write_prc(value)
177
+ return @prc = value unless mode1?
178
+
179
+ @latches &= value | 0xe0
180
+ update_air
181
+ end
182
+
183
+ def write_ddrc(value)
184
+ @ddrc = value
185
+ update_air
186
+ end
187
+
188
+ def write_control(value)
189
+ @control = value
190
+ @ca = ca_mode.anybits?(0x01) unless ca_mode.zero?
191
+ @cb = cb_mode.anybits?(0x01) unless cb_mode.zero?
192
+ update_air
193
+ end
194
+
195
+ def read_air
196
+ value = @air
197
+ return value unless mode1?
198
+
199
+ @latches &= ~value
200
+ @air = 0
201
+ if priority?
202
+ @stack.shift if @stack.length == STACK_DEPTH
203
+ @stack << value
204
+ end
205
+ update_air
206
+ value
207
+ end
208
+
209
+ def pop_air
210
+ return unless mode1?
211
+
212
+ @stack.pop
213
+ update_air
214
+ end
215
+
216
+ # The AIR's bits from the latches, the mask and, with priority, the
217
+ # interrupt in service.
218
+ def update_air
219
+ pending = mode1? ? @latches & mask : 0
220
+ return @air = pending unless priority?
221
+
222
+ highest = highest_bit(pending)
223
+ @air = highest > (@stack.last || 0) ? highest : 0
224
+ end
225
+
226
+ def highest_bit(bits)
227
+ bit = 0x10
228
+ bit >>= 1 while bit.positive? && bits.nobits?(bit)
229
+ bit
230
+ end
231
+ end
232
+ end
233
+ end
@@ -10,6 +10,13 @@ module Badline
10
10
  # The machine's bus pushes in what the VIC sees through #connect,
11
11
  # #map_character_rom and #map whenever it changes. Until then the bank
12
12
  # has a CIA 2, RAM, colour RAM and character ROM of its own.
13
+ #
14
+ # The P500 wires the VIC another way (#map_video_bus): its phi1
15
+ # reads go to the character ROM while VICDOTSEL is high, and its phi2
16
+ # reads to the 1K static video matrix while STATVID is high, VA12
17
+ # high picking the matrix and VA12 low reading 0. Otherwise both go to
18
+ # the DRAM's 16K bank that VA14 and VA15 pick (#video_bus_lines). The
19
+ # colour nibbles come from the colour RAM in every phi2 read.
13
20
  class Bank
14
21
  include Addressable
15
22
 
@@ -30,6 +37,11 @@ module Badline
30
37
  map(Memory.new([], length: 2**16, start: 0))
31
38
  @lines = 0b11
32
39
  @swapped = false
40
+ @video_bus = false
41
+ @matrix = @ram
42
+ @statvid = false
43
+ @dotsel = false
44
+ @video_base = 0
33
45
  end
34
46
 
35
47
  # CIA 2, whose port A picks the bank, and the colour RAM, read from
@@ -57,9 +69,31 @@ module Badline
57
69
  @base = base
58
70
  @starts = BANK_STARTS.map { |start| base + start }.freeze
59
71
  end
60
- @phi1_ultimax = phi1_ultimax
61
- @ultimax = ultimax
62
72
  @romh = romh
73
+ @phi1_other = phi1_ultimax
74
+ @phi2_other = ultimax
75
+ end
76
+
77
+ # The P500's video bus: +dram+ is segment 0's 64K, +matrix+ the
78
+ # static video matrix, read from its start address on, and
79
+ # +color_ram+ the colour RAM, read from +color_base+ on. The
80
+ # character ROM is the one #map_character_rom gives, read from
81
+ # +base+ on.
82
+ def map_video_bus(dram, matrix, color_ram, color_base)
83
+ @ram = dram
84
+ @matrix = matrix
85
+ @color_ram = color_ram
86
+ @color_base = color_base
87
+ @video_bus = true
88
+ @phi1_other = true
89
+ @phi2_other = true
90
+ end
91
+
92
+ # STATVID, VICDOTSEL and VA14-VA15, as the P500's TPIs drive them.
93
+ def video_bus_lines(statvid, dotsel, bank)
94
+ @statvid = statvid
95
+ @dotsel = dotsel
96
+ @video_base = bank << 14
63
97
  end
64
98
 
65
99
  # On the 8565, a port A write that swaps the two bank lines, driving
@@ -74,7 +108,7 @@ module Badline
74
108
  end
75
109
 
76
110
  def peek(offset)
77
- return ultimax_peek(offset) if @phi1_ultimax
111
+ return phi1_other(offset) if @phi1_other
78
112
 
79
113
  bits = bank_switch_register
80
114
  if (bits & @shadow_mask) == 0b01 && (offset & 0xf000) == 0x1000
@@ -88,7 +122,13 @@ module Badline
88
122
  # memory configuration the CPU does in that half. No cartridge drives
89
123
  # Ultimax mode in the first half alone, so otherwise it reads as #peek.
90
124
  def peek_phi2(offset)
91
- @ultimax ? ultimax_peek(offset) : peek(offset)
125
+ @phi2_other ? phi2_other(offset) : peek(offset)
126
+ end
127
+
128
+ # A sprite's s-access in phi2: on the P500's video bus as the other
129
+ # phi2 reads, and otherwise as #peek.
130
+ def peek_sprite(offset)
131
+ @video_bus ? phi2_other(offset) : peek(offset)
92
132
  end
93
133
 
94
134
  def peek_color(offset)
@@ -101,7 +141,7 @@ module Badline
101
141
 
102
142
  # True if the VIC reads the character ROM at this offset.
103
143
  def character_rom?(offset)
104
- !@phi1_ultimax && (bank_switch_register & @shadow_mask) == 0b01 && (offset & 0xf000) == 0x1000
144
+ !@phi1_other && (bank_switch_register & @shadow_mask) == 0b01 && (offset & 0xf000) == 0x1000
105
145
  end
106
146
 
107
147
  def start
@@ -110,6 +150,21 @@ module Badline
110
150
 
111
151
  private
112
152
 
153
+ def phi1_other(offset)
154
+ return ultimax_peek(offset) unless @video_bus
155
+ return @character_rom.peek(@character_base + (offset & 0x0fff)) if @dotsel
156
+
157
+ @ram.peek(@video_base + offset)
158
+ end
159
+
160
+ def phi2_other(offset)
161
+ return ultimax_peek(offset) unless @video_bus
162
+ return @ram.peek(@video_base + offset) unless @statvid
163
+ return 0 if offset.nobits?(0x1000)
164
+
165
+ @matrix.peek(@matrix.start + (offset & 0x03ff))
166
+ end
167
+
113
168
  # In Ultimax mode the cartridge ROMH replaces the character ROM
114
169
  # shadow, visible at $3000-$3FFF of the window. The MAX is in
115
170
  # Ultimax mode with no cartridge too.
@@ -322,12 +322,13 @@ module Badline
322
322
  end
323
323
 
324
324
  # The three s-accesses step MC through the sprite's block, wrapping
325
- # within it.
325
+ # within it. The first and third fall in phi2 and the second in
326
+ # phi1 (Bank#peek_sprite).
326
327
  def row_bits(counter)
327
328
  base = pointer * 64
328
- (@bank.peek(base + counter) << 16) |
329
+ (@bank.peek_sprite(base + counter) << 16) |
329
330
  (@bank.peek(base + ((counter + 1) & MC_MASK)) << 8) |
330
- @bank.peek(base + ((counter + 2) & MC_MASK))
331
+ @bank.peek_sprite(base + ((counter + 2) & MC_MASK))
331
332
  end
332
333
 
333
334
  def pointer = @bank.peek(@registers.screen_base + 0x3f8 + index)
@@ -86,13 +86,15 @@ module Badline
86
86
  LINE_LAYOUT = 1
87
87
 
88
88
  # The VIC-I's fields in a keyed snapshot module (Snapshot::Keyed): the
89
- # registers, the beam, the text window's counters and the fetch in
90
- # flight, the painter's lines under line.*, the picture, and the sound
89
+ # registers, the beam, the frame's length in lines and whether the
90
+ # next interlaced field is the long one, the text window's counters
91
+ # and the fetch in flight, the painter's lines under line.*, the picture, and the sound
91
92
  # under sound.* What the registers decode to, where the line's fetches
92
93
  # end and the next event are worked out on load.
93
94
  module KeyedState
94
95
  def save_fields(out)
95
96
  out.bytes("registers", @registers).int("raster", @rasterline).int("column", @column)
97
+ out.int("frame_lines", @frame_lines).bool("long_field", @long_field)
96
98
  out.bool("window_open", @window_open).bool("window_done", @window_done).int("row_start", @row_start)
97
99
  out.int("matrix", @matrix).int("char_line", @char_line).int("text_rows", @text_rows)
98
100
  out.int("fetch_from", @fetch_from).int("fetched", @fetched).int("code", @code)
@@ -106,6 +108,8 @@ module Badline
106
108
  input.bytes_into("registers", @registers)
107
109
  @rasterline = input.int("raster", @rasterline)
108
110
  @column = input.int("column", @column)
111
+ @frame_lines = input.int("frame_lines", @lines_per_frame)
112
+ @long_field = input.bool("long_field", true)
109
113
  @window_open = input.bool("window_open", @window_open)
110
114
  @window_done = input.bool("window_done", @window_done)
111
115
  @row_start = input.int("row_start", @row_start)
@@ -122,6 +126,7 @@ module Badline
122
126
  end
123
127
 
124
128
  def restored!
129
+ @frame_wrap = @frame_lines == @interlaced_lines ? @count_column : @wrap_column
125
130
  @origin_x = @registers[0] & 0x7f
126
131
  @columns = @registers[2] & 0x7f
127
132
  @origin_y = @registers[1]
@@ -7,13 +7,15 @@ require "badline/chips/vic_i/keyed_parts"
7
7
 
8
8
  module Badline
9
9
  module Chips
10
- # The VIC-I, the 6561 on PAL machines: its sixteen registers, the
10
+ # The VIC-I, the 6561 on PAL machines or the 6560 on NTSC ones: its
11
+ # sixteen registers, the
11
12
  # raster, the text window's fetches over the V-bus and the picture. The
12
13
  # writes to the sound's registers, $900A-$900E, go on to the Sound
13
14
  # attached as #sound.
14
15
  #
15
16
  # The registers repeat through $9000-$90FF. $9003 bit 7 and $9004 read
16
- # the raster line, its bit 0 and bits 8-1. $9006 and $9007 read the
17
+ # the raster count, its bit 0 and bits 8-1, which on the 6561 is the
18
+ # raster line (see Profile for the 6560's). $9006 and $9007 read the
17
19
  # light pen, which nothing triggers yet, and $9008 and $9009 the two
18
20
  # pots, which read $FF with nothing plugged in. The other registers
19
21
  # read back what was written to them.
@@ -35,23 +37,48 @@ module Badline
35
37
  # of a text row where the row started, and the next row starts where
36
38
  # the last line of the row left it. It starts the frame at zero.
37
39
  #
38
- # The picture is 4 pixels a cycle, so 284 a line. A character's 8
39
- # pixels come out in the two cycles after its pattern fetch. The
40
- # display's lines start 6 cycles after the raster count moves, as
41
- # xvic's view of the frame does, so the character fetched 16 cycles
42
- # into the line lands 48 pixels in. Lines 0-27 are blanked.
40
+ # The picture is 4 pixels a cycle, so 284 a line on the 6561 and 260 on
41
+ # the 6560. A character's 8 pixels come out in the two cycles after its
42
+ # pattern fetch. The display's lines start 6 cycles after the raster
43
+ # line moves, as xvic's view of the frame does, so the character
44
+ # fetched 16 cycles into the line lands 48 pixels in. Lines 0-27 are
45
+ # blanked.
43
46
  class VICI
44
47
  include TextWindow
45
48
 
46
49
  # What a VIC-I sets for the machine: the clock, the raster's cycles
47
- # per line and lines per frame, and how many lines at the top of the
48
- # frame it blanks. The 6560 of NTSC machines, with its half lines and
49
- # the interlace of $9000 bit 7, would be another.
50
- Profile = Data.define(:clock_hz, :cycles_per_line, :lines_per_frame, :blanked_lines)
50
+ # per line and lines per frame, how many lines at the top of the frame
51
+ # it blanks and how many it shows from there, and where in the line
52
+ # the raster count $9003 and $9004 read moves on, and how many lines
53
+ # the long field of an interlaced frame has, or 0 without interlace.
54
+ #
55
+ # The 6561's count is the raster line. The 6560's runs ahead of it: it
56
+ # reads the next line from +count_column+ on, and on the last line it
57
+ # reads one past the last line from there, then 0 from
58
+ # +wrap_column+ on, so the count's first and last values each last
59
+ # about half a line. The 6561's +count_column+ is past the end of the
60
+ # line, so it never moves early.
61
+ #
62
+ # With $9000 bit 7 set at the start of a frame, the 6560 interlaces:
63
+ # its frames alternate between the long field, +interlaced_lines+
64
+ # long, and the short one, a line shorter, the long one first. On the
65
+ # long field's last line the count wraps to 0 where it would move on,
66
+ # so its first and last values each last a whole line.
67
+ Profile = Data.define(:clock_hz, :cycles_per_line, :lines_per_frame, :blanked_lines, :shown_lines,
68
+ :count_column, :wrap_column, :interlaced_lines)
51
69
 
52
70
  # The 6561: 71 cycles by 312 lines, lines 0-27 blanked (Marko
53
71
  # Mäkelä's VIC-I.txt).
54
- PAL = Profile.new(clock_hz: 1_108_405, cycles_per_line: 71, lines_per_frame: 312, blanked_lines: 28)
72
+ PAL = Profile.new(clock_hz: 1_108_405, cycles_per_line: 71, lines_per_frame: 312, blanked_lines: 28,
73
+ shown_lines: 284, count_column: 71, wrap_column: 71, interlaced_lines: 0)
74
+
75
+ # The 6560: 65 cycles by 261 lines on a 14.31818 MHz crystal divided
76
+ # by 14. The count's columns and the lines shown are xvic's, measured
77
+ # as a black box: the count moves on 28 cycles into the line, and
78
+ # wraps to 0 on the last line 61 cycles in. Interlaced, its fields are
79
+ # 263 and 262 lines.
80
+ NTSC = Profile.new(clock_hz: 1_022_727, cycles_per_line: 65, lines_per_frame: 261, blanked_lines: 28,
81
+ shown_lines: 233, count_column: 28, wrap_column: 61, interlaced_lines: 263)
55
82
 
56
83
  PIXELS_PER_CYCLE = 4
57
84
 
@@ -71,6 +98,10 @@ module Badline
71
98
  0xb27240, 0xf2b27f, 0xc2655d, 0xbdffff, 0xf299ff, 0x8de57f, 0x806eda, 0xffffa5
72
99
  ].freeze
73
100
 
101
+ # The display's lines: the frame's, or the long interlaced field's
102
+ # where that is longer.
103
+ attr_reader :height
104
+
74
105
  attr_reader :rasterline, :column, :profile
75
106
  attr_accessor :sound
76
107
 
@@ -78,7 +109,11 @@ module Badline
78
109
  @profile = profile
79
110
  @cycles_per_line = profile.cycles_per_line
80
111
  @lines_per_frame = profile.lines_per_frame
81
- @painter = Painter.new(@lines_per_frame, @cycles_per_line * PIXELS_PER_CYCLE, profile.blanked_lines)
112
+ @count_column = profile.count_column
113
+ @wrap_column = profile.wrap_column
114
+ @interlaced_lines = profile.interlaced_lines
115
+ @height = [@lines_per_frame, @interlaced_lines].max
116
+ @painter = Painter.new(@height, @cycles_per_line * PIXELS_PER_CYCLE, profile.blanked_lines)
82
117
  @patterns = @painter.patterns
83
118
  @colors = @painter.colors
84
119
  @counts = @painter.counts
@@ -106,11 +141,14 @@ module Badline
106
141
  @origin_x = @origin_y = @columns = @rows = 0
107
142
  @char_shift = 3
108
143
  @screen_base = @char_base = 0
144
+ @long_field = true
145
+ @frame_lines = @lines_per_frame
146
+ @frame_wrap = @wrap_column
109
147
  new_frame
110
148
  @fetch_from = @fetch_to = 0
111
149
  @fetched = @slot = 0
112
150
  @code = 0
113
- @painter.power_on!(@lines_per_frame - 1)
151
+ @painter.power_on!(@frame_lines - 1)
114
152
  schedule
115
153
  end
116
154
 
@@ -131,14 +169,12 @@ module Badline
131
169
  # The sixteen registers as written.
132
170
  def register_file = @registers.dup
133
171
 
134
- # The display, a palette index a pixel, 284 to a line and every line
135
- # of the frame.
172
+ # The display, a palette index a pixel, 4 to a cycle of the line, and
173
+ # every line of the frame.
136
174
  def display = @painter.display
137
175
 
138
176
  def width = @painter.width
139
177
 
140
- def height = @lines_per_frame
141
-
142
178
  # Whether each line of the display has changed since the front end
143
179
  # last cleared them.
144
180
  def dirty_lines = @painter.dirty_lines
@@ -157,8 +193,8 @@ module Badline
157
193
  def peek(addr)
158
194
  register = addr & 0x0f
159
195
  case register
160
- when 0x03 then ((@rasterline & 0x01) << 7) | (@registers[3] & 0x7f)
161
- when 0x04 then @rasterline >> 1
196
+ when 0x03 then ((raster_count & 0x01) << 7) | (@registers[3] & 0x7f)
197
+ when 0x04 then raster_count >> 1
162
198
  when 0x06, 0x07 then 0
163
199
  when 0x08, 0x09 then 0xff
164
200
  else @registers[register]
@@ -181,6 +217,16 @@ module Badline
181
217
  end
182
218
  end
183
219
 
220
+ # The raster count $9003 and $9004 read (see Profile).
221
+ def raster_count
222
+ line = @rasterline
223
+ column = @column
224
+ return line if column < @count_column
225
+ return 0 if line == @frame_lines - 1 && column >= @frame_wrap
226
+
227
+ line + 1
228
+ end
229
+
184
230
  private
185
231
 
186
232
  def event(column)
@@ -205,9 +251,10 @@ module Badline
205
251
 
206
252
  def start_line
207
253
  line = @rasterline + 1
208
- if line == @lines_per_frame
254
+ if line == @frame_lines
209
255
  line = 0
210
256
  new_frame
257
+ next_field
211
258
  end
212
259
  @rasterline = line
213
260
  next_text_line if @window_open
@@ -215,6 +262,22 @@ module Badline
215
262
  place_fetches
216
263
  end
217
264
 
265
+ # How many lines the frame starting has, and where on its last line
266
+ # the count wraps: the field's, if $9000 bit 7 asks the 6560 to
267
+ # interlace, else the profile's.
268
+ def next_field
269
+ if @interlaced_lines.positive? && @registers[0] >= 0x80
270
+ long = @long_field
271
+ @frame_lines = long ? @interlaced_lines : @interlaced_lines - 1
272
+ @frame_wrap = long ? @count_column : @wrap_column
273
+ @long_field = !long
274
+ else
275
+ @frame_lines = @lines_per_frame
276
+ @frame_wrap = @wrap_column
277
+ @long_field = true
278
+ end
279
+ end
280
+
218
281
  def place_fetches
219
282
  line = @rasterline
220
283
  @slot = line * Painter::SLOTS
@@ -24,10 +24,10 @@ module Badline
24
24
  File.join(xdg, "badline")
25
25
  end
26
26
 
27
- # The quicksaves, autosaves or saves subfolder of data_path, created
28
- # along with data_path if it doesn't exist yet.
27
+ # The quicksaves, autosaves, saves or tapes subfolder of data_path,
28
+ # created along with data_path if it doesn't exist yet.
29
29
  def data_folder(name)
30
- raise ArgumentError, "unknown data folder: #{name}" unless %w[quicksaves autosaves saves].include?(name)
30
+ raise ArgumentError, "unknown data folder: #{name}" unless %w[quicksaves autosaves saves tapes].include?(name)
31
31
 
32
32
  path = File.join(data_path, name)
33
33
  FileUtils.mkdir_p(path)
@@ -10,7 +10,8 @@ module Badline
10
10
  # bytes and how far it has played. A tape that isn't in leaves tape.* out. A datasette that
11
11
  # times the read line's level adds when in the countdown the line
12
12
  # rises, and whether a recording's low half wave is on the tape. The
13
- # sense and flag handlers don't fire on load.
13
+ # sense and flag handlers don't fire on load, and the running handler
14
+ # does when the load starts or stops the tape.
14
15
  module KeyedState
15
16
  def save_fields(out)
16
17
  out.bool("playing", @playing).bool("motor", @motor).int("countdown", @countdown)
@@ -33,6 +34,13 @@ module Badline
33
34
  @write_high = input.bool("write_high", @write_high)
34
35
  @rise_at = input.int("rise_at", @rise_at)
35
36
  @low_recorded = input.bool("low_recorded", @low_recorded)
37
+ load_tape(input)
38
+ update_running
39
+ end
40
+
41
+ private
42
+
43
+ def load_tape(input)
36
44
  bytes = input.bytes("tape.bytes", "".b).bytes
37
45
  return @tape = nil if bytes.empty?
38
46