badline 0.17.0 → 0.19.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 (195) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +193 -0
  3. data/CLAUDE.md +19 -7
  4. data/CONTRIBUTING.md +11 -1
  5. data/README.md +178 -26
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +169 -1
  9. data/doc/snapshot-format.md +302 -35
  10. data/doc/snapshots.md +97 -26
  11. data/doc/vice.md +344 -0
  12. data/exe/badline-ruby +5 -0
  13. data/lib/badline/audio/renderer.rb +4 -8
  14. data/lib/badline/audio/terminal.rb +34 -2
  15. data/lib/badline/c128/keyed_state.rb +2 -0
  16. data/lib/badline/c128.rb +5 -0
  17. data/lib/badline/c64/bus/fittings.rb +6 -0
  18. data/lib/badline/c64/keyed_state.rb +2 -0
  19. data/lib/badline/c64.rb +7 -0
  20. data/lib/badline/cbm2/keyed_state.rb +4 -0
  21. data/lib/badline/cbm2/p500.rb +9 -3
  22. data/lib/badline/cbm2.rb +10 -3
  23. data/lib/badline/chips/cia/timer.rb +2 -2
  24. data/lib/badline/chips/cpu/core.rb +29 -4
  25. data/lib/badline/chips/cpu/instruction_set.rb +1 -1
  26. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  27. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  28. data/lib/badline/chips/cpu/microcode.rb +10 -0
  29. data/lib/badline/chips/m6809/arithmetic.rb +140 -0
  30. data/lib/badline/chips/m6809/bus_steps.rb +223 -0
  31. data/lib/badline/chips/m6809/core.rb +149 -0
  32. data/lib/badline/chips/m6809/indexed.rb +82 -0
  33. data/lib/badline/chips/m6809/interrupts.rb +159 -0
  34. data/lib/badline/chips/m6809/keyed_state.rb +147 -0
  35. data/lib/badline/chips/m6809/opcodes.rb +159 -0
  36. data/lib/badline/chips/m6809/operations.rb +258 -0
  37. data/lib/badline/chips/m6809/plans.rb +143 -0
  38. data/lib/badline/chips/m6809/registers.rb +82 -0
  39. data/lib/badline/chips/m6809/stack.rb +140 -0
  40. data/lib/badline/chips/m6809.rb +13 -0
  41. data/lib/badline/chips/riot/timer.rb +14 -3
  42. data/lib/badline/chips/rriot.rb +8 -3
  43. data/lib/badline/chips/sid/decimator.rb +1 -1
  44. data/lib/badline/chips/sid/filter.rb +4 -4
  45. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  46. data/lib/badline/chips/sid/waveform.rb +6 -6
  47. data/lib/badline/chips/sid.rb +6 -2
  48. data/lib/badline/chips/vic_i/keyed_parts.rb +12 -2
  49. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  50. data/lib/badline/chips/vic_i.rb +91 -23
  51. data/lib/badline/data_path.rb +3 -3
  52. data/lib/badline/drives/ieee_models.rb +54 -0
  53. data/lib/badline/drives/model1001.rb +37 -0
  54. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  55. data/lib/badline/drives/model1541.rb +1 -0
  56. data/lib/badline/drives/model1551.rb +1 -0
  57. data/lib/badline/drives/model1571.rb +1 -0
  58. data/lib/badline/drives/model1581.rb +1 -0
  59. data/lib/badline/drives/model2031.rb +7 -0
  60. data/lib/badline/drives/model4040/disk.rb +62 -0
  61. data/lib/badline/drives/model4040/ip_bus.rb +46 -0
  62. data/lib/badline/drives/model4040/keyed_state.rb +57 -0
  63. data/lib/badline/drives/model4040/mechanism.rb +195 -0
  64. data/lib/badline/drives/model4040/parts.rb +20 -0
  65. data/lib/badline/drives/model4040.rb +195 -0
  66. data/lib/badline/drives/model8050/board.rb +79 -0
  67. data/lib/badline/drives/model8050/dos_board.rb +134 -0
  68. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  69. data/lib/badline/drives/model8050/idle.rb +148 -31
  70. data/lib/badline/drives/model8050/keyed_state.rb +5 -2
  71. data/lib/badline/drives/model8050.rb +32 -130
  72. data/lib/badline/drives/model9060/idle.rb +169 -0
  73. data/lib/badline/drives/model9060/keyed_state.rb +123 -0
  74. data/lib/badline/drives/model9060/parts.rb +98 -0
  75. data/lib/badline/drives/model9060/sasi_bus.rb +88 -0
  76. data/lib/badline/drives/model9060/sasi_commands.rb +168 -0
  77. data/lib/badline/drives/model9060/sasi_target.rb +161 -0
  78. data/lib/badline/drives/model9060.rb +197 -0
  79. data/lib/badline/frontend/app.rb +8 -0
  80. data/lib/badline/frontend/boot.rb +3 -2
  81. data/lib/badline/frontend/controls.rb +62 -6
  82. data/lib/badline/frontend/keys.rb +36 -0
  83. data/lib/badline/frontend/kim1_panel.rb +208 -0
  84. data/lib/badline/frontend/media_slots.rb +8 -1
  85. data/lib/badline/frontend/menu/drive_types.rb +56 -0
  86. data/lib/badline/frontend/menu/menu_dialogs.rb +61 -14
  87. data/lib/badline/frontend/menu/menu_media.rb +42 -12
  88. data/lib/badline/frontend/menu/menu_pages.rb +97 -2
  89. data/lib/badline/frontend/menu/menu_ports.rb +38 -1
  90. data/lib/badline/frontend/menu/pause_menu.rb +5 -1
  91. data/lib/badline/frontend/pet_keyboard.rb +23 -1
  92. data/lib/badline/frontend/screen.rb +1 -1
  93. data/lib/badline/frontend/screens.rb +40 -2
  94. data/lib/badline/frontend/teletype_pane.rb +152 -0
  95. data/lib/badline/frontend/timeline.rb +31 -2
  96. data/lib/badline/frontend.rb +3 -0
  97. data/lib/badline/input/joystick.rb +15 -2
  98. data/lib/badline/kim1/bus.rb +117 -0
  99. data/lib/badline/kim1/cpu.rb +25 -0
  100. data/lib/badline/kim1/host_terminal.rb +74 -0
  101. data/lib/badline/kim1/key_entry.rb +74 -0
  102. data/lib/badline/kim1/keyed_modules.rb +53 -0
  103. data/lib/badline/kim1/keyed_state.rb +133 -0
  104. data/lib/badline/kim1/panel.rb +176 -0
  105. data/lib/badline/kim1/rriots.rb +28 -0
  106. data/lib/badline/kim1/sound.rb +29 -0
  107. data/lib/badline/kim1/tape.rb +118 -0
  108. data/lib/badline/kim1/teletype.rb +193 -0
  109. data/lib/badline/kim1.rb +194 -0
  110. data/lib/badline/machine/ieee_drives.rb +94 -117
  111. data/lib/badline/machine/keyboard_buffer.rb +20 -3
  112. data/lib/badline/machine/line_sound.rb +122 -0
  113. data/lib/badline/machine.rb +49 -15
  114. data/lib/badline/media/extensions.rb +8 -4
  115. data/lib/badline/media/kim1_media.rb +150 -0
  116. data/lib/badline/media/pet_media.rb +3 -1
  117. data/lib/badline/media/true_drive.rb +52 -45
  118. data/lib/badline/media/vic20_media.rb +4 -2
  119. data/lib/badline/media.rb +16 -6
  120. data/lib/badline/memory.rb +2 -4
  121. data/lib/badline/options/family.rb +98 -8
  122. data/lib/badline/options/help.rb +44 -12
  123. data/lib/badline/options/table.rb +44 -12
  124. data/lib/badline/options/validation.rb +44 -8
  125. data/lib/badline/options.rb +27 -8
  126. data/lib/badline/pet/bus.rb +16 -2
  127. data/lib/badline/pet/keyboard_ports.rb +9 -2
  128. data/lib/badline/pet/keyed_state.rb +4 -0
  129. data/lib/badline/pet/model.rb +11 -2
  130. data/lib/badline/pet/rom_sets.rb +1 -0
  131. data/lib/badline/pet/sound.rb +5 -105
  132. data/lib/badline/pet/superpet/acia.rb +12 -0
  133. data/lib/badline/pet/superpet/board.rb +221 -0
  134. data/lib/badline/pet/superpet/dongle.rb +92 -0
  135. data/lib/badline/pet/superpet/keyed_modules.rb +63 -0
  136. data/lib/badline/pet/superpet/keyed_parts.rb +76 -0
  137. data/lib/badline/pet/superpet/m6809.rb +32 -0
  138. data/lib/badline/pet/superpet/waterloo_traps.rb +199 -0
  139. data/lib/badline/pet/superpet.rb +221 -0
  140. data/lib/badline/pet.rb +23 -3
  141. data/lib/badline/plus4/keyed_state.rb +2 -0
  142. data/lib/badline/plus4.rb +5 -0
  143. data/lib/badline/roms/1001/fdc-251257-02a.rom +0 -0
  144. data/lib/badline/roms/4040/dos-1.2.rom +0 -0
  145. data/lib/badline/roms/4040/dos-2.1-rev.rom +0 -0
  146. data/lib/badline/roms/4040/dos-2.1.rom +0 -0
  147. data/lib/badline/roms/4040/fdc-901466-02.rom +0 -0
  148. data/lib/badline/roms/4040/fdc-901466-04.rom +0 -0
  149. data/lib/badline/roms/9060/dos-3.0.rom +0 -0
  150. data/lib/badline/roms/9060/sasi-300515-revb.rom +0 -0
  151. data/lib/badline/roms/README +176 -0
  152. data/lib/badline/roms/kim1/6530-002.rom +0 -0
  153. data/lib/badline/roms/kim1/6530-003.rom +0 -0
  154. data/lib/badline/roms/pet/characters-superpet.rom +0 -0
  155. data/lib/badline/roms/pet/superpet-a000.rom +0 -0
  156. data/lib/badline/roms/pet/superpet-c000.rom +0 -0
  157. data/lib/badline/roms/pet/superpet-e000-50hz.rom +0 -0
  158. data/lib/badline/roms/pet/superpet-e000.rom +0 -0
  159. data/lib/badline/snapshot/container.rb +8 -4
  160. data/lib/badline/snapshot/field_doc/markdown.rb +1 -1
  161. data/lib/badline/snapshot/field_doc.rb +35 -10
  162. data/lib/badline/snapshot/image.rb +19 -7
  163. data/lib/badline/snapshot/kim1_setup.rb +28 -0
  164. data/lib/badline/snapshot/pet_setup.rb +27 -11
  165. data/lib/badline/snapshot/storage_setup.rb +4 -1
  166. data/lib/badline/snapshot/vic20_setup.rb +11 -7
  167. data/lib/badline/snapshot/vice/pet.rb +14 -7
  168. data/lib/badline/snapshot/vice/pet_cpu.rb +39 -5
  169. data/lib/badline/snapshot/vice/pet_crtc.rb +15 -2
  170. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  171. data/lib/badline/snapshot/vice/pet_drive8050.rb +188 -68
  172. data/lib/badline/snapshot/vice/pet_mem.rb +55 -5
  173. data/lib/badline/snapshot/vice/pet_restore.rb +40 -11
  174. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  175. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  176. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  177. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  178. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  179. data/lib/badline/snapshot/vice.rb +1 -0
  180. data/lib/badline/snapshot.rb +8 -4
  181. data/lib/badline/status.rb +30 -30
  182. data/lib/badline/storage/d67_image.rb +55 -0
  183. data/lib/badline/storage/d90_image.rb +180 -0
  184. data/lib/badline/storage/kim_tape.rb +254 -0
  185. data/lib/badline/storage/papertape.rb +90 -0
  186. data/lib/badline/storage.rb +4 -0
  187. data/lib/badline/support/kim1_checkpoint.rb +41 -0
  188. data/lib/badline/support/superpet_checkpoint.rb +35 -0
  189. data/lib/badline/version.rb +1 -1
  190. data/lib/badline/vic20/keyed_modules.rb +6 -3
  191. data/lib/badline/vic20/keyed_state.rb +6 -4
  192. data/lib/badline/vic20/port_wiring.rb +11 -0
  193. data/lib/badline/vic20.rb +30 -12
  194. data/lib/badline.rb +1 -0
  195. metadata +85 -1
