badline 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +40 -0
  3. data/CLAUDE.md +8 -0
  4. data/CONTRIBUTING.md +4 -0
  5. data/README.md +10 -7
  6. data/doc/library.md +5 -1
  7. data/doc/media.md +3 -2
  8. data/doc/pinned-behaviour.md +41 -1
  9. data/doc/snapshot-format.md +56 -24
  10. data/doc/snapshots.md +45 -8
  11. data/doc/vice.md +344 -0
  12. data/lib/badline/audio/renderer.rb +4 -8
  13. data/lib/badline/c128.rb +4 -0
  14. data/lib/badline/c64/bus/fittings.rb +6 -0
  15. data/lib/badline/c64.rb +6 -0
  16. data/lib/badline/cbm2/p500.rb +4 -0
  17. data/lib/badline/cbm2.rb +4 -0
  18. data/lib/badline/chips/cpu/core.rb +29 -4
  19. data/lib/badline/chips/cpu/interrupts.rb +9 -0
  20. data/lib/badline/chips/cpu/keyed_state.rb +15 -9
  21. data/lib/badline/chips/cpu/microcode.rb +10 -0
  22. data/lib/badline/chips/rriot.rb +4 -1
  23. data/lib/badline/chips/sid/decimator.rb +1 -1
  24. data/lib/badline/chips/sid/keyed_parts.rb +51 -18
  25. data/lib/badline/chips/sid.rb +6 -2
  26. data/lib/badline/chips/vic_i/keyed_parts.rb +6 -1
  27. data/lib/badline/chips/vic_i/light_pen.rb +89 -0
  28. data/lib/badline/chips/vic_i.rb +12 -7
  29. data/lib/badline/drives/model1541/sector_reader.rb +20 -3
  30. data/lib/badline/drives/model1541.rb +1 -0
  31. data/lib/badline/drives/model1551.rb +1 -0
  32. data/lib/badline/drives/model1571.rb +1 -0
  33. data/lib/badline/drives/model1581.rb +1 -0
  34. data/lib/badline/drives/model2031.rb +1 -0
  35. data/lib/badline/drives/model4040/mechanism.rb +6 -3
  36. data/lib/badline/drives/model4040.rb +17 -1
  37. data/lib/badline/drives/model8050/board.rb +2 -0
  38. data/lib/badline/drives/model8050/dos_board.rb +49 -6
  39. data/lib/badline/drives/model8050/fdc_bus.rb +7 -0
  40. data/lib/badline/drives/model8050/idle.rb +137 -24
  41. data/lib/badline/drives/model8050.rb +14 -0
  42. data/lib/badline/drives/model9060/idle.rb +30 -10
  43. data/lib/badline/drives/model9060.rb +2 -0
  44. data/lib/badline/input/joystick.rb +15 -2
  45. data/lib/badline/kim1/tape.rb +3 -1
  46. data/lib/badline/kim1.rb +4 -0
  47. data/lib/badline/machine.rb +3 -0
  48. data/lib/badline/media/true_drive.rb +10 -0
  49. data/lib/badline/media/vic20_media.rb +4 -2
  50. data/lib/badline/media.rb +5 -1
  51. data/lib/badline/memory.rb +2 -4
  52. data/lib/badline/options/table.rb +1 -1
  53. data/lib/badline/pet/superpet/dongle.rb +13 -2
  54. data/lib/badline/pet/superpet.rb +5 -0
  55. data/lib/badline/pet.rb +4 -0
  56. data/lib/badline/plus4.rb +4 -0
  57. data/lib/badline/roms/README +11 -1
  58. data/lib/badline/snapshot/container.rb +6 -4
  59. data/lib/badline/snapshot/field_doc.rb +8 -4
  60. data/lib/badline/snapshot/image.rb +15 -6
  61. data/lib/badline/snapshot/vice/pet.rb +1 -0
  62. data/lib/badline/snapshot/vice/pet_dongle.rb +72 -0
  63. data/lib/badline/snapshot/vice/pet_mem.rb +11 -5
  64. data/lib/badline/snapshot/vice/vic20.rb +70 -0
  65. data/lib/badline/snapshot/vice/vic20_cpu.rb +61 -0
  66. data/lib/badline/snapshot/vice/vic20_mem.rb +85 -0
  67. data/lib/badline/snapshot/vice/vic20_restore.rb +124 -0
  68. data/lib/badline/snapshot/vice/vic20_vici.rb +200 -0
  69. data/lib/badline/snapshot/vice.rb +1 -0
  70. data/lib/badline/snapshot.rb +4 -4
  71. data/lib/badline/storage/disk_image/writing.rb +2 -2
  72. data/lib/badline/version.rb +1 -1
  73. data/lib/badline/vic20/port_wiring.rb +11 -0
  74. data/lib/badline/vic20.rb +4 -0
  75. metadata +9 -1
