badline 0.16.1 → 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 (143) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +60 -0
  3. data/CLAUDE.md +1 -1
  4. data/README.md +102 -12
  5. data/doc/media.md +5 -2
  6. data/doc/pinned-behaviour.md +61 -0
  7. data/doc/snapshot-format.md +376 -69
  8. data/doc/snapshots.md +148 -14
  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/model8050/disk.rb +163 -0
  46. data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
  47. data/lib/badline/drives/model8050/idle.rb +177 -0
  48. data/lib/badline/drives/model8050/ieee_port.rb +147 -0
  49. data/lib/badline/drives/model8050/ip_bus.rb +93 -0
  50. data/lib/badline/drives/model8050/keyed_state.rb +160 -0
  51. data/lib/badline/drives/model8050/mechanism.rb +230 -0
  52. data/lib/badline/drives/model8050/parts.rb +37 -0
  53. data/lib/badline/drives/model8050.rb +243 -0
  54. data/lib/badline/frontend/app.rb +10 -3
  55. data/lib/badline/frontend/boot.rb +1 -1
  56. data/lib/badline/frontend/controls.rb +51 -15
  57. data/lib/badline/frontend/gamepads.rb +3 -3
  58. data/lib/badline/frontend/keys.rb +61 -2
  59. data/lib/badline/frontend/media_slots.rb +1 -1
  60. data/lib/badline/frontend/menu/menu_media.rb +14 -11
  61. data/lib/badline/frontend/menu/menu_pages.rb +18 -4
  62. data/lib/badline/frontend/menu/menu_ports.rb +40 -4
  63. data/lib/badline/frontend/menu/pause_menu.rb +1 -1
  64. data/lib/badline/frontend/pet_keyboard.rb +23 -7
  65. data/lib/badline/frontend/timeline.rb +19 -10
  66. data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
  67. data/lib/badline/kernal_trap/dos/status.rb +3 -0
  68. data/lib/badline/kernal_trap/dos.rb +18 -5
  69. data/lib/badline/kernal_trap/ieee.rb +4 -2
  70. data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
  71. data/lib/badline/machine/ieee_drives.rb +194 -0
  72. data/lib/badline/machine/settling.rb +4 -0
  73. data/lib/badline/machine.rb +23 -6
  74. data/lib/badline/media/cbm2_media.rb +29 -0
  75. data/lib/badline/media/program.rb +6 -3
  76. data/lib/badline/media/true_drive.rb +38 -9
  77. data/lib/badline/media.rb +15 -6
  78. data/lib/badline/options/family.rb +15 -3
  79. data/lib/badline/options/help.rb +16 -2
  80. data/lib/badline/options/table.rb +18 -4
  81. data/lib/badline/options/validation.rb +27 -3
  82. data/lib/badline/options.rb +21 -7
  83. data/lib/badline/pet/cassettes.rb +22 -1
  84. data/lib/badline/pet/ieee_drive.rb +5 -64
  85. data/lib/badline/pet/ieee_ports.rb +7 -0
  86. data/lib/badline/pet.rb +15 -4
  87. data/lib/badline/plus4/acia.rb +4 -298
  88. data/lib/badline/plus4/keyed_parts.rb +0 -44
  89. data/lib/badline/plus4.rb +1 -0
  90. data/lib/badline/roms/8050/dos-2.7.rom +0 -0
  91. data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
  92. data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
  93. data/lib/badline/roms/README +111 -0
  94. data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
  95. data/lib/badline/roms/cbm2/basic128.rom +0 -0
  96. data/lib/badline/roms/cbm2/basic256.rom +0 -0
  97. data/lib/badline/roms/cbm2/characters-600.rom +0 -0
  98. data/lib/badline/roms/cbm2/characters-700.rom +0 -0
  99. data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
  100. data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
  101. data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
  102. data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
  103. data/lib/badline/snapshot/cbm2_setup.rb +53 -0
  104. data/lib/badline/snapshot/container.rb +7 -2
  105. data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
  106. data/lib/badline/snapshot/field_doc.rb +38 -3
  107. data/lib/badline/snapshot/image.rb +62 -9
  108. data/lib/badline/snapshot/vice/cbm2.rb +93 -0
  109. data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
  110. data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
  111. data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
  112. data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
  113. data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
  114. data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
  115. data/lib/badline/snapshot/vice/cias.rb +13 -9
  116. data/lib/badline/snapshot/vice/p500.rb +96 -0
  117. data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
  118. data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
  119. data/lib/badline/snapshot/vice/pet.rb +7 -3
  120. data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
  121. data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
  122. data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
  123. data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
  124. data/lib/badline/snapshot/vice/pet_restore.rb +70 -6
  125. data/lib/badline/snapshot/vice/pet_via.rb +6 -5
  126. data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
  127. data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
  128. data/lib/badline/snapshot/vice.rb +3 -0
  129. data/lib/badline/snapshot.rb +11 -3
  130. data/lib/badline/storage/crt_file.rb +7 -1
  131. data/lib/badline/storage/d64_image.rb +7 -0
  132. data/lib/badline/storage/d80_image.rb +17 -1
  133. data/lib/badline/storage/d81_image.rb +14 -0
  134. data/lib/badline/storage/disk_image/bam.rb +18 -3
  135. data/lib/badline/storage/disk_image/formatting.rb +69 -0
  136. data/lib/badline/storage/disk_image/writing.rb +35 -0
  137. data/lib/badline/storage/disk_image.rb +2 -0
  138. data/lib/badline/storage.rb +1 -0
  139. data/lib/badline/support/cbm2_checkpoint.rb +48 -0
  140. data/lib/badline/support/p500_checkpoint.rb +35 -0
  141. data/lib/badline/version.rb +1 -1
  142. data/lib/badline.rb +1 -0
  143. metadata +72 -2
