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
@@ -54,7 +54,9 @@ module Badline
54
54
  address_device if @listening
55
55
  end
56
56
 
57
- def addressed? = @bus.peek(zero_page.device) == @device
57
+ # The device TALK or LISTEN addresses: the one in A or in FA, as the
58
+ # layout says.
59
+ def addressed? = (@layout.device_in_a ? @cpu.a : @bus.peek(zero_page.device)) == @device
58
60
 
59
61
  # TALK and LISTEN end with the command sent under ATN, which stays
60
62
  # low, and A holding $FF.
@@ -116,7 +118,7 @@ module Badline
116
118
  def acptr
117
119
  return unless @talking
118
120
 
119
- @layout.listen_to_talker(@bus)
121
+ @layout.byte_received(@bus)
120
122
  byte, status = received
121
123
  time_serial_byte(Routine::ACPTR_TIMEOUT)
122
124
  @cpu.a = byte
@@ -2,70 +2,122 @@
2
2
 
3
3
  module Badline
4
4
  module KernalTrap
5
- # Where a PET KERNAL keeps the eight IEEE-488 bus primitives, which are
6
- # internal routines, not jump table entries, and the ZeroPage of its
7
- # file variables. Each ROM set has its own.
8
- #
9
- # TALK and LISTEN take no device number in A: they send $40 or $20
10
- # ORed with FA. UNTALK and UNLISTEN send $5F and $3F through the same
11
- # code, and SECOND, TKSA, UNTALK and UNLISTEN end by letting go of ATN
12
- # (VIA PB2) themselves. Each handshake times out on VIA timer 1, which
13
- # the ROM starts with a write of $FF to $E845. LOAD and SAVE run in the
14
- # ROM, through the primitives. +overwrites_device+ says UNTALK and
15
- # UNLISTEN store their command in FA before they send it.
5
+ # The +keep+ of an IEEELineStep that only reads its register.
6
+ IEEE_LINE_READ = -1
7
+
8
+ # One step a KERNAL takes on the IEEE-488 bus's lines: a write of +set+
9
+ # to the register at +address+, or with a +keep+ mask the register read
10
+ # first, its +keep+ bits kept and +set+ ORed in, or with keep
11
+ # IEEE_LINE_READ only a read. Returns the value written, or read.
12
+ IEEELineStep = Data.define(:address, :keep, :set) do
13
+ def act_on(bus)
14
+ return bus.peek(address) if keep == IEEE_LINE_READ
15
+
16
+ value = keep.zero? ? set : (bus.peek(address) & keep) | set
17
+ bus.poke(address, value)
18
+ value
19
+ end
20
+ end
21
+
22
+ # The steps a KERNAL takes on the bus's lines at each moment the traps
23
+ # stand in for, each run in order (#run_steps), the last one's value
24
+ # returned: +timer+ starts the timer that times out each byte's
25
+ # handshake, +attention+ readies the bus and pulls ATN low for TALK
26
+ # and LISTEN, +byte_sent+ ends a byte sent, +release_attention+ ends
27
+ # SECOND, TKSA, UNTALK and UNLISTEN by letting ATN go, +listen_to_talker+
28
+ # ends TKSA with the computer listening, +byte_received+ ends ACPTR,
29
+ # and +release+ ends UNTALK and UNLISTEN.
30
+ IEEELines = Data.define(:timer, :attention, :byte_sent, :release_attention, :listen_to_talker, :byte_received,
31
+ :release) do
32
+ def run_steps(steps, bus)
33
+ value = 0
34
+ steps.each { |step| value = step.act_on(bus) }
35
+ value
36
+ end
37
+ end
38
+
39
+ # Where a KERNAL keeps the eight IEEE-488 bus primitives, which are
40
+ # internal routines, not jump table entries, the ZeroPage of its file
41
+ # variables, and the steps it takes on the bus's lines (IEEELines).
42
+ # Each ROM set has its own.
16
43
  #
