badline 0.16.0 → 0.16.1

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 (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +16 -0
  3. data/README.md +32 -9
  4. data/doc/media.md +9 -0
  5. data/doc/snapshot-format.md +4 -2
  6. data/doc/snapshots.md +51 -10
  7. data/lib/badline/drives/ieee_bus.rb +50 -4
  8. data/lib/badline/drives/model2031/ieee_port.rb +133 -0
  9. data/lib/badline/drives/model2031/keyed_state.rb +66 -0
  10. data/lib/badline/drives/model2031/parts.rb +25 -0
  11. data/lib/badline/drives/model2031.rb +92 -0
  12. data/lib/badline/frontend/menu/menu_pages.rb +11 -2
  13. data/lib/badline/kernal_trap/ieee.rb +3 -0
  14. data/lib/badline/machine.rb +3 -2
  15. data/lib/badline/media/pet_media.rb +5 -5
  16. data/lib/badline/media/true_drive.rb +19 -6
  17. data/lib/badline/media.rb +6 -2
  18. data/lib/badline/options/help.rb +8 -4
  19. data/lib/badline/options/table.rb +9 -3
  20. data/lib/badline/options/validation.rb +20 -4
  21. data/lib/badline/options.rb +27 -8
  22. data/lib/badline/pet/bus/expansion.rb +99 -5
  23. data/lib/badline/pet/bus.rb +30 -12
  24. data/lib/badline/pet/characters.rb +8 -2
  25. data/lib/badline/pet/crtc_video.rb +20 -7
  26. data/lib/badline/pet/ieee_drive.rb +76 -0
  27. data/lib/badline/pet/ieee_ports.rb +12 -1
  28. data/lib/badline/pet/ieee_traps.rb +10 -5
  29. data/lib/badline/pet/keyed_modules.rb +4 -1
  30. data/lib/badline/pet/keyed_parts.rb +10 -6
  31. data/lib/badline/pet/model.rb +56 -6
  32. data/lib/badline/pet/rom_sets.rb +30 -6
  33. data/lib/badline/pet.rb +33 -15
  34. data/lib/badline/roms/README +33 -1
  35. data/lib/badline/roms/dos2031.rom +0 -0
  36. data/lib/badline/roms/pet/characters-din.rom +0 -0
  37. data/lib/badline/roms/pet/editor4-80-b-50hz-8296.rom +0 -0
  38. data/lib/badline/snapshot/container.rb +4 -1
  39. data/lib/badline/snapshot/field_doc.rb +4 -3
  40. data/lib/badline/snapshot/image.rb +22 -11
  41. data/lib/badline/snapshot/pet_setup.rb +30 -13
  42. data/lib/badline/snapshot/vice/pet.rb +117 -0
  43. data/lib/badline/snapshot/vice/pet_cpu.rb +67 -0
  44. data/lib/badline/snapshot/vice/pet_crtc.rb +246 -0
  45. data/lib/badline/snapshot/vice/pet_mem.rb +93 -0
  46. data/lib/badline/snapshot/vice/pet_pias.rb +56 -0
  47. data/lib/badline/snapshot/vice/pet_restore.rb +150 -0
  48. data/lib/badline/snapshot/vice/pet_via.rb +164 -0
  49. data/lib/badline/snapshot/vice.rb +1 -0
  50. data/lib/badline/version.rb +1 -1
  51. metadata +16 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e262717ff30b8634c869b951cbb627fead4c6d4270cb0f9251cfadb567c0edef
4
- data.tar.gz: 6c35c8a8313fe7de6de0d7df4a8436bebc160e1267e11132bd84c0be0abc50b0
3
+ metadata.gz: 3e6b53df8a82bbf9f9524441b6d948fb245c0e81c589757dcd23b7e69fbd4c69
4
+ data.tar.gz: 83e5a043f2a09ed982663e1df4a0f4e2c5483791417fd1220785dab8d0e39d42
5
5
  SHA512:
6
- metadata.gz: b9de5e45e9ab8b0d8a18c98815599c6055f0fc1a44a2324f34f60753a3096d5c254edad42852e7bf0bf1507b1fb845512cca41117fb6f5f51988fa3a41be0204
7
- data.tar.gz: 5102663a8eee7f811f9e88b81ff79948ad4f1011d790551a8d8bbba53de29c676f4055db0fe15a2e1085b0bbe3efee805e2db421ba5161b77c9abbaefb181623
6
+ metadata.gz: f739ad0afd029d46648ceff8307e010ca6290e4c8951175e30c70e08ec11385dba966c79ebd2d3d5a93e52db922b0362b45239825e3f42e750ee4f0fe56589a5
7
+ data.tar.gz: d5e22aa87453b2446e2d5c2168f99076e4a950a753022fd5dc5946fb56ababc156bcda2b0d57ed1e90a923dee9d693c7040187f4475d8c1a6e50afa5346ca7e3
data/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.16.1](https://github.com/elektronaut/badline/compare/v0.16.0...v0.16.1) (2026-10-18)
4
+
5
+
6
+ ### Features
7
+
8
+ * add the 2031 disk drive on the PET's IEEE-488 bus ([bcc76a5](https://github.com/elektronaut/badline/commit/bcc76a536a44bdb6173b26823c83a5026a788c4f))
9
+ * add the CBM 8296 ([48fb14d](https://github.com/elektronaut/badline/commit/48fb14d0d94b75cf003d7601c637158801acaef8))
10
+ * put a true 2031 on the PET's device 8 with --true-drive ([c64e1b4](https://github.com/elektronaut/badline/commit/c64e1b4245b1390ba6d57e5ffaad620c4055b5cd))
11
+ * read and write xpet's .vsf snapshots for the PET ([607050b](https://github.com/elektronaut/badline/commit/607050b9c72767e335d2b488afd0d3c4ce153c81))
12
+ * save a PET's 2031 in its snapshots ([a8d6880](https://github.com/elektronaut/badline/commit/a8d68806a34e34f45c02fb7a5a00dd2699ccb618))
13
+
14
+
15
+ ### Bug Fixes
16
+
17
+ * blank the CRTC PETs' character lines past the eighth ([f02db02](https://github.com/elektronaut/badline/commit/f02db02a6255944b552b6a5f3e8108483ae78875))
18
+
3
19
  ## [0.16.0](https://github.com/elektronaut/badline/compare/v0.15.1...v0.16.0) (2026-10-17)
4
20
 
5
21
 
data/README.md CHANGED
@@ -82,7 +82,7 @@ differences noted in the table. `--help` lists the options for either.
82
82
  | --- | --- |
83
83
  | `--no-autostart` | Attach the media and stop at `READY.`, so you can type the `LOAD` yourself |
84
84
  | `--writable` | Let the machine write to disk images. Without it, a disk goes in write-protected: the drive reports `26,WRITE PROTECT ON` for any write and the image file stays as it was |
85
- | `--true-drive` | Put an emulated 1541 on device 8 instead of the KERNAL traps, or a 1551 on the Plus/4. See [Media](#media) |
85
+ | `--true-drive` | Put an emulated 1541 on device 8 instead of the KERNAL traps, a 1551 on the Plus/4, or a 2031 on the PET. See [Media](#media) |
86
86
  | `--drive-type 1541` | On the Plus/4, make the true drive on device 8 an emulated 1541 on the serial bus in place of the 1551. It implies `--true-drive`. See [The Plus/4](#the-plus4) |
87
87
  | `-s`, `--subtune N` | Pick a subtune of a `.sid` file, counting from 1 |
88
88
  | `--sid 6581`, `--sid 8580` | Fit the older or newer SID. `--sid auto`, the default, takes a `.sid` tune's own |
@@ -115,7 +115,7 @@ differences noted in the table. `--help` lists the options for either.
115
115
  | `--script FILE` | Run the events in `FILE`, one `FRAME:EVENT` a line |
116
116
  | `--screenshot FILE` | Save the last frame as a `.bmp` |
117
117
  | `--save-snapshot FILE` | Save the machine as a `.vsf` snapshot after the last frame |
118
- | `--vice` | Add VICE's modules to `--save-snapshot`'s C64 snapshot, so x64sc opens it too |
118
+ | `--vice` | Add VICE's modules to `--save-snapshot`'s C64 or PET snapshot, so x64sc or xpet opens it too |
119
119
 
120
120
  ```sh
121
121
  badline --unpaced --frames 12000 --true-drive disk1.d64 \
@@ -302,7 +302,8 @@ badline pet --model 2001 # The first PET, with BASIC 1
302
302
 
303
303
  `--model` picks the machine: `8032` (the default, 32K, 80 columns and
304
304
  the business keyboard), `8096` (an 8032 with the 64K board at `$FFF0`),
305
- `4032` (40 columns on a CRTC and the graphics keyboard), `3032` (the
305
+ `8296` (128K on one board, below), `4032` (40 columns on a CRTC and the
306
+ graphics keyboard), `3032` (the
306
307
  discrete 40-column board with BASIC 2), `3032b` (the same with the
307
308
  business keyboard) and `2001` (8K, BASIC 1 and the chiclet keyboard).
308
309
  `--ram 4k`, `8k` or `16k` fits less RAM in place of the model's own, as
@@ -324,7 +325,22 @@ cycles, 400 to a frame. `4-80-b-50hz-0283`, the chip dated 0283, runs
324
325
  lines of 64 cycles, 313 to a frame, with the screen's rows a scanline
325
326
  closer together and lower down. Both boot to the same READY.
326
327
 
328
+ The 8296 is an 8032 on a board of its own, with 128K: 32K below
329
+ `$8000`, 32K above it, whose first 4K hold the screen and the rest lies
330
+ under the ROMs, and a 64K board banked through `$FFF0` as the 8096's is.
331
+ Writes from `$8000` up go to the RAM under the ROMs, and `--jumpers`
332
+ fits the jumpers that let reads find it there: `ram9` and `rama` for RAM
333
+ at `$9000` and `$A000`, as xpet's `-petram9` and `-petramA`, and
334
+ `userport` for the user port's PA0 to PA2 to switch it in over BASIC,
335
+ the editor and the KERNAL. Its CRTC's RA4 is an address line, so the
336
+ lines past the 16th of a tall character row show RAM from `$C000`. It
337
+ runs its own 50 Hz editor, `4-80-b-50hz-8296`, with lines of 59 cycles,
338
+ 339 to a frame, and takes the 8032's editors too. `--characters din`
339
+ fits the German boards' 4K character ROM, whose two halves R12's bit 5
340
+ picks between.
341
+
327
342
  ```sh
343
+ badline pet --model 8296 --jumpers ram9,rama # An 8296 with RAM at $9000 and $A000
328
344
  badline pet --model 2001 --basic 2 # A 2001 with BASIC 2
329
345
  badline pet --model 3032 --basic 4 # A 3032 with BASIC 4, without a CRTC
330
346
  badline pet --editor 4-80-b-60hz # An 8032 at 60 Hz, as --60hz runs it
@@ -341,11 +357,13 @@ A program loads at the start of BASIC, `$0401`, and runs. A `.d80`, a
341
357
  `.d82` or a tape made for the PET starts a PET without `pet`, but a
342
358
  program that loads at `$0401` starts a C64, since a VIC-20 with 3K loads
343
359
  there too. Disks go through the KERNAL traps on the IEEE-488 bus as
344
- device 8, and tapes play on cassette port 1. The keys type what the host's
360
+ device 8, or with `--true-drive` an emulated 2031 on the bus, which
361
+ reads `.d64` and `.g64` images, and tapes play on cassette port 1. The keys type what the host's
345
362
  keys type, on either keyboard, and `Alt` adds the PET's `SHIFT`:
346
363
  [Input](#input) lists the rest. The pause
347
364
  menu's Power page names the model, its RAM and its frame rate, and its
348
- Expansion port page its RAM and the 8096's 64K board. The PET has no
365
+ Expansion port page its RAM and the 8096's 64K board, or the 8296's
366
+ jumpers. The PET has no
349
367
  joysticks. `F11` and the Snapshots page save it as they do the others,
350
368
  but not for VICE.
351
369
 
@@ -357,7 +375,8 @@ newest, even after badline has quit and started again. badline also
357
375
  autosaves every two minutes of play, and the pause menu's Snapshots page
358
376
  saves under a name and loads any of them. Snapshots from badline 0.14.0
359
377
  on load in every later version. They use VICE's `.vsf` format: badline
360
- opens those x64sc saves, and x64sc opens badline's saved with `--vice`.
378
+ opens those x64sc and xpet save, and x64sc and xpet open badline's saved
379
+ with `--vice`.
361
380
  See [doc/snapshots.md](doc/snapshots.md) for where they're kept, what
362
381
  they hold and how far the two agree.
363
382
 
@@ -534,12 +553,16 @@ in cartridge 1 as it does from the command line.
534
553
  receives at its baud rates, with nothing on the user port; Ruby code
535
554
  driving the machine can plug in a terminal, whose bytes it feeds and
536
555
  drains, or a loopback plug (`Plus4::ACIA#plug`).
537
- - **PET and CBM**: the 2001, 3032, 3032b, 4032, 8032 and 8096, with
538
- BASIC 1, 2 or 4, the discrete video and the 6545 CRTC on 40 and 80
556
+ - **PET and CBM**: the 2001, 3032, 3032b, 4032, 8032, 8096 and 8296,
557
+ with BASIC 1, 2 or 4, the discrete video and the 6545 CRTC on 40 and 80
539
558
  columns, both 6520 PIAs and the 6522 VIA, the graphics and business
540
- keyboards, the sound on the VIA's CB2, the 8096's 64K board, both
559
+ keyboards, the sound on the VIA's CB2, the 8096's 64K board, the
560
+ 8296's 128K with its jumpers, RA4 and 4K character ROM, both
541
561
  cassette ports and device 8 on the IEEE-488 bus through the KERNAL
542
562
  traps, `.d80` and `.d82` among its disks (`badline pet`).
563
+ - **2031**: the PET's single drive, the 1541's board and DOS 2.6 on the
564
+ IEEE-488 bus, with the bus's handshake on every line
565
+ (`badline pet --true-drive`).
543
566
  - **1541**: an emulated drive running its own DOS (`--true-drive`).
544
567
  - **1571**: the C128D's drive running DOS 3.0, in 1541 mode and in 1571
545
568
  mode at 2 MHz with both sides of a `.d71` or `.g71`, and fast serial
data/doc/media.md CHANGED
@@ -38,6 +38,15 @@ On the Plus/4 and the C16, `--true-drive` puts an emulated 1551 there
38
38
  instead, on TCBM, the parallel cable to its interface in the expansion
39
39
  port, and `--drive-type 1541` a 1541 on the serial bus.
40
40
 
41
+ On the PET, `--true-drive` puts an emulated 2031 there, the 1541's board
42
+ with an IEEE-488 interface, running DOS 2.6 and answering the bus's
43
+ handshake line by line. It reads `.d64` and `.g64` images, which BASIC 2
44
+ and 4 autostart with `LOAD"*",8` and `RUN`; a `.d80` or `.d82` needs the
45
+ traps. BASIC 1's LOAD never finishes on the drive, since its UNLISTEN
46
+ leaves its command in the device number the LOAD then talks to, but its
47
+ OPEN, INPUT# and PRINT# work, and the error channel with them. `--basic
48
+ 2`, the upgrade Commodore sold, loads.
49
+
41
50
  A `.g64` holds the disk's raw GCR, half tracks and all, so copy
42
51
  protection and fast loaders that read it work.
43
52
 
@@ -68,9 +68,10 @@ Written by the C64, VIC-20, C128, Plus/4, PET.
68
68
  | `host.true_drive_model` | text: 1541 1551 | Plus/4: "1551" |
69
69
  | `refresh_hz` | int | PET: 50 or 60 |
70
70
  | `rom.basic` | text | PET: "pet/basic4.rom" or "pet/basic1.rom" |
71
- | `rom.editor` | text | PET: "pet/editor4-80-b-50hz.rom" or "pet/editor1.rom" |
71
+ | `rom.editor` | text | PET: "pet/editor4-80-b-50hz.rom" or "pet/editor1.rom" or "pet/editor4-80-b-50hz-8296.rom" |
72
72
  | `rom.kernal` | text | PET: "pet/kernal4.rom" or "pet/kernal1.rom" |
73
73
  | `rom.characters` | text | PET: "pet/characters2.rom" or "pet/characters1.rom" |
74
+ | `jumpers` | text | PET: "" |
74
75
  | `vertical_drive` | bool | PET: true |
75
76
 
76
77
  ## BL-RAM
@@ -84,9 +85,10 @@ Written by the C64, VIC-20, C128, Plus/4, PET.
84
85
  | `bus.high_bank` | int & 0x3 | Plus/4: 0 |
85
86
  | `bus.rom_selected` | bool | Plus/4: true |
86
87
  | `bus.data` | byte | Plus/4: 255; PET: 253 |
87
- | `screen` | bytes | PET: 2048 × 0 or 1024 × 0 |
88
+ | `screen` | bytes | PET: 2048 × 0 or 1024 × 0 or 32768 × 0 |
88
89
  | `expansion.control` | byte | PET: 0 |
89
90
  | `expansion.ram` | bytes | PET: 65536 × 0 |
91
+ | `expansion.user_port` | int | PET: 7 |
90
92
 
91
93
  ## BL-COLORRAM
92
94
 
data/doc/snapshots.md CHANGED
@@ -143,16 +143,57 @@ doesn't open it, and badline doesn't open xplus4's. `--vice` and
143
143
  ## The PET
144
144
 
145
145
  Every PET and CBM model saves and restores the same way. A snapshot of
146
- one holds its RAM, the screen RAM, an 8096's 64K board with its control
147
- register, both PIAs, the VIA, the CPU, the CRTC on the boards that have
148
- one, the picture with where the video is in its frame, the tapes in both
149
- datasettes with their places on them, and the disk or directory device 8
150
- serves through the IEEE-488 traps. It restores into a machine of the same
151
- model with the same RAM, the same editor frame rate and the same ROMs,
152
- which come with the model. Its file names its machine `PET`, as xpet's
153
- do, and holds only badline's modules, so xpet doesn't open it, and
154
- badline doesn't open xpet's. `--vice` and
155
- `computer.export_snapshot(path)` refuse a PET.
146
+ one holds its RAM, the screen RAM, or on an 8296 all 32K from `$8000`,
147
+ an 8096's or 8296's 64K board with its control register and the user
148
+ port lines the 8296's RAM follows, both PIAs, the VIA, the CPU, the
149
+ CRTC on the boards that have one, the picture with where the video is
150
+ in its frame, the tapes in both datasettes with their places on them,
151
+ and the disk or directory device 8 serves through the IEEE-488 traps.
152
+ It restores into a machine of the same model with the same RAM, the
153
+ same editor frame rate, the same ROMs, which come with the model, and
154
+ an 8296's same jumpers. Its file names its machine `PET`, as xpet's do,
155
+ and holds only badline's modules, so xpet doesn't open it unless it is
156
+ saved with `--vice` (see [xpet](#xpet)).
157
+
158
+ ### xpet
159
+
160
+ A PET snapshot saved with `--vice`, or `computer.export_snapshot(path)`,
161
+ also holds xpet's modules for the CPU, the RAM with the screen RAM, or
162
+ an 8296's 32K from `$8000`, an 8096's or 8296's board and its control
163
+ register, the CRTC, both PIAs and the VIA, and the ones xpet 3.10 needs
164
+ to open the file, with nothing open on the file system drive and
165
+ nothing on the user port. The CRTC module carries the discrete boards'
166
+ video too, in xpet's terms. The file says it is in VICE's snapshot
167
+ format 1.0, the only one xpet reads.
168
+
169
+ xpet 3.10 opens those snapshots as long as its model and editor match:
170
+ `-model 2001`, `3032`, `3032B`, `4032`, `8032`, `8096` or `8296`, and
171
+ for a badline editor other than xpet's own, `-editor` naming badline's
172
+ ROM. xpet's 8296 runs the 8032's editor, 901474-04 dated 0283, so an
173
+ 8296 saved for it has to run that editor (`--editor 4-80-b-50hz-0283`),
174
+ and xpet takes the 8296's jumpers from its own settings. It keeps its
175
+ own drives and datasettes and leaves out badline's tapes and disk. Its
176
+ CRTC loses a few lines on opening any snapshot, its own too, so a
177
+ program it runs on from one finds the vertical sync a few hundred
178
+ cycles later than one it ran without stopping.
179
+
180
+ Snapshots xpet saves open in badline through the same modules: the CPU
181
+ at its instruction boundary, the RAM, the screen RAM, an 8096's or
182
+ 8296's board, the CRTC's registers and beam, both PIAs' registers and
183
+ flags, and the VIA's registers, timers and shift register. A program
184
+ badline runs on from xpet's snapshot finds the vertical sync on the
185
+ cycle xpet does, so the two run on alike, cycle for cycle. The picture
186
+ starts again with the next frame. xpet doesn't save its ROMs, which
187
+ keyboard it has or an 8296's jumpers, so the board, the RAM and the
188
+ CRTC's frame build the machine: a 2001, a 3032 for any other discrete
189
+ board, a 4032, an 8032, an 8096 or an 8296 with none of its jumpers
190
+ fitted, at 50 or 60 Hz, and an 8032, 8096 or 8296 whose CRTC lines are
191
+ 64 cycles long runs xpet's own 50 Hz editor, 901474-04 dated 0283. A
192
+ SuperPET's snapshot fails. It reports the modules it leaves out, such
193
+ as the drives, the datasettes and the keyboard, and carries on without
194
+ them. Restored into a running PET, an xpet snapshot fails for a PET
195
+ built another way, such as a 3032B, and otherwise switches the machine
196
+ off and on, keeping its tapes and the disk device 8 serves.
156
197
 
157
198
  ## VICE
158
199
 
@@ -8,9 +8,13 @@ module Badline
8
8
  # pulls it low. The PET is the controller and always on the bus, and
9
9
  # reads back every line it drives.
10
10
  #
11
- # No device plugs in yet, so the lines carry the controller's levels
12
- # alone: nobody answers a handshake, and the KERNAL reports DEVICE NOT
13
- # PRESENT for every device the traps don't serve.
11
+ # The controller pushes the lines it pulls whenever they may have moved
12
+ # (host_lines=), and the bus holds them for the drives, which take them
13
+ # a host cycle late. Each drive on the bus answers the lines it pulls
14
+ # (ieee_low_lines) while ATN is or isn't low, from its own outputs, which
15
+ # hold still while it sleeps. With no drive on the bus nobody answers a
16
+ # handshake, and the KERNAL reports DEVICE NOT PRESENT for every device
17
+ # the traps don't serve.
14
18
  class IEEEBus
15
19
  # The lines as bits, the data lines in the low byte, DIO1 in bit 0.
16
20
  DIO = 0x00ff
@@ -20,9 +24,51 @@ module Badline
20
24
  NDAC = 0x0800
21
25
  ATN = 0x1000
22
26
 
27
+ # The lines the controller last pushed.
28
+ attr_reader :host_lines
29
+
30
+ attr_reader :drives
31
+
32
+ def initialize
33
+ @drives = []
34
+ @host_lines = 0
35
+ end
36
+
37
+ # The controller pushes the lines it pulls, and the drives look at
38
+ # them again.
39
+ def host_lines=(lines)
40
+ @host_lines = lines
41
+ drives = @drives
42
+ i = 0
43
+ while i < drives.length
44
+ drives[i].host_written!
45
+ i += 1
46
+ end
47
+ end
48
+
49
+ def attach(drive)
50
+ @drives << drive unless @drives.include?(drive)
51
+ end
52
+
53
+ def detach(drive)
54
+ @drives.delete(drive)
55
+ end
56
+
23
57
  # The lines low on the bus while the controller pulls the lines in
24
58
  # +controller+ low.
25
- def low_lines(controller) = controller
59
+ def low_lines(controller)
60
+ low = controller
61
+ drives = @drives
62
+ return low if drives.empty?
63
+
64
+ atn = controller.anybits?(ATN)
65
+ i = 0
66
+ while i < drives.length
67
+ low |= drives[i].ieee_low_lines(atn)
68
+ i += 1
69
+ end
70
+ low
71
+ end
26
72
  end
27
73
  end
28
74
  end
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model2031
6
+ # VIA 1's side of the IEEE-488 bus (Drives::IEEEBus), through a 75160
7
+ # for the data lines and a 75161 for the control lines (schematic
8
+ # 2031038, sheet 2):
9
+ #
10
+ # PA0-7 DIO1-8 PB4 T/R, high to talk
11
+ # PB0 ATN acknowledge (ATNA) PB5 HD SEL, unused
12
+ # PB1 NRFD PB6 DAV
13
+ # PB2 NDAC PB7 ATN in, high while ATN is low
14
+ # PB3 EOI CA1 ATN in, as PB7
15
+ #
16
+ # The ports read and drive the lines' levels, 0 for a low line. T/R
17
+ # turns the transceivers: talking, the drive drives the data lines,
18
+ # DAV and EOI, and reads NRFD and NDAC; listening, the other way round.
19
+ # EOI turns back to the bus while ATN is low. A pin the transceiver
20
+ # drives the bus from reads the drive's own side, high but for what
21
+ # the VIA or a jumper pulls low.
22
+ #
23
+ # An XOR of ATN in and ATNA drives two 7406s that pull NRFD and NDAC
24
+ # low on the bus, so the drive answers ATN before the DOS gets to it,
25
+ # and holds the bus until the DOS acknowledges. ATNA reaches the XOR
26
+ # only while PB0 is an output, and is low there otherwise, which
27
+ # leaves the bus alone while the DOS reads the jumpers.
28
+ #
29
+ # The device number jumpers are diodes from PB0 and PB1 to CA2: with
30
+ # CA2 low, a fitted diode pulls its line low, and the DOS reads the
31
+ # device number off the two at reset, 8 with both fitted. Lifting the
32
+ # one on PB0 adds 1, and the one on PB1 2.
33
+ #
34
+ # The drive takes the controller's lines a host cycle late, as the
35
+ # serial drives do (Engine::SerialLines): latch_host takes them at the
36
+ # end of each host cycle.
37
+ class IEEEPort
38
+ ATNA = 0x01
39
+ NRFD = 0x02
40
+ NDAC = 0x04
41
+ EOI = 0x08
42
+ TALK = 0x10
43
+ DAV = 0x40
44
+ ATN_IN = 0x80
45
+
46
+ attr_reader :bus
47
+
48
+ # The lines the controller pulled, as latch_host took them.
49
+ attr_reader :host
50
+
51
+ def initialize(device:, bus:)
52
+ raise ArgumentError, "a 2031 is device 8 to 11, not #{device}" unless device.between?(8, 11)
53
+
54
+ @diodes = ~(device - 8) & 0x03
55
+ @via = nil
56
+ @host = 0
57
+ self.bus = bus
58
+ end
59
+
60
+ # The drive's VIA 1, whose outputs it drives the bus with.
61
+ attr_writer :via
62
+
63
+ def bus=(bus)
64
+ @bus = bus
65
+ latch_host
66
+ end
67
+
68
+ # Takes the controller's lines for the drive cycles of the next host
69
+ # cycle.
70
+ def latch_host
71
+ @host = @bus.host_lines
72
+ end
73
+
74
+ # Whether the controller has moved ATN since the last latch_host.
75
+ def atn_moved? = (@bus.host_lines ^ @host).anybits?(IEEEBus::ATN)
76
+
77
+ def atn_low? = @host.anybits?(IEEEBus::ATN)
78
+
79
+ # The data lines, while the transceiver listens.
80
+ def read_a(_lines) = talking? ? 0xff : ~@bus.low_lines(@host) & 0xff
81
+
82
+ # The control lines the transceiver turns towards the drive, and
83
+ # ATN.
84
+ def read_b(_lines)
85
+ low = @bus.low_lines(@host)
86
+ value = 0x7f
87
+ if talking?
88
+ value &= ~NRFD if low.anybits?(IEEEBus::NRFD)
89
+ value &= ~NDAC if low.anybits?(IEEEBus::NDAC)
90
+ value &= ~EOI if low.anybits?(IEEEBus::EOI) && low.anybits?(IEEEBus::ATN)
91
+ else
92
+ value &= ~EOI if low.anybits?(IEEEBus::EOI)
93
+ value &= ~DAV if low.anybits?(IEEEBus::DAV)
94
+ end
95
+ value |= ATN_IN if low.anybits?(IEEEBus::ATN)
96
+ value & jumpers
97
+ end
98
+
99
+ # The lines the drive pulls low while ATN is low or not, as bus bits.
100
+ def ieee_low_lines(atn)
101
+ via = @via
102
+ lines = via.port_b_output & jumpers
103
+ low = 0
104
+ if lines.anybits?(TALK)
105
+ low |= ~via.port_a_output & IEEEBus::DIO
106
+ low |= IEEEBus::DAV if lines.nobits?(DAV)
107
+ low |= IEEEBus::EOI if lines.nobits?(EOI) && !atn
108
+ else
109
+ low |= IEEEBus::NRFD if lines.nobits?(NRFD)
110
+ low |= IEEEBus::NDAC if lines.nobits?(NDAC)
111
+ end
112
+ low |= IEEEBus::NRFD | IEEEBus::NDAC if atn != (lines & via.port_b_directions).anybits?(ATNA)
113
+ low
114
+ end
115
+
116
+ # The controller's lines as the port latched them, under `prefix`.
117
+ def save_fields(out, prefix) = out.int("#{prefix}host", @host)
118
+
119
+ def load_fields(input, prefix)
120
+ @host = input.int("#{prefix}host", @host)
121
+ end
122
+
123
+ private
124
+
125
+ def talking? = @via.port_b_output.anybits?(TALK)
126
+
127
+ # PB0 and PB1 as the jumper diodes leave them: pulled low through
128
+ # each fitted one while CA2 is low.
129
+ def jumpers = @via.ca2_output ? 0xff : ~@diodes & 0xff
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model2031
6
+ # The drive's own fields in a keyed snapshot module (BL-D8): its
7
+ # model, its clock phase and cycle count, the SO pulse due, and the
8
+ # controller's lines as the IEEE-488 port latched them under ieee.*. A
9
+ # save settles the drive first, so it is awake; a load forgets its
10
+ # orbits.
11
+ module KeyedState
12
+ def save_fields(out)
13
+ settle!
14
+ out.text("model", model_name).int("phase", @phase).int("cycles", @cycles)
15
+ out.bool("so_pending", @mechanism.so_pending)
16
+ @serial_port.save_fields(out, "ieee.")
17
+ out
18
+ end
19
+
20
+ def load_fields(input)
21
+ settle!
22
+ input.choice("model", [model_name], model_name)
23
+ @phase = input.int("phase", @phase)
24
+ @cycles = input.int("cycles", @cycles)
25
+ @mechanism.so_pending = input.bool("so_pending", @mechanism.so_pending)
26
+ @serial_port.load_fields(input, "ieee.")
27
+ end
28
+
29
+ # The drive as modules of its own, named from `name` (BL-D8 for
30
+ # device 8): its own fields, its RAM, each VIA, the CPU, the
31
+ # mechanism and the disk, when one is in, as the 1541's.
32
+ def save_modules(sections, name)
33
+ sections << Snapshot::Keyed.section(name) { |out| save_fields(out) }
34
+ sections << Snapshot::Keyed.section("#{name}-RAM") { |out| @bus.save_fields(out) }
35
+ sections << Snapshot::Keyed.section("#{name}-VIA1") { |out| @via1.save_fields(out) }
36
+ sections << Snapshot::Keyed.section("#{name}-VIA2") { |out| @via2.save_fields(out) }
37
+ sections << Snapshot::Keyed.section("#{name}-CPU") { |out| @cpu.save_fields(out) }
38
+ sections << Snapshot::Keyed.section("#{name}-MECH") { |out| @mechanism.save_fields(out) }
39
+ disk = @mechanism.disk
40
+ sections << Snapshot::Keyed.section("#{name}-DISK") { |out| disk.save_fields(out) } if disk
41
+ sections
42
+ end
43
+
44
+ # The drive from its modules, `input` being its own. No disk module
45
+ # means no disk in the drive.
46
+ def load_modules(modules, input, name)
47
+ load_fields(input)
48
+ @bus.load_fields(modules.required("#{name}-RAM"))
49
+ @via1.load_fields(modules.required("#{name}-VIA1"))
50
+ @via2.load_fields(modules.required("#{name}-VIA2"))
51
+ @cpu.load_fields(modules.required("#{name}-CPU"))
52
+ disk = modules.optional("#{name}-DISK")
53
+ mechanism = modules.required("#{name}-MECH")
54
+ @mechanism.load_fields(mechanism,
55
+ disk.nil? ? nil : Model1541::Disk.from_fields(disk, detached: modules.detached?))
56
+ @via1.restored!
57
+ @via2.restored!
58
+ @cpu.restored!
59
+ forget_orbits
60
+ end
61
+ end
62
+
63
+ include KeyedState
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ module Drives
5
+ class Model2031
6
+ # VIA 1, facing the IEEE-488 bus (IEEEPort), a class of its own so
7
+ # its peripheral keeps one type, apart from the serial drives'.
8
+ class IEEEVIA < Chips::VIA
9
+ # DDRB, whose PB0 says whether the VIA drives ATNA (IEEEPort).
10
+ def port_b_directions = @ddrb
11
+ end
12
+
13
+ # The drive CPU's bus, decoded as the 1541's (Model1541::Bus), with
14
+ # VIA 1 an IEEEVIA.
15
+ class Bus < Model1541::Bus
16
+ end
17
+
18
+ # The drive's 6502, on the drive's Bus.
19
+ class CPU
20
+ include Badline::Chips::CPU::Core
21
+ include Engine::IdleCPU
22
+ end
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,92 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "badline/drives/model1541"
4
+ require "badline/drives/ieee_bus"
5
+ require "badline/drives/model2031/parts"
6
+ require "badline/drives/model2031/ieee_port"
7
+ require "badline/drives/model2031/keyed_state"
8
+
9
+ module Badline
10
+ module Drives
11
+ # The 2031 disk drive of the PET and CBM: the 1541's board with an
12
+ # IEEE-488 interface in place of the serial one (Commodore schematic
13
+ # 2031038). A 6502 at 1 MHz runs DOS 2.6 from 16 KB of ROM, with 2 KB
14
+ # of RAM, VIA 1 at $1800 on the IEEE-488 bus (IEEEPort) and VIA 2 at
15
+ # $1C00 running the 1541's GCR Mechanism, decoded as the 1541's.
16
+ #
17
+ # Drives::Engine::Core clocks it against the host and sleeps through
18
+ # the DOS's idle loop, as the 1541's, until ATN moves or a timer is due
19
+ # (Engine::VIAs). The bus reads the lines it pulls without waking it.
20
+ class Model2031
21
+ include Engine::Core
22
+ include Engine::VIAs
23
+
24
+ CLOCK_HZ = 1_000_000
25
+
26
+ # Where the DOS's idle loop starts over (see Drives::Engine::Idle).
27
+ IDLE_LOOP = 0xec4d
28
+
29
+ # The host cycles by which a 2031 switched on with the machine has run
30
+ # its DOS's RAM test and ROM checksums and reads its jumpers.
31
+ BOOT_CYCLES = 1_300_000
32
+
33
+ attr_reader :ieee_bus
34
+
35
+ def model_name = "2031"
36
+
37
+ # +rom+ covers $C000-$FFFF, and defaults to the DOS image in the ROM
38
+ # path. +device+ is the number the jumpers set, 8 to 11.
39
+ def initialize(rom: nil, host_clock_hz: CLOCK_HZ, device: 8, debug: false)
40
+ @device = device
41
+ @ieee_bus = IEEEBus.new
42
+ @serial_port = IEEEPort.new(device:, bus: @ieee_bus)
43
+ @via1 = IEEEVIA.new(start: 0x1800, peripheral: @serial_port)
44
+ @serial_port.via = @via1
45
+ @mechanism = Model1541::Mechanism.new
46
+ @via2 = Model1541::DiskVIA.new(start: 0x1c00, mechanism: @mechanism)
47
+ @bus = Bus.new(rom: rom || ROM.load("dos2031.rom", 0xc000), via1: @via1, via2: @via2)
48
+ @cpu = CPU.new(@bus, debug:)
49
+ @clock_hz = CLOCK_HZ
50
+ @idle_loop = IDLE_LOOP
51
+ @host_clock_hz = host_clock_hz
52
+ @phase = 0
53
+ @cycles = 0
54
+ init_idle(debug)
55
+ @via1.ca1 = false
56
+ @via1.reset!
57
+ connect(@ieee_bus)
58
+ end
59
+
60
+ # Plugs the drive into an IEEE-488 bus, leaving the one it was on.
61
+ def connect(ieee_bus)
62
+ settle!
63
+ @ieee_bus.detach(self)
64
+ @ieee_bus = ieee_bus
65
+ @serial_port.bus = ieee_bus
66
+ ieee_bus.attach(self)
67
+ hear_atn
68
+ end
69
+
70
+ # The lines the drive pulls on the bus while ATN is low or not
71
+ # (IEEEPort). They hold still while it sleeps, so reading them leaves
72
+ # it asleep.
73
+ def ieee_low_lines(atn) = @serial_port.ieee_low_lines(atn)
74
+
75
+ private
76
+
77
+ # One drive cycle, as the 1541's (Model1541#step): the mechanism, then
78
+ # ATN on VIA 1's CA1, the VIAs and the CPU.
79
+ def step
80
+ so = @mechanism.take_so
81
+ @mechanism.cycle!
82
+ @via1.ca1 = @serial_port.atn_low?
83
+ @via1.cycle!
84
+ @via2.cycle!
85
+ @cpu.irq = @via1.irq? || @via2.irq?
86
+ @cpu.so! if so
87
+ @cpu.cycle!
88
+ @cycles += 1
89
+ end
90
+ end
91
+ end
92
+ end
@@ -156,10 +156,19 @@ module Badline
156
156
  toggle(plus4? ? "SID CARD" : "SID", :sid, [%w[6581 8580], %i[sid6581 sid8580], model == :mos8580 ? 1 : 0])
157
157
  end
158
158
 
159
- # The PET's memory: its RAM, and the 8096's 64K board.
159
+ # The PET's memory: its RAM, the 8096's 64K board, and the 8296's
160
+ # 128K with the jumpers that put RAM under its ROMs.
160
161
  def draw_pet_memory
162
+ model = @computer.model
163
+ return draw_8296_memory if model.cbm8296?
164
+
161
165
  info("RAM", "#{@computer.ram_kb}K")
162
- info("EXPANSION RAM", @computer.model.expansion ? "64K BOARD" : "")
166
+ info("EXPANSION RAM", model.expansion ? "64K BOARD" : "")
167
+ end
168
+
169
+ def draw_8296_memory
170
+ info("RAM", "128K, 64K OF IT BANKED")
171
+ info("JUMPERS", @computer.jumpers.join(", ").upcase)
163
172
  end
164
173
 
165
174
  # Saving now and by name, loading a named save, and the quicksaves
@@ -23,6 +23,9 @@ module Badline
23
23
  UNTALK_COMMAND = 0x5f
24
24
  UNLISTEN_COMMAND = 0x3f
25
25
 
26
+ # The device the traps serve, or nil to serve none.
27
+ attr_accessor :device
28
+
26
29
  def initialize(cpu:, bus:, layout:, dos:)
27
30
  @cpu = cpu
28
31
  @bus = bus