@@ -0,0 +1,199 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class PET
5
+ class SuperPET < PET
6
+ # The traps on the Waterloo 6809 ROM's IEEE-488 routines, which serve
7
+ # device 8 from the virtual drive (KernalTrap::DOS) the 6502's traps
8
+ # serve it from, after the ROM's source as Mike Naberezny hosts it
9
+ # (opsys-all-clean-20101210-src.txt). Other devices fall through to
10
+ # the ROM, which drives the bus.
11
+ #
12
+ # The routines take their argument in D and return with RTS: SetLstnr
13
+ # and SetTalkr a file control block, whose byte 9 is the device, byte
14
+ # 10 the secondary address and byte 0 the status, 3 after a timeout,
15
+ # SetLstnr with the command's offset pushed before the call ($F0 to
16
+ # open, $E0 to close, $60 for data), ByteOut_ the byte to send and
17
+ # the byte-in routine none, returning the byte in D and setting bit 0
18
+ # of the block's byte 12 at EOI. Each keeps the block's address at
19
+ # $02 and the bus at $77-$7A: $20 listening or $40 talking, the
20
+ # offset, the device and the secondary address. A language that hooks
21
+ # a routine through its flag at $0580 gets the ROM's routine, which
22
+ # calls the hook.
23
+ #
24
+ # The menu loads from "disk/1.", drive 1 of a dual drive. The virtual
25
+ # drive has one, so a name on drive 1 opens the file on drive 0.
26
+ class WaterlooTraps
27
+ BYTE_OUT = 0xbe6f
28
+ BYTE_IN = 0xbefb
29
+ SET_LISTENER = 0xc072
30
+ UNLISTEN = 0xc0d0
31
+ SET_TALKER = 0xc0dd
32
+ UNTALK = 0xc13c
33
+
34
+ # The routines' user hook numbers, whose flags sit at $0580 on.
35
+ HOOKS = { BYTE_OUT => 23, BYTE_IN => 24, SET_LISTENER => 27, UNLISTEN => 28, SET_TALKER => 29,
36
+ UNTALK => 30 }.freeze
37
+
38
+ BLOCK = 0x02
39
+ ACTIVE = 0x77
40
+ LISTENING = 0x20
41
+ TALKING = 0x40
42
+ TIMED_OUT = 3
43
+
44
+ OPEN = 0xf0
45
+ CLOSE = 0xe0
46
+
47
+ # The device the traps serve, or nil to serve none.
48
+ attr_accessor :device
49
+
50
+ def initialize(cpu:, bus:, dos:)
51
+ @cpu = cpu
52
+ @bus = bus
53
+ @dos = dos
54
+ @device = KernalTrap::Routine::DEVICE
55
+ @listening = false
56
+ @talking = false
57
+ @channel = 0
58
+ @frame = 0
59
+ @buffer = []
60
+ end
61
+
62
+ def install
63
+ HOOKS.each_key { |address| @cpu.install_trap(address) { call(address) } }
64
+ self
65
+ end
66
+
67
+ def uninstall
68
+ HOOKS.each_key { |address| @cpu.remove_trap(address) }
69
+ end
70
+
71
+ # Whether a served device listens and talks, the channel and the
72
+ # command, and the bytes sent so far, for a checkpoint.
73
+ def state = [@listening ? 1 : 0, @talking ? 1 : 0, @channel, @frame, *@buffer]
74
+
75
+ private
76
+
77
+ def call(address)
78
+ return unless @bus.peek(0x0580 + HOOKS.fetch(address)).zero?
79
+
80
+ case address
81
+ when SET_LISTENER then set_listener
82
+ when BYTE_OUT then byte_out
83
+ when UNLISTEN then unlisten
84
+ when SET_TALKER then set_talker
85
+ when BYTE_IN then byte_in
86
+ else untalk
87
+ end
88
+ end
89
+
90
+ def served?(block) = !@device.nil? && @bus.peek(block + 9) == @device
91
+
92
+ def set_listener
93
+ block = @cpu.d
94
+ return unless served?(block)
95
+
96
+ offset = @bus.peek(@cpu.s + 3)
97
+ select(block, LISTENING, offset)
98
+ @listening = true
99
+ @frame = offset & 0xf0
100
+ @buffer = []
101
+ @dos.close(@channel) if @frame == CLOSE
102
+ return_from_routine
103
+ end
104
+
105
+ def set_talker
106
+ block = @cpu.d
107
+ return unless served?(block)
108
+
109
+ select(block, TALKING, 0x60)
110
+ @talking = true
111
+ return_from_routine
112
+ end
113
+
114
+ # Ends what the bus was doing, then addresses the block's device as
115
+ # the routine does.
116
+ def select(block, active, offset)
117
+ finish_listening if @listening
118
+ @talking = false
119
+ @bus.poke(BLOCK, block >> 8)
120
+ @bus.poke(BLOCK + 1, block & 0xff)
121
+ @bus.poke(ACTIVE, active)
122
+ @bus.poke(ACTIVE + 1, offset)
123
+ @bus.poke(ACTIVE + 2, @bus.peek(block + 9))
124
+ secondary = @bus.peek(block + 10)
125
+ @bus.poke(ACTIVE + 3, secondary)
126
+ @channel = secondary & 0x0f
127
+ end
128
+
129
+ def byte_out
130
+ return unless @listening
131
+
132
+ block = current_block
133
+ if @bus.peek(block).zero?
134
+ @buffer << (@cpu.d & 0xff)
135
+ @bus.poke(block, TIMED_OUT) unless accepted?
136
+ end
137
+ @cpu.d = 0x00ff
138
+ return_from_routine
139
+ end
140
+
141
+ def accepted? = [OPEN, CLOSE].include?(@frame) || @dos.listening?(@channel)
142
+
143
+ def unlisten
144
+ return unless @listening
145
+
146
+ finish_listening
147
+ @bus.poke(ACTIVE, 0)
148
+ return_from_routine
149
+ end
150
+
151
+ def finish_listening
152
+ @listening = false
153
+ case @frame
154
+ when OPEN then @dos.open(@channel, on_drive_zero(@buffer.pack("C*")))
155
+ when CLOSE then nil
156
+ else @dos.write(@channel, @buffer)
157
+ end
158
+ @buffer = []
159
+ end
160
+
161
+ def on_drive_zero(name) = name.start_with?("1:") ? "0:#{name[2..]}" : name
162
+
163
+ def byte_in
164
+ return unless @talking
165
+
166
+ block = current_block
167
+ byte = 0x0d
168
+ if @bus.peek(block).zero?
169
+ read, eoi = @dos.read(@channel)
170
+ if read.nil?
171
+ @bus.poke(block, TIMED_OUT)
172
+ else
173
+ byte = read
174
+ @bus.poke(block + 12, @bus.peek(block + 12) | 0x01) if eoi
175
+ end
176
+ end
177
+ @cpu.d = byte
178
+ return_from_routine
179
+ end
180
+
181
+ def untalk
182
+ return unless @talking
183
+
184
+ @talking = false
185
+ @bus.poke(ACTIVE, 0)
186
+ return_from_routine
187
+ end
188
+
189
+ def current_block = (@bus.peek(BLOCK) << 8) | @bus.peek(BLOCK + 1)
190
+
191
+ def return_from_routine
192
+ s = @cpu.s
193
+ @cpu.pc = (@bus.peek(s) << 8) | @bus.peek(s + 1)
194
+ @cpu.s = (s + 2) & 0xffff
195
+ end
196
+ end
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,221 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/chips/acia"
4
+ require "badline/chips/m6809"
5
+ require "badline/pet/superpet/dongle"
6
+ require "badline/pet/superpet/acia"
7
+ require "badline/pet/superpet/board"
8
+ require "badline/pet/superpet/m6809"
9
+ require "badline/pet/superpet/waterloo_traps"
10
+ require "badline/pet/superpet/keyed_parts"
11
+ require "badline/pet/superpet/keyed_modules"
12
+
13
+ module Badline
14
+ class PET
15
+ # The SuperPET, the SP9000 or MMF 9000: an 8032 with the University of
16
+ # Waterloo's board in its CPU socket, with 64K of bank RAM, a 6551
17
+ # ACIA, the 6702 and a 6809E (Board). The board's ACIA pulls the PET's
18
+ # IRQ line with the PIAs and the VIA. Its character ROM, 901640-01,
19
+ # holds the 8032's in its first half and an ASCII and an APL set in
20
+ # its second, which the CRTC's MA13 picks.
21
+ #
22
+ # Both CPUs sit on the same bus at the same 1 MHz, and one runs at a
23
+ # time: SW2, the CPU switch, picks it, or with SW2 at PROG bit 0 of the
24
+ # system latch. The one that takes the bus over starts from its RESET
25
+ # (Fachat: "The 6502 does a RESET after being switched on"; on the
26
+ # 324037 schematic one-shots gate the 6809's RES), and the other stops
27
+ # where it is. The PET's IRQ line, the ACIA's with it, reaches both.
28
+ #
29
+ # The Waterloo ROMs hold only the menu, setup and the monitor. The
30
+ # menu loads every language, microBASIC, microAPL, FORTRAN, Pascal,
31
+ # COBOL, the editor and the development package, from disk into RAM,
32
+ # so 6809 mode needs a disk on device 8 to run one.
33
+ #
34
+ # In 6809 mode device 8 answers through traps on the Waterloo ROM's
35
+ # IEEE-488 routines (WaterlooTraps) as well as the KERNAL's, from the
36
+ # same disk, or a true drive on the bus answers. type_text types
37
+ # through the Waterloo ROM's key buffer, in ASCII, once its menu is up.
38
+ class SuperPET < PET
39
+ # The Waterloo ROM's key buffer: its read and write pointers, and the
40
+ # ring of bytes they run round.
41
+ KEYS_READ = 0x012c
42
+ KEYS_WRITE = 0x012e
43
+ KEYS_FIRST = 0x0130
44
+ KEYS_END = 0x0158
45
+
46
+ # The cycle by which the 6809 has put up the Waterloo menu, with a
47
+ # margin.
48
+ WATERLOO_BOOT = 600_000
49
+
50
+ attr_reader :board, :acia, :dongle, :m6809
51
+
52
+ # The traps on the Waterloo ROM's IEEE-488 routines, while a disk is
53
+ # on device 8 (WaterlooTraps).
54
+ attr_reader :waterloo_traps
55
+
56
+ # +cpu+ is SW2's position and +bank_ram+ SW1's, as Board names them.
57
+ # The other arguments are the PET's for an 8032, its true drive's too.
58
+ def initialize(refresh_hz: 50, debug: false, basic: nil, editor: nil, cpu: "6502", bank_ram: "rw",
59
+ true_drive_model: Drives::IEEEModels::DEFAULT)
60
+ raise ArgumentError, "the CPU switch has no position #{cpu}" unless Board::CPU_SWITCH.include?(cpu)
61
+
62
+ super(model: "superpet", refresh_hz:, debug:, basic:, editor:, true_drive_model:)
63
+ @acia_irq = false
64
+ @acia_due = Chips::ACIA::NEVER
65
+ @acia = ACIA.new(self, 1_000_000)
66
+ @dongle = Dongle.new(self)
67
+ @board = Board.new(@bus, dongle: @dongle, acia: @acia, keyboard_ports: @keyboard_ports, cpu_switch: cpu,
68
+ ram_switch: bank_ram, refresh_hz: @refresh_hz)
69
+ @m6809 = M6809.new(@bus)
70
+ @m6809_running = @board.m6809?
71
+ @waterloo_traps = nil
72
+ @board.on_select { cpu_selected }
73
+ @bus.fit_board(@board)
74
+ @init_threshold = WATERLOO_BOOT if @m6809_running
75
+ end
76
+
77
+ # Whether an autostart finds BASIC: not while the 6809 runs.
78
+ def boots_basic? = !@m6809_running
79
+
80
+ # Device 8 through the traps, for the Waterloo ROM's routines too.
81
+ def mount(storage)
82
+ trap = super
83
+ @waterloo_traps ||= WaterlooTraps.new(cpu: @m6809, bus: @bus, dos: @dos).install
84
+ @waterloo_traps.device = trap_device
85
+ trap
86
+ end
87
+
88
+ def unmount
89
+ super
90
+ @waterloo_traps&.uninstall
91
+ @waterloo_traps = nil
92
+ end
93
+
94
+ def attach_ieee_drive(drive)
95
+ super
96
+ @waterloo_traps&.device = trap_device
97
+ end
98
+
99
+ def detach_ieee_drive
100
+ super
101
+ @waterloo_traps&.device = trap_device
102
+ end
103
+
104
+ # Types into the Waterloo ROM's key buffer, in ASCII, while the 6809
105
+ # runs, and into the editor's otherwise.
106
+ def type_text(text)
107
+ return super unless @m6809_running
108
+
109
+ type_on_init(text.bytes)
110
+ end
111
+
112
+ # Whether the 6809 has the bus, rather than the 6502.
113
+ def m6809_running? = @m6809_running
114
+
115
+ def superpet? = true
116
+
117
+ # Moves the CPU switch to +position+, one of Board::CPU_SWITCH, and
118
+ # resets the machine, so the CPU the switch picks starts from its
119
+ # RESET.
120
+ def switch_cpu(position)
121
+ @board.cpu_switch = position
122
+ reset!
123
+ end
124
+
125
+ # Moves the bank RAM switch to +position+, one of Board::RAM_SWITCH.
126
+ def switch_bank_ram(position)
127
+ @board.ram_switch = position
128
+ end
129
+
130
+ # Whether the picture shows the character ROM's APL set: its second
131
+ # half, which the CRTC's MA13 picks, with VIA CA2 high.
132
+ def apl_characters? = @crtc.registers[12].anybits?(0x20) && @via.ca2_output
133
+
134
+ # Drive 1 while the 6809 runs, where the Waterloo menu's "disk/1."
135
+ # looks, and drive 0 in 6502 mode.
136
+ def disk_drive_number = @m6809_running ? 1 : 0
137
+
138
+ # The 32K below $8000 and the 64K board.
139
+ def model_label = "SUPERPET, 96K RAM, #{@refresh_hz} HZ"
140
+
141
+ # The PET's cycle, with the ACIA run when it falls due and its IRQ
142
+ # on the CPU's.
143
+ def cycle!
144
+ handle_init if @cycles == @init_threshold
145
+ feed_keyboard if @pending_keys
146
+
147
+ @crtc.cycle!
148
+ drive_retrace(!@crtc.vsync?)
149
+ @pia1.cycle!
150
+ @pia2.cycle!
151
+ @via.cycle!
152
+ @sound.cycle! if @sound.recording?
153
+ @cassettes.cycle! if @tapes_running
154
+ @acia.run!(@cycles) if @cycles >= @acia_due
155
+ irq = @pia1.irq? || @pia2.irq? || @via.irq? || @acia_irq
156
+ if @m6809_running
157
+ @m6809.irq = irq
158
+ @m6809.cycle!
159
+ else
160
+ @cpu.irq = irq
161
+ @cpu.cycle!
162
+ end
163
+ clock_ieee_drives
164
+
165
+ @cycles += 1
166
+ end
167
+
168
+ # Whether the ACIA pulls the IRQ line.
169
+ def acia_irq? = @acia_irq
170
+
171
+ # The ACIA's IRQ line, and the cycle it next has something to do at.
172
+ def acia_changed(irq, due)
173
+ @acia_irq = irq
174
+ @acia_due = due
175
+ end
176
+
177
+ # The RES line reaches the board's latches, the 6702, the ACIA and
178
+ # both CPUs too.
179
+ def reset!
180
+ super
181
+ @acia.reset!
182
+ @m6809.reset!
183
+ @board.reset!
184
+ end
185
+
186
+ def power_cycle!
187
+ @board.power_on!
188
+ super
189
+ end
190
+
191
+ private
192
+
193
+ # The Waterloo ROM's ring takes keys until its write pointer would
194
+ # meet its read pointer.
195
+ def feed_keyboard
196
+ return super unless @m6809_running
197
+
198
+ read = word_at(KEYS_READ)
199
+ write = word_at(KEYS_WRITE)
200
+ until @pending_keys.empty?
201
+ following = write + 1 == KEYS_END ? KEYS_FIRST : write + 1
202
+ break if following == read
203
+
204
+ @bus.poke(write, @pending_keys.shift)
205
+ write = following
206
+ end
207
+ @bus.poke(KEYS_WRITE, write >> 8)
208
+ @bus.poke(KEYS_WRITE + 1, write & 0xff)
209
+ @pending_keys = nil if @pending_keys.empty?
210
+ end
211
+
212
+ def word_at(addr) = (@bus.peek(addr) << 8) | @bus.peek(addr + 1)
213
+
214
+ # The CPU the board picks takes the bus over from its RESET.
215
+ def cpu_selected
216
+ @m6809_running = @board.m6809?
217
+ @m6809_running ? @m6809.reset! : @cpu.reset!
218
+ end
219
+ end
220
+ end
221
+ end
data/lib/badline/pet.rb CHANGED
@@ -75,6 +75,8 @@ module Badline
75
75
 
