badline 0.10.0 → 0.11.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 (78) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +34 -0
  3. data/CLAUDE.md +5 -3
  4. data/README.md +60 -5
  5. data/doc/library.md +15 -1
  6. data/doc/pinned-behaviour.md +75 -0
  7. data/doc/snapshots.md +19 -3
  8. data/lib/badline/address_bus/fittings.rb +38 -0
  9. data/lib/badline/address_bus/pla.rb +38 -21
  10. data/lib/badline/address_bus/roms.rb +12 -5
  11. data/lib/badline/address_bus.rb +20 -2
  12. data/lib/badline/c128/banks.rb +35 -0
  13. data/lib/badline/c128/bus.rb +347 -0
  14. data/lib/badline/c128/bus_state.rb +53 -0
  15. data/lib/badline/c128/color_lines.rb +39 -0
  16. data/lib/badline/c128/cpu.rb +10 -0
  17. data/lib/badline/c128/keys.rb +35 -0
  18. data/lib/badline/c128/mmu.rb +182 -0
  19. data/lib/badline/c128/mmu_pages.rb +105 -0
  20. data/lib/badline/c128/model.rb +39 -0
  21. data/lib/badline/c128/modes.rb +48 -0
  22. data/lib/badline/c128/saved_state.rb +120 -0
  23. data/lib/badline/c128/vdc/memory.rb +54 -0
  24. data/lib/badline/c128/vdc/painter.rb +143 -0
  25. data/lib/badline/c128/vdc/raster.rb +97 -0
  26. data/lib/badline/c128/vdc/window.rb +215 -0
  27. data/lib/badline/c128/vdc.rb +268 -0
  28. data/lib/badline/c128.rb +320 -0
  29. data/lib/badline/chrout_trap.rb +3 -0
  30. data/lib/badline/computer/attachments.rb +4 -13
  31. data/lib/badline/computer/kernal_traps.rb +29 -0
  32. data/lib/badline/computer.rb +13 -8
  33. data/lib/badline/frontend/app.rb +34 -21
  34. data/lib/badline/frontend/boot.rb +31 -1
  35. data/lib/badline/frontend/controls.rb +11 -0
  36. data/lib/badline/frontend/keys.rb +28 -1
  37. data/lib/badline/frontend/machine_window.rb +16 -13
  38. data/lib/badline/frontend/menu_pages.rb +6 -1
  39. data/lib/badline/frontend/menu_ports.rb +1 -1
  40. data/lib/badline/frontend/pause_menu.rb +1 -0
  41. data/lib/badline/frontend/screen.rb +33 -8
  42. data/lib/badline/frontend/screens.rb +90 -0
  43. data/lib/badline/frontend/timeline.rb +15 -4
  44. data/lib/badline/frontend.rb +1 -0
  45. data/lib/badline/kernal_trap/file.rb +1 -1
  46. data/lib/badline/kernal_trap/layout.rb +58 -8
  47. data/lib/badline/kernal_trap/load.rb +1 -1
  48. data/lib/badline/kernal_trap/save.rb +1 -1
  49. data/lib/badline/kernal_trap/serial.rb +5 -0
  50. data/lib/badline/keyboard.rb +12 -1
  51. data/lib/badline/machine.rb +16 -7
  52. data/lib/badline/media.rb +8 -6
  53. data/lib/badline/model.rb +29 -12
  54. data/lib/badline/options/event.rb +5 -1
  55. data/lib/badline/options/family.rb +52 -0
  56. data/lib/badline/options/help.rb +5 -1
  57. data/lib/badline/options/table.rb +8 -2
  58. data/lib/badline/options/validation.rb +14 -1
  59. data/lib/badline/options.rb +20 -19
  60. data/lib/badline/roms/README +51 -0
  61. data/lib/badline/roms/c128/basic.rom +0 -0
  62. data/lib/badline/roms/c128/character.rom +0 -0
  63. data/lib/badline/roms/c128/kernal.rom +0 -0
  64. data/lib/badline/roms/c64gs.rom +0 -0
  65. data/lib/badline/roms/kernal-pet64.rom +0 -0
  66. data/lib/badline/snapshot/container.rb +2 -0
  67. data/lib/badline/snapshot/image.rb +14 -0
  68. data/lib/badline/snapshot/setup.rb +15 -10
  69. data/lib/badline/snapshot/state.rb +5 -4
  70. data/lib/badline/snapshot/vice.rb +2 -1
  71. data/lib/badline/snapshot.rb +9 -2
  72. data/lib/badline/version.rb +1 -1
  73. data/lib/badline/vic/bank.rb +2 -1
  74. data/lib/badline/vic/display_state.rb +12 -0
  75. data/lib/badline/vic/registers.rb +3 -0
  76. data/lib/badline/vic.rb +124 -9
  77. data/lib/badline.rb +1 -0
  78. metadata +27 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bd764dccd3b8af3f8e0dcbb7d17debc3fceb24a9899f80871ebbf184ffefd65b
4
- data.tar.gz: 46a62d801813476242a407c09ab3ff4fec1126d10bfee134047f18e90fd86955
3
+ metadata.gz: 3f8a451e4b936c57d5cf39cc7253481e5c8deff31898cb015641ed54246a68da
4
+ data.tar.gz: 8d1e5c8aab0d9352e31a5f7b870896aaec254dbcd55c823fe24496d820b2befc
5
5
  SHA512:
6
- metadata.gz: 82a62fb4a3de3ef65e123bacf22ba300efe628f1bde38815dc950a39034d8c2b9423de79b3805fce84e19bcbaa8100a3e67488d5ef0a3ecb2de6434689886883
7
- data.tar.gz: 595b19f034d708db60089e707507e74281059d271527fe61e0a205d9e8e0853a4f6b4d077aeed648f59a86d06bf31d3ee6e5902345fdb52d5fb36ded8de94da3
6
+ metadata.gz: d97d1b2876eafb8a51cd290c651904c798be975e0f3f7ea9ba4960a3dcaeebb6a76cc9cdad8bb5bf3ac5c732af3ec0eeddb779916b9e793292350d0749ed730f
7
+ data.tar.gz: 2b7f0307a605b0270490ae3155c1671e1197473a0c22edf836bce953c08ff8884d1b09ca92fd7b3bc8c4038de63b66d5b92e3e82297ecfaddd5cde592ef74493
data/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.11.0](https://github.com/elektronaut/badline/compare/v0.10.1...v0.11.0) (2026-10-09)
4
+
5
+
6
+ ### Features
7
+
8
+ * add the C128 in C64 mode, headless ([17462eb](https://github.com/elektronaut/badline/commit/17462ebb94b27fe59c7cee8c51e82be340158979))
9
+ * boot badline c128 in C128 mode, with --c64 holding C= through the reset ([fbb57e5](https://github.com/elektronaut/badline/commit/fbb57e5e46bd1178bb63b2feedee6776a5e4cc57))
10
+ * boot the C128 in C128 mode through its own MMU and KERNAL ([8bdd810](https://github.com/elektronaut/badline/commit/8bdd810c03cb916b3484fabbf84172098af26835))
11
+ * bundle the C128's BASIC 7.0, KERNAL and character ROMs ([25329f8](https://github.com/elektronaut/badline/commit/25329f83e7a5e07c4df3ea49b85528184fd7f313))
12
+ * follow the C128 between its 40 and 80 column screens, with 40/80 DISPLAY on F6 and ESC on Esc ([4099600](https://github.com/elektronaut/badline/commit/4099600d413fa617dc7cea63ac80302b759c9f08))
13
+ * give the C128's VDC its 80 column display ([80b0831](https://github.com/elektronaut/badline/commit/80b083138629c6410c18a55b48f3e59d22d466f1))
14
+ * run the C128 in the window as badline c128, in both builds, with its VDC, keys and snapshots ([d58eaf6](https://github.com/elektronaut/badline/commit/d58eaf606932527fda9fd5de308c4010bd9fc4cf))
15
+ * run the C128's 2 MHz mode and TEST bit cycle by cycle ([9a0a483](https://github.com/elektronaut/badline/commit/9a0a483f775b2d06fabb32bd99988c1976753fa3))
16
+ * serve device 8 and CHROUT through the C128 KERNAL's traps in C128 mode ([76c1764](https://github.com/elektronaut/badline/commit/76c1764a54d68960194126e50d228a979ace93a2))
17
+
18
+ ## [0.10.1](https://github.com/elektronaut/badline/compare/v0.10.0...v0.10.1) (2026-10-08)
19
+
20
+
21
+ ### Features
22
+
23
+ * add the C64GS as --model c64gs ([ea04c0d](https://github.com/elektronaut/badline/commit/ea04c0d03b3a8c34426ff68a80ae0fc36db8123b))
24
+ * add the C64GS as --model c64gs ([e533932](https://github.com/elektronaut/badline/commit/e533932360befa88d05cf1369cc417f40cababa7))
25
+ * add the MAX Machine as --model ultimax ([fc48c40](https://github.com/elektronaut/badline/commit/fc48c40035892409175bf3d8fd08add6af0bbac9))
26
+ * add the MAX Machine as --model ultimax ([22aca64](https://github.com/elektronaut/badline/commit/22aca643abc0746e362209fb48977cee94c2b985))
27
+ * add the PET 64 as --model pet64 ([e17c0bd](https://github.com/elektronaut/badline/commit/e17c0bd0d13d15caf6d0b5377a5ac0a5756847cf))
28
+ * add the PET 64 as --model pet64 ([2320ac7](https://github.com/elektronaut/badline/commit/2320ac7cd41ef6a4865e56d433585ddebb7ef01f))
29
+ * bundle the C64GS's ROM, 390852-01 ([e31b280](https://github.com/elektronaut/badline/commit/e31b280044c18e639cc65df62a8b39d97dd874be))
30
+ * bundle the PET 64's KERNAL, 901246-01 ([9a029ec](https://github.com/elektronaut/badline/commit/9a029ecfe25c0d9a806d8858253c4c172e175b7f))
31
+
32
+
33
+ ### Miscellaneous Chores
34
+
35
+ * release 0.10.1 ([9d6f6ba](https://github.com/elektronaut/badline/commit/9d6f6ba71a647f24f00a830fbc772531fbcb4d92))
36
+
3
37
  ## [0.10.0](https://github.com/elektronaut/badline/compare/v0.9.0...v0.10.0) (2026-10-08)
4
38
 
5
39
 
data/CLAUDE.md CHANGED
@@ -50,7 +50,7 @@ requires only the namespace file.
50
50
  | rspec (`spec/`) | `bundle exec rspec` | Unit behaviour, all of `lib/` |
51
51
  | SingleStepTests 65x02 | `rake test` (100 sampled cases per opcode) | CPU, per-cycle bus traces |
52
52
  | Wolfgang Lorenz suite | `bin/lorenz` | CPU, CIA, interrupts |
53
- | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, cartridges with `--carts`, the true 1541 drive with `--drive`, and the VIC-20 with `--vic20` |
53
+ | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, cartridges with `--carts`, the true 1541 drive with `--drive`, the VIC-20 with `--vic20`, and the C128 in C64 mode with `--c128c64` |
54
54
  | VICE SID testprogs | `bin/sidtests` | SID |
55
55
  | Drive scenarios | `bin/drive_scenarios` | The true 1541 running the DOS ROM: save, format, the error channel, idle, write protect and autostart |
56
56
  | CIA offline grids | `bundle exec rspec --tag slow spec/badline/cia` | CIA timers and shift register, against a bare CIA in about 2 min |
@@ -144,7 +144,7 @@ overrides that, capped by the core count, but keep the default, because
144
144
  other worktrees share the machine. Whole runs at 4 shards on an M-series
145
145
  laptop take 15 min for `testbench` (`VICII/`), 11 for `testbench-cia`, 1 for
146
146
  `testbench-interrupts`, 37 for `testbench-irqdma`, 11 for
147
- `testbench-cpu`, 2 for `testbench-carts`, about 25 for `testbench-expansions` and under 1 for `testbench-vic20`. `sid` takes about 12 min. `lorenz` chains itself and takes
147
+ `testbench-cpu`, 2 for `testbench-carts`, about 25 for `testbench-expansions`, under 1 for `testbench-vic20` and about 30 for `testbench-c128c64`. `sid` takes about 12 min. `lorenz` chains itself and takes
148
148
  about 2.5 h on CI whole. `rake regression:lorenz-1` to `lorenz-4` run it as
149
149
  four stretches of about 40 min each, and they can run side by side.
150
150
  `test/baselines/README.md` has the full table. A killed `bin/testbench` run
@@ -196,7 +196,9 @@ the 6526A) for anything the interrupt register or the CIA model reaches; cartrid
196
196
  `testbench-carts`, plus `testbench-expansions` for banking; GEO-RAM, +60K,
197
197
  +256K, the REU or the VIC's BA line the REU follows → `testbench-expansions`; the 1541 drive, VIA or IEC bus →
198
198
  `testbench-drive` (`bin/testbench --drive`) and `drive-scenarios` (`bin/drive_scenarios <filter>`), plus `testbench-vic20` for the VIA; the VIC-20 (`vic20/`) →
199
- `testbench-vic20` (`bin/testbench --vic20`); SID → `sid`, plus `sid-8580` for anything the 8580
199
+ `testbench-vic20` (`bin/testbench --vic20`); the C128 (`c128.rb`, `c128/`), or
200
+ the VIC-IIe, CPU or PLA code it shares with the C64 → `testbench-c128c64`
201
+ (`bin/testbench --c128c64`); SID → `sid`, plus `sid-8580` for anything the 8580
200
202
  model reaches (`bin/sidtests --sid 8580`). Leave the Lorenz chain to CI,
201
203
  unless your code has a rule in `doc/pinned-behaviour.md` that names Lorenz
202
204
  tests: the interrupt polling, CPU port and CIA timer rules. Run just the
data/README.md CHANGED
@@ -13,7 +13,7 @@ are modelled at the cycle level.
13
13
  It runs programs, disk and tape images, cartridges and SID tunes, and
14
14
  the SDL2 front end supports the keyboard, joysticks, game controllers,
15
15
  paddles and a 1351 mouse. It also runs a PAL VIC-20, as
16
- `badline vic20`. See [What's emulated](#whats-emulated) for the details.
16
+ `badline vic20`, and the C128, in C128 mode on 40 or 80 columns and in C64 mode, as `badline c128`. See [What's emulated](#whats-emulated) for the details.
17
17
 
18
18
  It comes in two builds of the same emulator:
19
19
 
@@ -69,7 +69,8 @@ from it.
69
69
 
70
70
  The first argument can name the machine, as in `badline c64 game.prg`.
71
71
  The C64 is the default, and `--model` picks which C64. See
72
- [The VIC-20](#the-vic-20) for `badline vic20`.
72
+ [The VIC-20](#the-vic-20) for `badline vic20` and [The C128](#the-c128)
73
+ for `badline c128`.
73
74
 
74
75
  `badline-ruby` takes the same media and the same options, with the
75
76
  differences noted in the table. `--help` lists the options for either.
@@ -85,8 +86,9 @@ differences noted in the table. `--help` lists the options for either.
85
86
  | `--sid 6581`, `--sid 8580` | Fit the older or newer SID. `--sid auto`, the default, takes a `.sid` tune's own |
86
87
  | `--ram SIZE` | Give a VIC-20 the RAM expansion `SIZE`: `unexpanded`, `3k`, `8k`, `16k`, `24k`, `32k` or `all`. Without it, the VIC-20 gets the expansion its media needs. See [The VIC-20](#the-vic-20) |
87
88
  | `--reu SIZE` | Plug in a RAM Expansion Unit of `SIZE` K: 128, 256, 512 (a 1750) or up to 16384 |
88
- | `--model NAME` | Run another model of the machine, named as in VICE. For the C64: `c64` (the default, a PAL C64 with the 6569 VIC-II, 6526 CIAs and the 6581 SID), `c64c` (the PAL C64C, with the 8565, 6526As and the 8580), `ntsc` (the 6567R8), `newntsc` (the NTSC C64C, with the 8562, 6526As and the 8580), `oldntsc` (the first NTSC C64s' 6567R56A), `drean` (the Drean C64 of Argentina, PAL-N with the 6572) or `sx64` (the portable SX-64, with its own KERNAL and no datasette). `--sid` or a `.sid` tune's own SID takes the model's place |
89
+ | `--model NAME` | Run another model of the machine, named as in VICE. For the C64: `c64` (the default, a PAL C64 with the 6569 VIC-II, 6526 CIAs and the 6581 SID), `c64c` (the PAL C64C, with the 8565, 6526As and the 8580), `ntsc` (the 6567R8), `newntsc` (the NTSC C64C, with the 8562, 6526As and the 8580), `oldntsc` (the first NTSC C64s' 6567R56A), `drean` (the Drean C64 of Argentina, PAL-N with the 6572), `sx64` (the portable SX-64, with its own KERNAL and no datasette), `pet64` (the PET 64 or Educator 64, a PAL C64 in a PET's case with its own KERNAL and a green monochrome monitor) `c64gs` (the C64GS games console, a PAL C64C with its own BASIC and KERNAL, and no keyboard or datasette) or `ultimax` (the MAX Machine of Japan, NTSC with 2K of RAM, one CIA and no ROMs, which runs only a cartridge). `--sid` or a `.sid` tune's own SID takes the model's place |
89
90
  | `--ntsc` | Run an NTSC C64, as `--model ntsc` does |
91
+ | `--c64` | Start a C128 in C64 mode, holding `C=` at power-on. See [The C128](#the-c128) |
90
92
  | `--no-sound` | Don't play the SID (`badline` only, where sound is on by default) |
91
93
  | `--sound` | Play the SID (`badline-ruby`, where sound is off by default) |
92
94
  | `--no-vsync` | Pace the window by a timer, or by the sound while it plays, instead of the display's vsync |
@@ -163,6 +165,55 @@ goes in as a `.crt` does. Disks go through the KERNAL traps or, with
163
165
  save and load the VIC-20 too, in badline's own snapshots: xvic's `.vsf`
164
166
  files don't load.
165
167
 
168
+ ## The C128
169
+
170
+ `badline c128` runs a C128 in C128 mode, with BASIC 7.0, and takes the
171
+ same media as the C64:
172
+
173
+ ```sh
174
+ badline c128 # READY., in BASIC 7.0
175
+ badline c128 game.prg # Load and run a program
176
+ badline c128 game.d64 # Mount a disk image as device 8 and LOAD"*",8,1 it
177
+ badline c128 --c64 # Hold C= at power-on to start in C64 mode
178
+ badline c128 --model c128dcr game.crt
179
+ ```
180
+
181
+ `--c64` holds `C=` down through the reset, so the C128 KERNAL starts C64
182
+ mode, as on a real C128. A program that loads at `$0801`, where C64 BASIC
183
+ starts, and a `.sid` tune start C64 mode the same way, and so does a C64
184
+ cartridge, through the KERNAL. `GO64` gets there from BASIC 7.0. A
185
+ program that loads at `$1C01`, where BASIC 7.0 starts, runs, and other
186
+ programs load at their own address. The disk in device 8 is served
187
+ through the KERNAL traps in either mode. The C128's Z80 isn't emulated:
188
+ the 8502 starts the machine. `--model` picks the board: `c128` (the default, the flat C128
189
+ and the plastic C128D, PAL, with the 6581 SID and a VDC with 16K of
190
+ RAM), `c128dcr` (the metal C128DCR, with the 8580, 6526As and a VDC with
191
+ 64K), and `c128ntsc` and `c128dcrntsc` for their NTSC versions. `--sid`
192
+ takes the model's place. Without `c128`, a program that loads at
193
+ `$1C01`, where C128 BASIC starts, picks the C128 too, and a `.d71`
194
+ stays on the C64.
195
+
196
+ The window shows the screen the C128 prints to, and follows it when
197
+ it moves: to the VDC's 80 columns after `GRAPHIC 5`, `ESC X` or a reset
198
+ with `40/80 DISPLAY` locked down, and back to the VIC-IIe's 40 columns
199
+ after `GRAPHIC 0` or `ESC X` again. In C64 mode it shows the VIC-IIe.
200
+ `F8` switches the window to the other screen, as a monitor's 40/80
201
+ switch does, until the C128 next moves. The window takes the VDC's
202
+ size, which its registers set, a dot to a pixel with its lines doubled.
203
+ Only the chip shown is drawn.
204
+
205
+ Besides the C64's keys, the C128's own keys sit on their host keys: the
206
+ keypad on the keypad, `HELP` on `Insert` (or `Help`), `ALT` on the right
207
+ `Alt`, `NO SCROLL` on `Scroll Lock`, `CAPS LOCK` on `Caps Lock` and
208
+ `40/80 DISPLAY` on `F6` or `Pause`, both of which lock it down and up
209
+ like the real key. In C128 mode `Esc` is `ESC` and `RUN/STOP` is on
210
+ `Page Down` (`Fn`-`Down` on a Mac), and in C64 mode `Esc` stays
211
+ `RUN/STOP`. In C64 mode only a program that scans the C128's keys
212
+ itself sees them. `TAB`, `LINE FEED` and the separate cursor keys have
213
+ no host key yet: `Tab` and the arrows keep their C64 uses.
214
+ `F11`, `F12` and the pause menu save and load the C128 in badline's own
215
+ snapshots, and x128's `.vsf` files don't load.
216
+
166
217
  ## Snapshots
167
218
 
168
219
  `F11` quicksaves the whole machine to the next of five slots in
@@ -200,7 +251,7 @@ types `"`. These keys have no same-named host key:
200
251
 
201
252
  | C64 | Host |
202
253
  |-----|------|
203
- | `RUN/STOP` | `Escape` |
254
+ | `RUN/STOP` | `Escape`, or on the C128 `Page Down` (`Esc` is `ESC` in C128 mode) |
204
255
  | `CLR/HOME` | `Home` |
205
256
  | `INST/DEL` | `Backspace` |
206
257
  | `CRSR ⇔` / `CRSR ⇕` | `Right` / `Down`, and `Left` / `Up` for the shifted directions |
@@ -258,7 +309,7 @@ too.
258
309
  | Expansion port | The cartridge: insert, remove, and its freeze button if it has one |
259
310
  | Ports | The device in each control port, and where the keys go |
260
311
  | Sound | Mute, and the SID's model |
261
- | Power | Reset, power cycle and quit |
312
+ | Power | Reset, power cycle and quit, and on a C128 its model and mode |
262
313
 
263
314
  INSERT opens a file browser. A disk's set is the `.m3u` or `.vfl` list it
264
315
  was opened from, or one in its folder that lists it. Without one, it comes
@@ -294,6 +345,10 @@ program or `.sid` dropped on it opens the menu to ask first.
294
345
  both 6522 VIAs, the keyboard, the joystick and RESTORE, RAM
295
346
  expansions up to 35K (`--ram`), cartridges, the datasette and the
296
347
  serial bus to device 8 (`badline vic20`).
348
+ - **C128**: C128 mode with BASIC 7.0 and C64 mode, on the 8502 with its
349
+ port, FAST mode, the VIC-IIe with its extra keyboard lines, the 8722
350
+ MMU, the 8563 or 8568 VDC with its 80 column display, and 128K of RAM
351
+ (`badline c128`). The Z80 and the 1571 aren't emulated yet.
297
352
  - **1541**: an emulated drive running its own DOS (`--true-drive`).
298
353
  - **Cartridges**: standard 8K, 16K and Ultimax, Simons' BASIC, Ocean,
299
354
  Fun Play / Power Play, Super Games, Epyx FastLoad, Westermann
data/doc/library.md CHANGED
@@ -42,6 +42,19 @@ model (`Badline::Model::ALL`):
42
42
  - `kernal: :sx64` fits the SX-64's KERNAL, and `datasette: false`
43
43
  leaves the cassette port empty, so inserting a tape raises
44
44
  `Badline::Datasette::Missing`. The SX-64 has both.
45
+ - `kernal: :pet64` fits the PET 64's KERNAL, and `board: :pet64` its
46
+ green monochrome monitor: the VIC's `palette` is
47
+ `Badline::VIC::GREEN_PALETTE`, each colour's luminance in green. The
48
+ PET 64 has both.
49
+ - `kernal: :gs` fits the C64GS's BASIC and KERNAL, and `board: :gs` its
50
+ case, which has no keyboard: `computer.keyboard` takes no keys. The
51
+ C64GS has both, and no datasette.
52
+ - `board: :max` builds the MAX Machine's board: 2K of RAM at
53
+ `$0000-$07FF`, no ROMs mapped, CIA 1 alone, answering all through
54
+ `$DC00-$DFFF` but for the I/O 1 page a cartridge takes, and Ultimax
55
+ mode with or without a cartridge. CIA 2 is never on the bus, so the
56
+ VIC stays in bank 0. `Badline::Model::ULTIMAX` runs it on
57
+ `Badline::Region::NTSC_OLD`.
45
58
 
46
59
  ## Cartridges
47
60
 
@@ -63,7 +76,8 @@ Its contents are lost when the emulator quits.
63
76
  The KERNAL, BASIC and character ROMs come with the gem. To run other
64
77
  images, such as a patched KERNAL, point `BADLINE_ROM_PATH` at a
65
78
  directory that holds `kernal.rom`, `basic.rom` and `character.rom`, plus
66
- `kernal-sx64.rom` for the SX-64 and `eapi/eapi-am29f040-14` if you
79
+ `kernal-sx64.rom` for the SX-64, `kernal-pet64.rom` for the PET 64,
80
+ `c64gs.rom` for the C64GS and `eapi/eapi-am29f040-14` if you
67
81
  attach EasyFlash cartridges.
68
82
  `Badline.rom_path = dir` does the same before a `Badline::Computer` is
69
83
  built, and `nil` restores the bundled set.
@@ -25,6 +25,7 @@ only catches the rows that happen to move.
25
25
  - [VIC phi1 bus](#vic-phi1-bus)
26
26
  - [VIC light pen](#vic-light-pen)
27
27
  - [VIC-II 8565](#vic-ii-8565)
28
+ - [VIC-IIe 2 MHz and TEST bit](#vic-iie-2-mhz-and-test-bit)
28
29
  - [VIC-II NTSC](#vic-ii-ntsc)
29
30
  - [VIC-II 6572 (Drean)](#vic-ii-6572-drean)
30
31
  - [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
@@ -926,6 +927,80 @@ What the 8565 references don't settle, and so what stays as it is:
926
927
  6569's idle access, without the 6569's `$38ff` read where a DMA delay
927
928
  starts.
928
929
 
930
+ ## VIC-IIe 2 MHz and TEST bit
931
+
932
+ The C128's VIC-IIe (`:mos8566`, `:mos8564`) adds $D030: bit 0 (FAST)
933
+ runs the 8502 at 2 MHz, and bit 1 (TEST) clocks the raster counter in
934
+ every cycle. `C128#cycle!` and `#clock_fast` drive both, through
935
+ `VIC#take_cpu_bus`, `#test_step!` and `#refresh_cycle?`. The oracles are
936
+ the testprogs' own references for `c128/2mhzVIC` (3 rows, from the
937
+ author's machine) and `c128/d030tester` (33 rows), and the real-machine
938
+ photos in `c128/2mhztest`. Those are C128-mode programs that switch to C64
939
+ mode as they start, so no testbench list runs them yet: `spec/badline/
940
+ c128_2mhz_spec.rb` runs six d030tester builds from their machine code at
941
+ $1C0E and checks the readings they print.
942
+
943
+ - FAST and TEST take hold in the second cycle after the write that sets
944
+ or clears them. Pinned by d030tester's "Lines cut": STA/STX $D030 keeps
945
+ TEST on for 4 cycles, but for 3 when the STA also sets FAST, because
946
+ the STX then runs its operand fetches at 2 MHz. With the delay at 0
947
+ the FAST builds cut 2 lines, not 3. Spec guard: `c128_spec.rb`.
948
+ - In FAST mode the CPU runs in both halves of every cycle but Bauer's
949
+ refresh cycles 11-15, whose phi1 stays with the VIC: 121 CPU cycles a
950
+ PAL line. BA doesn't halt it. Pinned by `2mhz-vic-hires`,
951
+ `-multicolor` and `-extended`, whose drawing code assumes 121 cycles a
952
+ line.
953
+ - An access to $D000-$DFFF waits for phi2: one in phi1 takes the whole
954
+ cycle. Pinned by `timing-change0`, whose `INC $D020` takes 8 half
955
+ cycles (its comment says "6 cycles + 2"). The machine finds out after
956
+ the CPU's step that it reached I/O, so `CPU::Core` needn't say where
957
+ its next access goes.
958
+ - The VIC's accesses in a FAST cycle latch the CPU's bytes: the g-access,
959
+ or idle access, the byte of phi1, and a c-access the byte of phi2, as
960
+ one made before AEC does on the C64: $FF for the video matrix and the
961
+ byte's low nibble for colour. Pinned by `2mhz-vic-*` (the operands are
962
+ the pixels and the opcodes' low nibbles the colours) and by the "VIC
963
+ internal data" row of d030tester, which shows char $FF's glyph in
964
+ slow-mode lines after a FAST bad line.
965
+ - A c-access in a cycle where the CPU reads or writes a VIC register
966
+ takes that byte for the video matrix as well. Pinned by d030tester:
967
+ `STX $D030` with X=0 leaves an `@` (McCabe's readme: "the `@`
968
+ represents a 0 written to $D030"), and the `DEC $D030` build (2mhzdec)
969
+ leaves char $FD, the byte it reads.
970
+ - In the phi1 an I/O access waits through, the VIC sees the byte the CPU
971
+ writes, or the last byte the bus held for a read. Pinned by d030tester
972
+ and 2mhzdec, whose g-access there shows $00 (X) and $D0 (the operand's
973
+ high byte).
974
+ - AEC can't follow BA down in FAST mode, so on a bad line it falls three
975
+ cycles after the last FAST cycle, and the c-accesses before it read
976
+ $FF and the halted CPU's nibble (`DisplayState#keep_bus`). Pinned by
977
+ d030tester's "VIC internal data" row, where three cells after the FAST
978
+ cycles show colour 0 (the halted read's $D0) and the rest the screen.
979
+ - The TEST bit steps the raster counter ahead of each cycle it is set
980
+ in, except the line's last cycle, which steps it anyway, and the step
981
+ from the frame's last line to line 0 takes two cycles. Pinned by
982
+ d030tester: TEST across the line end (`32c_02_00`, `32c_03_00`) cuts
983
+ one line fewer, and the frame it keeps 312 lines long with two TEST
984
+ windows of a whole frame each reads 19656 cycles ($4CC8) only with the
985
+ two-cycle wrap. A step into a bad line's raster halts the CPU through
986
+ BA for the one cycle the raster matches: `2ae_02_00` and `2ed_02_00`
987
+ cut 5 lines for 4 cycles. Spec guard: `vic_iie_spec.rb`.
988
+ - The beam doesn't follow the raster counter's steps: it draws the next
989
+ display line after the last one (`VIC#output_line`), so the lines TEST
990
+ skips move the picture up, until a line that ends in the vertical sync
991
+ (PAL lines 303-305, three lines from the fourth blanked one) puts the
992
+ beam back on the raster line. A blanked line the beam draws off the
993
+ raster line shows black. Pinned by d030tester's `vadjust` builds, which
994
+ move the picture up a line per cycle of TEST, `vadjust1` included,
995
+ where the first window leaves the raster counter on line 304, and by
996
+ the colour bar that `173_02_00` draws four lines taller.
997
+ - Not matched: the d030tester references come from an emulator that
998
+ models PAL decoding. A line cut an odd number of times flips the colour
999
+ phase, so every row below it shows other hues (all 11 builds whose cut
1000
+ is odd), and lines drawn while TEST runs show alternating hues and
1001
+ per-cycle blanking. Their 8566 shows no grey dots. The 2mhzVIC
1002
+ references have none either.
1003
+
929
1004
  ## VIC-II NTSC
930
1005
 
931
1006
  The 6567R8 (`Region::NTSC`) and the 6567R56A (`Region::NTSC_OLD`) run
data/doc/snapshots.md CHANGED
@@ -62,9 +62,10 @@ Every model saves this way, PAL, NTSC, old NTSC and Drean alike, and so
62
62
  does a machine with an REU, with its RAM, its registers and a transfer
63
63
  part way through. A snapshot only restores in the badline version that
64
64
  wrote it, into a machine with the same chip models, region, RAM
65
- expansion, REU, KERNAL and datasette. One saved before badline recorded
66
- the KERNAL and the datasette restores as a machine with the C64's KERNAL
67
- and a datasette.
65
+ expansion, REU, KERNAL, datasette and board. One saved before badline
66
+ recorded the KERNAL and the datasette restores as a machine with the
67
+ C64's KERNAL and a datasette, and one saved before it recorded the board
68
+ restores on a C64's.
68
69
 
69
70
  ## The VIC-20
70
71
 
@@ -79,6 +80,21 @@ it, and badline doesn't open xvic's. Restoring a
79
80
  VIC-20 snapshot while a C64 runs, or a C64 one while a VIC-20 runs,
80
81
  swaps the machine in the window.
81
82
 
83
+ ## The C128
84
+
85
+ `badline c128` saves and restores the same way too. A C128 snapshot
86
+ holds its 128K of RAM, both colour RAM banks, the CPU port, the MMU's
87
+ registers and the page pointer writes it holds, the VIC-IIe, both CIAs,
88
+ the SID, the VDC with its RAM and registers, the CPU, a cartridge, the
89
+ tape, the disk device 8 serves through the traps and a true 1541, and
90
+ restores into a C128 of the same model with the same SID, built for the
91
+ same mode. The VDC's picture is left out and painted again while the
92
+ window shows it, and the window shows the VIC-IIe after a restore. Its
93
+ file names its machine `C128`, as x128's do, and holds only the
94
+ `BADLINE` module, so x128 doesn't open it, and badline doesn't open
95
+ x128's. Restoring a C128 snapshot while another machine runs swaps the
96
+ machine in the window, as for the VIC-20.
97
+
82
98
  ## VICE
83
99
 
84
100
  Snapshots use VICE's `.vsf` format. badline writes VICE's modules for
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class AddressBus
5
+ # What a model fits around the chips: its KERNAL, a datasette or none,
6
+ # and the board and case they sit in.
7
+ module Fittings
8
+ # The keywords #fit takes.
9
+ NAMES = %i[kernal datasette board].freeze
10
+
11
+ # The C64's board and case, the PET 64's, whose built-in monitor
12
+ # shows the VIC's output in shades of green, the C64GS's, which has
13
+ # no keyboard, or the MAX Machine's (:max): 2K of RAM, no ROMs, CIA 1
14
+ # alone at $DC00-$DFFF and the cartridge port in Ultimax mode.
15
+ BOARDS = %i[c64 pet64 gs max].freeze
16
+
17
+ attr_reader :board
18
+
19
+ # Fits the KERNAL ROM ROMs::KERNALS names, leaves the cassette port
20
+ # empty unless `datasette`, and puts the chips in the board BOARDS
21
+ # names.
22
+ def fit(kernal: :c64, datasette: true, board: :c64)
23
+ self.kernal = kernal unless kernal == :c64
24
+ @datasette.disconnect! unless datasette
25
+ self.board = board unless board == :c64
26
+ end
27
+
28
+ def board=(name)
29
+ raise ArgumentError, "no board named #{name}" unless BOARDS.include?(name)
30
+
31
+ @board = name
32
+ vic.palette = name == :pet64 ? VIC::GREEN_PALETTE : VIC::PALETTE
33
+ keyboard.connected = name != :gs
34
+ update_overlays!
35
+ end
36
+ end
37
+ end
38
+ end
@@ -5,9 +5,22 @@ module Badline
5
5
  # The C64's PLA: what each page of the CPU's view maps to, from the
6
6
  # CPU port's LORAM, HIRAM and CHAREN lines and the cartridge's EXROM
7
7
  # and GAME. The bus that includes it supplies the page tables, the
8
- # ROMs, the chips, and #map_ram_pages, which lays out the RAM that the
9
- # ROMs, the cartridge and I/O map over.
8
+ # ROMs, the chips, #map_ram_pages, which lays out the RAM that the
9
+ # ROMs, the cartridge and I/O map over, and #map_io_pages, which lays
10
+ # out the chips at $D000-$DFFF, with #map_cartridge_io for I/O 1 and 2.
10
11
  module PLA
12
+ # A chip's registers seen at another page, which reads and writes
13
+ # them as at the page the chip starts at.
14
+ class Mirror
15
+ def initialize(chip, start)
16
+ @chip = chip
17
+ @start = start
18
+ end
19
+
20
+ def peek(addr) = @chip.peek(@start | (addr & 0xff))
21
+ def poke(addr, value) = @chip.poke(@start | (addr & 0xff), value)
22
+ end
23
+
11
24
  private
12
25
 
13
26
  def map_pla_pages
@@ -27,19 +40,26 @@ module Badline
27
40
  end
28
41
  end
29
42
 
30
- # Ultimax cartridges ignore the $01 lines: 4K of RAM, ROML/ROMH windows,
43
+ # Ultimax mode ignores the $01 lines: 4K of RAM, ROML/ROMH windows,
31
44
  # I/O always visible and open address space everywhere else. The ROML
32
45
  # and ROMH selects fire on writes as well, so cartridge RAM or flash in
33
- # either window takes the writes there.
46
+ # either window takes the writes there. The MAX has 2K of RAM, and is
47
+ # in Ultimax mode with no cartridge too.
34
48
  def map_ultimax_pages
35
- @read_pages.fill(@open_bus, 0x10, 0xf0)
36
- @write_pages.fill(@open_bus, 0x10, 0xf0)
49
+ first = @board == :max ? 0x08 : 0x10
50
+ @read_pages.fill(@open_bus, first, 0x100 - first)
51
+ @write_pages.fill(@open_bus, first, 0x100 - first)
52
+ map_ultimax_cartridge if @cartridge
53
+ map_io_pages
54
+ map_max_io if @board == :max
55
+ end
56
+
57
+ def map_ultimax_cartridge
37
58
  @read_pages.fill(@cartridge.roml, 0x80, 0x20) if @cartridge.roml
38
59
  map_ultimax_writes(@cartridge.roml, 0x80)
39
60
  @read_pages.fill(@cartridge.romh, 0xe0, 0x20) if @cartridge.romh
40
61
  map_ultimax_writes(@cartridge.romh, 0xe0)
41
62
  map_ultimax_a000
42
- map_io_pages
43
63
  end
44
64
 
45
65
  def map_ultimax_writes(bank, first_page)
@@ -53,6 +73,17 @@ module Badline
53
73
  @write_pages.fill(window, 0xa0, 0x20)
54
74
  end
55
75
 
76
+ # The MAX has no CIA 2 and no I/O 2: CIA 1 answers all through
77
+ # $DC00-$DFFF, but for the I/O 1 page a cartridge takes.
78
+ def map_max_io
79
+ @cia1_mirror ||= Mirror.new(cia1, 0xdc00)
80
+ (0xdd..0xdf).each { |page| @read_pages[page] = @write_pages[page] = @cia1_mirror }
81
+ return unless @cartridge
82
+
83
+ @write_pages[0xde] = @cartridge
84
+ @read_pages[0xde] = @cartridge if @cartridge.readable_io_pages.include?(0xde)
85
+ end
86
+
56
87
  # EXROM and GAME select the cartridge whether or not it has a chip
57
88
  # there, and an empty socket leaves the bus floating.
58
89
  def map_rom_overlays
@@ -78,20 +109,6 @@ module Badline
78
109
  @write_pages.fill(bank, first_page, 0x20) if bank.is_a?(Cartridge::RAMBank)
79
110
  end
80
111
 
81
- def map_io_pages
82
- {
83
- vic => 0xd0..0xd3, sid => 0xd4..0xd7, color_ram => 0xd8..0xdb,
84
- cia1 => 0xdc..0xdc, cia2 => 0xdd..0xdd, @open_bus => 0xde..0xdf
85
- }.each do |chip, pages|
86
- pages.each { |p| @read_pages[p] = @write_pages[p] = chip }
87
- end
88
- @ram_expansion.map_io(@read_pages, @write_pages)
89
- @read_pages[0xd7] = @write_pages[0xd7] = @debug_register if @debug_register
90
- @read_pages[0xdf] = @write_pages[0xdf] = @reu if @reu
91
- map_cartridge_io if @cartridge
92
- map_extra_sids unless @sid_slots.empty?
93
- end
94
-
95
112
  def map_cartridge_io
96
113
  @write_pages.fill(@cartridge, 0xde, 2)
97
114
  @cartridge.readable_io_pages.each { |p| @read_pages[p] = @cartridge }
@@ -4,18 +4,25 @@ module Badline
4
4
  class AddressBus
5
5
  # The BASIC, character and KERNAL ROMs, and which KERNAL is fitted.
6
6
  module ROMs
7
- # The KERNAL ROMs a machine can be fitted with, by name: the C64's
8
- # and the SX-64's.
9
- KERNALS = { c64: "kernal.rom", sx64: "kernal-sx64.rom" }.freeze
7
+ # The KERNAL ROMs a machine can be fitted with, by name: the C64's,
8
+ # the SX-64's, the PET 64's and the C64GS's. The C64GS's is a 16K
9
+ # image of its BASIC and its KERNAL, and fits both.
10
+ KERNALS = {
11
+ c64: "kernal.rom", sx64: "kernal-sx64.rom", pet64: "kernal-pet64.rom", gs: "c64gs.rom"
12
+ }.freeze
10
13
 
11
14
  attr_reader :basic_rom, :character_rom, :kernal_rom, :kernal
12
15
 
13
- # Fits the KERNAL ROM KERNALS names `name`, in place of the one before.
16
+ # Fits the KERNAL ROM KERNALS names `name`, in place of the one before,
17
+ # with the BASIC of a 16K image or else the C64's.
14
18
  def kernal=(name)
15
19
  raise ArgumentError, "no KERNAL named #{name}" unless KERNALS.key?(name)
16
20
 
17
21
  @kernal = name
18
- @kernal_rom = ROM.load(KERNALS[name], 0xe000)
22
+ data = ROM.read(KERNALS[name])
23
+ basic = data.length == 0x4000 ? data[0, 0x2000] : ROM.read("basic.rom")
24
+ @basic_rom = ROM.new(basic, length: 0x2000, start: 0xa000)
25
+ @kernal_rom = ROM.new(data[-0x2000, 0x2000], length: 0x2000, start: 0xe000)
19
26
  update_overlays!
20
27
  end
21
28
 
@@ -5,6 +5,7 @@ require "badline/address_bus/sid_slots"
5
5
  require "badline/address_bus/extra_sids"
6
6
  require "badline/address_bus/pla"
7
7
  require "badline/address_bus/roms"
8
+ require "badline/address_bus/fittings"
8
9
 
9
10
  module Badline
10
11
  # Memory layout:
@@ -37,6 +38,7 @@ module Badline
37
38
  include PLA
38
39
  include ExtraSIDs
39
40
  include ROMs
41
+ include Fittings
40
42
 
41
43
  # I/O 1 and 2, and the Ultimax holes, with nothing on the bus. A read
42
44
  # picks up the byte the VIC fetched in the preceding phi1 half-cycle,
@@ -68,6 +70,7 @@ module Badline
68
70
  def initialize(sid_model: :mos6581, cia_model: :mos6526, vic_model: :mos6569, region: Region::PAL,
69
71
  ram_expansion: nil)
70
72
  @region = region
73
+ @board = :c64
71
74
  @ram = Memory.new(RAM_POWER_ON, length: 2**16, start: 0)
72
75
  @ram_expansion = RAMExpansion.build(ram_expansion, @ram) { update_overlays! }
73
76
  @cartridge = @reu = nil
@@ -201,8 +204,9 @@ module Badline
201
204
  # so reads and writes dispatch through per-page handler tables instead
202
205
  # of range checks.
203
206
  def update_overlays!
204
- @ultimax = @cartridge ? @cartridge.ultimax? : false
205
- @phi1_ultimax = @cartridge ? @cartridge.phi1_ultimax? : false
207
+ max = @board == :max
208
+ @ultimax = max || (@cartridge ? @cartridge.ultimax? : false)
209
+ @phi1_ultimax = max || (@cartridge ? @cartridge.phi1_ultimax? : false)
206
210
  @video_ram = @ram_expansion.video_ram
207
211
  @vic.vic_bank.map(@video_ram, phi1_ultimax: @phi1_ultimax, ultimax: @ultimax, romh: @cartridge&.romh)
208
212
 
@@ -213,5 +217,19 @@ module Badline
213
217
  def map_ram_pages
214
218
  @ram_expansion.map(@read_pages, @write_pages)
215
219
  end
220
+
221
+ def map_io_pages
222
+ {
223
+ vic => 0xd0..0xd3, sid => 0xd4..0xd7, color_ram => 0xd8..0xdb,
224
+ cia1 => 0xdc..0xdc, cia2 => 0xdd..0xdd, @open_bus => 0xde..0xdf
225
+ }.each do |chip, pages|
226
+ pages.each { |p| @read_pages[p] = @write_pages[p] = chip }
227
+ end
228
+ @ram_expansion.map_io(@read_pages, @write_pages)
229
+ @read_pages[0xd7] = @write_pages[0xd7] = @debug_register if @debug_register
230
+ @read_pages[0xdf] = @write_pages[0xdf] = @reu if @reu
231
+ map_cartridge_io if @cartridge
232
+ map_extra_sids unless @sid_slots.empty?
233
+ end
216
234
  end
217
235
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class C128
5
+ # The RAM the C128 KERNAL's bank numbers 0-15 reach, as its INDFET and
6
+ # INDSTA reach it for LOAD, SAVE and the file name: the RAM bank of the
7
+ # CR value the KERNAL's table at $F7F0 holds for the bank, with common
8
+ # RAM in bank 0. The ROMs and I/O some of those CR values map are left
9
+ # out.
10
+ class Banks
11
+ CONFIGURATIONS = 0xf7f0
12
+
13
+ def initialize(ram, mmu, kernal_rom)
14
+ @ram = ram
15
+ @mmu = mmu
16
+ @kernal_rom = kernal_rom
17
+ end
18
+
19
+ def peek(bank, addr) = @ram.peek(physical(bank, addr))
20
+
21
+ def write(bank, addr, bytes)
22
+ bytes.each_with_index { |byte, i| @ram.poke(physical(bank, (addr + i) & 0xffff), byte) }
23
+ end
24
+
25
+ private
26
+
27
+ def physical(bank, addr)
28
+ page = addr >> 8
29
+ common = page < @mmu.common_low_pages || page >= @mmu.common_high_start
30
+ ram_bank = common ? 0 : (@kernal_rom.peek(CONFIGURATIONS + (bank & 0x0f)) >> 6) & 0x01
31
+ (ram_bank << 16) | addr
32
+ end
33
+ end
34
+ end
35
+ end