17
- # The ROM drives the bus's control lines from three chips: ATN from VIA
18
- # PB2 and NRFD from PB1 at $E840, NDAC from PIA 2's CA2 and DAV from its
19
- # CB2, through the control registers at $E821 and $E823, which the ROM
20
- # writes $34 to pull a line low and $3C to let it go, and the data lines
21
- # from PIA 2's port B at $E822. The steps below leave the lines as each
22
- # primitive leaves them.
44
+ # On a PET, TALK and LISTEN take no device number in A: they send $40
45
+ # or $20 ORed with FA. UNTALK and UNLISTEN send $5F and $3F through the
46
+ # same code, and SECOND, TKSA, UNTALK and UNLISTEN end by letting go of
47
+ # ATN themselves. LOAD and SAVE run in the ROM, through the primitives.
48
+ # +overwrites_device+ says UNTALK and UNLISTEN store their command in
49
+ # FA before they send it. +device_in_a+ says TALK and LISTEN take the
50
+ # device in A, as the CBM-II's do, where a PET's ignore A and read FA.
23
51
  IEEELayout = Data.define(:talk, :listen, :second, :tksa, :ciout, :untalk, :unlisten, :acptr, :overwrites_device,
24
- :zero_page) do
25
- # The KERNAL is mapped unless an 8096's board lies over it.
52
+ :device_in_a, :zero_page, :lines) do
53
+ # The KERNAL is mapped unless something lies over it.
26
54
  def kernal_mapped?(bus) = bus.kernal_mapped?
27
55
 
28
- # Starts VIA timer 1, which times out the handshake of each byte.
56
+ # Starts the timer that times out the handshake of each byte.
29
57
  def time_serial_byte(bus, _timer_high)
30
- bus.poke(0xe845, 0xff)
58
+ lines.run_steps(lines.timer, bus)
31
59
  end
32
60
 
33
- # TALK and LISTEN: NRFD and NDAC let go, then ATN pulled low.
61
+ # TALK and LISTEN: the handshake lines let go, then ATN pulled low.
34
62
  def attention(bus)
35
- bus.poke(0xe840, bus.peek(0xe840) | 0x02)
36
- bus.poke(0xe821, 0x3c)
37
- bus.poke(0xe840, bus.peek(0xe840) & 0xfb)
63
+ lines.run_steps(lines.attention, bus)
38
64
  end
39
65
 
40
66
  # The end of a byte sent: DAV and the data lines let go.
41
67
  def byte_sent(bus)
42
- bus.poke(0xe823, 0x3c)
43
- bus.poke(0xe822, 0xff)
68
+ lines.run_steps(lines.byte_sent, bus)
44
69
  end
45
70
 
46
71
  # The end of SECOND, TKSA, UNTALK and UNLISTEN: ATN let go. Returns
47
72
  # the value written, which the ROM leaves in A.
48
73
  def release_attention(bus)
49
- value = bus.peek(0xe840) | 0x04
50
- bus.poke(0xe840, value)
51
- value
74
+ lines.run_steps(lines.release_attention, bus)
52
75
  end
53
76
 
54
- # TKSA and ACPTR end listening: NRFD and NDAC pulled low.
77
+ # TKSA ends listening: NRFD and NDAC pulled low.
55
78
  def listen_to_talker(bus)
56
- bus.poke(0xe840, bus.peek(0xe840) & 0xfd)
57
- bus.poke(0xe821, 0x34)
79
+ lines.run_steps(lines.listen_to_talker, bus)
80
+ end
81
+
82
+ # ACPTR ends listening as well.
83
+ def byte_received(bus)
84
+ lines.run_steps(lines.byte_received, bus)
58
85
  end
59
86
 
60
87
  # The end of UNTALK and UNLISTEN, which address the bus as TALK and
61
- # LISTEN do, send their command and let ATN go.
88
+ # LISTEN do, send their command and let ATN go. Returns the value
89
+ # the ROM leaves in A.
62
90
  def release_serial_lines(bus)