76
76
  def family = :pet
77
77
 
78
+ def portless_board = nil
79
+
78
80
  # The datasette on cassette port #1, where a tape goes in (Cassettes).
79
81
  def datasette = @cassettes.datasette1
80
82
 
@@ -83,10 +85,13 @@ module Badline
83
85
  # model runs its 50 Hz editor, or its 60 Hz one for +refresh_hz+ 60.
84
86
  # +basic+, +editor+ and +characters+ name ROMs the board takes in place
85
87
  # of its own (Model.upgraded, Model.fitted), and +jumpers+ the 8296's
86
- # board jumpers (ROMSets::JUMPERS).
88
+ # board jumpers (ROMSets::JUMPERS). +true_drive_model+ names the drive
89
+ # plug_true_drive plugs in (Drives::IEEEModels).
87
90
  def initialize(model: Model::DEFAULT, ram_kb: nil, refresh_hz: 50, debug: false, basic: nil, editor: nil,
88
- characters: nil, jumpers: [])
91
+ characters: nil, jumpers: [], true_drive_model: Drives::IEEEModels::DEFAULT)
89
92
  @model = Model.fitted(Model.upgraded(Model.named(model), basic, editor), characters)
93
+ raise ArgumentError, "the SuperPET is built as PET::SuperPET" if @model.superpet? && !superpet?
94
+
90
95
  ram_kb ||= @model.ram_kb
