badline 0.16.0 → 0.17.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 (164) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +76 -0
  3. data/CLAUDE.md +1 -1
  4. data/README.md +130 -17
  5. data/doc/media.md +14 -2
  6. data/doc/pinned-behaviour.md +61 -0
  7. data/doc/snapshot-format.md +379 -70
  8. data/doc/snapshots.md +185 -10
  9. data/lib/badline/cbm2/acia.rb +10 -0
  10. data/lib/badline/cbm2/bus.rb +263 -0
  11. data/lib/badline/cbm2/cpu.rb +58 -0
  12. data/lib/badline/cbm2/crtc_video.rb +133 -0
  13. data/lib/badline/cbm2/ieee_drive.rb +18 -0
  14. data/lib/badline/cbm2/ieee_layout.rb +69 -0
  15. data/lib/badline/cbm2/ieee_ports.rb +104 -0
  16. data/lib/badline/cbm2/ieee_traps.rb +81 -0
  17. data/lib/badline/cbm2/keyboard_ports.rb +60 -0
  18. data/lib/badline/cbm2/keyed_modules.rb +67 -0
  19. data/lib/badline/cbm2/keyed_state.rb +91 -0
  20. data/lib/badline/cbm2/model.rb +44 -0
  21. data/lib/badline/cbm2/p500/control_ports.rb +80 -0
  22. data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
  23. data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
  24. data/lib/badline/cbm2/p500/model.rb +39 -0
  25. data/lib/badline/cbm2/p500/pages.rb +57 -0
  26. data/lib/badline/cbm2/p500.rb +262 -0
  27. data/lib/badline/cbm2/sid_page.rb +19 -0
  28. data/lib/badline/cbm2.rb +267 -0
  29. data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
  30. data/lib/badline/chips/acia/keyed_state.rb +47 -0
  31. data/lib/badline/chips/acia.rb +307 -0
  32. data/lib/badline/chips/crtc.rb +3 -2
  33. data/lib/badline/chips/riot/fast_forward.rb +72 -0
  34. data/lib/badline/chips/riot/keyed_parts.rb +69 -0
  35. data/lib/badline/chips/riot/timer.rb +80 -0
  36. data/lib/badline/chips/riot.rb +162 -0
  37. data/lib/badline/chips/rriot/keyed_state.rb +30 -0
  38. data/lib/badline/chips/rriot.rb +132 -0
  39. data/lib/badline/chips/tpi/keyed_state.rb +38 -0
  40. data/lib/badline/chips/tpi.rb +233 -0
  41. data/lib/badline/chips/vic/bank.rb +60 -5
  42. data/lib/badline/chips/vic/sprite.rb +4 -3
  43. data/lib/badline/drives/datasette/keyed_state.rb +9 -1
  44. data/lib/badline/drives/datasette.rb +27 -3
  45. data/lib/badline/drives/ieee_bus.rb +50 -4
  46. data/lib/badline/drives/model2031/ieee_port.rb +133 -0
  47. data/lib/badline/drives/model2031/keyed_state.rb +66 -0
  48. data/lib/badline/drives/model2031/parts.rb +25 -0
  49. data/lib/badline/drives/model2031.rb +92 -0
  50. data/lib/badline/drives/model8050/disk.rb +163 -0
  51. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  52. data/lib/badline/drives/model8050/idle.rb +177 -0
  53. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  54. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  55. data/lib/badline/drives/model8050/keyed_state.rb +160 -0
  56. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  57. data/lib/badline/drives/model8050/parts.rb +37 -0
  58. data/lib/badline/drives/model8050.rb +243 -0
  59. data/lib/badline/frontend/app.rb +10 -3
  60. data/lib/badline/frontend/boot.rb +1 -1
  61. data/lib/badline/frontend/controls.rb +51 -15
  62. data/lib/badline/frontend/gamepads.rb +3 -3
  63. data/lib/badline/frontend/keys.rb +61 -2
  64. data/lib/badline/frontend/media_slots.rb +1 -1
  65. data/lib/badline/frontend/menu/menu_media.rb +14 -11
  66. data/lib/badline/frontend/menu/menu_pages.rb +29 -6
  67. data/lib/badline/frontend/menu/menu_ports.rb +40 -4
  68. data/lib/badline/frontend/menu/pause_menu.rb +1 -1
  69. data/lib/badline/frontend/pet_keyboard.rb +23 -7
  70. data/lib/badline/frontend/timeline.rb +19 -10
  71. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  72. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  73. data/lib/badline/kernal_trap/dos.rb +18 -5
  74. data/lib/badline/kernal_trap/ieee.rb +7 -2
  75. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  76. data/lib/badline/machine/ieee_drives.rb +194 -0
  77. data/lib/badline/machine/settling.rb +4 -0
  78. data/lib/badline/machine.rb +26 -8
  79. data/lib/badline/media/cbm2_media.rb +29 -0
  80. data/lib/badline/media/pet_media.rb +5 -5
  81. data/lib/badline/media/program.rb +6 -3
  82. data/lib/badline/media/true_drive.rb +50 -8
  83. data/lib/badline/media.rb +21 -8
  84. data/lib/badline/options/family.rb +15 -3
  85. data/lib/badline/options/help.rb +24 -6
  86. data/lib/badline/options/table.rb +25 -5
  87. data/lib/badline/options/validation.rb +47 -7
  88. data/lib/badline/options.rb +48 -15
  89. data/lib/badline/pet/bus/expansion.rb +99 -5
  90. data/lib/badline/pet/bus.rb +30 -12
  91. data/lib/badline/pet/cassettes.rb +22 -1
  92. data/lib/badline/pet/characters.rb +8 -2
  93. data/lib/badline/pet/crtc_video.rb +20 -7
  94. data/lib/badline/pet/ieee_drive.rb +17 -0
  95. data/lib/badline/pet/ieee_ports.rb +19 -1
  96. data/lib/badline/pet/ieee_traps.rb +10 -5
  97. data/lib/badline/pet/keyed_modules.rb +4 -1
  98. data/lib/badline/pet/keyed_parts.rb +10 -6
  99. data/lib/badline/pet/model.rb +56 -6
  100. data/lib/badline/pet/rom_sets.rb +30 -6
  101. data/lib/badline/pet.rb +45 -16
  102. data/lib/badline/plus4/acia.rb +4 -298
  103. data/lib/badline/plus4/keyed_parts.rb +0 -44
  104. data/lib/badline/plus4.rb +1 -0
  105. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  106. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  107. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  108. data/lib/badline/roms/README +144 -1
  109. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  110. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  111. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  112. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  113. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  114. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  115. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  116. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  117. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  118. data/lib/badline/roms/dos2031.rom +0 -0
  119. data/lib/badline/roms/pet/characters-din.rom +0 -0
  120. data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
  121. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  122. data/lib/badline/snapshot/container.rb +9 -1
  123. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  124. data/lib/badline/snapshot/field_doc.rb +39 -3
  125. data/lib/badline/snapshot/image.rb +79 -15
  126. data/lib/badline/snapshot/pet_setup.rb +30 -13
  127. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  128. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  129. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  130. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  131. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  132. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  133. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  134. data/lib/badline/snapshot/vice/cias.rb +13 -9
  135. data/lib/badline/snapshot/vice/p500.rb +96 -0
  136. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  137. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  138. data/lib/badline/snapshot/vice/pet.rb +121 -0
  139. data/lib/badline/snapshot/vice/pet_cpu.rb +70 -0
  140. data/lib/badline/snapshot/vice/pet_crtc.rb +241 -0
  141. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  142. data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
  143. data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
  144. data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
  145. data/lib/badline/snapshot/vice/pet_restore.rb +214 -0
  146. data/lib/badline/snapshot/vice/pet_via.rb +165 -0
  147. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  148. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  149. data/lib/badline/snapshot/vice.rb +4 -0
  150. data/lib/badline/snapshot.rb +11 -3
  151. data/lib/badline/storage/crt_file.rb +7 -1
  152. data/lib/badline/storage/d64_image.rb +7 -0
  153. data/lib/badline/storage/d80_image.rb +17 -1
  154. data/lib/badline/storage/d81_image.rb +14 -0
  155. data/lib/badline/storage/disk_image/bam.rb +18 -3
  156. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  157. data/lib/badline/storage/disk_image/writing.rb +35 -0
  158. data/lib/badline/storage/disk_image.rb +2 -0
  159. data/lib/badline/storage.rb +1 -0
  160. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  161. data/lib/badline/support/p500_checkpoint.rb +35 -0
  162. data/lib/badline/version.rb +1 -1
  163. data/lib/badline.rb +1 -0
  164. metadata +87 -2