63
- attention(bus)
64
- byte_sent(bus)
65
- release_attention(bus)
91
+ lines.run_steps(lines.release, bus)
66
92
  end
67
93
  end
68
94
 
95
+ # A PET drives the bus's control lines from three chips: ATN from VIA
96
+ # PB2 and NRFD from PB1 at $E840, NDAC from PIA 2's CA2 and DAV from its
97
+ # CB2, through the control registers at $E821 and $E823, which the ROM
98
+ # writes $34 to pull a line low and $3C to let it go, and the data lines
99
+ # from PIA 2's port B at $E822. Each handshake times out on VIA timer 1,
100
+ # which the ROM starts with a write of $FF to $E845. The steps leave the
101
+ # lines as each primitive leaves them.
102
+ PET_ATTENTION = [
103
+ IEEELineStep.new(address: 0xe840, keep: 0xff, set: 0x02),
104
+ IEEELineStep.new(address: 0xe821, keep: 0, set: 0x3c),
105
+ IEEELineStep.new(address: 0xe840, keep: 0xfb, set: 0)
106
+ ].freeze
107
+ PET_BYTE_SENT = [
108
+ IEEELineStep.new(address: 0xe823, keep: 0, set: 0x3c),
109
+ IEEELineStep.new(address: 0xe822, keep: 0, set: 0xff)
110
+ ].freeze
111
+ PET_RELEASE_ATTENTION = [IEEELineStep.new(address: 0xe840, keep: 0xff, set: 0x04)].freeze
112
+ PET_LISTEN = [IEEELineStep.new(address: 0xe840, keep: 0xfd, set: 0),
113
+ IEEELineStep.new(address: 0xe821, keep: 0, set: 0x34)].freeze
114
+ PET_LINES = IEEELines.new(
115
+ timer: [IEEELineStep.new(address: 0xe845, keep: 0, set: 0xff)].freeze,
116
+ attention: PET_ATTENTION, byte_sent: PET_BYTE_SENT, release_attention: PET_RELEASE_ATTENTION,
117
+ listen_to_talker: PET_LISTEN, byte_received: PET_LISTEN,
118
+ release: (PET_ATTENTION + PET_BYTE_SENT + PET_RELEASE_ATTENTION).freeze
119
+ )
120
+
69
121
  # The file variables of the BASIC 2 and BASIC 4 KERNALs, which keep
70
122
  # them at the same addresses. SAVE copies STAL at $FB to SAL at $C7 and
71
123
  # walks SAL up to EAL at $C9. LOAD keeps no MEMUSS: it stores each byte
@@ -90,24 +142,24 @@ module Badline
90
142
  # LISTEN without a secondary address.
91
143
  PET_BASIC1_LAYOUT = IEEELayout.new(
92
144
  talk: 0xf0b6, listen: 0xf0ba, second: 0xf12c, tksa: 0xf15b,
93
- ciout: 0xf167, untalk: 0xf17a, unlisten: 0xf17e, acptr: 0xf187, overwrites_device: true,
94
- zero_page: PET_BASIC1_ZERO_PAGE
145
+ ciout: 0xf167, untalk: 0xf17a, unlisten: 0xf17e, acptr: 0xf187, overwrites_device: true, device_in_a: false,
146
+ zero_page: PET_BASIC1_ZERO_PAGE, lines: PET_LINES
95
147
  )
96
148
 
97
149
  # BASIC 2, 901465-03. Its LOAD sends the secondary address after TALK
98
150
  # with SECOND, not TKSA.
99
151
  PET_BASIC2_LAYOUT = IEEELayout.new(
100
152
  talk: 0xf0b6, listen: 0xf0ba, second: 0xf128, tksa: 0xf164,
101
- ciout: 0xf16f, untalk: 0xf17f, unlisten: 0xf183, acptr: 0xf18c, overwrites_device: false,
102
- zero_page: PET_ZERO_PAGE
153
+ ciout: 0xf16f, untalk: 0xf17f, unlisten: 0xf183, acptr: 0xf18c, overwrites_device: false, device_in_a: false,
154
+ zero_page: PET_ZERO_PAGE, lines: PET_LINES
103
155
  )