91
96
  raise ArgumentError, "the #{@model.name} has no #{ram_kb}K RAM board" unless Model.ram_fits?(@model, ram_kb)
92
97
 
@@ -99,6 +104,7 @@ module Badline
99
104
  matrix = @model.keyboard == :business ? KeyboardPorts::BUSINESS_MATRIX : KeyboardPorts::GRAPHICS_MATRIX
100
105
  @keyboard = Input::Keyboard.new(matrix:)
101
106
  keyboard_ports = KeyboardPorts.new(@keyboard)
107
+ @keyboard_ports = keyboard_ports
102
108
  registers = Array.new(Chips::CRTC::MASKS.length, 0)
103
109
  @pia1 = Chips::PIA.new(peripheral: keyboard_ports)
104
110
  @ieee = IEEEPorts.new(Drives::IEEEBus.new)
@@ -125,16 +131,27 @@ module Badline
125
131
  ieee.lines_written
126
132
  end
127
133
  @cpu = CPU.new(@bus, debug:)
134
+ @cpu.power_on!
128
135
  @cycles = 0
129
136
  @init_handlers = []
130
137
  @pending_keys = nil
138
+ @init_keys = nil
131
139
  @dos = nil
132
140
  @ieee_trap = nil
133
141
  @capture_output = nil