@@ -20,7 +20,17 @@ module Badline
20
20
 
21
21
  # The PETs and CBMs, as PET::Model names them, the 8032 first, the
22
22
  # default.
23
- PET_MODELS = %w[8032 8096 4032 3032 3032b 2001].freeze
23
+ PET_MODELS = %w[8032 8096 8296 4032 3032 3032b 2001].freeze
24
+
25
+ # The CBM-II's B series, as CBM2::Model names them, the 610 first, the
26
+ # default, and the P500, as CBM2::P500::Model names them.
27
+ CBM2_MODELS = %w[610 620 710 720 510 510ntsc].freeze
28
+
29
+ # The B series' KERNALs --kernal names, 901244-04a first, the default.
30
+ CBM2_KERNALS = %w[04a 03b].freeze
31
+
32
+ # The P500's one KERNAL, 901234-02.
33
+ P500_KERNALS = %w[02].freeze
24
34
 
25
35
  # The true drives --drive-type names for the Plus/4.
26
36
  PLUS4_DRIVE_TYPES = %w[1541 1551].freeze
@@ -47,7 +57,8 @@ module Badline
47
57
  section: "Window options", needs: :window),
48
58
  Option.new("--no-sound", "Don't play the SID", section: "Window options", needs: :window, build: :native),
49
59
  Option.new("--true-drive", "Put a true 1541 on device 8 instead of the KERNAL traps, a 1571 on the C128, " \
50
- "a 1551 on the Plus/4",
60
+ "a 1551 on the Plus/4, a 2031 on the PET or the CBM-II, or for a .d80 or .d82 " \
61
+ "an 8050 or 8250",
51
62
  section: "Window options", needs: :window),