104
156
 
105
157
  # BASIC 4, 901465-22. LISTEN at $F0D5 is the operand of the BIT
106
158
  # instruction that skips it on the way from TALK.
107
159
  PET_BASIC4_LAYOUT = IEEELayout.new(
108
160
  talk: 0xf0d2, listen: 0xf0d5, second: 0xf143, tksa: 0xf193,
109
- ciout: 0xf19e, untalk: 0xf1ae, unlisten: 0xf1b9, acptr: 0xf1c0, overwrites_device: false,
110
- zero_page: PET_ZERO_PAGE
161
+ ciout: 0xf19e, untalk: 0xf1ae, unlisten: 0xf1b9, acptr: 0xf1c0, overwrites_device: false, device_in_a: false,
162
+ zero_page: PET_ZERO_PAGE, lines: PET_LINES
111
163
  )
112
164
  end
113
165
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class KIM1
5
+ # The CPU's bus. The board decodes A10-A12 into eight 1K blocks and
6
+ # leaves A13-A15 alone, so the 8K repeats through all 64K, and the
7
+ # vectors at $FFFA-$FFFF are the monitor's at $1FFA-$1FFF (User Manual,
8
+ # figure 3.12 and section 6.2):
9
+ #
10
+ # $0000-$03FF - the 1K of RAM
11
+ # $0400-$16FF - nothing
12
+ # $1700-$173F - U3's ports and timer, its 16 registers repeated
13
+ # $1740-$177F - U2's
14
+ # $1780-$17BF - U3's 64 bytes of RAM
15
+ # $17C0-$17FF - U2's
16
+ # $1800-$1BFF - U3's ROM, the tape routines
17
+ # $1C00-$1FFF - U2's ROM, the monitor
18
+ #
19
+ # A read from nothing finds the last byte on the bus. Writes to ROM
20
+ # and to nothing go nowhere.
21
+ #
22
+ # The board takes more RAM, as RAM_CONFIGURATIONS names it: the stock
23
+ # 1K, 5K with a board in K1-K4 filling $0400-$13FF, or those 5K and
24
+ # RAM at $2000-$DFFF, with an expansion that decodes A13-A15 and
25
+ # repeats the first 8K only at $E000-$FFFF, so the vectors are still
26
+ # the monitor's (User Manual, sections 6.1 and 6.2).
27
+ class Bus
28
+ include Addressable
29
+
30
+ RAM_CONFIGURATIONS = %i[1k 5k expanded].freeze
31
+
32
+ # The RAM, and the one of RAM_CONFIGURATIONS it comes as.
33
+ attr_reader :ram, :ram_configuration
34
+
35
+ # The last byte the CPU read or wrote.
36
+ attr_reader :data
37
+
38
+ # +monitor+ is U2, the 6530-002, and +user+ U3, the 6530-003, with
39
+ # the RAM `ram` names.
40
+ def initialize(monitor, user, ram: :"1k")
41
+ addressable_at(0, length: 2**16)
42
+ raise ArgumentError, "no KIM-1 RAM configuration named #{ram}" unless RAM_CONFIGURATIONS.include?(ram)
43
+
44
+ @u2 = monitor
45
+ @u3 = user
46
+ @ram_configuration = ram
47
+ # The end of RAM in the first 8K, and where the first 8K stops
48
+ # repeating, past the RAM above it.
49
+ @low_end = ram == :"1k" ? 0x0400 : 0x1400
50
+ @mirror_end = ram == :expanded ? 0xe000 : 0x2000
51
+ @ram = Memory.new([], length: @mirror_end == 0x2000 ? @low_end : @mirror_end, start: 0)
52
+ @data = 0
53
+ end
54
+
55
+ # The RAM in K, without the 6530s' 64 bytes each.
56
+ def ram_kb = (@low_end + @mirror_end - 0x2000) >> 10
57
+
58
+ # Whether RAM answers at `addr`, the board's or a 6530's.
59
+ def ram?(addr)
60
+ offset = addr < 0x2000 || addr >= @mirror_end ? addr & 0x1fff : addr
61
+ offset < @low_end || offset >= 0x2000 || offset.between?(0x1780, 0x17ff)
62
+ end
63
+
64
+ def peek(addr)
65
+ offset = addr < 0x2000 || addr >= @mirror_end ? addr & 0x1fff : addr
66
+ @data = if offset < @low_end || offset >= 0x2000
67
+ @ram.peek(offset)
68
+ elsif offset >= 0x1c00
69
+ @u2.read_rom(offset)
70
+ elsif offset >= 0x1800
71
+ @u3.read_rom(offset)
72
+ elsif (offset >> 8) == 0x17
73
+ read_chips(offset)
74
+ else
75
+ @data
76
+ end
77
+ end
78
+
79
+ def poke(addr, value)
80
+ @data = value
81
+ offset = addr < 0x2000 || addr >= @mirror_end ? addr & 0x1fff : addr
82
+ if offset < @low_end || offset >= 0x2000
83
+ @ram.poke(offset, value)
84
+ elsif (offset >> 8) == 0x17
85
+ write_chips(offset, value)
86
+ end
87
+ end
88
+
89
+ def power_on!
90
+ @ram.clear!
91
+ @u2.ram.map! { 0 }
92
+ @u3.ram.map! { 0 }
93
+ end
94
+
95
+ private
96
+
97
+ # A6 picks U2 over U3, and A7 the RAM over the registers.
98
+ def read_chips(offset)
99
+ ram = offset.anybits?(0x80)
100
+ if offset.anybits?(0x40)
101
+ ram ? @u2.read_ram(offset) : @u2.peek(offset & 0x0f)
102
+ else
103
+ ram ? @u3.read_ram(offset) : @u3.peek(offset & 0x0f)
104
+ end
105
+ end
106
+
107
+ def write_chips(offset, value)
108
+ ram = offset.anybits?(0x80)
109
+ if offset.anybits?(0x40)
110
+ ram ? @u2.write_ram(offset, value) : @u2.poke(offset & 0x0f, value)
111
+ else
112
+ ram ? @u3.write_ram(offset, value) : @u3.poke(offset & 0x0f, value)
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class KIM1
5
+ # The KIM-1's 6502, U1, on the KIM1::Bus.
6
+ #
7
+ # With the SST switch on, the board's U26 gate pulls NMI low through
8
+ # each opcode fetch outside K7, $1C00-$1FFF and its mirrors, where the
9
+ # monitor runs. The CPU takes the NMI at the end of the instruction
10
+ # that fetch starts, a 2-cycle one too, so the monitor steps a program
11
+ # one instruction at a time (User Manual, figure 5.6).
12
+ class CPU
13
+ include Badline::Chips::CPU::Core
14
+
15
+ # The K7 block's address lines, A10-A12, which the board decodes.
16
+ K7 = 0x1c00
17
+
18
+ # Pulls NMI if the next cycle fetches an opcode outside K7. The
19
+ # board calls it ahead of each cycle while SST is on.
20
+ def single_step
21
+ @nmi = true if boundary? && (@program_counter & K7) != K7
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class KIM1
5
+ # The KIM-1's teletype on the host's terminal, for `badline kim1
6
+ # --headless`: what the terminal types goes down the line, and what
7
+ # the teletype prints comes out on it, without the NULs and RUBOUTs a
8
+ # terminal doesn't show. The machine runs at its own 1 MHz against the
9
+ # host's clock, catching up at most CATCH_UP seconds at a time, until
10
+ # Ctrl-C.
11
+ #
12
+ # `console` answers #typing, #read_text and #print_text, as
13
+ # Audio::Terminal does.
14
+ class HostTerminal
15
+ SLICE = 0.01
16
+ CATCH_UP = 0.1
17
+
18
+ def self.run(machine, console) = new(machine, console).run
19
+
20
+ # Builds the KIM-1 the command line's `options` name, loads the
21
+ # program they name (Media::KIM1Media), and runs it on `console`
22
+ # until Ctrl-C. Returns the exit status, 1 when the program won't
23
+ # load, which it warns about.
24
+ def self.start(options, console)
25
+ path = options.media_path
26
+ machine = Machine.for_options(options, path)
27
+ unless path.nil?
28
+ loaded = Media.attach(machine, path, autostart: true, kim1: Media::KIM1Media.loading(options))
29
+ console.print_text("#{loaded}\r\n")
30
+ end
31
+ run(machine, console)
32
+ rescue Machine::Unsupported, Storage::Papertape::FormatError, SystemCallError => e
33
+ warn "#{options.program}: #{path}: #{e.message}"
34
+ 1
35
+ end
36
+
37
+ def initialize(machine, console)
38
+ @machine = machine
39
+ @console = console
40
+ @printed = 0
41
+ @started = 0.0
42
+ end
43
+
44
+ # Runs until Ctrl-C, and returns the exit status.
45
+ def run
46
+ @started = now
47
+ @console.print_text("KIM-1 teletype at #{@machine.teletype.baud} baud. Ctrl-C: Quit\r\n")
48
+ @console.typing { loop { step } }
49
+ rescue Interrupt
50
+ 0
51
+ end
52
+
53
+ private
54
+
55
+ def step
56
+ text = @console.read_text(SLICE)
57
+ @machine.teletype.type(text) unless text.empty?
58
+ behind = ((now - @started) * 1_000_000).to_i - @machine.cycles
59
+ @machine.run_cycles([behind, (CATCH_UP * 1_000_000).to_i].min) if behind.positive?
60
+ print_output
61
+ end
62
+
63
+ def print_output
64
+ output = @machine.teletype.output
65
+ return if output.size == @printed
66
+
67
+ @console.print_text(output[@printed, output.size - @printed].gsub("\x00", "").gsub("\x7f", ""))
68
+ @printed = output.size
69
+ end
70
+
71
+ def now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class KIM1
5
+ # Pressing the panel's keys in turn, each long enough for the monitor
6
+ # to take it. The monitor takes a key it reads down on two scans in a
7
+ # row, after a scan with no key down, and a scan takes some 4,300
8
+ # cycles, so each key is held for HOLD_CYCLES and let go for
9
+ # RELEASE_CYCLES.
10
+ module KeyEntry
11
+ HOLD_CYCLES = 12_000
12
+ RELEASE_CYCLES = 8_000
13
+
14
+ # The panel's keys that type_keys takes by name. Any other word is a
15
+ # run of hex keys.
16
+ NAMED_KEYS = %w[AD DA GO PC + ST RS].freeze
17
+
18
+ # Presses the keys `text` names once the monitor is running: AD, DA,
19
+ # GO, PC, + and the hex digits, ST, which pulls NMI, and RS, which
20
+ # resets the machine. Words go by whitespace, and a word that isn't a
21
+ # key's name is a hex key for each of its characters, so
22
+ # "AD 0200 DA A9 +" presses nine keys, and the byte $AD goes in as
23
+ # "A D".
24
+ def type_keys(text)
25
+ keys = text.upcase.split.flat_map { |word| NAMED_KEYS.include?(word) ? [word] : word.chars }
26
+ unknown = keys.find { |key| !NAMED_KEYS.include?(key) && !Panel::KEYS.include?(key) }
27
+ raise ArgumentError, "no KIM-1 key named #{unknown}" unless unknown.nil?
28
+
29
+ on_init { queue_keys(keys) }
30
+ end
31
+
32
+ # Whether keys type_keys was given are still to be pressed.
33
+ def typing? = !@pending_keys.nil?
34
+
35
+ private
36
+
37
+ def queue_keys(keys)
38
+ @pending_keys = keys
39
+ @key_down = false
40
+ @next_key_at = @cycles
41
+ end
42
+
43
+ def feed_keys
44
+ return if @cycles < @next_key_at
45
+
46
+ if @key_down
47
+ release_key
48
+ @key_down = false
49
+ @next_key_at = @cycles + RELEASE_CYCLES
50
+ else
51
+ press_next_key
52
+ end
53
+ end
54
+
55
+ def press_next_key
56
+ keys = @pending_keys
57
+ name = keys.shift
58
+ if name.nil?
59
+ @pending_keys = nil
60
+ return
61
+ end
62
+ if name == "ST"
63
+ press_stop
64
+ elsif name == "RS"
65
+ press_reset
66
+ else
67
+ press_key(name)
68
+ end
69
+ @key_down = true
70
+ @next_key_at = @cycles + HOLD_CYCLES
71
+ end
72
+ end
73
+ end
74
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class KIM1
5
+ # The whole machine as keyed snapshot modules (Snapshot::Keyed), in the
6
+ # order a load applies them: BL-MACHINE with the setup, the RAM, U2 and
7
+ # U3, each with its 64 bytes, ports and timer, the panel, the cassette
8
+ # deck, the teletype, the sound's wiring and the CPU.
9
+ module KeyedModules
10
+ def save_modules
11
+ sound = @sound
12
+ [Snapshot::Keyed.section("BL-MACHINE") { |out| save_machine_fields(out) },
13
+ Snapshot::Keyed.section("BL-CPU") { |out| @cpu.save_fields(out) },
14
+ Snapshot::Keyed.section("BL-RAM") { |out| @bus.save_fields(out) },
15
+ Snapshot::Keyed.section("BL-U2") { |out| @u2.save_fields(out) },
16
+ Snapshot::Keyed.section("BL-U3") { |out| @u3.save_fields(out) },
17
+ Snapshot::Keyed.section("BL-PANEL") { |out| @panel.save_fields(out) },
18
+ Snapshot::Keyed.section("BL-DECK") { |out| @panel.tape.save_fields(out) },
19
+ Snapshot::Keyed.section("BL-TTY") { |out| @teletype.save_fields(out) },
20
+ Snapshot::Keyed.section("BL-SOUND") { |out| out.bool("speaker", sound.speaker) }]
21
+ end
22
+
23
+ # Raises Snapshot::FormatError for the modules of a machine built
24
+ # another way.
25
+ def load_modules(modules)
26
+ machine = modules.machine
27
+ check_snapshot_setup(Snapshot::KIM1Setup.read_fields(machine))
28
+ load_machine_fields(machine)
29
+ @bus.load_fields(modules.required("BL-RAM"))
30
+ @u2.load_fields(modules.required("BL-U2"))
31
+ @u3.load_fields(modules.required("BL-U3"))
32
+ @panel.load_fields(modules.required("BL-PANEL"))
33
+ @panel.tape.load_fields(modules.required("BL-DECK"))
34
+ @teletype.load_fields(modules.required("BL-TTY"))
35
+ @sound.speaker = modules.required("BL-SOUND").bool("speaker", false)
36
+ @cpu.load_fields(modules.required("BL-CPU"))
37
+ @cpu.restored!
38
+ self
39
+ end
40
+
41
+ private
42
+
43
+ def check_snapshot_setup(setup)
44
+ ours = Snapshot::KIM1Setup.of(self)
45
+ return if setup == ours
46
+
47
+ raise Snapshot::FormatError, "the snapshot is of a #{setup}, not a #{ours}"
48
+ end
49
+ end
50
+
51
+ include KeyedModules
52
+ end
53
+ end