134
- init_ieee_drives
142
+ init_ieee_drives(true_drive_model)
135
143
  @init_threshold = boot_cycles(ram_kb)
136
144
  end
137
145
 
146
+ # Whether this is the SuperPET's board (SuperPET).
147
+ def superpet? = false
148
+
149
+ # Whether the picture shows the SuperPET's APL characters.
150
+ def apl_characters? = false
151
+
152
+ # Whether an autostart finds BASIC to type into.
153
+ def boots_basic? = true
154
+
138
155
  # The vertical drive line, high but in the retrace.
139
156
  def vertical_drive? = @vertical_drive
140
157
 
@@ -208,6 +225,7 @@ module Badline
208
225
  def power_cycle!
209
226
  @bus.power_on!
210
227
  reset!
228
+ @cpu.power_on!
211
229
  end
212
230
 
213
231
  # Puts a program file's bytes where its first two say, through the bus,
@@ -253,3 +271,5 @@ module Badline
253
271
  end
254
272
  end
255
273
  end
274
+
275
+ require "badline/pet/superpet"
@@ -28,6 +28,7 @@ module Badline
28
28
  out.int("cycles", @cycles)
29
29
  pending = @pending_keys
30
30
  out.ints("host.pending_keys", pending) if pending
31
+ save_init_keys(out)
31
32
  out.int("host.init_pending", booting? ? @init_handlers.length : 0)