52
63
  Option.new("--drive-type MODEL", "The Plus/4's true drive: 1541 or 1551 (default: 1551); implies --true-drive",
53
64
  section: "Window options", needs: :window),
@@ -55,7 +66,7 @@ module Badline
55
66
  section: "Window options", needs: :window),
56
67
  Option.new("--model NAME", "C64 to run: #{MODELS.join(', ')} (default: #{MODELS.first}); VIC-20: pal; " \
57
68
  "C128: #{C128_MODELS.join(', ')}; Plus/4: #{PLUS4_MODELS.join(', ')}; " \
58
- "PET: #{PET_MODELS.join(', ')}",
69
+ "PET: #{PET_MODELS.join(', ')}; CBM-II: #{CBM2_MODELS.join(', ')}",
59
70
  section: "Window options", needs: :window),
60
71
  Option.new("--ntsc", "Run an NTSC C64, as --model ntsc does", section: "Window options", needs: :window),
61
72
  Option.new("--c64", "Start the C128 in C64 mode, holding C= at power-on",
@@ -63,13 +74,22 @@ module Badline
63
74
  Option.new("--ram SIZE", "VIC-20 RAM expansion: #{RAM_NAMES.join(', ')} (default: unexpanded); " \
64
75
  "Plus/4 RAM: 16k, 32k or 64k; PET RAM: 4k, 8k, 16k or 32k (default: the model's own)",
65
76
  section: "Window options", needs: :window),
66
- Option.new("--60hz", "Run a CRTC PET's 60 Hz editor instead of its 50 Hz one",
77
+ Option.new("--60hz", "Run a CRTC PET's 60 Hz editor instead of its 50 Hz one, or a CBM-II on 60 Hz mains",
78
+ section: "Window options", needs: :window),
79
+ Option.new("--kernal VERSION", "Fit a CBM-II with KERNAL #{CBM2_KERNALS.join(' or ')} " \
80
+ "(default: #{CBM2_KERNALS.first}); the P500 takes #{P500_KERNALS.first}",
67
81
  section: "Window options", needs: :window),
68
82
  Option.new("--basic VERSION", "Fit a PET with BASIC 1, 2 or 4, as its board takes (default: its own)",
69
83
  section: "Window options", needs: :window),
70
84
  Option.new("--editor NAME", "Fit a PET with an editor ROM its board and BASIC take: " \
71
85
  "#{PET::ROMSets::EDITORS.keys.join(', ')}",
72
86
  section: "Window options", needs: :window),
87
+ Option.new("--characters NAME", "Fit an 8296 with a character ROM: ascii or din (default: ascii)",
88
+ section: "Window options", needs: :window),
89
+ Option.new("--jumpers LIST", "Fit an 8296's board with jumpers, comma-separated: ram9 and rama for RAM at " \
90
+ "$9000 and $A000, userport for the user port's PA0-PA2 to switch in " \
91
+ "the RAM under the ROMs",
92
+ section: "Window options", needs: :window),
73
93
  Option.new("--c1lo FILE", "Put a .bin ROM image in the Plus/4's cartridge 1, low slot ($8000)",
74
94
  section: "Window options", needs: :window),
75
95
  Option.new("--c1hi FILE", "Put a .bin ROM image in cartridge 1's high slot ($C000)",
@@ -112,7 +132,7 @@ module Badline
112
132
  section: "Testing options", needs: :window),
113
133
  Option.new("--save-snapshot FILE", "Save the machine as a .vsf snapshot after the last frame",
114
134
  section: "Testing options", needs: :window),
115
- Option.new("--vice", "Add VICE's modules to --save-snapshot's C64 snapshot, for x64sc",
135
+ Option.new("--vice", "Add VICE's modules to --save-snapshot's C64, PET or CBM-II snapshot",
116
136
  section: "Testing options", needs: :window),
117
137
  Option.new("--at FRAME:EVENT", "Run EVENT once FRAME frames have run (see above)",
118
138
  section: "Testing options", needs: :window),
@@ -57,6 +57,12 @@ module Badline
57
57
  value
58
58
  end
59
59
 
60
+ def kernal_for(value)
61
+ raise Error, "invalid argument: --kernal #{value}" unless (CBM2_KERNALS + P500_KERNALS).include?(value)
62
+
63
+ value
64
+ end
65
+
60
66
  def reu_size(value)
61
67
  raise Error, "invalid argument: --reu #{value}" unless REU_SIZES.include?(value)
62
68
 
@@ -102,6 +108,7 @@ module Badline
102
108
  validate_vic20 if @family == :vic20
103
109
  @family == :plus4 ? validate_plus4 : refuse_plus4_options
104
110
  @family == :pet ? validate_pet : refuse_pet_options
111
+ @family == :cbm2 ? validate_cbm2 : refuse_cbm2_options
105
112
  if @family == :c128
106
113
  validate_c128
107
114
  elsif @c64_mode
@@ -170,24 +177,40 @@ module Badline
170
177
  raise Error, "--sid-card-address needs --sid-card" unless @sid_card_address.nil? || @sid_card
171
178
  end
172
179
 
173
- # Only the PET has a frame rate to pick and BASIC and editor ROMs to
174
- # fit.
180
+ # Only the PET has BASIC and editor ROMs to fit, and only it and the
181
+ # CBM-II a frame rate to pick.
175
182
  def refuse_pet_options
176
- raise Error, "--60hz needs pet" unless @refresh_hz.nil?
183
+ raise Error, "--60hz needs pet or cbm2" unless @refresh_hz.nil? || @family == :cbm2
177
184
  raise Error, "--basic needs pet" unless @basic.nil?
178
185
  raise Error, "--editor needs pet" unless @editor.nil?
186
+ raise Error, "--characters needs pet" unless @characters.nil?
187
+ raise Error, "--jumpers needs pet" unless @jumpers.empty?
179
188
  end
180
189
 
181
- # The PET runs in the window, without the C64's SID and REU or a true
182
- # drive, at the frame rate its editor sets.
190
+ # The PET runs in the window, without the C64's SID and REU, at the
191
+ # frame rate its editor sets.
183
192
  def validate_pet
184
193
  raise Error, "--ntsc needs the C64; a CRTC PET's 60 Hz editor is --60hz" if @models.include?("ntsc")
185
194
  raise Error, "--sid needs the C64" unless @sid_model.nil?
186
195
  raise Error, "--reu needs the C64" unless @reu.nil?
187
- raise Error, "the PET has no true drive yet" if @true_drive
188
196
  raise Error, "the PET needs the window" if headless?
189
197
 
190
- validate_pet_roms if PET_MODELS.include?(model)
198
+ return unless PET_MODELS.include?(model)
199
+
200
+ validate_pet_roms
201
+ validate_pet_board
202
+ end
203
+
204
+ # --characters names a character ROM the board takes, --jumpers the
205
+ # 8296 board's jumpers, and the 8296 has all of its RAM soldered on.
206
+ def validate_pet_board
207
+ unless @characters.nil? || PET::ROMSets.characters(model).include?(@characters)
208
+ raise Error, "the #{model} takes no character ROM #{@characters}"
209
+ end
210
+
211
+ unknown = @jumpers.find { |jumper| !PET::ROMSets.jumpers(model).include?(jumper) }
212
+ raise Error, "the #{model} has no jumper #{unknown}" unless unknown.nil?
213
+ raise Error, "the 8296 has 32k of RAM" if model == "8296" && ![nil, 32].include?(@ram_kb)
191
214
  end
192
215
 
193
216
  # --basic and --editor name ROMs the model's board takes
@@ -216,6 +239,23 @@ module Badline
216
239
  @refresh_hz = editor_hz
217
240
  end
218
241
 
242
+ # Only the CBM-II has a KERNAL to pick.
243
+ def refuse_cbm2_options
244
+ raise Error, "--kernal needs cbm2" unless @kernal.nil?
245
+ end
246
+
247
+ # The CBM-II runs in the window, without the C64's SID and REU, and
248
+ # its model takes the KERNAL named.
249
+ def validate_cbm2
250
+ raise Error, "--ntsc needs the C64; a CBM-II on 60 Hz mains is --60hz" if @models.include?("ntsc")
251
+ raise Error, "--sid needs the C64" unless @sid_model.nil?
252
+ raise Error, "--reu needs the C64" unless @reu.nil?
253
+ raise Error, "the CBM-II needs the window" if headless?
254
+
255
+ kernals = p500? ? P500_KERNALS : CBM2_KERNALS
256
+ raise Error, "the #{model} takes KERNAL #{kernals.join(' or ')}" unless kernals.include?(kernal)
257
+ end
258
+
219
259
  # `sid` takes directories as well as tunes.
220
260
  def validate_tune(path)
221
261
  return if !File.exist?(path) || File.extname(path).casecmp?(".sid")
@@ -13,10 +13,11 @@ module Badline
13
13
  # Either opens the window for any media, or with --headless or
14
14
  # --audio-out plays or renders a .sid tune without one. `sid`, given
15
15
  # first, plays .sid tunes and directories of them in the terminal.
16
- # `c64`, `vic20`, `c128`, `plus4` or `pet`, given first, names the
17
- # machine family, and without one the family is the C64, the C128 for a
18
- # program that loads where C128 BASIC starts, or the PET for an 8050's
19
- # or 8250's disk or a PET tape.
16
+ # `c64`, `vic20`, `c128`, `plus4`, `pet` or `cbm2`, given first, names
17
+ # the machine family, and without one the family is the C64, the C128
18
+ # for a program that loads where C128 BASIC starts, the PET for an
19
+ # 8050's or 8250's disk or a PET tape, or the CBM-II for a cartridge
20
+ # signed as its own.
20
21
  # The native build adds --no-sound and --version, and badline-ruby adds
21
22
  # --disable-jit.
22
23
  #
@@ -77,12 +78,16 @@ module Badline
77
78
  :audio_out, :seconds, :songlengths, :filter_chunk, :timeline
78
79
 
79
80
  # The machine family to build (Badline::Machine), :c64, :vic20, :c128,
80
- # :plus4 or :pet.
81
+ # :plus4, :pet or :cbm2.
81
82
  attr_reader :family
82
83
 
83
- # The ROMs --basic and --editor fit a PET with, keys of
84
- # PET::ROMSets::BASICS and PET::ROMSets::EDITORS, or nil for its own.
85
- attr_reader :basic, :editor
84
+ # The ROMs --basic, --editor and --characters fit a PET with, keys of
85
+ # PET::ROMSets::BASICS, PET::ROMSets::EDITORS and
86
+ # PET::ROMSets::CHARACTERS, or nil for its own.
87
+ attr_reader :basic, :editor, :characters
88
+
89
+ # The 8296's board jumpers --jumpers names (PET::Bus::Expansion::JUMPERS).
90
+ attr_reader :jumpers
86
91
 
87
92
  # The VIC-20's RAM expansion --ram names, a key of
88
93
  # Vic20::Bus::RAM_CONFIGURATIONS, or nil without --ram.
@@ -132,9 +137,8 @@ module Badline
132
137
  @ram = nil
133
138
  @ram_kb = nil
134
139
  @ram_name = nil
135
- @refresh_hz = nil
136
- @basic = nil
137
- @editor = nil
140
+ initialize_pet
141
+ @kernal = nil
138
142
  @cartridges = {}
139
143
  @autostart = true
140
144
  @writable = false
@@ -244,9 +248,20 @@ module Badline
244
248
  def c64_mode? = @c64_mode
245
249
 
246
250
  # The frame rate of a CRTC PET's editor: 60 with --60hz or a 60 Hz
247
- # --editor, else 50.
251
+ # --editor, else 50. A B series CBM-II's mains, which its editor
252
+ # follows.
248
253
  def refresh_hz = @refresh_hz || 50
249
254
 
255
+ # The CBM-II's mains, 60 with --60hz, or nil for the model's own.
256
+ def mains_hz = @refresh_hz
257
+
258
+ # The CBM-II's KERNAL --kernal names, one of CBM2_KERNALS or, on the
259
+ # P500, P500_KERNALS, the first of them without it.
260
+ def kernal = @kernal || (p500? ? P500_KERNALS : CBM2_KERNALS).first
261
+
262
+ # Whether the CBM-II to run is a P500, a 510 of either region.
263
+ def p500? = @family == :cbm2 && model.start_with?("510")
264
+
250
265
  def help? = @help
251
266
 
252
267
  def version? = @version
@@ -262,7 +277,7 @@ module Badline
262
277
  # A .sid on its own plays in the SID player, as `sid` would, unless an
263
278
  # option of the emulator's window asks for the machine.
264
279
  def play_lone_tune
265
- return if @sid_command || %i[vic20 plus4 pet].include?(@family) || headless? || !@window_only.empty?
280
+ return if @sid_command || %i[vic20 plus4 pet cbm2].include?(@family) || headless? || !@window_only.empty?
266
281
  return if @media_path.nil?
267
282
 
268
283
  @sid_command = File.extname(@media_path).casecmp?(".sid")
@@ -322,9 +337,9 @@ module Badline
322
337
  when "--model" then @models << value
323
338
  when "--reu" then @reu = reu_size(value)
324
339
  when "--ram" then @ram_name = value
325
- when "--basic" then @basic = value
326
- when "--editor" then @editor = value
340
+ when "--basic", "--editor", "--characters", "--jumpers" then pet_option(name, value)
327
341
  when "--drive-type" then @drive_type = drive_type_for(value)
342
+ when "--kernal" then @kernal = kernal_for(value)
328
343
  when "--c1lo", "--c1hi", "--c2lo", "--c2hi" then @cartridges[CARTRIDGE_SLOTS.fetch(name)] = value
329
344
  when "--frames" then @frames = number(flag, value)
330
345
  when "--screenshot" then @screenshot = value
@@ -338,6 +353,24 @@ module Badline
338
353
  end
339
354
  end
340
355
 
356
+ # The PET's frame rate, ROMs and board jumpers, its own until named.
357
+ def initialize_pet
358
+ @refresh_hz = nil
359
+ @basic = nil
360
+ @editor = nil
361
+ @characters = nil
362
+ @jumpers = []
363
+ end
364
+
365
+ def pet_option(name, value)
366
+ case name
367
+ when "--basic" then @basic = value
368
+ when "--editor" then @editor = value
369
+ when "--characters" then @characters = value
370
+ else @jumpers = value.split(",")
371
+ end
372
+ end
373
+
341
374
  def switch(name)
342
375
  case name
343
376
  when "--no-autostart" then @autostart = false
@@ -3,10 +3,11 @@
3
3
  module Badline
4
4
  class PET
5
5
  class Bus
6
- # The 8096's 64K board: four 16K banks, which a write-only control
7
- # register at $FFF0 lays over $8000-$FFFF, two at a time:
6
+ # The 64K board of the 8096 and the 8296: four 16K banks, which a
7
+ # write-only control register at $FFF0 lays over $8000-$FFFF, two at
8
+ # a time:
8
9
  #
9
- # bit 7 the board on; when clear, a plain 8032 and the rest ignored
10
+ # bit 7 the board on; when clear, the rest ignored
10
11
  # bit 6 I/O shows through at $E800-$EFFF
11
12
  # bit 5 the screen shows through at $8000-$8FFF
12
13
  # bit 3 bank 3 at $C000-$FFFF, else bank 1
@@ -16,6 +17,26 @@ module Badline
16
17
  #
17
18
  # A write to $FFF0 lands where $FFF0 was before it, too. The RES line
18
19
  # clears the register, so the 8096 starts as an 8032.
20
+ #
21
+ # The 8296 decodes its memory in a PLA, after the logic equations
22
+ # André Fachat read out of it (8296desc.txt on zimmers.net), with 32K
23
+ # of RAM from $8000 (Bus#screen) besides the board:
24
+ #
25
+ # - With bit 7 clear, every write from $8000 up but to $E8xx goes to
26
+ # that RAM, under the ROMs too. Reads find the screen at $8000,
27
+ # the empty sockets at $9000 and $A000, BASIC, the editor at
28
+ # $E000-$E7FF and again at $E900-$EFFF, and the KERNAL, unless the
29
+ # lines /RAMSEL9, /RAMSELA and /RAMON put RAM there (#ram_lines).
30
+ # RAMSEL9 puts it at $9000, RAMSELA at $A000, RAMON at
31
+ # $8000-$DFFF, RAMON with either of the others at $E000-$EFFF but
32
+ # $E8xx, and RAMON with RAMSELA at $F000 as well.
33
+ # - With bit 7 set, I/O showing through brings back the editor at
34
+ # $E900-$EFFF, which an 8096 leaves empty.
35
+ #
36
+ # Jumpers tie the lines: JU2 RAMSEL9 low and JU1 RAMSELA low
37
+ # (:ram9, :rama), or JU4, JU3 and JU5 RAMSEL9, RAMSELA and RAMON to
38
+ # the user port's PA1, PA0 and PA2 (:userport), each active low.
39
+ # With none fitted, as on xpet's 8296, the lines stay high.
19
40
  class Expansion
20
41
  # A 16K bank of the board, seen from +start+.
21
42
  class Window
@@ -50,9 +71,16 @@ module Badline
50
71
  end
51
72
  end
52
73
 
74
+ # The user port lines that RAMSELA, RAMSEL9 and RAMON follow.
75
+ USER_PORT_LINES = 0x07
76
+
53
77
  attr_reader :cells, :control
54
78
 
55
- def initialize(bus)
79
+ # The user port's PA0-PA2 as the board last saw them.
80
+ attr_reader :lines
81
+
82
+ # +jumpers+ are the 8296's (ROMSets::JUMPERS) when +board8296+ is.
83
+ def initialize(bus, board8296: false, jumpers: [])
56
84
  @bus = bus
57
85
  @cells = Array.new(0x10000, 0)
58
86
  @banks = [0x8000, 0xc000, 0x8000, 0xc000].each_with_index.map do |start, bank|
@@ -60,13 +88,32 @@ module Badline
60
88
  end
61
89
  @port = ControlPort.new(self, @banks[1])
62
90
  @control = 0
91
+ @board8296 = board8296
92
+ @ram9 = jumpers.include?("ram9")
93
+ @rama = jumpers.include?("rama")
94
+ @user_port = jumpers.include?("userport")
95
+ @lines = USER_PORT_LINES
63
96
  end
64
97
 
98
+ def board8296? = @board8296
99
+
100
+ # Whether the board follows the user port's lines.
101
+ def user_port? = @user_port
102
+
65
103
  def control=(value)
66
104
  @control = value
67
105
  @bus.map_pages
68
106
  end
69
107
 
108
+ # Follows the user port's lines, +port_a+ as the VIA drives them.
109
+ def lines=(port_a)
110
+ lines = port_a & USER_PORT_LINES
111
+ return if lines == @lines
112
+
113
+ @lines = lines
114
+ @bus.map_pages
115
+ end
116
+
70
117
  def reset!
71
118
  self.control = 0
72
119
  end
@@ -75,16 +122,33 @@ module Badline
75
122
  @cells.fill(0)
76
123
  end
77
124
 
125
+ # Whether the CPU reads the KERNAL at $F000-$FFFF.
126
+ def kernal_mapped?
127
+ return false if @control.anybits?(0x80)
128
+
129
+ !(@board8296 && ram_on? && ram_a?)
130
+ end
131
+
78
132
  # Lays the banks the register picks over the pages, and the
79
133
  # control register over page $FF's writes.
80
134
  def map(reads, writes)
81
- map_banks(reads, writes) if @control.anybits?(0x80)
135
+ if @control.anybits?(0x80)
136
+ map_banks(reads, writes)
137
+ elsif @board8296
138
+ map_ram8296(reads, writes)
139
+ end
82
140
  @port.under = writes[0xff]
83
141
  writes[0xff] = @port
84
142
  end
85
143
 
86
144
  private
87
145
 
146
+ def ram_on? = @user_port && @lines.nobits?(0x04)
147
+
148
+ def ram9? = @ram9 || (@user_port && @lines.nobits?(0x02))
149
+
150
+ def ram_a? = @rama || (@user_port && @lines.nobits?(0x01))
151
+
88
152
  def map_banks(reads, writes)
89
153
  low = @banks[@control.anybits?(0x04) ? 2 : 0]
90
154
  high = @banks[@control.anybits?(0x08) ? 3 : 1]
@@ -99,6 +163,36 @@ module Badline
99
163
  reads.fill(high, page, count)
100
164
  writes.fill(high, page, count)
101
165
  end
166
+ map_editor_through(reads, writes) if @board8296
167
+ end
168
+
169
+ # I/O showing through on the 8296 leaves $E900-$EFFF to the editor,
170
+ # and its writes to nothing.
171
+ def map_editor_through(reads, writes)
172
+ return unless @control.anybits?(0x40)
173
+
174
+ reads.fill(@bus.editor_rom, 0xe9, 0x07)
175
+ writes.fill(@bus.open_bus, 0xe9, 0x07)
176
+ end
177
+
178
+ def map_ram8296(reads, writes)
179
+ ram = @bus.screen
180
+ writes.fill(ram, 0x90, 0x58)
181
+ writes.fill(ram, 0xe9, 0x17)
182
+ reads.fill(@bus.editor_rom, 0xe9, 0x07)
183
+ ram_on = ram_on?
184
+ ram9 = ram9?
185
+ ram_a = ram_a?
186
+ reads.fill(ram, 0x90, 0x10) if ram9 || ram_on
187
+ reads.fill(ram, 0xa0, 0x10) if ram_a || ram_on
188
+ return unless ram_on
189
+
190
+ reads.fill(ram, 0xb0, 0x30)
191
+ return unless ram9 || ram_a
192
+
193
+ reads.fill(ram, 0xe0, 0x08)
194
+ reads.fill(ram, 0xe9, 0x07)
195
+ reads.fill(ram, 0xf0, 0x10) if ram_a
102
196
  end
103
197
  end
104
198
  end
@@ -19,7 +19,8 @@ module Badline
19
19
  # empty spot finds it, mostly the high byte of the address just
20
20
  # fetched, as PET/unconnected shows on a real 3016. Writes to ROM and
21
21
  # to empty spots go nowhere. An 8096 banks its 64K board over
22
- # $8000-$FFFF (Expansion).
22
+ # $8000-$FFFF (Expansion), and an 8296 its board and the RAM under its
23
+ # ROMs.
23
24
  class Bus
24
25
  include Addressable
25
26
 
@@ -33,7 +34,8 @@ module Badline
33
34
  def poke(_addr, _value); end
34
35
  end
35
36
 
36
- # Screen RAM, repeated over the pages it answers in.
37
+ # Screen RAM, repeated over the pages it answers in. On the 8296 it is
38
+ # all 32K of RAM from $8000, which the board lays under the ROMs.
37
39
  class ScreenRAM
38
40
  attr_reader :cells
39
41
 
@@ -107,29 +109,31 @@ module Badline
107
109
  end
108
110
  end
109
111
 
110
- attr_reader :ram, :screen, :expansion
112
+ attr_reader :ram, :screen, :expansion, :editor_rom, :open_bus
111
113
 
112
114
  # The last byte the CPU read or wrote.
113
115
  attr_reader :data
114
116
 
115
117
  # +model+ is a Model::Profile, +ram_kb+ the RAM below $8000 and
116
118
  # +editor+ the editor ROM's file. The CRTC answers only on a board
117
- # the model fits one on.
118
- def initialize(model:, ram_kb:, editor:, pia1:, pia2:, via:, crtc:)
119
+ # the model fits one on. +jumpers+ are the 8296's (ROMSets::JUMPERS).
120
+ def initialize(model:, ram_kb:, editor:, pia1:, pia2:, via:, crtc:, jumpers: [])
119
121
  addressable_at(0, length: 2**16)
120
122
  @ram = Memory.new([], length: ram_kb * 1024, start: 0)
121
123
  @screen = ScreenRAM.new(model.screen_bytes)
122
124
  @screen_pages = model.screen_pages
123
125
  basic = ROM.read(model.basic)
124
126
  @basic_rom = ROM.new(basic, length: basic.length, start: 0xe000 - basic.length)
125
- @editor_rom = ROM.load(editor, 0xe000)
127
+ @editor_rom = model.cbm8296? ? editor_socket(editor) : ROM.load(editor, 0xe000)
126
128
  @kernal_rom = ROM.load(model.kernal, 0xf000)
127
129
  @io = IO.new(self, pia1:, pia2:, via:, crtc:, crtc_fitted: model.crtc?)
128
130
  @open_bus = OpenBus.new(self)
129
131
  @data = 0
130
132
  @read_pages = Array.new(256, @open_bus)
131
133
  @write_pages = Array.new(256, @open_bus)
132
- @expansion = model.expansion ? Expansion.new(self) : nil
134
+ @expansion = model.expansion ? Expansion.new(self, board8296: model.cbm8296?, jumpers:) : nil
135
+ @via = via
136
+ @user_port = !@expansion.nil? && @expansion.user_port?
133
137
  @debug_register = nil
134
138
  @io_write_handler = nil
135
139
  map_pages
@@ -143,11 +147,16 @@ module Badline
143
147
 
144
148
  def io_written
145
149
  @io_write_handler&.call
150
+ @expansion.lines = @via.port_a_output if @user_port
146
151
  end
147
152
 
148
- # The RES line reaches the 8096's control register.
153
+ # The RES line reaches the board's control register, and the user
154
+ # port's lines float high with the VIA's.
149
155
  def reset!
150
- @expansion&.reset!
156
+ return if @expansion.nil?
157
+
158
+ @expansion.reset!
159
+ @expansion.lines = @via.port_a_output if @user_port
151
160
  end
152
161
 
153
162
  def power_on!
@@ -166,8 +175,9 @@ module Badline
166
175
  end
167
176
 
168
177
  # Whether the CPU reads the KERNAL ROM at $F000-$FFFF, which an 8096's
169
- # board covers while its control register turns it on.
170
- def kernal_mapped? = @expansion.nil? || @expansion.control.nobits?(0x80)
178
+ # board covers while its control register turns it on, and an 8296's
179
+ # RAM too.
180
+ def kernal_mapped? = @expansion.nil? || @expansion.kernal_mapped?
171
181
 
172
182
  # Calls the block with each byte written to $8BFF (DebugRegister).
173
183
  def install_debug_register(&)
@@ -175,7 +185,7 @@ module Badline
175
185
  map_pages
176
186
  end
177
187
 
178
- # Lays the pages out again, for the 8096's control register.
188
+ # Lays the pages out again, for the board's control register.
179
189
  def map_pages
180
190
  map(@open_bus, 0x00, 0x100)
181
191
  map(@ram, 0x00, @ram.length >> 8)
@@ -190,6 +200,14 @@ module Badline
190
200
 
191
201
  private
192
202
 
203
+ # The 8296's editor socket, which answers at $E000-$E7FF and
204
+ # $E900-$EFFF: a 4K ROM's halves there, or a 2K one twice over.
205
+ def editor_socket(file)
206
+ bytes = ROM.read(file)
207
+ bytes += bytes if bytes.length == 0x800
208
+ ROM.new(bytes, length: 0x1000, start: 0xe000)
209
+ end
210
+
193
211
  def map_debug_register
194
212
  @debug_register.under = @write_pages[0x8b]
195
213
  @write_pages[0x8b] = @debug_register
@@ -11,7 +11,8 @@ module Badline
11
11
  # Both record from VIA PB3. The KERNAL reads each tape as the falling
12
12
  # edges its read line makes, as the C64 and VIC-20 do. The machine
13
13
  # passes the motor and write lines on after each write to the I/O
14
- # chips (#lines_written).
14
+ # chips (#lines_written), and clocks the decks only while a tape runs
15
+ # (#running?).
15
16
  class Cassettes
16
17
  attr_reader :datasette1, :datasette2
17
18
 
@@ -22,6 +23,18 @@ module Badline
22
23
  @datasette2 = Drives::Datasette.new
23
24
  @datasette1.on_flag { pulse1 }
24
25
  @datasette2.on_flag { pulse2 }
26
+ @running = false
27
+ @running_handler = nil
28
+ @datasette1.on_running_change { update_running }
29
+ @datasette2.on_running_change { update_running }
30
+ end
31
+
32
+ # Whether either deck's tape runs.
33
+ def running? = @running
34
+
35
+ # Whether either deck's tape runs, each time that changes.
36
+ def on_running_change(&handler)
37
+ @running_handler = handler
25
38
  end
26
39
 
27
40
  def cycle!
@@ -48,6 +61,14 @@ module Badline
48
61
 
49
62
  private
50
63
 
64
+ def update_running
65
+ running = @datasette1.running? || @datasette2.running?
66
+ return if running == @running
67
+
68
+ @running = running
69
+ @running_handler&.call
70
+ end
71
+
51
72
  # A falling edge on a read line, and the line back up.
52
73
  def pulse1
53
74
  @pia1.ca1 = false
@@ -6,21 +6,27 @@ module Badline
6
6
  # (upper case and graphics) from $000 and text (lower and upper case)
7
7
  # from $400, 128 characters of 8 rows each. Bit 7 of a screen code
8
8
  # inverts the character's rows, as the hardware does, so #rows holds
9
- # 256 codes a set, each row at (set << 11) | (code << 3) | row.
9
+ # 256 codes a set, each row at (set << 11) | (code << 3) | row. A 4K
10
+ # ROM, the 8296's, holds two such halves, the second at HALF.
10
11
  class Characters
11
12
  TEXT = 0x800
12
13
 
14
+ HALF = 0x1000
15
+
13
16
  attr_reader :rows
14
17
 
15
18
  def initialize(file)
16
19
  rom = ROM.read(file)
17
- @rows = Array.new(0x1000) do |index|
20
+ @rows = Array.new(rom.length << 1) do |index|
18
21
  set = index >> 11
19
22
  code = (index >> 3) & 0xff
20
23
  bits = rom[(set << 10) | ((code & 0x7f) << 3) | (index & 0x07)]
21
24
  code >= 0x80 ? bits ^ 0xff : bits
22
25
  end
23
26
  end
27
+
28
+ # Whether the ROM holds two halves.
29
+ def halves? = @rows.length > HALF
24
30
  end
25
31
  end
26
32
  end