data/doc/vice.md ADDED
@@ -0,0 +1,344 @@
1
+ # VICE
2
+
3
+ badline checks itself against VICE 3.10's emulators, run as black boxes:
4
+ their command lines, their remote monitor, their screenshots, sound
5
+ recordings and snapshots. VICE's source isn't read for this, and badline
6
+ holds none of its code, ROMs or data. The VICE-testprogs repository is a
7
+ separate project: its testlists, hooks and reference screenshots are what
8
+ `bin/testbench` runs (see [CLAUDE.md](../CLAUDE.md#oracles)). This page
9
+ covers what each emulator checks, how they're driven, what they get
10
+ wrong or leave out, the `.vsf` modules badline reads and writes, and
11
+ where badline knowingly does otherwise.
12
+
13
+ - [Which emulator checks what](#which-emulator-checks-what)
14
+ - [Driving them](#driving-them)
15
+ - [Quirks and bugs](#quirks-and-bugs)
16
+ - [Snapshot modules](#snapshot-modules)
17
+ - [Where badline differs](#where-badline-differs)
18
+
19
+ ## Which emulator checks what
20
+
21
+ | Emulator | Machines | What it checks |
22
+ | --- | --- | --- |
23
+ | `x64sc` | C64, every `--model` | The testbench's published results, runs from an exact start, and `.vsf` both ways |
24
+ | `x128` | C128 | The VDC (`-exitscreenshot`), the MMU and both modes' boot screens. Not the C64-mode VIC-II or 2 MHz (below) |
25
+ | `xvic` | VIC-20, PAL and NTSC | The 6560's raster count and interlace, the VIC-I's sound by recording, the power-on RAM, cycle-timed writes by screenshot, `.vsf` both ways |
26
+ | `xplus4` | Plus/4, C16, C116 | The TED's fetches and picture by screenshot and trace probe, its sound by recording, the 1551's load time, the ACIA's registers and timing |
27
+ | `xpet` | Every PET and CBM model, the SuperPET | Boot banners and BYTES FREE, frame lengths, the CRTC's frame, the IEEE-488 drives' listings and error channels, `.vsf` both ways |
28
+ | `xcbm2` | The B series | Boot, the CRTC's frame, the burn-in disk on a true 8050, `.vsf` both ways |
29
+ | `xcbm5x0` | The P500 | Boot, the VIC-II's cycle stealing, `.vsf` both ways |
30
+ | `c1541` | Disk images | Blank `.d67` and `.d90` images, which `D67Image` and `D90Image` format alike byte for byte (their specs' fixtures), and listing and validating the images badline's true drives write |
31
+ | `petcat` | BASIC text | `petcat -w2 -o out.prg in.txt` turns a BASIC listing into a `.prg` |
32
+
33
+ VICE has no KIM-1. No row of VICE's testlists runs the PET's IEEE-488
34
+ floppies and hard disks, the SuperPET's 6809 or a 1581, so those rest
35
+ on badline's own drive scenarios and specs, the schematics and the ROMs.
36
+
37
+ `x128` runs older VIC-II emulation than `x64sc`: in its results for the
38
+ C64-mode testlist (r45942) about 180 rows that `x64sc` passes fail, among
39
+ them 52 `spritesplit`, 25 `irqdma` and 18 `reutiming2` rows, and it fails
40
+ all 33 `d030tester` and all 4 `2mhzVIC` screenshot rows in C128 mode. So
41
+ `testbench-c128c64` is held to badline's own C64C verdicts, and the 2 MHz
42
+ rows to the testprogs' references, which come from real machines.
43
+
44
+ ## Driving them
45
+
46
+ - Run each by its full path, such as `/opt/homebrew/bin/x64sc`. A bare
47
+ `x64sc` failed with "argv[0] is NULL". Headless, `x64sc` needs
48
+ `-console`, or it aborts for want of GSettings schemas.
49
+ - **Exit codes.** `-debugcart` gives the program a debug register, and a
50
+ write to it ends the run with that exit code, `$00` for a pass and
51
+ `$FF` for a fail. It is `$D7FF` on the C64 and the C128, `$910F` on
52
+ the VIC-20, `$FDCF` on the 264 series, `$8BFF` on the PET and `$DAFF`
53
+ in segment 15 on the CBM-II. `-limitcycles N` ends a run that hasn't
54
+ written it, as a timeout. `bin/testbench` installs the same register
55
+ (`install_debug_register`) and the same cycle budgets.
56
+ - **Screenshots.** `-exitscreenshot` writes a PNG as the run ends
57
+ (`-exitscreenshotvicii` for the C128's 40 columns, with
58
+ `-exitscreenshot` its VDC). Sizes: 384x288 PAL and 384x242 NTSC on
59
+ `xplus4`; 384x217 for the PET's discrete board, 384x273 for 40 CRTC
60
+ columns and 704x273 for 80 on `xpet`; 704x267 for a 600 and 704x367 for
61
+ a 700 on `xcbm2`; 384x272 PAL and 384x247 NTSC on `xcbm5x0`. The
62
+ testbench hooks compare from (32,35) on the C64, the 264 and the
63
+ CBM-II ((32,23) on an NTSC `xcbm2`), (32,33) on the PET, and (96,48)
64
+ PAL and (40,22) NTSC on the VIC-20.
65
+ - **Typing.** `-keybuf` types into the keyboard buffer once the machine
66
+ is up. On `xpet` keep each line under 80 characters. The monitor's
67
+ `keybuf` with `\"` escapes typed nothing there.
68
+ - **The remote monitor.** `-remotemonitor -remotemonitoraddress
69
+ ip4://127.0.0.1:PORT` takes monitor commands over a socket: registers
70
+ (`r`, and `r PC = $xxxx` to set one), memory (`m`, `bsave`), `io`,
71
+ breakpoints, the stopwatch, `dump` and `undump` for snapshots, and
72
+ `resourceget "Name"`, with the name quoted. A breakpoint hit is
73
+ reported only after the next command, so poll with `r`, then `x`.
74
+ Numbers are hex unless marked, counts too (`ignore 1 36` skips 54
75
+ hits). Its `CYC` is badline's column at the instruction boundary plus
76
+ one. `-moncommands` with the GTK monitor waits for input at reset and
77
+ hangs; `-nativemonitor` with the commands on stdin runs them.
78
+ - **An exact start.** `x64sc`'s autostart adds a random delay, so one run
79
+ of a test that depends on where it starts is no reference: the
80
+ published results show `irq-ack-vicii` passing 15 runs in 16 for that
81
+ reason. To compare from the same place, park the CPU in a loop at
82
+ `$C000` in both emulators, break on its first fetch on a chosen raster
83
+ line, and resume at the program's entry through a stub of N cycles.
84
+ badline and `x64sc` started this way agreed cycle for cycle over 91
85
+ start positions.
86
+ - **Power-on RAM.** `xvic -default -memory all -initbreak reset
87
+ -moncommands cmds.txt`, with `bsave "path" 0 0000 1fff` and `quit` in
88
+ the file, dumps RAM before the KERNAL touches it.
89
+ - **Sound.** `-sounddev wav -soundarg f.wav +autostart-warp` records
90
+ from power-on in real time. `-sounddev dummy -soundrecdev wav` writes
91
+ an empty file, because autostart turns warp on. The Homebrew `xpet`
92
+ has no WAV driver.
93
+ - **Models.** Use `-model vic20ntsc` for an NTSC VIC-20: `xvic -ntsc`
94
+ keeps the PAL KERNAL. `x64sc` 3.10's default VIC-II is the 8565, so a
95
+ 6569 snapshot needs `-model c64`. The 264's RAM expansions are
96
+ `-memoryexphack 1|2|3` (Csory 256K, Hannes 256K, Hannes 1M);
97
+ `-ramsize` takes only 16, 32 and 64. The B series' autostart needs a
98
+ device on 8 (`-busdevice8 +drive8truedrive`).
99
+ - **Paths.** `xpet`'s `-editor` and `-autostart` need absolute paths. A
100
+ relative one gives a blank or stuck screen.
101
+ - **True drives.** `-drive8type T -drive8truedrive` with the disk as `-8`.
102
+ `-attach8ro` is a flag of its own: `-attach8ro -8 <img>` mounts an
103
+ image write-protected.
104
+
105
+ ## Quirks and bugs
106
+
107
+ What follows was observed from outside. Each is a reason a row, a
108
+ recording or a snapshot from that emulator isn't a reference for the
109
+ part named.
110
+
111
+ ### x64sc
112
+
113
+ - `undump` of a snapshot with any module short of what it expects resets
114
+ the machine without saying so, beyond "pre 2.4.30 snapshot" in the log.
115
+ MAINCPU 1.4 is 103 bytes, and 3.10 reads that layout whatever the minor
116
+ version.
117
+ - `-limitcycles` below a snapshot's own clock exits before a frame.
118
+ - Its true 1581 caches a track and writes it back later, so a run that
119
+ formats and exits through the debug register leaves the `.d81`
120
+ unwritten. Send `I0` and wait before the exit.
121
+ - In the remote monitor, a condition on `RL` works, but `RL == … && CY
122
+ >= …` never fired.
123
+
124
+ ### xvic
125
+
126
+ - Its remote monitor stopped answering a new connection after an earlier
127
+ client disconnected. Drive a session over one socket, from one script
128
+ that starts `xvic` and stops it by its PID.
129
+ - `xvic -ntsc` keeps the PAL KERNAL (above).
130
+ - Its monitor's `LIN`, and so a breakpoint's `RL`, stays modulo 261
131
+ under interlace, while `$9003`/`$9004` and its own snapshots follow
132
+ the 263- and 262-line fields: a frame after a long field shows `LIN`
133
+ 2 more than the line, and so on.
134
+ - `undump` of a snapshot taken in an interlaced frame ends that frame
135
+ after 261 lines, its own snapshots too: the field after it starts 2
136
+ lines early after a long field and 1 after a short one. The fields
137
+ alternate as before from there.
138
+ - It refuses a snapshot whose file isn't in format 3.1, and resets on the
139
+ first instruction without `FSDRIVE`, `TAPEPORT`, `JOYPORT0`,
140
+ `JOYSTICK0` and `USERPORT`. Without any drive module it keeps its own
141
+ drives, but with `DRIVE8` and no `DRIVECPU0` and the 1541's VIAs it
142
+ resets too.
143
+
144
+ ### xplus4
145
+
146
+ - Its default drive 8 is a 1551, on the `plus4`, `c16` and `c16ntsc`
147
+ models. `Plus4/tcbm/test.prg` passes only with it.
148
+ - It clocks a SID card only with a real sound device: with `-sounddev
149
+ dummy`, OSC3 reads 0.
150
+ - r45284 shows a bitmap fetched from empty ROM space wrongly against the
151
+ photo of a real machine in `Plus4/openmem`.
152
+ - It steps the TED's line about 3 single clock cycles after the
153
+ datasheet's decode, and fails `trap10`, `trap12` and `trap14` of the
154
+ Lorenz suite's plus4 build (below).
155
+ - Its C16 model loads no tape, not even one it saved itself, and never
156
+ asks for PLAY: LOAD goes straight to SEARCHING. Its Plus/4 model loads
157
+ the tapes badline saves for the 264 (#704).
158
+ - It writes a version 2 `.tap` with one byte per whole wave (`$36`, `$6B`,
159
+ `$D5`, version 1's values), where the format and badline give one per
160
+ half wave (`$1A`, `$35`, `$6A`). Both time the same signal.
161
+
162
+ ### xpet
163
+
164
+ - **Its 6809 runs with the wrong instruction timing.** Single-stepped
165
+ stopwatch deltas: BSR 5, PULS D 5, SUBD # 1, LDX # 0, ABX 2, JSR [,X]
166
+ 8, LEAS 3, CLRA 1, 25 cycles for a sequence the datasheet times at
167
+ about 41. `-limitcycles` never fires under `-cpu6809`, so take 6809
168
+ screenshots through the monitor. Its 6809 runs one cycle per PET cycle,
169
+ 16,650 cycles between the Waterloo ROM's frame interrupts.
170
+ - **The SuperPET has no software CPU select and no write protect.** With
171
+ `-cpuprog` it boots the 6502, and no write to `$EFF4`-`$EFFF` starts
172
+ the 6809. Writes to the bank RAM stick after any one bit written to
173
+ `$EFF4`-`$EFFB`, with the switch at 6502 or at PROG. The
174
+ CPU switch (`CPUswitch`) is read only at reset. `$EFFD` acts as
175
+ `$EFFC`. Its 6702 at `$EFE0` answers 16 reads with no write with `$D6`,
176
+ and writes and reads in turn of 0 to 15 with `d6 c6 c6 56 56 44 44 d6
177
+ d6 84 84 15 15 01 01 91`, as badline's does. It saves the 6702 in
178
+ PETMEM's settings (below), and every other PET's settings hold one as
179
+ RES leaves it.
180
+ - **Its 2040 isn't usable.** On a 4032 or a 3032 `OPEN15,8,15` hangs and
181
+ `LOAD"$0",8` never finishes; on a 2001 the directory comes back empty.
182
+ - **It runs only the interface processor of an IEEE-488 drive** with two
183
+ processors: the 8050, 8250, SFD-1001, 4040, 3040, 2040 and its type
184
+ 9000. The floppy controller and the hard disk's SASI side run at a high
185
+ level, in a module `FDC0`, and the 9000 loads no SASI ROM. Nothing in
186
+ `xpet` times the second processor, the mechanism or SASI. The monitor
187
+ reaches no device for the controller.
188
+ - On `-model 2001` its screen mirror takes the `$8BFF` write before the
189
+ debug cartridge sees it, so `pet-pass.prg` times out with PET-PASS on
190
+ the screen; `+screen2001` gives exit 0. On `-model 8296` the same row
191
+ times out.
192
+ - It patches the 2001's BASIC 1 ROM at start ("patching 2001 ROM to make
193
+ IEEE488 work!"), so a 2001's LOAD from a drive in `xpet` is no
194
+ reference for the stock ROM.
195
+ - It repeats a CRTC row's character lines past the eighth, where the
196
+ 8032029 and 8032087 schematics blank them. Its results mark `crtc3`,
197
+ `crtc10` and `crtc13` as errors, and `crtc13`'s reference shows `xpet`'s
198
+ own power-on RAM. Its 8296 runs the 8032's editor, 901474-04 dated
199
+ 0283.
200
+ - Its discrete board's monitor `LIN` and `CYC` run on a 20,032-cycle
201
+ raster, out of step with its 16,640-cycle retrace. `-pal` and `-ntsc`
202
+ change nothing measurable on a PET.
203
+ - `-autostart` refuses ("Autostart is not available on this setup") once
204
+ `-editor` names a ROM it doesn't know by checksum, and `-initbreak
205
+ ready` never breaks then. Load through the monitor instead
206
+ (`l "path.prg" 0`, `g 0410`), or break on a store.
207
+ - `undump` wants snapshot format 1.0, and with `FSDRIVE`, `TAPEPORT` or
208
+ `USERPORT` missing it loads, then resets on the first instruction
209
+ stepped. A snapshot it saved while its true 2031 searched for a file
210
+ never finishes the LOAD, its own included; mid-transfer ones resume.
211
+ Its CRTC loses a few lines on opening any snapshot.
212
+ - `-autostart` with `-limitcycles` sometimes times out on a 3032; a rerun
213
+ passes.
214
+
215
+ ### xcbm2
216
+
217
+ - **It ticks the mains once a frame of its CRTC**, as vertical sync
218
+ ends, whatever its mains is set to: 40,064 cycles apart on a 600 at
219
+ 50 Hz, where 50 Hz is 40,000, and its 700 has no 60 Hz. Every 610 run,
220
+ `-ntsc` and `-power60` included, used the 50 Hz CRTC table. Its 710
221
+ defaults to `-ntsc -power60`.
222
+ - **It has no DRAM refresh**, so the burn-in disk's 6526 timer cell
223
+ reads BAD on its 610, after one cycle of the test and after three.
224
+ - **The 610 + 2031 TALK race reproduces in it.** With a true 2031,
225
+ `LOAD"$",8` typed at some phases after power-on gives `?FILE NOT
226
+ FOUND`: the KERNAL's TALK puts DAV about 44 cycles after ATN, and the
227
+ 2031's DOS raises NRFD while ATNA is still set. `xcbm2` fails at
228
+ `-keybuf-delay` 0 and 10 to 50 and lists at 5 and 100, as badline fails
229
+ at some phases and not others. The first `LOAD"$"` after power-on gives
230
+ FILE NOT FOUND in it too.
231
+ - **It reads the wrong top bits of TPI1's port C in mode 1.** At READY
232
+ on a 610, with CR `$E3` (CA low, CB high) and no interrupt, its monitor
233
+ reads `$C0` at `$DE02`, though its own `io de00` says CA is low and no
234
+ interrupt is active. The 6525 datasheet reads the pins there, CB, CA
235
+ and IRQ, so badline reads `$A0`: CB high, CA low and IRQ released.
236
+ - It saves no mains and no TPI priority stack. Its cursor blinks 32
237
+ fields on and 32 off, where the 6845 blinks 16 and 16. Opening any
238
+ snapshot costs it a couple of hundred cycles of its CRTC.
239
+
240
+ ### xcbm5x0
241
+
242
+ - It runs VICE's line-based VIC-II, not `x64sc`'s, so it reads the
243
+ raster a cycle off now and then.
244
+ - It ticks the mains every 19,656 cycles, a PAL frame, on NTSC too:
245
+ about 52 Hz, where the NTSC 510's mains is 60.
246
+ - A sprite's phi2 bytes in text mode read the KERNAL from `$E040` on,
247
+ where the FPLA puts the video bus.
248
+
249
+ ### c1541
250
+
251
+ - It refuses `d60` as an image type, but formats a zero-filled `.d90` of
252
+ a D9060's size.
253
+
254
+ ## Snapshot modules
255
+
256
+ No VICE document lays out the `.vsf` modules, so badline's readers and
257
+ writers in `lib/badline/snapshot/vice/` were mapped from dumps: known
258
+ pokes into memory, register writes and breakpoints at known delays,
259
+ compared with the monitor at the same moment. Each file's doc comment
260
+ gives its module's layout and what maps across.
261
+ [snapshots.md](snapshots.md#vice) covers what each emulator opens and
262
+ what it leaves out.
263
+
264
+ | Emulator | Machine name, format | Modules (files) |
265
+ | --- | --- | --- |
266
+ | `x64sc` | `C64SC`, 2.0 | MAINCPU 1.4 (`main_cpu.rb`), C64MEM (`c64_mem.rb`), CIA1 and CIA2 (`cias.rb`, `cias_import.rb`), SID and SIDEXTENDED (`sid_registers.rb`, `sid_extended.rb`), VIC-II (`vicii.rb`, `vicii_import.rb`), REU1764 (`reu1764.rb`), and the empty ports it refuses a file without (`peripherals.rb`) |
267
+ | `xpet` | `PET`, 1.0 | MAINCPU 1.3 (`pet_cpu.rb`), PETMEM 1.3 (`pet_mem.rb`), CRTC 1.2 (`pet_crtc.rb`), PIA1 and PIA2 (`pet_pias.rb`), VIA 2.2 (`pet_via.rb`), CPU6809 1.1 (`pet_cpu.rb`) and Acia1 (`cbm2_acia.rb`) for the SuperPET, a 2031 (`pet_drive.rb`) and the two-processor drives (`pet_drive8050.rb`) |
268
+ | `xcbm2` | `CBM-II`, 1.0 | The PET's CPU, CBM2MEM 1.0 (`cbm2_mem.rb`), CRTC 1.2 (`cbm2_crtc.rb`), TPI1 and TPI2 (`cbm2_tpi.rb`), Acia1 1.1 (`cbm2_acia.rb`), the C64's CIA and SID, the mains (`cbm2_mains.rb`) |
269
+ | `xcbm5x0` | `CBM-II-5x0`, 0.0 | As `xcbm2`, with its own VIC-II 1.3 (`p500_vicii.rb`) and C500DATA, the cycles to the mains' next tick (`p500.rb`) |
270
+ | `xvic` | `VIC20`, 3.1 | MAINCPU 1.4 (`vic20_cpu.rb`), VIC20MEM 1.1 (`vic20_mem.rb`), VIC-I 0.4 with the colour RAM (`vic20_vici.rb`), VIA1 and VIA2 (`pet_via.rb`), and the empty ports it resets without (`vic20.rb`) |
271
+ | `x128`, `xplus4` | `C128` 1.0, `PLUS4` | None read or written |
272
+
273
+ Facts the layouts rest on that the files' comments state in full:
274
+
275
+ - CBM2MEM's header is the RAM board's size in 128K units, the RAM mapped
276
+ into segment 15 beyond the B series' own (`$40` on a P500), the model
277
+ line (0 a 700, 1 a 600 at 60 Hz, 2 a 600 at 50 Hz) and both segment
278
+ registers.
279
+ - A TPI module is the eight registers, then the latched interrupts and CA
280
+ and CB. In mode 1 `xcbm2` keeps the latches in PRC's low five bits and
281
+ the mask in DDRC's.
282
+ - PETMEM's 24 bytes of settings at the end hold the SuperPET's 6702 from
283
+ the fifth byte: the byte it reads, the last write that shifted it, 1
284
+ while the last write left D0 clear, then a little-endian word per data
285
+ line, bit k the XOR of stages k and k + 1 of its shift register. Mapped
286
+ from dumps after 56 writes to `$EFE0`, which all decode to the
287
+ registers badline's 6702 has after the same writes.
288
+ - CPU6809 1.1 is an 8-byte clock, X, Y, U, S and PC, DP, CC, A and B,
289
+ then 5 zero bytes.
290
+ - xvic's MAINCPU 1.4 is 83 bytes, x64sc's without the BA flags and the
291
+ two clocks after the interrupt counts. VIC20MEM 1.1 is a byte of the
292
+ blocks that hold RAM (bit 0 the 3K, bits 1-3 BLK1-BLK3, bit 5 BLK5),
293
+ both data lines' last bytes, a byte badline leaves at 0, then
294
+ `$0000`-`$03FF`, `$1000`-`$1FFF`, the 3K and the blocks in turn.
295
+ - VIC-I 0.4 holds the interlace flag and field, the frame's first clock,
296
+ the beam, the text window's state, its counters and the line's fetch
297
+ state, the light pen, the colour RAM, the registers and xvic's picture
298
+ (1,648 by 632 bytes on PAL, 1,536 by 532 on NTSC). xvic's monitor's
299
+ cycle at an instruction boundary is badline's column.
300
+ - The two-processor drives all save the 8050's set: DRIVE8 2.0,
301
+ DRIVECPU0 1.3 for the interface processor, RIOT1D0 and RIOT2D0, FDC0
302
+ (8 bytes) and FSDRIVE. DRIVE8's type word tells the models apart, and
303
+ the SFD-1001's and the 9000's DRIVE8 holds one drive. A RIOT module's
304
+ timer flag, bit 7, is set only while the timer's interrupt is enabled.
305
+
306
+ ## Where badline differs
307
+
308
+ Where VICE and a schematic, a datasheet or a test from real hardware
309
+ disagree, badline follows the hardware source. These are the places a
310
+ program can tell:
311
+
312
+ - **The CBM-II's DRAM refresh**, a cycle in about 21, which `xcbm2`
313
+ lacks. It passes the burn-in's timer cell, and a program badline runs
314
+ on from an `xcbm2` snapshot runs about 5% slower than in `xcbm2`.
315
+ `refresh: false` builds a B series without it, for comparing with
316
+ `xcbm2`. [CBM-II DRAM refresh](pinned-behaviour.md#cbm-ii-dram-refresh).
317
+ - **The TED's line step and the 7501's port**: the datasheet's decode,
318
+ about 3 cycles ahead of `xplus4`, and the trap tests `xplus4` fails.
319
+ [TED beam and the 7501 port](pinned-behaviour.md#ted-beam-and-the-7501-port).
320
+ Where the TED's fetches, picture and sound follow `xplus4` over the
321
+ datasheet, such as a voice's 1023 - x period, the same doc says so
322
+ under the TED's entries.
323
+ - **The 6809's timing** follows the MC6809E datasheet and the neetandev
324
+ vectors, never `xpet`.
325
+ [6809 dead cycles](pinned-behaviour.md#6809-dead-cycles).
326
+ - **The SuperPET's CPU select and write protect** through the system
327
+ latch at `$EFF8`, after the 324037 schematic, Levak and Fachat
328
+ (`PET::SuperPET::Board`). A program that uses them runs otherwise on
329
+ `xpet`. With SW2 at PROG, badline starts the 6809 at power-on, as the
330
+ latch's clear bit 0 picks it; `xpet` boots the 6502.
331
+ - **A CRTC PET's rows taller than eight lines** blank the lines past the
332
+ eighth, after the schematics, where `xpet` repeats them
333
+ (`PET::CRTCVideo`). Don't take `xpet` as a reference for tall rows.
334
+ - **A 6525's port C in mode 1** reads the CB, CA and IRQ pins in its top
335
+ three bits, after the datasheet (`Chips::TPI`): `$A0` at a B series'
336
+ READY, where `xcbm2` reads `$C0`.
337
+ - **The P500's sprite bytes in phi2** come from the video bus the FPLA
338
+ describes (`Chips::VIC::Bank#peek_sprite`), not the KERNAL bytes
339
+ `xcbm5x0` returns.
340
+ - **The PET's power-on RAM** is zeros. `crtc13`'s reference shows
341
+ `xpet`'s own pattern, and no dump of a real PET's is known.
342
+ - **The VIC-I's sound**: its volume scales the output linearly, where
343
+ `xvic`'s doesn't, and its loudness and output stage are fitted, not
344
+ pinned. [VIC-I sound](pinned-behaviour.md#vic-i-sound).
@@ -143,7 +143,6 @@ module Badline
143
143
  audio = modules.required("BL-AUDIO")
144
144
  restore_player(modules, audio)
145
145
  record
146
- player.stereo.sids.each_with_index { |sid, index| sid.load_recording_fields(audio, "sid#{index + 1}.rec.") }
147
146
  @level = audio.int("level", 0)
148
147
  @still = audio.int("still", 0)
149
148
  audio.int("played", 0)
@@ -176,9 +175,9 @@ module Badline
176
175
  end
177
176
 
178
177
  # The player's modules, after a BL-AUDIO module with the cycles into
179
- # the subtune, each SID's model and how far its recording has got,
180
- # and the silence count. The SIDs have caught up as the frame drained
181
- # them, so they leave no samples behind.
178
+ # the subtune, each SID's model and the silence count. Each SID's own
179
+ # module holds how far its recording has got. The SIDs have caught up
180
+ # as the frame drained them, so they leave no samples behind.
182
181
  def save_checkpoint(played, clock_hz)
183
182
  checkpoints = @checkpoints
184
183
  return if checkpoints.nil? || !checkpoints.due?(played, clock_hz)
@@ -187,10 +186,7 @@ module Badline
187
186
  sections = player.save_modules
188
187
  audio = Snapshot::Keyed.section("BL-AUDIO") do |out|
189
188
  out.int("played", played)
190
- sids.each_with_index do |sid, index|
191
- out.text("sid#{index + 1}.model", sid.model.to_s)
192
- sid.save_recording_fields(out, "sid#{index + 1}.rec.")
193
- end
189
+ sids.each_with_index { |sid, index| out.text("sid#{index + 1}.model", sid.model.to_s) }
194
190
  out.int("level", @level).int("still", @still)
195
191
  end
196
192
  checkpoints.keep(played, [audio, *sections])
data/lib/badline/c128.rb CHANGED
@@ -74,6 +74,8 @@ module Badline
74
74
 
75
75
  def family = :c128
76
76
 
77
+ def portless_board = nil
78
+
77
79
  def address_bus = @bus
78
80
 
79
81
  def region = @bus.region
@@ -124,6 +126,7 @@ module Badline
124
126
  @holding_commodore = false
125
127
  @bus = Bus.new(@model, sid_model: sid_model || @model.sid_model, mode:)
126
128
  @cpu = CPU.new(@bus, debug:)
129
+ @cpu.power_on! if @c64_built
127
130
  build_z80
128
131
  @vic = @bus.vic
129
132
  @vic.open_bus = -> { @bus.ram.peek(@cpu.program_counter) }
@@ -204,6 +207,7 @@ module Badline
204
207
  @vdc.power_on!
205
208
  @bus.power_on!
206
209
  reset!
210
+ @cpu.power_on! if @c64_built
207
211
  enter_c64_mode if @c64_built
208
212
  end
209
213
 
@@ -15,8 +15,14 @@ module Badline
15
15
  # alone at $DC00-$DFFF and the cartridge port in Ultimax mode.
16
16
  BOARDS = %i[c64 pet64 gs max].freeze
17
17
 
18
+ # The boards with no serial port for a disk drive: the C64GS's case
19
+ # covers its board's, and the MAX Machine has none.
20
+ NO_SERIAL_PORT = { gs: "the C64GS", max: "the MAX Machine" }.freeze
21
+
18
22
  attr_reader :board
19
23
 
24
+ def portless_board = NO_SERIAL_PORT[@board]
25
+
20
26
  # Fits the KERNAL ROM ROMs::KERNALS names, leaves the cassette port
21
27
  # empty unless `datasette`, and puts the chips in the board BOARDS
22
28
  # names.
data/lib/badline/c64.rb CHANGED
@@ -48,6 +48,10 @@ module Badline
48
48
 
49
49
  def datasette = address_bus.datasette
50
50
 
51
+ # The board's name when it has no serial port for a disk drive, or nil.
52
+ # Every family answers it, nil on all but these C64 boards.
53
+ def portless_board = address_bus.portless_board
54
+
51
55
  def install_debug_register(&) = address_bus.install_debug_register(&)
52
56
 
53
57
  # The machine options (sid_model:, cia_model:, vic_model:, region: and
@@ -63,6 +67,7 @@ module Badline
63
67
  @address_bus = Bus.new(**machine.except(*Bus::Fittings::NAMES))
64
68
  @address_bus.fit(**machine.slice(*Bus::Fittings::NAMES))
65
69
  @cpu = Chips::CPU.new(@address_bus, debug:)
70
+ @cpu.power_on!
66
71
  @vic = @address_bus.vic
67
72
  @vic.open_bus = -> { @address_bus.ram.peek(@cpu.program_counter) }
68
73
  @cia1 = @address_bus.cia1
@@ -141,6 +146,7 @@ module Badline
141
146
  vic.power_on!
142
147
  address_bus.power_on!
143
148
  reset!
149
+ cpu.power_on!
144
150
  end
145
151
 
146
152
  # The RES line reaches the CPU and its port, both CIAs, the SID, the
@@ -55,6 +55,8 @@ module Badline
55
55
 
56
56
  def family = :cbm2
57
57
 
58
+ def portless_board = nil
59
+
58
60
  def peek(addr) = @bus.peek(addr)
59
61
 
60
62
  def poke(addr, value) = @bus.poke(addr, value)
@@ -102,6 +104,7 @@ module Badline
102
104
  keyboard_ports.connect(@tpi2)
103
105
  map_io
104
106
  @cpu = CPU.new(@bus, debug:)
107
+ @cpu.power_on!
105
108
  @cycles = 0
106
109
  @mains_half_period = clock_hz / (2 * @mains_hz)
107
110
  @mains_countdown = @mains_half_period
@@ -211,6 +214,7 @@ module Badline
211
214
  @color_ram.clear!
212
215
  @vic.power_on!
213
216
  reset!
217
+ @cpu.power_on!
214
218
  end
215
219
 
216
220
  # The cycle by which the KERNAL has tested segments 0 and 1 and BASIC
data/lib/badline/cbm2.rb CHANGED
@@ -74,6 +74,8 @@ module Badline
74
74
 
75
75
  def family = :cbm2
76
76
 
77
+ def portless_board = nil
78
+
77
79
  # Reads and writes memory as the CPU sees it, in its execution segment.
78
80
  def peek(addr) = @bus.peek(addr)
79
81
 
@@ -123,6 +125,7 @@ module Badline
123
125
  keyboard_ports.connect(@tpi2)
124
126
  map_io
125
127
  @cpu = CPU.new(@bus, debug:)
128
+ @cpu.power_on!
126
129
  @cycles = 0
127
130
  @mains_half_period = CLOCK_HZ / (2 * mains_hz)
128
131
  @mains_countdown = @mains_half_period
@@ -224,6 +227,7 @@ module Badline
224
227
  def power_cycle!
225
228
  @bus.power_on!
226
229
  reset!
230
+ @cpu.power_on!
227
231
  end
228
232
 
229
233
  private
@@ -25,8 +25,8 @@ module Badline
25
25
  include Boundary
26
26
  include KeyedState
27
27
 
28
- attr_reader :memory, :instructions, :boundary_crossed, :cycles, :pending_write
29
- attr_accessor :program_counter, :stack_pointer, :status, :a, :x, :y, :nmi, :irq
28
+ attr_reader :memory, :instructions, :boundary_crossed, :cycles, :pending_write, :program_counter
29
+ attr_accessor :stack_pointer, :status, :a, :x, :y, :nmi, :irq
30
30
 
31
31
  alias pending_write? pending_write
32
32
 
@@ -56,15 +56,32 @@ module Badline
56
56
  end_sequence
57
57
  end
58
58
 
59
+ # Clears the registers and runs the reset sequence from its first
60
+ # stack read, the two reads at the program counter having run while
61
+ # the reset circuit held RES. The first opcode fetch comes 5 cycles
62
+ # after power-on, where x64sc's does.
63
+ def power_on!
64
+ @program_counter = @stack_pointer = 0
65
+ @a = @x = @y = 0
66
+ reset!
67
+ @index = 2
68
+ end
69
+
70
+ # Starts the reset sequence, RESET_PLAN, which reads the program
71
+ # counter from $FFFC and fetches the first opcode 7 cycles later. A,
72
+ # X and Y keep their values, and S ends 3 lower.
59
73
  def reset!
60
74
  status.interrupt = true
61
- reset_registers
62
75
  @nmi = false
63
76
  @irq_sample = @irq_pending = false
64
77
  @nmi_sample = @nmi_pending = false
65
78
  @skip_poll = false
66
79
  @pending_write = false
67
- end_sequence
80
+ @interrupt = 0xfffc
81
+ @brk = false
82
+ @plan = RESET_PLAN
83
+ @writes = RESET_WRITES
84
+ @index = 0
68
85
  end
69
86
 
70
87
  # Drops the instruction or interrupt sequence in flight and the
@@ -83,6 +100,14 @@ module Badline
83
100
  @pending_write = false
84
101
  end
85
102
 
103
+ # Sets the program counter. A CPU in its reset sequence leaves it,
104
+ # so the next cycle fetches an opcode at +address+ and not at the
105
+ # reset vector's.
106
+ def program_counter=(address)
107
+ @program_counter = address
108
+ end_sequence if @plan.equal?(RESET_PLAN)
109
+ end
110
+
86
111
  def p
87
112
  status.value
88
113
  end
@@ -82,6 +82,15 @@ module Badline
82
82
  @nmi = false if @interrupt == 0xfffa
83
83
  end
84
84
 
85
+ def op_reset_dummy
86
+ @memory.peek(@program_counter)
87
+ end
88
+
89
+ def op_reset_stack
90
+ @memory.peek(stack_address)
91
+ @stack_pointer = (@stack_pointer - 1) & 0xff
92
+ end
93
+
85
94
  def op_int_vector_low
86
95
  @address = @memory.peek(@interrupt)
87
96
  end
@@ -4,14 +4,17 @@ module Badline
4
4
  module Chips
5
5
  class CPU
6
6
  # Where a CPU caught inside an instruction is, as a snapshot names it:
7
- # an opcode's instruction, an IRQ or NMI sequence, or jammed.
8
- POSITIONS = %w[boundary op interrupt jam].freeze
7
+ # an opcode's instruction, an IRQ or NMI sequence, jammed, or the
8
+ # reset sequence.
9
+ POSITIONS = %w[boundary op interrupt jam reset].freeze
9
10
 
10
11
  # The CPU's fields in a keyed snapshot module (Snapshot::Keyed): the
11
12
  # registers, the interrupt lines' pipeline, the counts and, inside an
12
13
  # instruction, where it is in hardware terms (pos.*), rather than as
13
14
  # the microcode lays it out.
14
15
  module KeyedState
16
+ SEQUENCE_NAMES = { INTERRUPT => "interrupt", RESET => "reset" }.freeze
17
+
15
18
  def save_fields(out)
16
19
  out.int("pc", @program_counter).int("sp", @stack_pointer).int("a", @a).int("x", @x).int("y", @y)
17
20
  out.int("p", @status.value).bool("nmi", @nmi).bool("so_high", @so_high)
@@ -75,8 +78,8 @@ module Badline
75
78
  return
76
79
  end
77
80
 
78
- out.text("pos.seq", source == INTERRUPT ? "interrupt" : "op")
79
- out.int("pos.op", source) unless source == INTERRUPT
81
+ out.text("pos.seq", SEQUENCE_NAMES.fetch(source, "op"))
82
+ out.int("pos.op", source) unless SEQUENCE_NAMES.key?(source)
80
83
  out.int("pos.t", position_t(source)).bool("pos.crossed", @boundary_crossed)
81
84
  out.bool("pos.branch_taken", @branch_taken == true).bool("pos.brk", @brk)
82
85
  out.optional_int("pos.vector", @interrupt)
@@ -88,7 +91,7 @@ module Badline
88
91
  # The cycles of the instruction or sequence run so far, counting the
89
92
  # opcode fetch as 1 and leaving out stalled cycles.
90
93
  def position_t(source)
91
- return @index if source == INTERRUPT
94
+ return @index if SEQUENCE_NAMES.key?(source)
92
95
 
93
96
  MICROCODE.fetch(source).t_at(@index, @boundary_crossed)
94
97
  end
@@ -96,7 +99,8 @@ module Badline
96
99
  def load_position(input)
97
100
  case input.choice("pos.seq", POSITIONS, "boundary")
98
101
  when "op" then load_instruction(input)
99
- when "interrupt" then load_interrupt(input)
102
+ when "interrupt" then load_sequence(input, INTERRUPT_PLAN, INTERRUPT_WRITES, 1)
103
+ when "reset" then load_sequence(input, RESET_PLAN, RESET_WRITES, 0)
100
104
  when "jam" then load_jam
101
105
  else load_boundary
102
106
  end
@@ -112,10 +116,11 @@ module Badline
112
116
  load_latches(input)
113
117
  end
114
118
 
115
- def load_interrupt(input)
116
- use_plan(INTERRUPT_PLAN, INTERRUPT_WRITES)
119
+ # An interrupt or reset sequence, whose plan starts at step +first+.
120
+ def load_sequence(input, plan, writes, first)
121
+ use_plan(plan, writes)
117
122
  @boundary_crossed = input.bool("pos.crossed", false)
118
- @index = input.int("pos.t", 1).clamp(1, INTERRUPT_PLAN.length - 1)
123
+ @index = input.int("pos.t", first).clamp(first, plan.length - 1)
119
124
  load_latches(input)
120
125
  end
121
126
 
@@ -162,6 +167,7 @@ module Badline
162
167
  return FETCH if @plan.equal?(FETCH_PLAN)
163
168
  return INTERRUPT if @plan.equal?(INTERRUPT_PLAN)
164
169
  return JAMMED if @plan.equal?(JAMMED_PLAN)
170
+ return RESET if @plan.equal?(RESET_PLAN)
165
171
 
166
172
  MICROCODE.index { |micro| micro.plan.equal?(@plan) && micro.operation == @operation } ||
167
173
  MICROCODE.index { |micro| micro.plan.equal?(@plan) }