32
33
  out.text("host.true_drive_model", @true_drive_model)
33
34
  end
@@ -36,6 +37,7 @@ module Badline
36
37
  @cycles = input.int("cycles", @cycles)
37
38
  pending = input.ints("host.pending_keys", [])
38
39
  @pending_keys = pending.empty? ? nil : pending.dup
40
+ load_init_keys(input)
39
41
  @init_handlers_lost = [input.int("host.init_pending", 0) - @init_handlers.length, 0].max
40
42
  @true_drive_model = input.choice("host.true_drive_model", TCBMDrive::TRUE_DRIVE_MODELS, @true_drive_model)
41
43
  end
data/lib/badline/plus4.rb CHANGED
@@ -67,6 +67,8 @@ module Badline
67
67
 
68
68
  def family = :plus4
69
69
 
70
+ def portless_board = nil
71
+
70
72
  # The joysticks reach the TED's keyboard latch, so there are no C64
71
73
  # control ports to plug a pot device into.
72
74
  def control_ports = nil
@@ -139,11 +141,13 @@ module Badline
139
141
  @tias = tcbm ? [Chips::TIA.new, Chips::TIA.new] : []
140
142
  @bus.plug_tcbm(drive8: @tias[0], drive9: @tias[1]) if tcbm
141
143
  @cpu = CPU.new(@bus, debug:)
144
+ @cpu.power_on!
142
145
  @sound = Chips::TED::Sound.new(self, @model.ted.clock_hz)
143
146
  @ted.sound = @sound
144
147
  @sound_mix = @sid_card ? SoundMix.new(@sound, @sid_card) : nil
145
148
  @init_handlers = []
146
149
  @pending_keys = nil
150
+ @init_keys = nil
147
151
  @dos = nil
148
152
  @serial_trap = nil
149
153
  @save_trap = nil
@@ -201,6 +205,7 @@ module Badline
201
205
  @ted.power_on!
202
206
  @sound.power_on!
203
207
  reset!
208
+ @cpu.power_on!
204
209
  end
205
210
 
206
211
  # The ACIA's IRQ line, and the cycle it next has something to do at.
Binary file
Binary file
Binary file