data/doc/snapshots.md CHANGED
@@ -160,11 +160,13 @@ saved with `--vice` (see [xpet](#xpet)).
160
160
  A PET snapshot saved with `--vice`, or `computer.export_snapshot(path)`,
161
161
  also holds xpet's modules for the CPU, the RAM with the screen RAM, or
162
162
  an 8296's 32K from `$8000`, an 8096's or 8296's board and its control
163
- register, the CRTC, both PIAs and the VIA, and the ones xpet 3.10 needs
164
- to open the file, with nothing open on the file system drive and
165
- nothing on the user port. The CRTC module carries the discrete boards'
166
- video too, in xpet's terms. The file says it is in VICE's snapshot
167
- format 1.0, the only one xpet reads.
163
+ register, the CRTC, both PIAs and the VIA, a true 2031's CPU, RAM, VIAs
164
+ and head, or a true 8050's or 8250's interface processor with its RAM
165
+ and 6532s and the heads' tracks, and the ones xpet 3.10 needs to open
166
+ the file, with nothing open on the file system drive and nothing on the
167
+ user port. The CRTC module carries the discrete boards' video too, in
168
+ xpet's terms. The file says it is in VICE's snapshot format 1.0, the
169
+ only one xpet reads.
168
170
 
169
171
  xpet 3.10 opens those snapshots as long as its model and editor match:
170
172
  `-model 2001`, `3032`, `3032B`, `4032`, `8032`, `8096` or `8296`, and
@@ -172,15 +174,33 @@ for a badline editor other than xpet's own, `-editor` naming badline's
172
174
  ROM. xpet's 8296 runs the 8032's editor, 901474-04 dated 0283, so an
173
175
  8296 saved for it has to run that editor (`--editor 4-80-b-50hz-0283`),
174
176
  and xpet takes the 8296's jumpers from its own settings. It keeps its
175
- own drives and datasettes and leaves out badline's tapes and disk. Its
176
- CRTC loses a few lines on opening any snapshot, its own too, so a
177
- program it runs on from one finds the vertical sync a few hundred
178
- cycles later than one it ran without stopping.
177
+ own datasettes and leaves out badline's tapes and disks. A true drive
178
+ needs `-drive8type 2031`, `8050` or `8250` with `-drive8truedrive` and
179
+ the disk attached as `-8`. Its snapshot is settled with the 2031's CPU
180
+ between instructions too, and one saved while the 2031 talks on the bus
181
+ can stall there in xpet, as xpet's own snapshots saved while it searches
182
+ for a file do. xpet runs an 8050's floppy controller at a high level, so
183
+ a snapshot for it is settled until badline's controller sits in its idle
184
+ loop with both processors between instructions, and xpet's controller
185
+ starts from idle there, taking up any job the interface processor has
186
+ left it. A unit that doesn't reach its idle loop within about 262,000
187
+ cycles, such as one still starting up, is left out of xpet's modules,
188
+ and badline's own modules keep it. Its CRTC loses a few lines on
189
+ opening any snapshot, its own too, so a program it runs on from one
190
+ finds the vertical sync a few hundred cycles later than one it ran
191
+ without stopping.
179
192
 
180
193
  Snapshots xpet saves open in badline through the same modules: the CPU
181
194
  at its instruction boundary, the RAM, the screen RAM, an 8096's or
182
195
  8296's board, the CRTC's registers and beam, both PIAs' registers and
183
- flags, and the VIA's registers, timers and shift register. A program
196
+ flags, the VIA's registers, timers and shift register, a true 2031's
197
+ CPU, RAM, VIAs and head, which go on the bus with the disk badline's
198
+ 2031 had in it, and a true 8050's or 8250's interface processor, its RAM
199
+ and both 6532s, which go on the bus with the disks badline's unit of the
200
+ same model had in it. Its floppy controller comes back as a unit
201
+ switched on has it, in its idle loop with the heads on track 1: its next
202
+ job steps them out to the track, and a job xpet's controller had under
203
+ way starts again from there. A program
184
204
  badline runs on from xpet's snapshot finds the vertical sync on the
185
205
  cycle xpet does, so the two run on alike, cycle for cycle. The picture
186
206
  starts again with the next frame. xpet doesn't save its ROMs, which
@@ -190,10 +210,124 @@ board, a 4032, an 8032, an 8096 or an 8296 with none of its jumpers
190
210
  fitted, at 50 or 60 Hz, and an 8032, 8096 or 8296 whose CRTC lines are
191
211
  64 cycles long runs xpet's own 50 Hz editor, 901474-04 dated 0283. A
192
212
  SuperPET's snapshot fails. It reports the modules it leaves out, such
193
- as the drives, the datasettes and the keyboard, and carries on without
194
- them. Restored into a running PET, an xpet snapshot fails for a PET
195
- built another way, such as a 3032B, and otherwise switches the machine
196
- off and on, keeping its tapes and the disk device 8 serves.
213
+ as the drives past device 8, the datasettes and the keyboard, and
214
+ carries on without them, and a snapshot without a true drive takes
215
+ badline's, a 2031, an 8050 or an 8250, off the bus and says so.
216
+ Restored into a running PET, an xpet snapshot fails for a PET built
217
+ another way, such as a 3032B, and otherwise switches the machine off
218
+ and on, keeping its tapes and the disk device 8 serves.
219
+
220
+ ## The CBM-II
221
+
222
+ The B series, the 610, 620, 710 and 720, saves and restores the same
223
+ way. A snapshot of one holds segment 15's RAM, the screen RAM, the RAM
224
+ board's segments, both segment registers, a cartridge's ROMs, the CPU,
225
+ the CRTC with the picture, the SID, the CIA, the ACIA, both TPIs with
226
+ their priority stacks, the mains' place in its period, and the disk or
227
+ directory device 8 serves through the IEEE-488 traps. It restores into
228
+ a machine of the same model with the same KERNAL and mains. Its file
229
+ names its machine `CBM-II`, as xcbm2's do, and holds only badline's
230
+ modules, so xcbm2 doesn't open it unless it is saved with `--vice` (see
231
+ [xcbm2](#xcbm2)).
232
+
233
+ ### xcbm2
234
+
235
+ A CBM-II snapshot saved with `--vice`, or `computer.export_snapshot(path)`, also holds
236
+ xcbm2's modules for the CPU, the RAM with both segment registers, the
237
+ CRTC, the CIA, both TPIs, the ACIA and the SID, a true 2031, 8050 or
238
+ 8250 as xpet's are written (see [xpet](#xpet)), or with no true drive,
239
+ none on any device, and the ones xcbm2 3.10 needs to open the file, with
240
+ no cartridge, nothing open on the file system drive and nothing on the
241
+ user port. The file says it is in
242
+ VICE's snapshot format 1.0, the only one xcbm2 reads.
243
+
244
+ xcbm2 3.10 opens those snapshots as long as its model matches: `-model
245
+ 610`, `620`, `710` or `720`. It takes a 600's 60 Hz from the snapshot
246
+ and leaves out badline's cartridge and disks. A true drive needs
247
+ `-drive8type 2031`, `8050` or `8250` with `-drive8truedrive` and the
248
+ disk attached as `-8`, and with no true drive in the snapshot xcbm2
249
+ takes its own off the bus. It
250
+ runs on from badline's snapshot just as it runs on from one it saved
251
+ itself, cycle for cycle. Opening any snapshot, its own too, costs it a
252
+ couple of hundred cycles of its CRTC, so the mains ticks that much later
253
+ than in a run that didn't stop.
254
+
255
+ xcbm2 saves no mains. It ticks the mains once a frame of its CRTC, as
256
+ vertical sync ends, whatever its mains is set to, so its ticks come a
257
+ frame apart: 40,064 cycles on a 600 at 50 Hz, where badline's come
258
+ 40,000 apart, and its 700 has no 60 Hz. A snapshot saved for xcbm2 ticks
259
+ where xcbm2's CRTC says, not where badline's mains had it. Neither
260
+ does xcbm2 save a TPI's priority stack, so the interrupts in service are
261
+ lost both ways. Its cursor blinks 32 fields on and 32 off, where the
262
+ 6845 blinks 16 and 16, so it comes back showing or hidden as it was,
263
+ with its place in the blink scaled.
264
+
265
+ Snapshots xcbm2 saves open in badline through the same modules: the CPU
266
+ at its instruction boundary, the RAM and both segment registers, the
267
+ CRTC's registers and beam, the CIA's registers, timers and clock, both
268
+ TPIs' registers, latches, mask, AIR, CA and CB, the ACIA's registers and
269
+ the byte it waits to send, and the SID's registers and reSID voice
270
+ state. The mains' next falling edge comes where xcbm2 would tick it, and
271
+ the ones after at badline's 50 or 60 Hz, so the two part from the
272
+ second tick on. xcbm2 has no DRAM refresh, so a program badline runs
273
+ on from xcbm2's snapshot runs about 5% slower than in xcbm2, as the
274
+ refresh takes a cycle from the CPU about every 21. On a machine built
275
+ with `refresh: false` it reads the mains, the CIA and the rest as xcbm2
276
+ does until that second tick, cycle for cycle. The picture starts again with the next frame. xcbm2 doesn't
277
+ save its ROMs, so the RAM board and the model line build the machine:
278
+ a 610, 620, 710 or 720 with xcbm2's KERNAL, 901244-04a, at 60 Hz for a
279
+ 600 whose model line says so and at 50 Hz otherwise. A board of 512K or
280
+ more, or RAM mapped into segment 15, fails. A true 2031, 8050 or 8250
281
+ goes on the bus as xpet's does, with the disks badline's drive of that
282
+ model had, and xcbm2 runs a true 2031 on device 8 unless told otherwise,
283
+ so its snapshots mostly put one there. One saved part way into a `LOAD`
284
+ from xcbm2's true drive goes on with it in badline. A snapshot without
285
+ a true drive takes badline's off the bus and says so. It reports the
286
+ modules it leaves out, such as the datasette and the keyboard, and
287
+ carries on without them. Restored into a running CBM-II, an xcbm2
288
+ snapshot fails for a CBM-II built another way, and otherwise switches
289
+ the machine off and on, keeping the disk device 8 serves through the
290
+ traps.
291
+
292
+ ### xcbm5x0
293
+
294
+ The P500, the 510 on PAL and NTSC, saves and restores as the B series
295
+ does, with the VIC-II and its colour RAM in place of the CRTC. Its file
296
+ names its machine `CBM-II-5x0`, as xcbm5x0's do. Saved with `--vice`,
297
+ or `computer.export_snapshot(path)`, it also holds xcbm5x0's modules
298
+ for the CPU, the RAM with the video matrix, the colour RAM and both
299
+ segment registers, the CIA, both TPIs, the ACIA, the SID, a true drive
300
+ or none as for the B series, the VIC-II and the mains, and the ones
301
+ xcbm5x0 3.10 needs to open the file, with
302
+ no cartridge, nothing open on the file system drive and the joysticks
303
+ released. The file says it is in VICE's snapshot format 0.0, as
304
+ xcbm5x0 writes it.
305
+
306
+ xcbm5x0 3.10 opens those snapshots with `-model 510`, and `-ntsc` for
307
+ an NTSC 510, takes the true drive as xcbm2 does and leaves out badline's
308
+ cartridge and disks. It runs on from badline's snapshot as it runs on from its own,
309
+ cycle for cycle. xcbm5x0 runs VICE's line-based VIC-II, not x64sc's, and
310
+ its module is laid out otherwise: it carries the registers, colour RAM,
311
+ the beam, VC, VCBASE, whether the sequencer is idle, the buffers and
312
+ the sprites' DMA, and badline writes the rest as xcbm5x0 leaves it.
313
+ xcbm5x0 saves the cycles to the mains' next tick, which badline reads
314
+ and writes. It ticks the mains every 19,656 cycles, a PAL frame, on
315
+ NTSC too, about 52 Hz there, where badline's NTSC 510 runs at 60 Hz, so
316
+ a tick more than a 60 Hz period away comes back sooner.
317
+
318
+ Snapshots xcbm5x0 saves open in badline through the same modules, as
319
+ xcbm2's do, with the VIC-II's registers, beam, counters and colour RAM,
320
+ and the mains' next tick on xcbm5x0's cycle. The row counter follows
321
+ from the line, and the sprites' counters, the borders' flip-flops and
322
+ the pixel pipeline start afresh. xcbm5x0's VIC isn't cycle exact as
323
+ x64sc's is, so a program badline runs on from its snapshot reads the
324
+ raster a cycle off now and then, and its cycles drift from xcbm5x0's by
325
+ a few over a second. The picture starts again with the next frame. The
326
+ RAM's size and the VIC's picture build the machine: a 510, on NTSC for
327
+ NTSC's picture. More RAM fails. A true drive goes on the bus as from
328
+ xcbm2's snapshots. Restored into a running P500, an
329
+ xcbm5x0 snapshot fails for a P500 built another way, and otherwise
330
+ switches the machine off and on, keeping the disk device 8 serves.
197
331
 
198
332
  ## VICE
199
333
 
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # The B series' 6551 at $DD00, the RS-232 port's, whose IRQ reaches
6
+ # TPI1's I4.
7
+ class ACIA < Chips::ACIA
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,263 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # The 6509's bus: sixteen segments of 64K, which the CPU's four extra
6
+ # address lines pick from the execution register at $0000 or, for the
7
+ # data of LDA (zp),Y and STA (zp),Y, the indirection register at $0001
8
+ # (CPU). Both registers answer at $0000 and $0001 in every segment.
9
+ #
10
+ # Segment 15 is the system's:
11
+ #
12
+ # $0000-$07FF - 2K of static RAM
13
+ # $0800-$0FFF - nothing: the buffer RAM socket
14
+ # $1000-$7FFF - the disk ROM's socket and the cartridge's three 8K
15
+ # selects, nothing until a ROM is in (#attach_cartridge)
16
+ # $8000-$BFFF - BASIC
17
+ # $C000-$CFFF - nothing
18
+ # $D000-$D7FF - the screen RAM
19
+ # $D800-$DFFF - I/O, a chip a page (#map_io), nothing where none is
20
+ # $E000-$FFFF - the KERNAL
21
+ #
22
+ # The RAM board fills the segments +ram_segments+ names, and the rest
23
+ # hold nothing. The data lines hold the last byte they carried, which
24
+ # a read from nothing finds, and a write to ROM or to nothing goes
25
+ # nowhere.
26
+ #
27
+ # Each segment is a pair of page tables, one read and one write. The
28
+ # CPU reads and writes through the execution segment's (#peek and
29
+ # #poke), and a write to $0000 switches them, so an access pays
30
+ # nothing for the banking. For an indirect access the CPU switches to
31
+ # the indirection segment's tables and back (#select_indirect! and
32
+ # #select_execute!).
33
+ class Bus
34
+ include Addressable
35
+
36
+ SEGMENTS = 16
37
+ SYSTEM = 15
38
+
39
+ # The last byte the CPU moved, which nothing reads back.
40
+ class OpenBus
41
+ def initialize(bus)
42
+ @bus = bus
43
+ end
44
+
45
+ def peek(_addr) = @bus.data
46
+ def poke(_addr, _value); end
47
+ end
48
+
49
+ # A segment's page 0: the execution register at $0000 and the
50
+ # indirection register at $0001, whose reads return the register in
51
+ # the low four bits and 0 in the high four, over what the page holds.
52
+ class Registers
53
+ def initialize(bus, under)
54
+ @bus = bus
55
+ @under = under
56
+ end
57
+
58
+ def peek(addr)
59
+ case addr
60
+ when 0 then @bus.execution_segment
61
+ when 1 then @bus.indirect_segment
62
+ else @under.peek(addr)
63
+ end
64
+ end
65
+
66
+ def poke(addr, value)
67
+ case addr
68
+ when 0 then @bus.execution_segment = value & 0x0f
69
+ when 1 then @bus.indirect_segment = value & 0x0f
70
+ else @under.poke(addr, value)
71
+ end
72
+ end
73
+ end
74
+
75
+ # VICE's debug cartridge register at $DAFF in segment 15, which a
76
+ # write calls the handler with, the exit code the VICE testprogs
77
+ # report. Every other write to its page passes to the chip there.
78
+ class DebugRegister
79
+ ADDRESS = 0xdaff
80
+
81
+ attr_accessor :under
82
+
83
+ def initialize(&handler)
84
+ @handler = handler
85
+ @under = nil
86
+ end
87
+
88
+ def poke(addr, value)
89
+ return @handler.call(value) if addr == ADDRESS
90
+
91
+ @under.poke(addr, value)
92
+ end
93
+ end
94
+
95
+ attr_reader :ram, :screen, :segment_ram, :execution_segment
96
+ attr_accessor :indirect_segment
97
+
98
+ # The last byte the CPU read or wrote.
99
+ attr_reader :data
100
+
101
+ # +basic+ and +kernal+ are ROM files for $8000-$BFFF and
102
+ # $E000-$FFFF, +ram_segments+ the segments the RAM board fills, and
103
+ # +screen_length+ the bytes of screen RAM from $D000 on.
104
+ def initialize(basic:, kernal:, ram_segments:, screen_length: 0x800)
105
+ addressable_at(0, length: 2**16)
106
+ @ram = Memory.new([], length: 0x800, start: 0)
107
+ @screen = Memory.new([], length: screen_length, start: 0xd000)
108
+ @screen_pages = screen_length >> 8
109
+ @basic_rom = ROM.load(basic, 0x8000)
110
+ @kernal_rom = ROM.load(kernal, 0xe000)
111
+ @open_bus = OpenBus.new(self)
112
+ @data = 0
113
+ @segment_ram = Array.new(SEGMENTS) do |segment|
114
+ ram_segments.include?(segment) ? Memory.new([], length: 0x10000, start: 0) : nil
115
+ end
116
+ @io = Array.new(8, @open_bus)
117
+ @cartridge = []
118
+ @video = Array.new(0x18, nil)
119
+ @video_writable = Array.new(0x18, false)
120
+ @debug_register = nil
121
+ @reads = Array.new(SEGMENTS) { Array.new(256, @open_bus) }
122
+ @writes = Array.new(SEGMENTS) { Array.new(256, @open_bus) }
123
+ reset!
124
+ map_pages
125
+ end
126
+
127
+ # The RES line sets both registers to 15.
128
+ def reset!
129
+ self.execution_segment = SYSTEM
130
+ @indirect_segment = SYSTEM
131
+ end
132
+
133
+ def power_on!
134
+ @ram.clear!
135
+ @screen.clear!
136
+ @segment_ram.each { |memory| memory&.clear! }
137
+ end
138
+
139
+ def execution_segment=(segment)
140
+ @execution_segment = segment
141
+ select_execute!
142
+ end
143
+
144
+ # Switches the CPU's accesses to the indirection segment, for the one
145
+ # access LDA (zp),Y or STA (zp),Y moves its data with.
146
+ def select_indirect!
147
+ @read_pages = @reads[@indirect_segment]
148
+ @write_pages = @writes[@indirect_segment]
149
+ end
150
+
151
+ # Switches them back to the execution segment.
152
+ def select_execute!
153
+ @read_pages = @reads[@execution_segment]
154
+ @write_pages = @writes[@execution_segment]
155
+ end
156
+
157
+ def peek(addr)
158
+ @data = @read_pages[addr >> 8].peek(addr)
159
+ end
160
+
161
+ def poke(addr, value)
162
+ @data = value
163
+ @write_pages[addr >> 8].poke(addr, value)
164
+ end
165
+
166
+ # Reads and writes +segment+ as the CPU would, wherever it executes.
167
+ def peek_bank(segment, addr) = @reads[segment][addr >> 8].peek(addr)
168
+
169
+ def poke_bank(segment, addr, value)
170
+ @writes[segment][addr >> 8].poke(addr, value)
171
+ end
172
+
173
+ # Whether the CPU runs the KERNAL: it executes in segment 15.
174
+ def kernal_mapped? = @execution_segment == SYSTEM
175
+
176
+ # Puts +chip+ at I/O page +page+, $D8 to $DF of segment 15. The chip
177
+ # hears the whole address.
178
+ def map_io(page, chip)
179
+ @io[page - 0xd8] = chip
180
+ map_pages
181
+ end
182
+
183
+ # Puts +chip+ at +count+ pages of segment 15 from +page+ on, between
184
+ # $C000 and $D7FF, over what was there: the P500's character ROM, and
185
+ # its colour RAM, which takes writes as well (+writable+). The chip
186
+ # hears the whole address.
187
+ def map_video(chip, page, count, writable: false)
188
+ count.times do |i|
189
+ @video[page + i - 0xc0] = chip
190
+ @video_writable[page + i - 0xc0] = writable
191
+ end
192
+ map_pages
193
+ end
194
+
195
+ # Puts the cartridge's ROM chips, Storage::CRTFile::Chips, at their
196
+ # load addresses in segment 15, from $1000 to $7FFF, in place of what
197
+ # was there.
198
+ def attach_cartridge(chips)
199
+ @cartridge = chips.map { |chip| ROM.new(chip.data, length: chip.data.length, start: chip.address) }
200
+ map_pages
201
+ end
202
+
203
+ def detach_cartridge
204
+ @cartridge = []
205
+ map_pages
206
+ end
207
+
208
+ # Calls the block with each byte written to $DAFF (DebugRegister).
209
+ def install_debug_register(&)
210
+ @debug_register = DebugRegister.new(&)
211
+ map_pages
212
+ end
213
+
214
+ private
215
+
216
+ def map_pages
217
+ SEGMENTS.times do |segment|
218
+ memory = @segment_ram[segment]
219
+ if memory
220
+ map(segment, memory, 0x00, 0x100)
221
+ else
222
+ map(segment, @open_bus, 0x00, 0x100)
223
+ end
224
+ end
225
+ map_system
226
+ SEGMENTS.times { |segment| map_registers(segment) }
227
+ select_execute!
228
+ end
229
+
230
+ def map_system
231
+ map(SYSTEM, @open_bus, 0x00, 0x100)
232
+ map(SYSTEM, @ram, 0x00, 0x08)
233
+ @cartridge.each { |rom| @reads[SYSTEM].fill(rom, rom.start >> 8, rom.length >> 8) }
234
+ @reads[SYSTEM].fill(@basic_rom, 0x80, 0x40)
235
+ map(SYSTEM, @screen, 0xd0, @screen_pages)
236
+ @video.each_with_index { |chip, i| map_video_page(chip, 0xc0 + i, @video_writable[i]) if chip }
237
+ @io.each_with_index { |chip, i| map(SYSTEM, chip, 0xd8 + i, 1) }
238
+ @reads[SYSTEM].fill(@kernal_rom, 0xe0, 0x20)
239
+ map_debug_register if @debug_register
240
+ end
241
+
242
+ def map_video_page(chip, page, writable)
243
+ @reads[SYSTEM][page] = chip
244
+ @writes[SYSTEM][page] = writable ? chip : @open_bus
245
+ end
246
+
247
+ def map_registers(segment)
248
+ @reads[segment][0] = Registers.new(self, @reads[segment][0])
249
+ @writes[segment][0] = Registers.new(self, @writes[segment][0])
250
+ end
251
+
252
+ def map_debug_register
253
+ @debug_register.under = @writes[SYSTEM][0xda]
254
+ @writes[SYSTEM][0xda] = @debug_register
255
+ end
256
+
257
+ def map(segment, chip, first, count)
258
+ @reads[segment].fill(chip, first, count)
259
+ @writes[segment].fill(chip, first, count)
260
+ end
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # The 6509: a 6502 core with four more address lines, which pick one of
6
+ # the Bus's sixteen segments. Every access goes to the execution
7
+ # segment, but the ones at the address LDA (zp),Y and STA (zp),Y index,
8
+ # their data and the dummy read before it, which go to the indirection
9
+ # segment: the opcode and the zero-page pointer stay in the execution
10
+ # segment. Interrupts leave both segments alone.
11
+ class CPU
12
+ include Badline::Chips::CPU::Core
13
+
14
+ def initialize(memory, debug: false)
15
+ super
16
+ @indirect = false
17
+ end
18
+
19
+ def reset!
20
+ super
21
+ @indirect = false
22
+ end
23
+
24
+ private
25
+
26
+ def op_pointer_high_y
27
+ super
28
+ @indirect = @operation == :lda || @operation == :sta
29
+ end
30
+
31
+ def op_idx_dummy
32
+ return super unless @indirect
33
+
34
+ @memory.select_indirect!
35
+ super
36
+ @memory.select_execute!
37
+ end
38
+
39
+ def op_mem_read
40
+ return super unless @indirect
41
+
42
+ @indirect = false
43
+ @memory.select_indirect!
44
+ super
45
+ @memory.select_execute!
46
+ end
47
+
48
+ def op_mem_write
49
+ return super unless @indirect
50
+
51
+ @indirect = false
52
+ @memory.select_indirect!
53
+ super
54
+ @memory.select_execute!
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class CBM2
5
+ # What the B series makes of its 6845's outputs. Each character the
6
+ # CRTC displays reads the 2K screen RAM at its memory address, MA0-MA10,
7
+ # and the character ROM's 16-line glyph for the screen code's low seven
8
+ # bits, from the set TPI1's CA picks: the first 128 glyphs while it is
9
+ # low, the second while it is high. Bit 7 of the screen code reverses
10
+ # the glyph. The ROM's row is the CRTC's raster line, RA0-RA3, so the
11
+ # 600's eight lines show the 8x8 glyphs in the first half of each and
12
+ # the 700's fourteen the 8x16 ones.
13
+ #
14
+ # The cursor is the 6845's: on the character at R14:R15, from line
15
+ # R10's low five bits to line R11, it reverses the dots. R10's bits 5
16
+ # and 6 pick its blink: 00 steady, 01 no cursor, 10 on for 8 fields and
17
+ # off for 8, 11 on for 16 and off for 16.
18
+ #
19
+ # The picture is a PET::Canvas framed as xcbm2 frames its screenshots:
20
+ # 704 dots wide, the first displayed character 32 dots in, and 266
21
+ # lines high with the first row 33 lines down on a 600, or 367 lines
22
+ # with it 8 down on a 700. Each frame paints its displayed lines from
23
+ # there down. A frame laid out otherwise than the last, its lines,
24
+ # rows or sync moved, starts on a black canvas, so nothing the old
25
+ # layout painted stays.
26
+ class CRTCVideo
27
+ LEFT = 32
28
+ WIDTH = 704
29
+
30
+ # The lines of the picture and the line its first row starts on, for
31
+ # a 600 and a 700.
32
+ FRAMES = { false => [266, 33], true => [367, 8] }.freeze
33
+
34
+ attr_reader :canvas, :frames
35
+
36
+ # Whether it paints the characters: a run that never shows the
37
+ # picture can leave it alone.
38
+ attr_accessor :render
39
+
40
+ # +characters+ is the character ROM's bytes, +screen+ the screen RAM,
41
+ # +tpi+ TPI1, whose CA picks the set, and +registers+ the CRTC's.
42
+ def initialize(characters, screen, tpi, registers, high_profile:)
43
+ @glyphs = characters
44
+ @screen = screen
45
+ @tpi = tpi
46
+ @registers = registers
47
+ height, @top = FRAMES.fetch(high_profile)
48
+ @canvas = PET::Canvas.new(WIDTH, height)
49
+ @frames = 0
50
+ @line = 0
51
+ @line_shown = false
52
+ @origin = LEFT
53
+ @layout = []
54
+ @render = true
55
+ frame_start
56
+ end
57
+
58
+ def character(address, raster, since_hsync)
59
+ @line_shown = true
60
+ y = @top + @line
61
+ return if y >= @canvas.height || !@render
62
+
63
+ @canvas.paint(@origin + (since_hsync << 3), y, row_bits(address, raster))
64
+ end
65
+
66
+ def hsync; end
67
+
68
+ def line_end
69
+ @line += 1 if @line_shown
70
+ @line_shown = false
71
+ end
72
+
73
+ def rows_end; end
74
+
75
+ # A frame starts painting from the first row's line again, its
76
+ # characters placed so the first one the line displays sits LEFT
77
+ # dots in.
78
+ def frame_start
79
+ regs = @registers
80
+ @frames += 1
81
+ @line = 0
82
+ @origin = LEFT - ((regs[0] + 1 - regs[2]) << 3)
83
+ layout = regs.values_at(0, 1, 2, 4, 5, 6, 9, 12, 13)
84
+ return if layout == @layout
85
+
86
+ @layout = layout
87
+ @canvas.clear!
88
+ end
89
+
90
+ # The picture under canvas.*, the fields counted, the line the frame
91
+ # paints next and the layout it was framed by, in a keyed snapshot
92
+ # module (BL-VIDEO).
93
+ def save_fields(out)
94
+ out.int("frames", @frames).int("line", @line).bool("line_shown", @line_shown).int("origin", @origin)
95
+ out.ints("layout", @layout)
96
+ @canvas.save_fields(out, "canvas.")
97
+ end
98
+
99
+ def load_fields(input)
100
+ @frames = input.int("frames", @frames)
101
+ @line = input.int("line", @line)
102
+ @line_shown = input.bool("line_shown", @line_shown)
103
+ @origin = input.int("origin", @origin)
104
+ @layout = input.ints("layout", @layout).dup
105
+ @canvas.load_fields(input, "canvas.")
106
+ end
107
+
108
+ private
109
+
110
+ def row_bits(address, raster)
111
+ code = @screen.peek(0xd000 | (address & 0x7ff))
112
+ glyph = (code & 0x7f) | (@tpi.ca? ? 0x80 : 0)
113
+ bits = @glyphs[(glyph << 4) | (raster & 0x0f)]
114
+ bits ^= 0xff if code >= 0x80
115
+ bits ^= 0xff if cursor?(address, raster)
116
+ bits
117
+ end
118
+
119
+ def cursor?(address, raster)
120
+ regs = @registers
121
+ return false unless address == ((regs[14] << 8) | regs[15])
122
+ return false if raster < (regs[10] & 0x1f) || raster > regs[11]
123
+
124
+ case regs[10] & 0x60
125
+ when 0x00 then true
126
+ when 0x20 then false
127
+ when 0x40 then @frames.nobits?(0x08)
128
+ else @frames.nobits?(0x10)
129
+ end
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/machine/ieee_drives"
4
+
5
+ module Badline
6
+ class CBM2
7
+ # The CBM-II's true drive, a 2031, 8050 or 8250 on its IEEE-488 bus
8
+ # (IEEEPorts), as Machine::IEEEDrives has it, at the machine's clock:
9
+ # 2 MHz on the B series, the VIC-II's on the P500. Both include it.
10
+ module IEEEDrive
11
+ include Machine::IEEEDrives
12
+
13
+ private
14
+
15
+ def ieee_host_clock_hz = timing.clock_hz
16
+ end
17
+ end
18
+ end