badline 0.6.0 → 0.6.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +40 -0
- data/CLAUDE.md +10 -9
- data/README.md +24 -10
- data/doc/pinned-behaviour.md +131 -30
- data/exe/badline-ruby +20 -24
- data/lib/badline/address_bus.rb +10 -1
- data/lib/badline/audio/cli.rb +18 -5
- data/lib/badline/audio/console.rb +2 -1
- data/lib/badline/audio.rb +0 -3
- data/lib/badline/computer.rb +6 -3
- data/lib/badline/cpu/boundary.rb +54 -0
- data/lib/badline/cpu/core.rb +165 -0
- data/lib/badline/cpu.rb +3 -214
- data/lib/badline/drive1541/cpu.rb +29 -0
- data/lib/badline/drive1541.rb +1 -0
- data/lib/badline/ffi.rb +157 -0
- data/lib/badline/frontend/app.rb +225 -0
- data/lib/badline/frontend/audio_sink.rb +72 -0
- data/lib/badline/frontend/boot.rb +52 -0
- data/lib/badline/frontend/controls.rb +163 -0
- data/lib/badline/frontend/drive_led.rb +51 -0
- data/lib/badline/frontend/frame_rate.rb +83 -0
- data/lib/badline/frontend/gamepads.rb +76 -0
- data/lib/badline/frontend/keys.rb +48 -0
- data/lib/badline/frontend/pacer.rb +100 -0
- data/lib/badline/frontend/pad_port.rb +61 -0
- data/lib/badline/frontend/screen.rb +65 -0
- data/lib/badline/frontend/screenshot.rb +22 -0
- data/lib/badline/frontend/snapshots.rb +60 -0
- data/lib/badline/frontend/sound.rb +153 -0
- data/lib/badline/frontend/timeline.rb +171 -0
- data/lib/badline/frontend.rb +18 -0
- data/lib/badline/options/event.rb +94 -0
- data/lib/badline/options/help.rb +78 -0
- data/lib/badline/options/option.rb +29 -0
- data/lib/badline/options/table.rb +62 -0
- data/lib/badline/options.rb +181 -145
- data/lib/badline/reu/dma/saved_state.rb +50 -0
- data/lib/badline/reu/dma.rb +56 -47
- data/lib/badline/reu/saved_state.rb +2 -1
- data/lib/badline/reu.rb +19 -10
- data/lib/badline/sdl.rb +123 -51
- data/lib/badline/sid/waveform/noise_writeback.rb +33 -11
- data/lib/badline/sid/waveform.rb +2 -3
- data/lib/badline/snapshot/state.rb +1 -1
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +21 -0
- data/lib/badline/vic.rb +15 -6
- metadata +26 -19
- data/.claude/hooks/session-start.sh +0 -57
- data/.claude/settings.json +0 -14
- data/lib/badline/audio/sdl_sink.rb +0 -70
- data/lib/badline/audio/stream.rb +0 -123
- data/lib/badline/gui/application.rb +0 -243
- data/lib/badline/gui/drive_led_pane.rb +0 -29
- data/lib/badline/gui/gamepads.rb +0 -104
- data/lib/badline/gui/joy_map.rb +0 -27
- data/lib/badline/gui/key_map.rb +0 -39
- data/lib/badline/gui/palette.rb +0 -35
- data/lib/badline/gui/pane.rb +0 -36
- data/lib/badline/gui/screen_pane.rb +0 -46
- data/lib/badline/gui/snapshots.rb +0 -67
- data/lib/badline/gui/window.rb +0 -61
- data/lib/badline/gui.rb +0 -14
- data/lib/badline/sdl/events.rb +0 -49
- data/lib/badline/sdl/functions.rb +0 -74
- data/vendor/.gitkeep +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d667997df6e72a7efd6d8789896402ef09b757c49ac0e1890f9041a22d37d83b
|
|
4
|
+
data.tar.gz: ca5e42cebc1e7c1b4879062fab85bd97961242213f6701db86fa57717b270e8d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8965b452eb8836de285dc76e7d8220a3b302b81341ee277ab9738ea334e5d27f108b14e1b162b3fe28e88f7aa2fba1e1e30e4dd5fe2b4f7c579643c45e820070
|
|
7
|
+
data.tar.gz: 6603b0599428aef7a8bd0b1b5b99300281d02dbde1bd327895dc055a30ce16f2bdfc758b37661bca18eca256b0e98e5080926426dabbf0997c6a96762d7e3793
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.6.1](https://github.com/elektronaut/badline/compare/v0.6.0...v0.6.1) (2026-09-30)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* run both builds on one shared front end ([0e1ebfb](https://github.com/elektronaut/badline/commit/0e1ebfb07c4705b2e46c1bb44f1ea76f22370b24))
|
|
9
|
+
* run both builds on one shared front end ([3181d5f](https://github.com/elektronaut/badline/commit/3181d5f2ab73628d515ea88c42ddef4b11f74ac0))
|
|
10
|
+
* script key presses, media swaps and screenshots at given frames ([7a87069](https://github.com/elektronaut/badline/commit/7a87069d9b1491297e9a6ebd4fc0a860be3afd27))
|
|
11
|
+
* script key presses, media swaps and screenshots at given frames ([5554511](https://github.com/elektronaut/badline/commit/5554511868d7986950cac829c3341689c88a32a7))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* build the native badline with a Spinel that prints the headless player's display ([d2d3df3](https://github.com/elektronaut/badline/commit/d2d3df32c67c02c0e0f20308a03faca5430dfa26))
|
|
17
|
+
* build the native badline with a Spinel that prints the headless player's display ([8dccecb](https://github.com/elektronaut/badline/commit/8dccecb2a2b8d044162b149203ca8aaf75aa4957))
|
|
18
|
+
* build the native badline with a Spinel that renders .wav files ([bacfbea](https://github.com/elektronaut/badline/commit/bacfbea0c2d9af1c61c34aa09ff828ba767b2af3))
|
|
19
|
+
* build the native badline with a Spinel that renders .wav files ([afc4267](https://github.com/elektronaut/badline/commit/afc42670f8543c50e74268ccc81284e46838df48))
|
|
20
|
+
* end the run at a failed insert and say when there's nothing to eject ([f6b686d](https://github.com/elektronaut/badline/commit/f6b686d4094fbf526bffa37c7aa13dba0908bb71))
|
|
21
|
+
* key testbench results by test key, so a row listed twice gets its own line ([a0f4d55](https://github.com/elektronaut/badline/commit/a0f4d555f568c2e82b08971adc00fa4199c1eac4))
|
|
22
|
+
* leave .claude/ and vendor/ out of the gem ([c3ec867](https://github.com/elektronaut/badline/commit/c3ec867f04ee1840a01dfb0de2cce7cdd7aa30c6))
|
|
23
|
+
* save the REU swap's pending re-read in snapshots, from a module of its own ([3448ac8](https://github.com/elektronaut/badline/commit/3448ac8a8827587b01ea2da6101c1f1b2009bc88))
|
|
24
|
+
* show bank 3 for a cycle after a $dd00 bank swap on the 8565 ([2529eff](https://github.com/elektronaut/badline/commit/2529effb0c5fc3cf463e1d0e5b65e2321b3ed06d))
|
|
25
|
+
* show bank 3 for the cycle after a $dd00 write swaps the bank lines on the 8565 ([c9959f4](https://github.com/elektronaut/badline/commit/c9959f430ac7a95397d906e1c70013bbc25b5ca2))
|
|
26
|
+
* time the REU's swaps and fetches against the VIC, and take the bus from an RMW write ([60e48bd](https://github.com/elektronaut/badline/commit/60e48bd8531d130252101d6227ef296ed0b6395b))
|
|
27
|
+
* time the REU's swaps and fetches against the VIC, and take the bus from an RMW write ([eb045fa](https://github.com/elektronaut/badline/commit/eb045fa3d20e0a1abc9acdf68bfb1f9388d7ca57))
|
|
28
|
+
* write a fetch's first byte twice when BA falls as it starts ([35c4cf4](https://github.com/elektronaut/badline/commit/35c4cf419178bdf1b704f94c815dce259368fa26))
|
|
29
|
+
* write a fetch's first byte twice when BA falls as it starts ([1ab691a](https://github.com/elektronaut/badline/commit/1ab691a512b54d58670d5a2ae244f917c715bb25))
|
|
30
|
+
* write the 6581's pulse+noise lines only mid-shift ([95f84dd](https://github.com/elektronaut/badline/commit/95f84dd6e842bf76e562dd9867f24916039debd5))
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Performance Improvements
|
|
34
|
+
|
|
35
|
+
* give the 1541's 6502 a class of its own ([df64d23](https://github.com/elektronaut/badline/commit/df64d231f97824f2a6dc7bd37a1e4ef4294dc8da))
|
|
36
|
+
* give the 1541's 6502 a class of its own ([8193a52](https://github.com/elektronaut/badline/commit/8193a52c642a8c176cc29591d99beffb91300620))
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
### Miscellaneous Chores
|
|
40
|
+
|
|
41
|
+
* release 0.6.1 ([059cd50](https://github.com/elektronaut/badline/commit/059cd502dd356760179e5df823402891b99349bd))
|
|
42
|
+
|
|
3
43
|
## [0.6.0](https://github.com/elektronaut/badline/compare/v0.5.1...v0.6.0) (2026-09-29)
|
|
4
44
|
|
|
5
45
|
|
data/CLAUDE.md
CHANGED
|
@@ -25,14 +25,18 @@ live in subdirectories:
|
|
|
25
25
|
- **Media and host I/O**: `storage/` (disk, tape and cartridge image
|
|
26
26
|
formats), `cartridge/` (mappers), `kernal_trap/` (the LOAD/SAVE and IEC
|
|
27
27
|
traps that stand in for a drive), `media.rb` (attach and autostart),
|
|
28
|
-
`datasette.rb`, and `
|
|
29
|
-
- **
|
|
30
|
-
|
|
28
|
+
`datasette.rb`, and `input/` (the mouse and paddles)
|
|
29
|
+
- **Front end**: `frontend/`, the SDL window both builds run (app loop,
|
|
30
|
+
screen, pacer, sound, controls, gamepads, snapshots), in Spinel's
|
|
31
|
+
subset, over the one binding in `sdl.rb` (`ffi.rb` implements it on
|
|
32
|
+
CRuby)
|
|
33
|
+
- **Native**: `native/` holds the native `badline`, the core and the
|
|
34
|
+
front end compiled with Spinel (`rake native:build`, `native/README.md`).
|
|
31
35
|
`spinel/` holds the Spinel test harnesses
|
|
32
36
|
|
|
33
37
|
Timing-critical code lives in `cpu`, `interrupts`, `vic`, `cia` and `sid` and
|
|
34
38
|
in the order `Computer#cycle!` clocks them. Changes there move the test
|
|
35
|
-
suites below. `
|
|
39
|
+
suites below. `frontend/` changes move none of them.
|
|
36
40
|
|
|
37
41
|
Namespaced groups live in `lib/badline/<namespace>/`, with a sibling
|
|
38
42
|
`lib/badline/<namespace>.rb` that requires the members, directly or through
|
|
@@ -157,8 +161,8 @@ the rows your change can't reach tell you nothing about it.
|
|
|
157
161
|
`ruby --yjit bin/lorenz [image] [start_test] [max_cycles]` (resumes the
|
|
158
162
|
chain at a named test, and `--stop-after NAME` ends it once that test is
|
|
159
163
|
done). These take seconds to minutes
|
|
160
|
-
- A pull request's CI is the verdict. It runs every suite
|
|
161
|
-
|
|
164
|
+
- A pull request's CI is the verdict. It runs every suite on the Spinel
|
|
165
|
+
build, compared row by row against
|
|
162
166
|
`test/baselines/`, and every job is a required check. A row that moved
|
|
163
167
|
fails it. Re-record only those rows, in the same change:
|
|
164
168
|
`rake "regression:record:<suite>[filter,...]"` (quote it, because zsh
|
|
@@ -174,9 +178,6 @@ the rows your change can't reach tell you nothing about it.
|
|
|
174
178
|
- For a change whose reach you can't bound to a set of filters, such as
|
|
175
179
|
reordering `Computer#cycle!` or changing the LOAD trap every suite loads
|
|
176
180
|
through, push and let CI run the suites
|
|
177
|
-
- `testbench-drive` needs the true drive, which the Spinel testbench
|
|
178
|
-
harness doesn't have, so CI leaves it out. Run the rows your change can
|
|
179
|
-
reach locally
|
|
180
181
|
- The Regression workflow runs the suites on CRuby, started by hand from
|
|
181
182
|
the Actions tab, for a person checking that CRuby and Spinel agree.
|
|
182
183
|
Agents don't start it
|
data/README.md
CHANGED
|
@@ -83,14 +83,28 @@ plus `eapi/eapi-am29f040-14` if you attach EasyFlash cartridges. From
|
|
|
83
83
|
Ruby, `Badline.rom_path = dir` does the same before a
|
|
84
84
|
`Badline::Computer` is built, and `nil` restores the bundled set.
|
|
85
85
|
|
|
86
|
-
`badline` plays the SID through the host's
|
|
87
|
-
mutes and unmutes it. `--no-sound` turns it
|
|
88
|
-
the display's vsync; `--no-vsync` paces it
|
|
89
|
-
while it plays.
|
|
86
|
+
Both run the same window. `badline` plays the SID through the host's
|
|
87
|
+
audio device, and `F10` mutes and unmutes it. `--no-sound` turns it
|
|
88
|
+
off. The window is paced by the display's vsync; `--no-vsync` paces it
|
|
89
|
+
by a timer, or by the sound while it plays.
|
|
90
90
|
|
|
91
91
|
`--verbose` prints the display's refresh rate, the sound's sample rate
|
|
92
|
-
and the game controllers found as the window opens
|
|
93
|
-
|
|
92
|
+
and the game controllers found as the window opens, then the frame rate
|
|
93
|
+
and the time each frame takes, once a second.
|
|
94
|
+
|
|
95
|
+
For a scripted run, `--frames N` quits after N frames, `--unpaced` runs
|
|
96
|
+
as fast as it can and `--at FRAME:EVENT` presses keys, types, swaps
|
|
97
|
+
media and takes screenshots once that many frames have run. `--script
|
|
98
|
+
FILE` takes one `FRAME:EVENT` a line:
|
|
99
|
+
|
|
100
|
+
```sh
|
|
101
|
+
badline --unpaced --frames 12000 --true-drive disk1.d64 \
|
|
102
|
+
--at 3500:key=space --at 5600:insert=disk2.d64 \
|
|
103
|
+
--at 250,500,6000:screenshot=shot%05d.bmp
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
`--help` lists the events, and [native/README.md](native/README.md#running)
|
|
107
|
+
describes them.
|
|
94
108
|
|
|
95
109
|
In `badline-ruby` sound is off by default, and `--sound` turns it on.
|
|
96
110
|
The machine runs below real time there, so the sound stutters: it plays
|
|
@@ -250,9 +264,9 @@ without stuttering. See
|
|
|
250
264
|
|
|
251
265
|
## Input
|
|
252
266
|
|
|
253
|
-
Keys map by their
|
|
254
|
-
character, not the host's: Shift-2
|
|
255
|
-
same-named host key:
|
|
267
|
+
Keys map by their position on a US keyboard, whatever the host's layout,
|
|
268
|
+
and Shift gives the C64's shifted character, not the host's: Shift-2
|
|
269
|
+
types `"`. These keys have no same-named host key:
|
|
256
270
|
|
|
257
271
|
| C64 | Host |
|
|
258
272
|
|-----|------|
|
|
@@ -275,7 +289,7 @@ window title shows the current mode:
|
|
|
275
289
|
| Mode | What the host drives |
|
|
276
290
|
|------|----------------------|
|
|
277
291
|
| (none) | The keyboard |
|
|
278
|
-
| `[JOY]` | Arrows and Space are joystick
|
|
292
|
+
| `[JOY 2]` / `[JOY 1]` | Arrows and Space (or Right Ctrl) are the joystick named, `WASD` and Left Shift the other. `F9` swaps them |
|
|
279
293
|
| `[MOUSE 1]` / `[MOUSE 2]` | A 1351 mouse in control port 1 or 2 |
|
|
280
294
|
| `[PADDLE 1]` / `[PADDLE 2]` | A pair of paddles in control port 1 or 2 |
|
|
281
295
|
|
data/doc/pinned-behaviour.md
CHANGED
|
@@ -869,6 +869,18 @@ are the row's diff with the rule removed.
|
|
|
869
869
|
fails with two.
|
|
870
870
|
- Spec guard: *adds one extra half-pixel on the 8565* in
|
|
871
871
|
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
872
|
+
- **A `$dd00` write that swaps the bank lines shows bank 3 for a cycle.**
|
|
873
|
+
Where one bank line goes high as the other goes low, every VIC access in
|
|
874
|
+
the cycle after the write reads bank 3 (both lines low), and the new bank
|
|
875
|
+
from the cycle after that (`VIC::Bank#sample_lines`). A swap the DDR
|
|
876
|
+
makes, releasing a line to float high, shows nothing, and neither does a
|
|
877
|
+
write that moves one line or both the same way.
|
|
878
|
+
- Pinned by `fetchsplit` (154 → 16 px): every 8565 capture its readme
|
|
879
|
+
lists shows `3` in the first character after each `$dd00` swap, while
|
|
880
|
+
the 6569 reference shows none. `M1` also shows it after the DDR swaps;
|
|
881
|
+
`M7`, which the `-8565` reference is made from, doesn't.
|
|
882
|
+
- Spec guard: the `#sample_lines` group in
|
|
883
|
+
[`bank_spec.rb`](../spec/badline/vic/bank_spec.rb).
|
|
872
884
|
|
|
873
885
|
What the 8565 references don't settle, and so what stays as it is:
|
|
874
886
|
|
|
@@ -882,12 +894,21 @@ What the 8565 references don't settle, and so what stays as it is:
|
|
|
882
894
|
`vicii_reg_timing-ff` (7 px, the hi-res bitmap into text row) stay FAIL.
|
|
883
895
|
No emulator in the testbench results passes `vicii_reg_timing-a5` or
|
|
884
896
|
`-ff` on the 8565, and only Hoxs64 passes `modesplit`, against the
|
|
885
|
-
`8565early` reference.
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
897
|
+
`8565early` reference. Listed by transition, the references disagree
|
|
898
|
+
on the same pixel: out of ECM+BMM into ECM text a foreground pixel 0 is
|
|
899
|
+
on time in `videomode-w` and black in `modesplit`; out of ECM into hi-res
|
|
900
|
+
text a background pixel 0 selecting `$d022` shows the new mode in the
|
|
901
|
+
`vicii_reg_timing` ECM row and the old one in `modesplit`; out of hi-res
|
|
902
|
+
bitmap into text a foreground pixel 0 is new in `-a5` and old in `-ff`,
|
|
903
|
+
with the same screen, colour and pixel values. Each test's position is
|
|
904
|
+
independent of where it starts: booting 0 to 20000 cycles longer before
|
|
905
|
+
attaching gives the same diff.
|
|
906
|
+
- `fetchsplit` (16 px) stays FAIL on the one swap the DDR makes that the
|
|
907
|
+
`-8565` reference shows as bank 3: the swap in column 34 of the `line1c`
|
|
908
|
+
rows, in the text and ECM blocks but not the bitmap one, while the same
|
|
909
|
+
swap in column 4 of `line1b` shows nothing. The readme says its 8565
|
|
910
|
+
artefacts differ from chip to chip and change as the machine warms up,
|
|
911
|
+
and its `M1` capture shows bank 3 after every DDR swap.
|
|
891
912
|
- VICE's idle g-access also reads ECM from the `$d011` of the column
|
|
892
913
|
before on the 8565. No testprog tells it apart, so the 8565 keeps the
|
|
893
914
|
6569's idle access, without the 6569's `$38ff` read where a DMA delay
|
|
@@ -933,6 +954,22 @@ region moves the sprites. The positions live in `Region::Profile` and
|
|
|
933
954
|
`testbench-ntsc` screenshots, VICE's 247 lines from line 28 running on
|
|
934
955
|
into the next frame, see every line they show painted.
|
|
935
956
|
|
|
957
|
+
What the NTSC references don't settle, and so what stays as it is:
|
|
958
|
+
|
|
959
|
+
- `modesplit` on the 6567R56A (62 px) is compared against the PAL
|
|
960
|
+
reference, since there is no `-ntscold` one, and every pixel of its
|
|
961
|
+
diff sits in the label bar, where the test prints the line length and
|
|
962
|
+
line count it measured, and the reference shows the PAL ones.
|
|
963
|
+
- `modesplit` on the 6567R8 (458 px) matches the PAL reference on every
|
|
964
|
+
row but the label bar. Its `-ntsc` reference differs from the PAL one
|
|
965
|
+
in ways no 6567R8 rule here produces: light grey on the first pixel
|
|
966
|
+
after each `$d021` write (a grey dot, 20 px), the ECM split of section
|
|
967
|
+
2 alone starting and ending a character later (most of the diff) while
|
|
968
|
+
sections 1 and 3 keep their PAL positions, and single pixels in
|
|
969
|
+
section 1. The
|
|
970
|
+
readme says it was matched against screenshots, and the one 6567R8
|
|
971
|
+
photograph in the testprogs is of revision R01.
|
|
972
|
+
|
|
936
973
|
## CIA 6526 timer pipeline
|
|
937
974
|
|
|
938
975
|
- Start and stop go through two stages. A force load lands one tick after
|
|
@@ -1292,27 +1329,78 @@ and each was knocked out: removing it fails the rows named.
|
|
|
1292
1329
|
The REC's timing against the VIC, each rule derived from the REU
|
|
1293
1330
|
testprogs named.
|
|
1294
1331
|
|
|
1295
|
-
- A requested transfer takes the bus on the
|
|
1296
|
-
|
|
1297
|
-
`REU/xfertiming` row and `REU/reutiming/reutiming`.
|
|
1332
|
+
- A requested transfer takes the bus on the next cycle with BA high, and
|
|
1333
|
+
moves its first byte there. Starting a cycle later moves every
|
|
1334
|
+
`REU/xfertiming` row and `REU/reutiming/reutiming`. /DMA takes the bus
|
|
1335
|
+
from the CPU from the cycle after the request, even while the REC waits
|
|
1336
|
+
for BA, and RDY only halts the CPU on a read, so a write the CPU makes
|
|
1337
|
+
then goes nowhere (`AddressBus#cycle_cpu_off_bus`). That is how an `INC
|
|
1338
|
+
$FF00` starts an armed transfer without its second write reaching RAM.
|
|
1339
|
+
Pinned by `REU/rmw-trigger/rmwtrigger-ram` and `rmwtrigger-rom`.
|
|
1298
1340
|
- After reading C64 memory the REC waits out every BA-low cycle. After
|
|
1299
1341
|
writing it goes on through the first BA-low cycle and waits from the
|
|
1300
1342
|
second, and a fetch or swap whose last write it waited out takes one
|
|
1301
1343
|
more cycle before handing the bus back (`REU::DMA#note_ba`,
|
|
1302
1344
|
`#wind_down`). Pinned by `REU/bonzai/spritetiming`, whose 45 bytes a
|
|
1303
1345
|
line with eight sprites on is 63 less the 18 cycles this leaves.
|
|
1304
|
-
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1346
|
+
- A fetch's last write doesn't go through the first BA-low cycle: the REC
|
|
1347
|
+
waits for BA to rise and writes it then. Pinned by `REU/reutiming2/d`
|
|
1348
|
+
and `d2`, and it takes `REU/reutiming2/c` from 6,403 px to 337.
|
|
1349
|
+
- A fetch requested on the last cycle before BA falls writes its first
|
|
1350
|
+
byte on the first BA-low cycle, without counting it, then waits for BA
|
|
1351
|
+
and writes the same byte again when it starts (`REU::DMA#write_ahead`).
|
|
1352
|
+
One requested on a BA-low cycle writes nothing until BA rises. Pinned by
|
|
1353
|
+
`REU/badoublewrite`, whose sixth transfer a frame, requested on the
|
|
1354
|
+
cycle before BA falls for raster `$63`, a bad line, holds `$d021` at its
|
|
1355
|
+
first byte for the whole line, where the program's text says "d021
|
|
1356
|
+
doublewrite on this line". The row captures its screen too early to
|
|
1357
|
+
show this (see below), but the frame after matches the reference to the
|
|
1358
|
+
pixel, and without the rule that line is 199 px off.
|
|
1359
|
+
- `REU/badoublewrite` writes `$D7FF` from its raster interrupt on the
|
|
1360
|
+
third transfer of the first frame it runs them in, at raster 77, so the
|
|
1361
|
+
screenshot taken at that write shows the frame before below it, with
|
|
1362
|
+
the text half printed and the background still blue. Its reference
|
|
1363
|
+
shows a whole frame of twelve transfers on black, which only a
|
|
1364
|
+
screenshot a frame or more later can. The exit is locked to the raster:
|
|
1365
|
+
starting the program anywhere up to a frame later leaves it on raster
|
|
1366
|
+
77 with the same 58,880 px. VICE's own x64sc and x64 fail the row in every result the
|
|
1367
|
+
testbench keeps, r41951 to r45942, while Denise passes it from 2023.
|
|
1368
|
+
The row stays at 58,880 px.
|
|
1369
|
+
- `REU/reutiming2/c`'s reference shows its last transfer, from raster 250
|
|
1370
|
+
into 251 (`$fb`), waiting on raster 251 as the transfers above it wait
|
|
1371
|
+
on their bad lines: the border holds the transfer's byte `$11` up to
|
|
1372
|
+
x 336 and `$12` from x 337, where BA rises on a bad line. Raster 251 is
|
|
1373
|
+
past the bad line range, `$30`–`$f7`, and the program turns on no
|
|
1374
|
+
sprites, so nothing pulls BA there, and badline writes `$12` on the
|
|
1375
|
+
cycle after `$11`, before the line's visible part starts. The 8565 and
|
|
1376
|
+
8565early references show the same wait. No emulator or FPGA core in
|
|
1377
|
+
the testbench's 59 kept results passes the row, VICE, the Ultimate 64
|
|
1378
|
+
and the Chameleon included, and the testlist marks it `warn:vicefail`.
|
|
1379
|
+
badline keeps the bad line range, and the row stays at 337 px, all of
|
|
1380
|
+
them on raster 251.
|
|
1381
|
+
- On the line whose raster matches sprite 0's Y, a REC that read or wrote
|
|
1382
|
+
on the cycle before doesn't see BA fall on the first cycle of sprite
|
|
1383
|
+
0's window (`VIC#reu_ba_late?`). Without it `REU/bonzai/spritetiming`
|
|
1384
|
+
reads `$5a,$87` where a real REU gives `$5b,$88`. A swap's read on that
|
|
1385
|
+
cycle still sees BA low, and is held as the next rule says (pinned by
|
|
1386
|
+
`REU/reutiming2/e4-m2` and `e6-m2`).
|
|
1308
1387
|
- A swap's read that falls on the first BA-low cycle, after its write, is
|
|
1309
1388
|
held open while AEC stays high and takes the byte on the bus two cycles
|
|
1310
1389
|
later. If it is the transfer's last byte it is read there instead, its
|
|
1311
1390
|
write follows on the next cycle, and the REC hands the bus back
|
|
1312
1391
|
(`REU::DMA#swap_read_on_ba`). Pinned by `REU/reutiming2/e5-m2`, `f3-m2`
|
|
1313
|
-
and `f4-m2`, whose patterns were captured on a breadbin.
|
|
1314
|
-
|
|
1315
|
-
|
|
1392
|
+
and `f4-m2`, whose patterns were captured on a breadbin.
|
|
1393
|
+
- A swap's read made with BA high, when BA falls on the next cycle before
|
|
1394
|
+
its write, is made again when BA rises, and the write follows it
|
|
1395
|
+
(`REU::DMA#read_swap_again`). So is a held read when BA runs straight
|
|
1396
|
+
from a bad line's window into sprite 0's (`VIC#reu_ba_handed_on?`).
|
|
1397
|
+
Pinned by `REU/reutiming2/e4-m2`, `e6-m2`, `g3-m2` and `g4-m2`.
|
|
1398
|
+
- The `reutiming2` references without `-m2` read `$42`, the RAM under
|
|
1399
|
+
`$DC04`, on some of the cycles a swap's or stash's read starts with the
|
|
1400
|
+
VIC's DMA. The readme puts that down to a C64C board or an 8565, while
|
|
1401
|
+
the testlist runs those rows on the 6569, the same machine as the `-m2`
|
|
1402
|
+
rows, whose references read the timer there. The two sets can't both
|
|
1403
|
+
pass on one machine, and badline follows the breadbin `-m2` ones.
|
|
1316
1404
|
|
|
1317
1405
|
## SID oscillator
|
|
1318
1406
|
|
|
@@ -1340,7 +1428,8 @@ testprogs named.
|
|
|
1340
1428
|
its output is latched instead. The cycle after that is phase 2: the
|
|
1341
1429
|
latched output is written over the taps, then the register shifts.
|
|
1342
1430
|
Phase 2 writes back by the test bit release rule below, with the old and
|
|
1343
|
-
new waveform the same: noise combined with anything but pulse alone.
|
|
1431
|
+
new waveform the same: noise combined with anything but pulse alone. On
|
|
1432
|
+
the 6581, phase 2 also writes pulse+noise's lines (below).
|
|
1344
1433
|
Setting the test bit drops a shift in flight.
|
|
1345
1434
|
- Pinned by `SID/noisewriteback`'s `noise_writeback_test2` (both chips).
|
|
1346
1435
|
It releases the test bit into noise+triangle, which pulls every tap low,
|
|
@@ -1364,6 +1453,9 @@ testprogs named.
|
|
|
1364
1453
|
`$f8`/`$80` pairs set the test bit onto all four waveforms from noise
|
|
1365
1454
|
alone, and zero the register only if this writes back. The release that
|
|
1366
1455
|
follows goes to noise alone, which writes nothing back (below).
|
|
1456
|
+
- On the 6581, pulse+noise writes its `$00` read back here, as in a
|
|
1457
|
+
shift's phase 2 (below). Pinned by `SID/wf12nsr`'s pulse+noise row,
|
|
1458
|
+
which sets the test bit onto pulse+noise.
|
|
1367
1459
|
- Spec guard: *as the test bit rises* in
|
|
1368
1460
|
[`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
|
|
1369
1461
|
- The test bit does not clear the LFSR. It stalls it halfway through a
|
|
@@ -1445,9 +1537,10 @@ testprogs named.
|
|
|
1445
1537
|
register and writes `$fc` back, the only pair among `$f8`, `$fc` and the
|
|
1446
1538
|
full noise that passes `SID/wf12nsr`'s pulse+noise row. That `$fc` is what
|
|
1447
1539
|
`SID/wb_testsuite`'s `C`→`9` and `C`→`E` rows need written at release.
|
|
1448
|
-
The 6581 reads `$
|
|
1449
|
-
|
|
1450
|
-
|
|
1540
|
+
The 6581 reads `$00` of a full register, and pulls nothing down while
|
|
1541
|
+
the register holds. Its lines land only part way through a shift: in
|
|
1542
|
+
a shift's phase 2, and as the test bit rises onto pulse+noise. Both
|
|
1543
|
+
write the `$00` read, so every tap goes low.
|
|
1451
1544
|
- Pinned by `SID/wf12nsr` (wf9/wfa on both chips, wfc on the 8580) and
|
|
1452
1545
|
the `SID/wb_testsuite` rows above. Knock-outs: writing back the plain
|
|
1453
1546
|
AND fails wf9 and wfa on both chips; on the 8580, reading and writing
|
|
@@ -1455,17 +1548,25 @@ testprogs named.
|
|
|
1455
1548
|
fails wfc and the `C`→`9`/`C`→`E` rows; applying the lone-line rule to
|
|
1456
1549
|
the read as well makes `noise_writeback_test2` read `$00` on both
|
|
1457
1550
|
chips.
|
|
1458
|
-
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1551
|
+
- The 6581's pulse+noise is pinned by `SID/wf12nsr`'s wfc row and
|
|
1552
|
+
`SID/wb_testsuite`'s `8`→`C` and `C`→`C` rows. The wfc row releases the
|
|
1553
|
+
test bit into pulse+noise at frequency `$ffff` with the pulse width
|
|
1554
|
+
never written, so zero and the pulse high, and reads `$00` on the next
|
|
1555
|
+
instruction, before bit 19 first rises. That read is of a full
|
|
1556
|
+
register, so the `$00` is the read itself. Its noise run afterwards
|
|
1557
|
+
matches the other combined rows, whose taps were all pulled low. The
|
|
1558
|
+
`8`→`C` and `C`→`C` rows hold pulse+noise at frequency zero, where no
|
|
1559
|
+
shift comes, and leave the register alone. Knock-outs: writing the
|
|
1560
|
+
`$00` back every cycle and at release fails `8`→`C`, `C`→`C` and ten
|
|
1561
|
+
more 6581 rows (the recorded VICE r45942 results pass wfc and fail
|
|
1562
|
+
`8`→`C` and `C`→`C`); dropping the phase 2 write or the write as the
|
|
1563
|
+
test bit rises fails wfc. The readme
|
|
1564
|
+
of `SID/wf12nsr` has a warmed-up 6581 reading `$fc` with the test bit
|
|
1565
|
+
held (`quicktest.prg`, unscored), which this model doesn't reproduce.
|
|
1467
1566
|
- Spec guard: *writes a lone line of a noise combination back low*, *with
|
|
1468
|
-
pulse+noise on the 8580*
|
|
1567
|
+
pulse+noise on the 8580*, *reads nothing of pulse+noise on the 6581*,
|
|
1568
|
+
*with pulse+noise on the 6581*, *as the test bit rises* and *as the
|
|
1569
|
+
test bit falls* in
|
|
1469
1570
|
[`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
|
|
1470
1571
|
- The pulse comparator's output reaches the lines a cycle after the
|
|
1471
1572
|
accumulator it compared, on both chips. Setting the test bit forces it
|
data/exe/badline-ruby
CHANGED
|
@@ -26,39 +26,35 @@ RubyVM::YJIT.enable if options.jit? && jit_available
|
|
|
26
26
|
|
|
27
27
|
require "badline"
|
|
28
28
|
|
|
29
|
+
# Quiets IO::Buffer's experimental warning.
|
|
30
|
+
Warning[:experimental] = false
|
|
31
|
+
|
|
32
|
+
def load_sdl
|
|
33
|
+
require "badline/ffi"
|
|
34
|
+
require "badline/frontend"
|
|
35
|
+
end
|
|
36
|
+
|
|
29
37
|
if options.headless?
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
rescue Interrupt
|
|
37
|
-
exit 130
|
|
38
|
+
require "badline/audio/console"
|
|
39
|
+
sink = lambda do |rate:, exact_rate:|
|
|
40
|
+
load_sdl
|
|
41
|
+
Badline::Frontend::AudioSink.new(rate:, exact_rate:)
|
|
42
|
+
rescue Badline::FFI::Error => e
|
|
43
|
+
raise Badline::Audio::Playback::DeviceError, e.message
|
|
38
44
|
end
|
|
39
|
-
exit
|
|
45
|
+
exit Badline::Audio::CLI.run(options, sink:, console: Badline::Audio::Console.method(:new))
|
|
40
46
|
end
|
|
41
47
|
|
|
42
48
|
begin
|
|
43
|
-
|
|
44
|
-
rescue Badline::
|
|
49
|
+
load_sdl
|
|
50
|
+
rescue Badline::FFI::Error => e
|
|
45
51
|
warn "badline-ruby: can't open the window: #{e.message}. " \
|
|
46
52
|
"Install SDL2 with 'brew install sdl2' or 'apt install libsdl2-2.0-0'."
|
|
47
53
|
exit 1
|
|
48
54
|
end
|
|
49
55
|
|
|
50
56
|
begin
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
true_drive: options.true_drive?, ntsc: options.ntsc? },
|
|
55
|
-
disk: { read_only: options.read_only? })
|
|
56
|
-
rescue Badline::Media::TrueDrive::Error, Badline::Storage::SIDFile::FormatError, Badline::Storage::T64::FormatError,
|
|
57
|
-
Badline::Storage::TAP::FormatError, Badline::Storage::CRTFile::FormatError,
|
|
58
|
-
Badline::Storage::G64Image::FormatError, Badline::Cartridge::UnsupportedTypeError,
|
|
59
|
-
Badline::Snapshot::FormatError => e
|
|
60
|
-
warn "badline-ruby: #{options.media_path}: #{e.message}"
|
|
61
|
-
exit 1
|
|
57
|
+
exit Badline::Frontend.run(options)
|
|
58
|
+
rescue Interrupt
|
|
59
|
+
exit 130
|
|
62
60
|
end
|
|
63
|
-
|
|
64
|
-
application.run
|
data/lib/badline/address_bus.rb
CHANGED
|
@@ -89,6 +89,7 @@ module Badline
|
|
|
89
89
|
|
|
90
90
|
@color_ram = ColorMemory.new(@vic)
|
|
91
91
|
@open_bus = OpenBus.new(@vic)
|
|
92
|
+
@cpu_off_bus = false
|
|
92
93
|
|
|
93
94
|
@port_ddr = 0x00
|
|
94
95
|
@port_out = 0x00
|
|
@@ -154,11 +155,19 @@ module Badline
|
|
|
154
155
|
@ram.poke(addr, @vic.phi1_data)
|
|
155
156
|
addr.zero? ? @port_ddr = value : @port_out = value
|
|
156
157
|
update_port!
|
|
157
|
-
|
|
158
|
+
elsif !@cpu_off_bus
|
|
158
159
|
@write_pages[addr >> 8].poke(addr, value)
|
|
159
160
|
end
|
|
160
161
|
end
|
|
161
162
|
|
|
163
|
+
# Runs a CPU cycle with the CPU off the bus, as an REU's /DMA line
|
|
164
|
+
# holds it: a write it makes reaches nothing but its own port.
|
|
165
|
+
def cycle_cpu_off_bus(cpu)
|
|
166
|
+
@cpu_off_bus = true
|
|
167
|
+
cpu.cycle!
|
|
168
|
+
@cpu_off_bus = false
|
|
169
|
+
end
|
|
170
|
+
|
|
162
171
|
def inspect
|
|
163
172
|
"#<#{self.class.name} port=#{format('0x%02x', @io_port.value)} " \
|
|
164
173
|
"cartridge=#{@cartridge ? @cartridge.class.name : 'none'} " \
|
data/lib/badline/audio/cli.rb
CHANGED
|
@@ -2,18 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
module Badline
|
|
4
4
|
module Audio
|
|
5
|
-
# Runs
|
|
6
|
-
# parsed: picks the song and its length, then plays it or renders
|
|
7
|
-
# to a file. Played on a terminal, the console lets the listener step
|
|
5
|
+
# Runs `--headless` or `--audio-out`, in either build, once its options
|
|
6
|
+
# have parsed: picks the song and its length, then plays it or renders
|
|
7
|
+
# it to a file. Played on a terminal, the console lets the listener step
|
|
8
8
|
# between songs.
|
|
9
9
|
class CLI
|
|
10
10
|
class Error < StandardError; end
|
|
11
11
|
|
|
12
|
+
# Plays or renders the tune as #run does, reports an error on stderr
|
|
13
|
+
# under the program's name, and returns the exit status.
|
|
14
|
+
def self.run(options, sink:, console:)
|
|
15
|
+
new(options, sink:, console:).run
|
|
16
|
+
0
|
|
17
|
+
rescue Error => e
|
|
18
|
+
warn "#{options.program}: #{e.message}"
|
|
19
|
+
1
|
|
20
|
+
rescue Interrupt
|
|
21
|
+
130
|
|
22
|
+
end
|
|
23
|
+
|
|
12
24
|
# Playing needs `sink`, which builds the audio device given the rate
|
|
13
25
|
# to ask for and whether it has to be exact, and, on a terminal,
|
|
14
26
|
# `console`, which builds the terminal to play on given the input and
|
|
15
|
-
# output.
|
|
16
|
-
#
|
|
27
|
+
# output. Each build's entry point hands them in: both builds' sink is
|
|
28
|
+
# Frontend::AudioSink, and the console is badline-ruby's Console or
|
|
29
|
+
# the native badline's Native::Console.
|
|
17
30
|
def initialize(options, sink: nil, console: nil, out: $stdout, input: $stdin)
|
|
18
31
|
@options = options
|
|
19
32
|
@out = out
|
|
@@ -6,7 +6,8 @@ require "io/wait"
|
|
|
6
6
|
module Badline
|
|
7
7
|
module Audio
|
|
8
8
|
# The terminal of `badline-ruby --headless`, put in raw mode with
|
|
9
|
-
# io/console.
|
|
9
|
+
# io/console. It is badline-ruby's side of the console seam, and
|
|
10
|
+
# exe/badline-ruby requires it: the native badline's is Native::Console.
|
|
10
11
|
class Console < Terminal
|
|
11
12
|
private
|
|
12
13
|
|
data/lib/badline/audio.rb
CHANGED
|
@@ -7,9 +7,6 @@ require "badline/audio/bare_player"
|
|
|
7
7
|
require "badline/audio/machine_player"
|
|
8
8
|
require "badline/audio/renderer"
|
|
9
9
|
require "badline/audio/playback"
|
|
10
|
-
require "badline/audio/sdl_sink"
|
|
11
|
-
require "badline/audio/stream"
|
|
12
10
|
require "badline/audio/terminal"
|
|
13
|
-
require "badline/audio/console"
|
|
14
11
|
require "badline/audio/jukebox"
|
|
15
12
|
require "badline/audio/cli"
|
data/lib/badline/computer.rb
CHANGED
|
@@ -226,12 +226,15 @@ module Badline
|
|
|
226
226
|
@cpu.pending_write? || !@vic.ba_low? ? @cpu.cycle! : @cpu.stall!
|
|
227
227
|
end
|
|
228
228
|
|
|
229
|
+
# The REU's /DMA line takes the bus from the CPU from the cycle after
|
|
230
|
+
# it asks for it, even while it waits for BA, but RDY only halts the
|
|
231
|
+
# CPU on a read, so a write it makes then goes nowhere.
|
|
229
232
|
def dma_cycle!
|
|
230
233
|
writing = @cpu.pending_write?
|
|
231
|
-
@reu.dma_cycle!(@vic.
|
|
232
|
-
return @cpu.stall! if @reu.
|
|
234
|
+
@reu.dma_cycle!(@vic.ba_low?)
|
|
235
|
+
return writing ? @address_bus.cycle_cpu_off_bus(@cpu) : @cpu.stall! if @reu.dma?
|
|
233
236
|
|
|
234
|
-
@dma =
|
|
237
|
+
@dma = false
|
|
235
238
|
writing || !@vic.ba_low? ? @cpu.cycle! : @cpu.stall!
|
|
236
239
|
end
|
|
237
240
|
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Badline
|
|
4
|
+
class CPU
|
|
5
|
+
# The steps at an instruction boundary: the opcode fetch that starts
|
|
6
|
+
# an instruction, and the ends of one.
|
|
7
|
+
module Boundary
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
# The first cycle of every instruction. Starts an interrupt if one was
|
|
11
|
+
# pending when the previous instruction ended. Otherwise reads the
|
|
12
|
+
# opcode and switches to its plan.
|
|
13
|
+
def op_fetch
|
|
14
|
+
return start_interrupt if @boundary_nmi || @boundary_irq
|
|
15
|
+
|
|
16
|
+
run_traps
|
|
17
|
+
opcode = @memory.peek(@program_counter)
|
|
18
|
+
log(opcode) if @debug
|
|
19
|
+
micro = MICROCODE[opcode]
|
|
20
|
+
@operation = micro.operation
|
|
21
|
+
@plan = micro.plan
|
|
22
|
+
@writes = micro.writes
|
|
23
|
+
@optional_dummy = micro.optional_dummy
|
|
24
|
+
@index = 1
|
|
25
|
+
@boundary_crossed = false
|
|
26
|
+
@program_counter = (@program_counter + 1) & 0xffff
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def end_instruction
|
|
30
|
+
@instructions += 1
|
|
31
|
+
end_sequence
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Makes the next cycle an opcode fetch, and saves the pending interrupt
|
|
35
|
+
# flags for op_fetch. The next cycle polls before op_fetch runs, which
|
|
36
|
+
# updates the live flags.
|
|
37
|
+
def end_sequence
|
|
38
|
+
@plan = FETCH_PLAN
|
|
39
|
+
@writes = FETCH_WRITES
|
|
40
|
+
@index = 0
|
|
41
|
+
@boundary_irq = @irq_pending
|
|
42
|
+
@boundary_nmi = @nmi_pending
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def log(opcode)
|
|
46
|
+
instruction = Instruction.find(opcode)
|
|
47
|
+
puts(
|
|
48
|
+
"#{@cycles}: PC: #{format16(@program_counter)} - " \
|
|
49
|
+
"#{instruction.name.upcase} #{instruction.addressing_mode}"
|
|
50
|
+
)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|