badline 0.4.2 → 0.5.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 (69) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +25 -0
  3. data/CLAUDE.md +10 -5
  4. data/README.md +54 -23
  5. data/doc/pinned-behaviour.md +221 -0
  6. data/exe/badline-ruby +7 -4
  7. data/lib/badline/address_bus/ultimax_pages.rb +38 -0
  8. data/lib/badline/address_bus.rb +19 -29
  9. data/lib/badline/audio/jukebox.rb +6 -4
  10. data/lib/badline/audio/machine_player.rb +10 -6
  11. data/lib/badline/cartridge/kcs_power.rb +3 -3
  12. data/lib/badline/cia.rb +5 -1
  13. data/lib/badline/computer.rb +86 -7
  14. data/lib/badline/cpu.rb +39 -0
  15. data/lib/badline/drive1541/bus.rb +134 -0
  16. data/lib/badline/drive1541/disk.rb +256 -0
  17. data/lib/badline/drive1541/disk_via.rb +33 -0
  18. data/lib/badline/drive1541/gcr.rb +57 -0
  19. data/lib/badline/drive1541/idle.rb +184 -0
  20. data/lib/badline/drive1541/mechanism.rb +277 -0
  21. data/lib/badline/drive1541/rotation.rb +111 -0
  22. data/lib/badline/drive1541/sector_reader.rb +76 -0
  23. data/lib/badline/drive1541/serial_port.rb +67 -0
  24. data/lib/badline/drive1541/track.rb +98 -0
  25. data/lib/badline/drive1541.rb +218 -0
  26. data/lib/badline/gui/application.rb +15 -3
  27. data/lib/badline/gui/drive_led_pane.rb +29 -0
  28. data/lib/badline/gui.rb +1 -0
  29. data/lib/badline/iec_bus.rb +93 -0
  30. data/lib/badline/kernal_trap/channel.rb +9 -1
  31. data/lib/badline/kernal_trap/serial.rb +9 -3
  32. data/lib/badline/media/true_drive.rb +54 -0
  33. data/lib/badline/media.rb +22 -1
  34. data/lib/badline/options.rb +24 -4
  35. data/lib/badline/region.rb +35 -10
  36. data/lib/badline/reu/dma.rb +228 -0
  37. data/lib/badline/reu/ram.rb +65 -0
  38. data/lib/badline/reu/trigger.rb +32 -0
  39. data/lib/badline/reu.rb +276 -0
  40. data/lib/badline/roms/README +10 -0
  41. data/lib/badline/roms/dos1541.rom +0 -0
  42. data/lib/badline/sdl/functions.rb +1 -0
  43. data/lib/badline/storage/d64_image.rb +18 -9
  44. data/lib/badline/storage/d81_image.rb +1 -0
  45. data/lib/badline/storage/disk_image/catalog.rb +52 -0
  46. data/lib/badline/storage/disk_image/writing.rb +16 -0
  47. data/lib/badline/storage/disk_image.rb +2 -0
  48. data/lib/badline/storage/g64_image.rb +222 -0
  49. data/lib/badline/storage/host_directory.rb +21 -1
  50. data/lib/badline/storage/listing.rb +118 -0
  51. data/lib/badline/storage/t64.rb +14 -0
  52. data/lib/badline/storage.rb +2 -0
  53. data/lib/badline/traps.rb +4 -0
  54. data/lib/badline/version.rb +1 -1
  55. data/lib/badline/via/control_lines.rb +3 -0
  56. data/lib/badline/via/fast_forward.rb +38 -0
  57. data/lib/badline/via/shift_register.rb +8 -3
  58. data/lib/badline/via/timer1.rb +30 -0
  59. data/lib/badline/via/timer2.rb +25 -0
  60. data/lib/badline/via.rb +2 -0
  61. data/lib/badline/vic/display_state.rb +7 -7
  62. data/lib/badline/vic/sprite/internal_bus.rb +11 -7
  63. data/lib/badline/vic/sprite/shifter.rb +10 -15
  64. data/lib/badline/vic/sprite/timing.rb +60 -0
  65. data/lib/badline/vic/sprite.rb +14 -12
  66. data/lib/badline/vic/sprites.rb +2 -2
  67. data/lib/badline/vic.rb +86 -39
  68. data/lib/badline.rb +3 -0
  69. metadata +26 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3a05f51e8da6e71ad0d80cc763b3daa5d806245164be400d4736ab63de9c0006
4
- data.tar.gz: 58353de430b44bec2aff4f1b401186f1684b7a1898a1f3bb903d2e43b2abbe88
3
+ metadata.gz: 0c6abfa72fe3a5e5f8e88efe9d53e04b4e6eaeb355550ef5ac23c47c1a0916fc
4
+ data.tar.gz: e15d996a7121eb244ba6ad7a8870694389f59ab8139977b2cf7b9d344f35bbe7
5
5
  SHA512:
