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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +60 -0
- data/CLAUDE.md +1 -1
- data/README.md +102 -12
- data/doc/media.md +5 -2
- data/doc/pinned-behaviour.md +61 -0
- data/doc/snapshot-format.md +376 -69
- data/doc/snapshots.md +148 -14
- data/lib/badline/cbm2/acia.rb +10 -0
- data/lib/badline/cbm2/bus.rb +263 -0
- data/lib/badline/cbm2/cpu.rb +58 -0
- data/lib/badline/cbm2/crtc_video.rb +133 -0
- data/lib/badline/cbm2/ieee_drive.rb +18 -0
- data/lib/badline/cbm2/ieee_layout.rb +69 -0
- data/lib/badline/cbm2/ieee_ports.rb +104 -0
- data/lib/badline/cbm2/ieee_traps.rb +81 -0
- data/lib/badline/cbm2/keyboard_ports.rb +60 -0
- data/lib/badline/cbm2/keyed_modules.rb +67 -0
- data/lib/badline/cbm2/keyed_state.rb +91 -0
- data/lib/badline/cbm2/model.rb +44 -0
- data/lib/badline/cbm2/p500/control_ports.rb +80 -0
- data/lib/badline/cbm2/p500/ieee_layout.rb +41 -0
- data/lib/badline/cbm2/p500/keyed_state.rb +85 -0
- data/lib/badline/cbm2/p500/model.rb +39 -0
- data/lib/badline/cbm2/p500/pages.rb +57 -0
- data/lib/badline/cbm2/p500.rb +262 -0
- data/lib/badline/cbm2/sid_page.rb +19 -0
- data/lib/badline/cbm2.rb +267 -0
- data/lib/badline/{plus4 → chips}/acia/frames.rb +1 -1
- data/lib/badline/chips/acia/keyed_state.rb +47 -0
- data/lib/badline/chips/acia.rb +307 -0
- data/lib/badline/chips/crtc.rb +3 -2
- data/lib/badline/chips/riot/fast_forward.rb +72 -0
- data/lib/badline/chips/riot/keyed_parts.rb +69 -0
- data/lib/badline/chips/riot/timer.rb +80 -0
- data/lib/badline/chips/riot.rb +162 -0
- data/lib/badline/chips/rriot/keyed_state.rb +30 -0
- data/lib/badline/chips/rriot.rb +132 -0
- data/lib/badline/chips/tpi/keyed_state.rb +38 -0
- data/lib/badline/chips/tpi.rb +233 -0
- data/lib/badline/chips/vic/bank.rb +60 -5
- data/lib/badline/chips/vic/sprite.rb +4 -3
- data/lib/badline/drives/datasette/keyed_state.rb +9 -1
- data/lib/badline/drives/datasette.rb +27 -3
- data/lib/badline/drives/model8050/disk.rb +163 -0
- data/lib/badline/drives/model8050/fdc_bus.rb +102 -0
- data/lib/badline/drives/model8050/idle.rb +177 -0
- data/lib/badline/drives/model8050/ieee_port.rb +147 -0
- data/lib/badline/drives/model8050/ip_bus.rb +93 -0
- data/lib/badline/drives/model8050/keyed_state.rb +160 -0
- data/lib/badline/drives/model8050/mechanism.rb +230 -0
- data/lib/badline/drives/model8050/parts.rb +37 -0
- data/lib/badline/drives/model8050.rb +243 -0
- data/lib/badline/frontend/app.rb +10 -3
- data/lib/badline/frontend/boot.rb +1 -1
- data/lib/badline/frontend/controls.rb +51 -15
- data/lib/badline/frontend/gamepads.rb +3 -3
- data/lib/badline/frontend/keys.rb +61 -2
- data/lib/badline/frontend/media_slots.rb +1 -1
- data/lib/badline/frontend/menu/menu_media.rb +14 -11
- data/lib/badline/frontend/menu/menu_pages.rb +18 -4
- data/lib/badline/frontend/menu/menu_ports.rb +40 -4
- data/lib/badline/frontend/menu/pause_menu.rb +1 -1
- data/lib/badline/frontend/pet_keyboard.rb +23 -7
- data/lib/badline/frontend/timeline.rb +19 -10
- data/lib/badline/kernal_trap/dos/disk_commands.rb +80 -0
- data/lib/badline/kernal_trap/dos/status.rb +3 -0
- data/lib/badline/kernal_trap/dos.rb +18 -5
- data/lib/badline/kernal_trap/ieee.rb +4 -2
- data/lib/badline/kernal_trap/ieee_layout.rb +95 -43
- data/lib/badline/machine/ieee_drives.rb +194 -0
- data/lib/badline/machine/settling.rb +4 -0
- data/lib/badline/machine.rb +23 -6
- data/lib/badline/media/cbm2_media.rb +29 -0
- data/lib/badline/media/program.rb +6 -3
- data/lib/badline/media/true_drive.rb +38 -9
- data/lib/badline/media.rb +15 -6
- data/lib/badline/options/family.rb +15 -3
- data/lib/badline/options/help.rb +16 -2
- data/lib/badline/options/table.rb +18 -4
- data/lib/badline/options/validation.rb +27 -3
- data/lib/badline/options.rb +21 -7
- data/lib/badline/pet/cassettes.rb +22 -1
- data/lib/badline/pet/ieee_drive.rb +5 -64
- data/lib/badline/pet/ieee_ports.rb +7 -0
- data/lib/badline/pet.rb +15 -4
- data/lib/badline/plus4/acia.rb +4 -298
- data/lib/badline/plus4/keyed_parts.rb +0 -44
- data/lib/badline/plus4.rb +1 -0
- data/lib/badline/roms/8050/dos-2.7.rom +0 -0
- data/lib/badline/roms/8050/fdc-901869-01.rom +0 -0
- data/lib/badline/roms/8050/fdc-901885-04.rom +0 -0
- data/lib/badline/roms/README +111 -0
- data/lib/badline/roms/cbm2/basic128-02b.rom +0 -0
- data/lib/badline/roms/cbm2/basic128.rom +0 -0
- data/lib/badline/roms/cbm2/basic256.rom +0 -0
- data/lib/badline/roms/cbm2/characters-600.rom +0 -0
- data/lib/badline/roms/cbm2/characters-700.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-03b.rom +0 -0
- data/lib/badline/roms/cbm2/kernal-04a.rom +0 -0
- data/lib/badline/roms/cbm2/p500-basic.rom +0 -0
- data/lib/badline/roms/cbm2/p500-kernal.rom +0 -0
- data/lib/badline/snapshot/cbm2_setup.rb +53 -0
- data/lib/badline/snapshot/container.rb +7 -2
- data/lib/badline/snapshot/field_doc/markdown.rb +4 -2
- data/lib/badline/snapshot/field_doc.rb +38 -3
- data/lib/badline/snapshot/image.rb +62 -9
- data/lib/badline/snapshot/vice/cbm2.rb +93 -0
- data/lib/badline/snapshot/vice/cbm2_acia.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_crtc.rb +127 -0
- data/lib/badline/snapshot/vice/cbm2_mains.rb +49 -0
- data/lib/badline/snapshot/vice/cbm2_mem.rb +75 -0
- data/lib/badline/snapshot/vice/cbm2_restore.rb +204 -0
- data/lib/badline/snapshot/vice/cbm2_tpi.rb +58 -0
- data/lib/badline/snapshot/vice/cias.rb +13 -9
- data/lib/badline/snapshot/vice/p500.rb +96 -0
- data/lib/badline/snapshot/vice/p500_restore.rb +130 -0
- data/lib/badline/snapshot/vice/p500_vicii.rb +166 -0
- data/lib/badline/snapshot/vice/pet.rb +7 -3
- data/lib/badline/snapshot/vice/pet_cpu.rb +5 -2
- data/lib/badline/snapshot/vice/pet_crtc.rb +10 -15
- data/lib/badline/snapshot/vice/pet_drive.rb +180 -0
- data/lib/badline/snapshot/vice/pet_drive8050.rb +216 -0
- data/lib/badline/snapshot/vice/pet_restore.rb +70 -6
- data/lib/badline/snapshot/vice/pet_via.rb +6 -5
- data/lib/badline/snapshot/vice/sid_registers.rb +2 -1
- data/lib/badline/snapshot/vice/vicii_import.rb +10 -6
- data/lib/badline/snapshot/vice.rb +3 -0
- data/lib/badline/snapshot.rb +11 -3
- data/lib/badline/storage/crt_file.rb +7 -1
- data/lib/badline/storage/d64_image.rb +7 -0
- data/lib/badline/storage/d80_image.rb +17 -1
- data/lib/badline/storage/d81_image.rb +14 -0
- data/lib/badline/storage/disk_image/bam.rb +18 -3
- data/lib/badline/storage/disk_image/formatting.rb +69 -0
- data/lib/badline/storage/disk_image/writing.rb +35 -0
- data/lib/badline/storage/disk_image.rb +2 -0
- data/lib/badline/storage.rb +1 -0
- data/lib/badline/support/cbm2_checkpoint.rb +48 -0
- data/lib/badline/support/p500_checkpoint.rb +35 -0
- data/lib/badline/version.rb +1 -1
- data/lib/badline.rb +1 -0
- 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,
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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,
|
|
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
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
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,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
|