6
- metadata.gz: d58d47e49a8618017a7cf36ed5c7627a4cd3b88f0a0862497ced34d67935be5d96276d5ab2b8cccb2f07a69c9a48acc4188eeac308839a94d68c20e6ac0435b8
7
- data.tar.gz: caa5504d1cd1f9ebacd1661a85b1ea39e86756d91fc2a0b276a4a416113510b836f9f0f61643712a99874c7c0e0ced5b40b35f84696e77bd36dec159c875c7c9
6
+ metadata.gz: d6fdecfc6b65d6b67d296fafb91e1cbdad7920e0326f146c5bae4bfed08fdd3fad9b4b38007a7f3bdb6a859ff5f1428ff7d94f573e13b68dfdb2e31c5c15e9fe
7
+ data.tar.gz: 6843561cca4725f4d6a93cff613ea9b0a9db78d1d45aebee9c4ba7573c49205b0162c4431d4cb78b81e12ded0e84388866017e88b94e065790934e2aaa0c4e8e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## [0.5.0](https://github.com/elektronaut/badline/compare/v0.4.2...v0.5.0) (2026-09-29)
4
+
5
+
6
+ ### Features
7
+
8
+ * add a --true-drive option ([3df4183](https://github.com/elektronaut/badline/commit/3df4183dec298ff6b386a6b6a1c12fb04ce3cca0))
9
+ * run the 1541 as its own machine ([9a4bafa](https://github.com/elektronaut/badline/commit/9a4bafab4c53fc7dc479a8c78527e8711d96c8cf))
10
+ * join the C64 and the 1541 over the IEC serial bus ([7671045](https://github.com/elektronaut/badline/commit/7671045bd47fdf363eb9a57faae3c463835c7544))
11
+ * read D64 disks through the 1541's GCR mechanism ([4c812fa](https://github.com/elektronaut/badline/commit/4c812fa25bad92c5c18219fc22c7a0ed804fe159))
12
+ * write and format disks through the 1541 ([22a2a8b](https://github.com/elektronaut/badline/commit/22a2a8be6b9f84b9fc4d7e214603165462c7ddef))
13
+ * read and write .g64 disk images ([884ac19](https://github.com/elektronaut/badline/commit/884ac19baeb83468444e0631c2fd5840bdffc327))
14
+ * take .g64 images in the native badline ([8d9ab92](https://github.com/elektronaut/badline/commit/8d9ab9285555eaa0681a3113290af872a79d9f0b))
15
+ * skip the 1541's cycles while it idles ([d8502cd](https://github.com/elektronaut/badline/commit/d8502cd589aed3ab7590cf2467b9ea57a76330fc))
16
+ * list a mounted disk with LOAD"$",8 through the KERNAL traps ([c634e18](https://github.com/elektronaut/badline/commit/c634e18e3aa8e1b5ba3096d9b6b4ec9d41afa77b)), closes [#324](https://github.com/elektronaut/badline/issues/324)
17
+ * emulate the REU and run the testbench REU rows ([3d5d69e](https://github.com/elektronaut/badline/commit/3d5d69e7570b9961bfd823adf00c2924c65d45e9)), closes [#232](https://github.com/elektronaut/badline/issues/232)
18
+ * run NTSC machines and the testbench's NTSC rows ([e01ebf1](https://github.com/elektronaut/badline/commit/e01ebf17a03d606ff4a71afdb89506af962300e6)), closes [#306](https://github.com/elektronaut/badline/issues/306)
19
+ * power RAM on in a $00/$ff pattern ([59822c3](https://github.com/elektronaut/badline/commit/59822c3dc2956cd871a6e218cac213cfb38f6bad)), closes [#319](https://github.com/elektronaut/badline/issues/319)
20
+ * go on to the next subtune when one ends in the SID player ([0b2f2a2](https://github.com/elektronaut/badline/commit/0b2f2a209f3cb47d8117126f80217e4cc9030342)), closes [#359](https://github.com/elektronaut/badline/issues/359)
21
+
22
+
23
+ ### Bug Fixes
24
+
25
+ * keep the KCS Power Cartridge's RAM apart from the C64's ([c538b43](https://github.com/elektronaut/badline/commit/c538b43e6794cbeedcc4056fb71b38598525bfa9))
26
+ * write stdout as it goes, so errors appear in order ([42ccb02](https://github.com/elektronaut/badline/commit/42ccb026d6fe92c1ec5b67ab6f561084c5b154b2)), closes [#369](https://github.com/elektronaut/badline/issues/369)
27
+
3
28
  ## [0.4.2](https://github.com/elektronaut/badline/compare/v0.4.1...v0.4.2) (2026-09-28)
4
29
 
5
30
  ### Features
data/CLAUDE.md CHANGED
@@ -17,6 +17,9 @@ live in subdirectories:
17
17
  - **VIC-II**: `vic.rb` and `vic/`, covering the sequencer, sprites, graphics
18
18
  modes, border and register timing
19
19
  - **CIA**: `cia.rb` and `cia/` (timers, serial), plus `time_of_day.rb`
20
+ - **REU**: `reu.rb` and `reu/`, the RAM Expansion Unit's registers and
21
+ its DMA, which `Computer#cycle!` clocks in place of the CPU while it
22
+ holds the bus
20
23
  - **SID**: `sid.rb` and `sid/`. `audio/` plays and renders tunes for
21
24
  `badline-ruby --headless` and `--audio-out`
22
25
  - **Media and host I/O**: `storage/` (disk, tape and cartridge image
@@ -43,7 +46,7 @@ requires only the namespace file.
43
46
  | rspec (`spec/`) | `bundle exec rspec` | Unit behaviour, all of `lib/` |
44
47
  | SingleStepTests 65x02 | `rake test` (100 sampled cases per opcode) | CPU, per-cycle bus traces |
45
48
  | Wolfgang Lorenz suite | `bin/lorenz` | CPU, CIA, interrupts |
46
- | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, and cartridges with `--carts` |
49
+ | VICE testbench | `bin/testbench <subtree>` | `VICII/`, `CIA/`, `interrupts/`, `CPU/`, cartridges with `--carts`, and the true 1541 drive with `--drive` |
47
50
  | VICE SID testprogs | `bin/sidtests` | SID |
48
51
  | CIA offline grids | `bundle exec rspec --tag slow spec/badline/cia` | CIA timers and shift register, against a bare CIA in about 2 min |
49
52
 
@@ -136,7 +139,7 @@ overrides that, capped by the core count, but keep the default, because
136
139
  other worktrees share the machine. Whole runs at 4 shards on an M-series
137
140
  laptop take 15 min for `testbench` (`VICII/`), 11 for `testbench-cia`, 1 for
138
141
  `testbench-interrupts`, 37 for `testbench-irqdma`, 11 for
139
- `testbench-cpu`, 2 for `testbench-carts` and 10 for `testbench-expansions`. `sid` takes about 12 min. `lorenz` chains itself and takes
142
+ `testbench-cpu`, 2 for `testbench-carts` and about 25 for `testbench-expansions`. `sid` takes about 12 min. `lorenz` chains itself and takes
140
143
  about 2.5 h on CI whole. `rake regression:lorenz-1` to `lorenz-4` run it as
141
144
  four stretches of about 40 min each, and they can run side by side.
142
145
  `test/baselines/README.md` has the full table. A killed `bin/testbench` run
@@ -179,13 +182,15 @@ the rows your change can't reach tell you nothing about it.
179
182
 
180
183
  Pick the filters from the suites your change can move: VIC → `testbench`,
181
184
  plus `testbench-vicii-new` (`bin/testbench --vicii-new`, the 8565) for
182
- anything the VIC model reaches;
185
+ anything the VIC model reaches, and `testbench-ntsc` (`bin/testbench --ntsc`,
186
+ the 6567R8 and 6567R56A) for anything a region's timing reaches;
183
187
  CPU, interrupts or timing → the matching `testbench-*` suite, plus
184
188
  `rake test` for CPU; CIA → the slow CIA specs first, then the matching
185
189
  `testbench-cia` rows, plus `testbench-cia-new` (`bin/testbench --cia-new`,
186
190
  the 6526A) for anything the interrupt register or the CIA model reaches; cartridge mappers, banking or power-on state →
187
- `testbench-carts`, plus `testbench-expansions` for banking; GEO-RAM, +60K
188
- or +256K → `testbench-expansions`; SID → `sid`, plus `sid-8580` for anything the 8580
191
+ `testbench-carts`, plus `testbench-expansions` for banking; GEO-RAM, +60K,
192
+ +256K, the REU or the VIC's BA line the REU follows → `testbench-expansions`; the 1541 drive, VIA or IEC bus →
193
+ `testbench-drive` (`bin/testbench --drive`); SID → `sid`, plus `sid-8580` for anything the 8580
189
194
  model reaches (`bin/sidtests --sid 8580`). Lorenz isn't a per-change check:
190
195
  its full chain runs nightly, and the planner assigns any row it moves. The
191
196
  exception is code whose rule in `doc/pinned-behaviour.md` names Lorenz
data/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  # Badline
5
5
 
6
6
  Badline is a Commodore 64 emulator written in Ruby. It emulates a PAL
7
- machine one clock cycle at a time, stepping the 6510, the VIC-II, both
7
+ or NTSC machine one clock cycle at a time, stepping the 6510, the VIC-II, both
8
8
  CIAs and the SID together, so raster timing, bad lines and sprite DMA
9
9
  are modelled at the cycle level.
10
10
 
@@ -69,7 +69,9 @@ from it. `--no-autostart` attaches the media and stops at `READY.`, so
69
69
  you can type the `LOAD` yourself. `--read-only` mounts a disk image
70
70
  write-protected, so the drive reports `26,WRITE PROTECT ON` for any
71
71
  write and the image file stays as it was. `--song N` picks a subtune of a
72
- `.sid` file and `--sid 8580` fits the newer SID. `badline-ruby` also
72
+ `.sid` file, `--sid 8580` fits the newer SID and `--reu 512` plugs in a
73
+ 512K RAM Expansion Unit. `--ntsc` runs an NTSC C64, with the 6567R8
74
+ VIC-II, instead of a PAL one. `badline-ruby` also
73
75
  takes `--disable-jit`, which runs without YJIT, otherwise switched on at
74
76
  startup. `--help` lists the options.
75
77
 
@@ -100,27 +102,42 @@ slows the emulation down.
100
102
  |--------|----------|
101
103
  | `.prg`, `.p00` | Loaded into memory after boot. A program at the BASIC start (`$0801`) is `RUN`, anything else is left for you to `SYS` |
102
104
  | `.d64`, `.d71`, `.d81` | Mounted read-write as device 8, then `LOAD"*",8,1` and `RUN`. Writes go straight back to the image file. With `--read-only`, or an image the host can't write, it acts as a write-protected disk |
105
+ | `.g64` | Put in a true 1541, which is plugged in as device 8 for it, then `LOAD"*",8,1` and `RUN`. The image holds the disk's raw GCR, half tracks and all, so copy protection and fast loaders that read it work. Tracks the drive writes go back to the image file. With `--read-only`, or an image the host can't write, it acts as a write-protected disk |
103
106
  | `.t64` | Mounted read-only as device 8 and loaded like a disk image. The files load by name, and no tape is involved |
104
107
  | `.tap` | Inserted in the datasette with PLAY pressed, then `LOAD` and `RUN`. It loads at the speed of a real tape |
105
108
  | `.crt` | The hardware types listed under [Cartridges](#whats-emulated). Other types are rejected |
106
109
  | `.sid` | PSID and RSID tunes, started through a small driver after boot |
107
110
  | A directory | Mounted read-write as device 8. It serves the `.prg` and `.p00` files in it and the contents of any `.t64`, and `SAVE` writes a new `.prg` |
108
111
 
109
- There is no 1541. Device 8 works by trapping the KERNAL's `LOAD` and
110
- `SAVE` routines and its serial bus primitives, so files open by name
111
- through `OPEN` and `CHRIN` as well. The command channel answers `I`,
112
- `B-P` and `U1` block reads, which covers loaders that read blocks
113
- directly. On a disk image it also takes `SAVE` (with `@0:` to replace a
114
- file), files opened for writing or appending, `S` to scratch, `U2` and
115
- `B-W` block writes, and `B-A` and `B-F`. Loaders that upload their own
116
- code to the drive with `M-W` and `M-E`, and copy protection that reads
117
- raw GCR, won't work.
112
+ Apart from a `.g64`, which only a true drive can read, there is no 1541
113
+ unless `--true-drive` asks for one. Device 8 works by trapping the
114
+ KERNAL's `LOAD` and `SAVE` routines and its serial bus primitives, so
115
+ files open by name through `OPEN` and `CHRIN` as well. `LOAD"$",8` lists
116
+ the directory of any medium mounted there, as a 1541 does. The command
117
+ channel answers `I`, `B-P` and `U1` block reads, which covers loaders
118
+ that read blocks directly. On a disk image it also takes `SAVE` (with
119
+ `@0:` to replace a file), files opened for writing or appending, `S` to
120
+ scratch, `U2` and `B-W` block writes, and `B-A` and `B-F`. Loaders that
121
+ upload their own code to the drive with `M-W` and `M-E`, and copy
122
+ protection that reads raw GCR, won't work there, but do on a true
123
+ drive.
124
+
125
+ `--true-drive` puts an emulated 1541 on device 8 instead, running its
126
+ own DOS ROM on its own 6502 and talking to the machine over the serial
127
+ bus. It reads `.d64` and `.g64` images, which it autostarts with the
128
+ same `LOAD"*",8,1` and `RUN`, but not `.d71`, `.d81` or `.t64` images
129
+ or directories. `--read-only` puts the disk in write-protected. Loading
130
+ runs at the speed of a real 1541, and the drive's red LED lights in the
131
+ bottom right corner of the border. Both executables take it.
118
132
 
119
133
  `Badline::Media.insert_disk(computer, path)` swaps the disk image or
120
134
  directory in device 8 while the machine runs, for software that asks for
121
135
  another disk. It takes `read_only: true`, and `Badline::Media.attach`
122
136
  takes `disk: { read_only: true }`, to mount a disk image write-protected,
123
- as the test harnesses under `bin/` do.
137
+ as the test harnesses under `bin/` do. A `.g64` goes in the true 1541,
138
+ and takes out a disk mounted through the traps, so `LOAD` and `SAVE`
139
+ reach the 1541 too. With the 1541 in device 8, a `.d64` goes in its
140
+ drive as well.
124
141
 
125
142
  ## Playing and rendering SID tunes
126
143
 
@@ -139,16 +156,19 @@ badline-ruby --filter-chunk 1 tune.sid --audio-out out.wav # exact filter, slo
139
156
  ```
140
157
 
141
158
  Both modes take the same options. The window's own, `--no-autostart`,
142
- `--sound` and `--verbose`, don't apply to them. `--song` (or `-s`) picks the
143
- subtune, counting from 1 as HVSC does, and defaults to the tune's own
144
- start song. Playback asks the device for 44.1 kHz and takes whatever rate it
145
- offers, unless `--rate` says otherwise. Ctrl-C stops it.
159
+ `--read-only`, `--sound`, `--true-drive`, `--reu`, `--ntsc` and
160
+ `--verbose`, don't apply to them. `--song` (or `-s`) picks the subtune, counting from 1 as HVSC
161
+ does, and defaults to the tune's own start song. Playback asks the
162
+ device for 44.1 kHz and takes whatever rate it offers, unless `--rate`
163
+ says otherwise. Ctrl-C stops it.
146
164
 
147
165
  Played on a terminal, `--headless` shows the tune's name, author and
148
166
  release, the song number and the time played against the song's
149
- length. `n` or → skips to the next song, `p` or ← goes back one, space
150
- pauses and `q` quits. `--no-tui`, or output that isn't a terminal,
151
- gives plain progress output instead.
167
+ length. When a song reaches its length it goes on to the next, and it
168
+ stops after the last. `n` or → skips to the next song, `p` or ← goes
169
+ back one, space pauses and `q` quits. `--no-tui`, or output that isn't a
170
+ terminal, gives plain progress output instead and plays just the one
171
+ song, as `--audio-out` renders just the one.
152
172
 
153
173
  A `.sid` file doesn't store its length, so `badline-ruby` looks the
154
174
  tune up by MD5 in HVSC's `Songlengths.md5`. It finds the database
@@ -236,6 +256,13 @@ while it's off.
236
256
  `Badline::Computer.new(vic_model: :mos8565)` fits the C64C's 8565
237
257
  instead, with its grey dots on colour register writes and its own
238
258
  timing for mode splits, sprite multicolour splits and the light pen.
259
+ `Badline::Computer.new(region: Badline::Region::NTSC)` builds an NTSC
260
+ machine instead: the 6567R8's 65 cycles by 263 lines at 1,022,727 Hz,
261
+ with its later sprite fetches and its X counter, and TOD clocks on
262
+ 60 Hz mains. `Badline::Region::NTSC_OLD` is the first NTSC C64s'
263
+ 6567R56A, 64 cycles by 262 lines. The stock KERNAL tells them from PAL
264
+ by the raster, so every region boots the same ROMs. `--ntsc` picks the
265
+ 6567R8.
239
266
  - **CIA 1 and 2**: timers, time-of-day clocks with alarms, the serial
240
267
  shift register, interrupts, the keyboard matrix with its ghost keys,
241
268
  the control ports and the paddle multiplexer. The machine has the
@@ -246,6 +273,11 @@ while it's off.
246
273
  and the RC network on the board that removes the DC offset from the
247
274
  output. The machine has a 6581 unless a `.sid` tune asks for an 8580
248
275
  in its header, and `--sid 6581` or `--sid 8580` overrides either.
276
+ - **REU**: the 1700, 1764 and 1750 RAM Expansion Units, and the bigger
277
+ units up to 16M built on the same REC chip, with DMA timed against the
278
+ VIC's bad lines and sprites. `--reu SIZE` plugs one in, and
279
+ `Badline::Computer.new(reu: 512)` does the same from Ruby. An
280
+ REU beside a cartridge loses I/O 2 to the cartridge.
249
281
  - **Datasette**: `.tap` playback into CIA 1's FLAG line, with the motor
250
282
  and sense lines on the 6510 port.
251
283
  - **Cartridges**: standard 8K, 16K and Ultimax, Simons' BASIC, Ocean,
@@ -276,11 +308,10 @@ Known gaps:
276
308
  - `badline-ruby` runs below real time, so its live audio stutters.
277
309
  `badline` plays smoothly, and so do PSID tunes under
278
310
  `badline-ruby --headless`.
279
- - No drive emulation, so fast loaders and anything else that runs code
311
+ - Without `--true-drive`, fast loaders and anything else that runs code
280
312
  on the drive won't work (see [Media](#media)). The command channel
281
- doesn't rename, copy, format or validate disks, and `LOAD"$",8`
282
- doesn't list a disk's directory yet.
283
- - No NTSC machine and no REU.
313
+ doesn't rename, copy, format or validate disks.
314
+ - No PAL-N (Drean) machine.
284
315
  - The emulator window has no freeze button yet, so a freezer cartridge
285
316
  runs its menu but can't freeze a program.
286
317
 
@@ -25,10 +25,15 @@ 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-II NTSC](#vic-ii-ntsc)
28
29
  - [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
29
30
  - [CIA 6526A interrupt register](#cia-6526a-interrupt-register)
30
31
  - [CIA serial shift register](#cia-serial-shift-register)
31
32
  - [6510 I/O port](#6510-io-port)
33
+ - [RAM power-on pattern](#ram-power-on-pattern)
34
+ - [1541 serial port](#1541-serial-port)
35
+ - [1541 disk mechanism](#1541-disk-mechanism)
36
+ - [REU DMA](#reu-dma)
32
37
  - [SID oscillator](#sid-oscillator)
33
38
  - [SID register writes](#sid-register-writes)
34
39
  - [SID data bus](#sid-data-bus)
@@ -888,6 +893,46 @@ What the 8565 references don't settle, and so what stays as it is:
888
893
  6569's idle access, without the 6569's `$38ff` read where a DMA delay
889
894
  starts.
890
895
 
896
+ ## VIC-II NTSC
897
+
898
+ The 6567R8 (`Region::NTSC`) and the 6567R56A (`Region::NTSC_OLD`) run
899
+ every VIC rule above, on a line of 65 or 64 cycles, except where the
900
+ region moves the sprites. The positions live in `Region::Profile` and
901
+ `VIC::Sprite::Timing`.
902
+
903
+ - **Sprite fetches.** Sprite 0's p-access runs in cycle 59 instead of
904
+ 58, and everything that hangs off the fetches moves with it: the BA
905
+ windows (`VIC#layout_columns`), the DMA compares (columns 54 and 55
906
+ instead of 53 and 54), the phi1 accesses (`VIC#phi1_address`, where the
907
+ 6567R8 idles in cycle 10), the column whose BA a late DMA start misses
908
+ (`Sprite::Timing#ba_column`), the reload pixels
909
+ (`Sprite::Timing#reload_x`) and the idle-bus row of sprites 3-7
910
+ (`Sprite::InternalBus#row`). On the 6567R8 sprite 3's fetch then falls
911
+ in the last two cycles of the line, so it reloads with the next line's
912
+ row as sprites 0-2 do. The expansion flip-flop still toggles in column
913
+ 54.
914
+ - Pinned by `spritesteal_ntsc` and `_ntscold`, `spritex/testsuite_ntsc`
915
+ and `_ntscold`, `phi1timing_ntsc` and `_ntscold`, and
916
+ `spriteenable1_ntsc` and `2_ntsc` and their `_ntscold` twins (235 and
917
+ 458 px), all in `testbench-ntsc`: with sprite 0 back in cycle 58 each
918
+ of them fails, and every other row of the sprite, phi1, dmadelay, vsp
919
+ and screenpos subsets passes as before.
920
+ - Spec guard: *sprite DMA cycle stealing on NTSC* and *#phi1_data on
921
+ NTSC* in [`vic_spec.rb`](../spec/badline/vic_spec.rb), and
922
+ [`timing_spec.rb`](../spec/badline/vic/sprite/timing_spec.rb).
923
+ - **Display compare and X counter.** The 6567R8 turns a sprite's display
924
+ on and off in cycle 59 (`sprite_display_cycle`), where the 6567R56A
925
+ keeps the 6569's cycle 58. Its X counter holds for 8 pixels
926
+ (`x_hold`), reading $184-$187 three times, so it reaches $1ff on a
927
+ 520-pixel line. The 6567R56A's runs straight through 512 pixels.
928
+ Neither is pinned yet: moving the 6567R8's display compare to 58, the
929
+ 6567R56A's to 59, or running the 6567R8's counter straight through and
930
+ wrapping it at 512 fails no row of those subsets.
931
+ - **Blanking and crop.** The blanked lines are the ones VICE's NTSC view
932
+ leaves out, 12-27 on the 6567R8 and 13-27 on the 6567R56A, so the
933
+ `testbench-ntsc` screenshots, VICE's 247 lines from line 28 running on
934
+ into the next frame, see every line they show painted.
935
+
891
936
  ## CIA 6526 timer pipeline
892
937
 
893
938
  - Start and stop go through two stages. A force load lands one tick after
@@ -1091,6 +1136,182 @@ and each was knocked out: removing it fails the rows named.
1091
1136
  - Spec guard: *when a program writes to the port* in
1092
1137
  [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
1093
1138
 
1139
+ ## RAM power-on pattern
1140
+
1141
+ - RAM powers on in runs of `$00,$00,$ff,$ff,$ff,$ff,$00,$00`, inverted in
1142
+ `$4000-$7fff` and `$c000-$ffff`, on every power cycle. The RAM under
1143
+ `$00`/`$01` follows the pattern like the rest. It is the pattern of a
1144
+ C64C (ASSY 250469 R4) in `C64/raminitpattern/readme.txt`
1145
+ (`-raminitstartvalue 0 -raminitvalueinvert 4 -raminitvalueoffset 2
1146
+ -raminitpatterninvert 16384 -raminitpatterninvertvalue 255`), without
1147
+ that machine's occasional random bytes. `AddressBus::RAM_POWER_ON`
1148
+ holds it.
1149
+ - Pinned by `C64/raminitpattern`: `cyberloadtest` fails when
1150
+ `$f379-$f478` holds one value, `darkstarbbstest` when the first ten
1151
+ bytes of one of the pages `$4000`, `$5000` … `$9000` do, `platoontest`
1152
+ when `$1000-$10ff` holds five equal bytes in a row and 140 or more
1153
+ bytes of one value, and `typicaltest`
1154
+ checks `$3fff` against a table of values the Typical demo survives.
1155
+ All-`$00` fails the first three, and each other pattern the readme
1156
+ lists fails at least one of the four.
1157
+ - Spec guard: *RAM at power-on* in
1158
+ [`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
1159
+ - The RAM expansions' extra banks still power on at `$00`.
1160
+
1161
+ ## 1541 serial port
1162
+
1163
+ - The drive sees the C64's side of the serial bus a host cycle late. The
1164
+ drive runs after the C64 in each host cycle, so `SerialPort` reads the
1165
+ C64's lines as `latch_host` took them at the end of the previous host
1166
+ cycle, both on VIA 1's port B (DATA IN, CLK IN, ATN IN) and on CA1,
1167
+ which ATN reaches. A CIA 2 write lands for the drive's cycles of the
1168
+ next host cycle, the way the CIA's pins change at the end of the cycle
1169
+ that writes them. The drive's own lines, and what the C64 reads back,
1170
+ stay live.
1171
+ - The 2-bit loader that uploads the drive tests' code times its transfer
1172
+ from an ATN edge, and seeing that edge a cycle early loses the first bit
1173
+ pair.
1174
+ - Pinned by the `testbench-drive` rows below. Each was checked by having
1175
+ the drive read the live lines (`@bus.atn_low?` and `@bus.low_lines`
1176
+ with no argument) and rerunning the rows:
1177
+ - Fail without the latch (`exit=$ff`): `drive/selftest`,
1178
+ `drive/diskid/diskid1`, `drive/interrupts/timera`, all six
1179
+ `drive/scanner` rows, both `drive/openbus` rows and
1180
+ `drive/viavarious/via1`, `via3a` and `via10`.
1181
+ - Pass either way: `drive/defaults`, `drive/rpm/rpm1` and `rpm2`,
1182
+ `drive/skew/skew1` and `drive/iecdelay/iec-bus-delay-auto`.
1183
+ - Spec guard: *sees an assertion from the host cycle after the one it
1184
+ lands in* and *shows the drive the C64's CLK from the host cycle after
1185
+ the write* in [`iec_bus_spec.rb`](../spec/badline/iec_bus_spec.rb).
1186
+
1187
+ ## 1541 disk mechanism
1188
+
1189
+ - A step out against the stop at track 1 slips the stepper's phases: the
1190
+ head stays on half track 2, and the phase that pulled it outwards
1191
+ becomes that half track's. The DOS's bump steps out 92 half tracks, a
1192
+ whole number of phase turns, and then takes the phase it ends on as
1193
+ track 1's: `N:` formats track 1 there and steps in two phases a track.
1194
+ With each half track holding its own phase from power-on instead, the
1195
+ bump ended two half tracks off track 1's phase, the first step in
1196
+ pulled the head against the stop, and the DOS wrote track 2 over track
1197
+ 1 and every later track one track out, at half track 2n - 2.
1198
+ - The DOS reads its own format back either way, since each header
1199
+ carries the track the DOS thought it was on: `drive/format` passes
1200
+ without the slip, and so doesn't pin it. Reading the disk back into a
1201
+ D64 does, since each header has to name the track the image holds it
1202
+ on.
1203
+ - Spec guard: *steps in from the stop with the first phase after the
1204
+ bump* in
1205
+ [`mechanism_spec.rb`](../spec/badline/drive1541/mechanism_spec.rb),
1206
+ and *lists it through the traps* (`:slow`) in
1207
+ [`drive1541_spec.rb`](../spec/badline/drive1541_spec.rb).
1208
+ - The disk turns at 300 rpm whatever bit rate VIA 2's PB5-6 select: a
1209
+ turn is 200,000 drive cycles over every track and over a half track
1210
+ without data. Each track's bits pass under the head at the rate they
1211
+ were written at: spread evenly over the turn, so a track as long as a
1212
+ turn holds at its zone's rate passes at that rate and a `.g64` track
1213
+ longer or shorter than that passes faster or slower, and a `.g64`
1214
+ speed map widens each byte's cells by its zone. The read clock runs at
1215
+ the selected rate and each flux transition brings it back into step,
1216
+ so a track read in its own zone reads clean and one read far enough
1217
+ off garbles (zone 0 against zone 3). With the rate selected turning
1218
+ the disk instead, a zone 3 track read in zone 0 read clean and took
1219
+ 246 ms a turn, and a `.g64` track turned in its length of bytes.
1220
+ - Pinned by `drive/rpm` (`rpm1`, `rpm2` and `rpm3`, on the `.d64` and
1221
+ the `.g64`): each times a turn and passes within 297 to 303 rpm.
1222
+ - Pinned by `drive/skew/skew2`, on a `.g64` whose tracks all start
1223
+ sector 0 at the same angle: the head keeps the disk's angle across
1224
+ the tracks and half tracks it steps over only while every track
1225
+ turns in the same time.
1226
+ - Pinned by `drive/scanner` (all six programs, on the `.d64` and the
1227
+ `.g64`), which reads every track and each track's error map.
1228
+ - Spec guard: the *at 300 rpm* examples and *turns once in 200 ms* in
1229
+ [`mechanism_spec.rb`](../spec/badline/drive1541/mechanism_spec.rb),
1230
+ and *#cell_at* in
1231
+ [`track_spec.rb`](../spec/badline/drive1541/track_spec.rb).
1232
+ - A write lays its bits one to a cell from the cell under the head. A
1233
+ half track without data gets a blank track first, as long as a turn at
1234
+ the rate the drive writes at, not at the rate of the track's own zone,
1235
+ and a track written in another zone is laid out again as a turn at the
1236
+ rate written, its flux kept at its angle to within a cell. A track
1237
+ written in the selected zone keeps its length, so a `.g64` track
1238
+ longer or shorter than a turn keeps the sectors around the one written.
1239
+ - Pinned by `drive/rpm/rpm3`, which writes track 36, past a 35-track
1240
+ D64, at the zone 2 rate the DOS leaves selected there: a turn and a
1241
+ half of SYNC and five bytes, and times a turn by reading it back.
1242
+ With a zone 0 track of 6250 bytes a turn took 175,001 cycles
1243
+ (342.86 rpm, `exit=$ff`). With the last byte's leftover part of a
1244
+ cell widening the track's last cell, instead of the cells spread
1245
+ evenly over the turn, the SYNC broke there and the turn read as
1246
+ 194,728 cycles (`exit=$ff`).
1247
+ - Spec guard: *gives a half track a blank track 7142 bytes around,
1248
+ written in zone 2* in
1249
+ [`disk_spec.rb`](../spec/badline/drive1541/disk_spec.rb), and *lays
1250
+ a track written in another zone out again as a turn at the zone
1251
+ written* and *writes a turn of SYNC that reads back without a break*
1252
+ in [`mechanism_spec.rb`](../spec/badline/drive1541/mechanism_spec.rb).
1253
+ - A disk made from a `.d64` starts sector 0 of each track where the DOS's
1254
+ `N:` leaves it: track 1 at the index angle, and each track after it
1255
+ round from the last by the skew `N:` leaves in its zone, 0.6869 of a
1256
+ turn in zone 3, 0.8915 in zone 2, 0.0897 in zone 1 and 0.2916 in zone
1257
+ 0. Those are measured off a disk formatted here with `N:`.
1258
+ - Pinned by `drive/skew/skew1`, which expects a `.d64` to read with
1259
+ the skew a DOS format leaves. With every track starting sector 0 at
1260
+ the same angle it read `kernal format, tracks are aligned`
1261
+ (`exit=$ff`).
1262
+ - The skew moves which block the DOS meets first on each track, and
1263
+ `drive/scanner`'s three error-map rows (`scanner35e`, `40e`, `42e`)
1264
+ read track 4's error 22 only when the header comes first. They pass
1265
+ with this skew at 300 rpm. With the rate selected turning the disk,
1266
+ the same skew made them read track 4 as error 27, which is why
1267
+ `skew1` failed before.
1268
+ - Spec guard: *starts sector 0 of track N where the DOS's N: leaves
1269
+ it* in [`disk_spec.rb`](../spec/badline/drive1541/disk_spec.rb).
1270
+ - The CPU sees BYTE READY on SO a cycle late: BYTE READY sets V for the
1271
+ next cycle's instruction step, not the one in the cycle it falls in.
1272
+ - Pinned by `drive/hls-protection`. Its drive code counts the bytes
1273
+ from one SYNC mark to the next, reading `$1C00` for SYNC once per
1274
+ byte, 20 to 22 cycles after each BYTE READY with the delay (19 to 21
1275
+ without). The SYNC marks it has to find follow an `$AF`, whose four
1276
+ trailing 1 bits make SYNC six bits, 19 cycles in zone 3, after its
1277
+ BYTE READY; the ones it has to miss follow a `$2B`, eight bits on.
1278
+ With V set in the same cycle, the read sometimes came a cycle
1279
+ before SYNC, depending on the bit phase: on track 1 two of the 16
1280
+ counts read `$02C8`, two sectors' worth, and the testbench row failed
1281
+ (`exit=$ff`) on track 17. With the delay, tracks 1, 5, 10, 12 and 17
1282
+ (the 4-cycle loop) and 18, 20 and 24 (the 6-cycle loop) all read as
1283
+ the test expects.
1284
+ - Spec guard: *sets V through SO while CA2 is high, a cycle after BYTE
1285
+ READY* in
1286
+ [`mechanism_spec.rb`](../spec/badline/drive1541/mechanism_spec.rb).
1287
+
1288
+ ## REU DMA
1289
+
1290
+ The REC's timing against the VIC, each rule derived from the REU
1291
+ testprogs named.
1292
+
1293
+ - A requested transfer takes the bus on the CPU's next read cycle with BA
1294
+ high, and moves its first byte there. Starting a cycle later moves every
1295
+ `REU/xfertiming` row and `REU/reutiming/reutiming`.
1296
+ - After reading C64 memory the REC waits out every BA-low cycle. After
1297
+ writing it goes on through the first BA-low cycle and waits from the
1298
+ second, and a fetch or swap whose last write it waited out takes one
1299
+ more cycle before handing the bus back (`REU::DMA#note_ba`,
1300
+ `#wind_down`). Pinned by `REU/bonzai/spritetiming`, whose 45 bytes a
1301
+ line with eight sprites on is 63 less the 18 cycles this leaves.
1302
+ - On the line whose raster matches sprite 0's Y, the REU doesn't see BA
1303
+ fall on the first cycle of sprite 0's window (`VIC#reu_ba_low?`).
1304
+ Without it `REU/bonzai/spritetiming` reads `$5a,$87` where a real REU
1305
+ gives `$5b,$88`.
1306
+ - A swap's read that falls on the first BA-low cycle, after its write, is
1307
+ held open while AEC stays high and takes the byte on the bus two cycles
1308
+ later. If it is the transfer's last byte it is read there instead, its
1309
+ write follows on the next cycle, and the REC hands the bus back
1310
+ (`REU::DMA#swap_read_on_ba`). Pinned by `REU/reutiming2/e5-m2`, `f3-m2`
1311
+ and `f4-m2`, whose patterns were captured on a breadbin. The
1312
+ `reutiming2` references without `-m2` read `$42` or a timer value there
1313
+ instead, and still fail.
1314
+
1094
1315
  ## SID oscillator
1095
1316
 
1096
1317
  - The phase accumulator powers on at `$555555` (all bits high, with the odd
data/exe/badline-ruby CHANGED
@@ -6,6 +6,8 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
6
6
 
7
7
  require "badline/options"
8
8
 
9
+ $stdout.sync = true
10
+
9
11
  begin
10
12
  options = Badline::Options.parse(ARGV)
11
13
  rescue Badline::Options::Error => e
@@ -47,12 +49,13 @@ end
47
49
 
48
50
  begin
49
51
  application = Badline::GUI::Application.new(media_path: options.media_path, autostart: options.autostart?,
50
- song: options.song, sid_model: options.sid_model,
51
- sound: options.sound?, verbose: options.verbose?,
52
+ song: options.song, sound: options.sound?, verbose: options.verbose?,
53
+ machine: { sid_model: options.sid_model, reu: options.reu,
54
+ true_drive: options.true_drive?, ntsc: options.ntsc? },
52
55
  disk: { read_only: options.read_only? })
53
- rescue Badline::Storage::SIDFile::FormatError, Badline::Storage::T64::FormatError,
56
+ rescue Badline::Media::TrueDrive::Error, Badline::Storage::SIDFile::FormatError, Badline::Storage::T64::FormatError,
54
57
  Badline::Storage::TAP::FormatError, Badline::Storage::CRTFile::FormatError,
55
- Badline::Cartridge::UnsupportedTypeError => e
58
+ Badline::Storage::G64Image::FormatError, Badline::Cartridge::UnsupportedTypeError => e
56
59
  warn "badline-ruby: #{options.media_path}: #{e.message}"
57
60
  exit 1
58
61
  end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Badline
4
+ class AddressBus
5
+ # The page tables of an Ultimax cartridge, which AddressBus lays out
6
+ # in place of the $01 banking while the cartridge holds GAME low and
7
+ # EXROM high.
8
+ module UltimaxPages
9
+ private
10
+
11
+ # Ultimax cartridges ignore the $01 lines: 4K of RAM, ROML/ROMH windows,
12
+ # I/O always visible and open address space everywhere else. The ROML
13
+ # and ROMH selects fire on writes as well, so cartridge RAM or flash in
14
+ # either window takes the writes there.
15
+ def map_ultimax_pages
16
+ @read_pages.fill(@open_bus, 0x10, 0xf0)
17
+ @write_pages.fill(@open_bus, 0x10, 0xf0)
18
+ @read_pages.fill(@cartridge.roml, 0x80, 0x20) if @cartridge.roml
19
+ map_ultimax_writes(@cartridge.roml, 0x80)
20
+ @read_pages.fill(@cartridge.romh, 0xe0, 0x20) if @cartridge.romh
21
+ map_ultimax_writes(@cartridge.romh, 0xe0)
22
+ map_ultimax_a000
23
+ map_io_pages
24
+ end
25
+
26
+ def map_ultimax_writes(bank, first_page)
27
+ @write_pages.fill(bank, first_page, 0x20) if bank.respond_to?(:poke)
28
+ end
29
+
30
+ def map_ultimax_a000
31
+ return unless (window = @cartridge.ultimax_a000)
32
+
33
+ @read_pages.fill(window, 0xa0, 0x20)
34
+ @write_pages.fill(window, 0xa0, 0x20)
35
+ end
36
+ end
37
+ end
38
+ end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "badline/address_bus/ultimax_pages"
4
+
3
5
  module Badline
4
6
  # Memory layout:
5
7
  #
@@ -28,6 +30,7 @@ module Badline
28
30
  # 0xE000-0xFFFF - KERNAL ROM / Cartridge ROM (high) - 8kb
29
31
  class AddressBus
30
32
  include Addressable
33
+ include UltimaxPages
31
34
 
32
35
  # I/O 1 and 2, and the Ultimax holes, with nothing on the bus. A read
33
36
  # picks up the byte the VIC fetched in the preceding phi1 half-cycle,
@@ -45,11 +48,15 @@ module Badline
45
48
  PORT_FLOATING = 0b1100_1000
46
49
  TAPE_SENSE = 0b0001_0000
47
50
 
48
- RAM_POWER_ON = [0xff, 0x07].freeze
51
+ # RAM powers on in runs of $00 $00 $FF $FF $FF $FF $00 $00, inverted in
52
+ # the second and fourth 16K: a C64C (ASSY 250469 R4) from
53
+ # C64/raminitpattern/readme.txt, without its occasional random bytes.
54
+ # See doc/pinned-behaviour.md.
55
+ RAM_POWER_ON = Array.new(2**16) { |addr| (((addr + 2) / 4) ^ (addr / 0x4000)).odd? ? 0xff : 0x00 }.freeze
49
56
 
50
57
  attr_reader :io_port, :ram, :basic_rom, :character_rom, :kernal_rom,
51
58
  :vic, :sid, :color_ram, :cia1, :cia2, :keyboard, :joystick1, :joystick2,
52
- :control_ports, :cartridge, :ultimax, :phi1_ultimax, :datasette, :region, :video_ram
59
+ :control_ports, :cartridge, :ultimax, :phi1_ultimax, :datasette, :region, :video_ram, :reu
53
60
 
54
61
  # ram_expansion fits a +60K (:plus60k) or +256K (:plus256k).
55
62
  def initialize(sid_model: :mos6581, cia_model: :mos6526, vic_model: :mos6569, region: Region::PAL,
@@ -57,7 +64,7 @@ module Badline
57
64
  @region = region
58
65
  @ram = Memory.new(RAM_POWER_ON, length: 2**16, start: 0)
59
66
  @ram_expansion = RAMExpansion.build(ram_expansion, @ram) { update_overlays! }
60
- @cartridge = nil
67
+ @cartridge = @reu = nil
61
68
  @debug_register = nil
62
69
 
63
70
  @basic_rom = ROM.load("basic.rom", 0xa000)
@@ -99,6 +106,13 @@ module Badline
99
106
  update_overlays!
100
107
  end
101
108
 
109
+ # An REU takes I/O 2 unless a cartridge claims it, and watches writes
110
+ # to $FF00 for the one that starts an armed transfer.
111
+ def attach_reu(reu)
112
+ @reu = reu
113
+ update_overlays!
114
+ end
115
+
102
116
  def power_on!
103
117
  @ram.clear!(RAM_POWER_ON)
104
118
  @ram_expansion.power_on!
@@ -177,6 +191,7 @@ module Badline
177
191
  @video_ram = @ram_expansion.video_ram
178
192
 
179
193
  @ultimax ? map_ultimax_pages : map_banked_pages
194
+ @write_pages[0xff] = @reu.trigger.wrap(@write_pages[0xff]) if @reu
180
195
  end
181
196
 
182
197
  def map_banked_pages
@@ -216,32 +231,6 @@ module Badline
216
231
  @write_pages.fill(bank, first_page, 0x20) if bank.is_a?(Cartridge::RAMBank)
217
232
  end
218
233
 
219
- # Ultimax cartridges ignore the $01 lines: 4K of RAM, ROML/ROMH windows,
220
- # I/O always visible and open address space everywhere else. The ROML
221
- # and ROMH selects fire on writes as well, so cartridge RAM or flash in
222
- # either window takes the writes there.
223
- def map_ultimax_pages
224
- @read_pages.fill(@open_bus, 0x10, 0xf0)
225
- @write_pages.fill(@open_bus, 0x10, 0xf0)
226
- @read_pages.fill(@cartridge.roml, 0x80, 0x20) if @cartridge.roml
227
- map_ultimax_writes(@cartridge.roml, 0x80)
228
- @read_pages.fill(@cartridge.romh, 0xe0, 0x20) if @cartridge.romh
229
- map_ultimax_writes(@cartridge.romh, 0xe0)
230
- map_ultimax_a000
231
- map_io_pages
232
- end
233
-
234
- def map_ultimax_writes(bank, first_page)
235
- @write_pages.fill(bank, first_page, 0x20) if bank.respond_to?(:poke)
236
- end
237
-
238
- def map_ultimax_a000
239
- return unless (window = @cartridge.ultimax_a000)
240
-
241
- @read_pages.fill(window, 0xa0, 0x20)
242
- @write_pages.fill(window, 0xa0, 0x20)
243
- end
244
-
245
234
  def map_io_pages
246
235
  {
247
236
  vic => 0xd0..0xd3, sid => 0xd4..0xd7, color_ram => 0xd8..0xdb,
@@ -251,6 +240,7 @@ module Badline
251
240
  end
252
241
  @ram_expansion.map_io(@read_pages, @write_pages)
253
242
  @read_pages[0xd7] = @write_pages[0xd7] = @debug_register if @debug_register
243
+ @read_pages[0xdf] = @write_pages[0xdf] = @reu if @reu
254
244
  map_cartridge_io if @cartridge
255
245
  end
256
246