badline 0.1.0 → 0.2.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 +185 -0
- data/CLAUDE.md +217 -0
- data/CODE_OF_CONDUCT.md +92 -0
- data/CONTRIBUTING.md +51 -0
- data/README.md +224 -31
- data/doc/pinned-behaviour.md +1117 -0
- data/exe/badline +22 -2
- data/exe/badline-sid +34 -0
- data/lib/badline/address_bus.rb +96 -25
- data/lib/badline/audio/aiff.rb +36 -0
- data/lib/badline/audio/bare_player.rb +129 -0
- data/lib/badline/audio/cli.rb +133 -0
- data/lib/badline/audio/console.rb +75 -0
- data/lib/badline/audio/jukebox.rb +82 -0
- data/lib/badline/audio/machine_player.rb +57 -0
- data/lib/badline/audio/options.rb +137 -0
- data/lib/badline/audio/pcm_writer.rb +57 -0
- data/lib/badline/audio/playback.rb +108 -0
- data/lib/badline/audio/renderer.rb +85 -0
- data/lib/badline/audio/sdl_sink.rb +102 -0
- data/lib/badline/audio/wav.rb +23 -0
- data/lib/badline/audio.rb +14 -0
- data/lib/badline/cartridge/action_replay.rb +111 -0
- data/lib/badline/cartridge/atomic_power.rb +24 -0
- data/lib/badline/cartridge/bank.rb +81 -0
- data/lib/badline/cartridge/comal80.rb +48 -0
- data/lib/badline/cartridge/dinamic.rb +34 -0
- data/lib/badline/cartridge/easy_flash.rb +122 -0
- data/lib/badline/cartridge/epyx_fastload.rb +71 -0
- data/lib/badline/cartridge/final_cartridge3.rb +77 -0
- data/lib/badline/cartridge/flash.rb +265 -0
- data/lib/badline/cartridge/freezer.rb +31 -0
- data/lib/badline/cartridge/fun_play.rb +38 -0
- data/lib/badline/cartridge/g_mod2.rb +62 -0
- data/lib/badline/cartridge/game_system.rb +40 -0
- data/lib/badline/cartridge/kcs_power.rb +72 -0
- data/lib/badline/cartridge/mach5.rb +34 -0
- data/lib/badline/cartridge/magic_desk.rb +6 -3
- data/lib/badline/cartridge/ocean.rb +11 -12
- data/lib/badline/cartridge/pagefox.rb +56 -0
- data/lib/badline/cartridge/retro_replay.rb +276 -0
- data/lib/badline/cartridge/rex_utility.rb +33 -0
- data/lib/badline/cartridge/rgcd.rb +48 -0
- data/lib/badline/cartridge/simons_basic.rb +40 -0
- data/lib/badline/cartridge/standard.rb +3 -19
- data/lib/badline/cartridge/super_games.rb +36 -0
- data/lib/badline/cartridge/westermann.rb +34 -0
- data/lib/badline/cartridge/zaxxon.rb +50 -0
- data/lib/badline/cartridge.rb +132 -16
- data/lib/badline/cia/interrupt_register.rb +102 -0
- data/lib/badline/cia/serial.rb +219 -0
- data/lib/badline/cia/timer.rb +43 -31
- data/lib/badline/cia.rb +138 -88
- data/lib/badline/color_memory.rb +14 -5
- data/lib/badline/computer.rb +97 -12
- data/lib/badline/control_ports.rb +54 -9
- data/lib/badline/cpu/addressing.rb +131 -0
- data/lib/badline/cpu/microcode.rb +169 -0
- data/lib/badline/cpu/operations.rb +88 -0
- data/lib/badline/cpu/stack_operations.rb +82 -0
- data/lib/badline/cpu.rb +109 -168
- data/lib/badline/cycleable.rb +3 -0
- data/lib/badline/datasette.rb +81 -0
- data/lib/badline/debug_register.rb +28 -0
- data/lib/badline/gui/application.rb +79 -18
- data/lib/badline/gui/gamepads.rb +97 -0
- data/lib/badline/gui/joy_map.rb +15 -7
- data/lib/badline/gui.rb +1 -0
- data/lib/badline/input/mouse1351.rb +41 -0
- data/lib/badline/input/paddles.rb +36 -0
- data/lib/badline/input.rb +4 -0
- data/lib/badline/instruction_set/arithmetic.rb +16 -21
- data/lib/badline/instruction_set/bitwise.rb +17 -22
- data/lib/badline/instruction_set/branch.rb +20 -19
- data/lib/badline/instruction_set/flag.rb +7 -7
- data/lib/badline/instruction_set/illegal.rb +14 -22
- data/lib/badline/instruction_set/inc_dec.rb +8 -10
- data/lib/badline/instruction_set/stack.rb +5 -57
- data/lib/badline/instruction_set/transfer.rb +9 -9
- data/lib/badline/instruction_set.rb +9 -31
- data/lib/badline/interrupts.rb +104 -0
- data/lib/badline/joystick.rb +9 -9
- data/lib/badline/kernal_trap/channel.rb +51 -0
- data/lib/badline/kernal_trap/drive/status.rb +49 -0
- data/lib/badline/kernal_trap/drive.rb +277 -0
- data/lib/badline/kernal_trap/file.rb +5 -25
- data/lib/badline/kernal_trap/load.rb +86 -24
- data/lib/badline/kernal_trap/routine.rb +51 -0
- data/lib/badline/kernal_trap/save.rb +46 -14
- data/lib/badline/kernal_trap/serial.rb +171 -0
- data/lib/badline/kernal_trap.rb +4 -0
- data/lib/badline/keyboard.rb +40 -16
- data/lib/badline/media.rb +48 -10
- data/lib/badline/memory.rb +5 -0
- data/lib/badline/roms/eapi/LICENSE.md +15 -0
- data/lib/badline/roms/eapi/README +41 -0
- data/lib/badline/roms/eapi/eapi-am29f040-14 +0 -0
- data/lib/badline/sid/decimator.rb +46 -0
- data/lib/badline/sid/envelope.rb +232 -0
- data/lib/badline/sid/filter.rb +213 -0
- data/lib/badline/sid/voice.rb +61 -0
- data/lib/badline/sid/waveform/combined.rb +126 -0
- data/lib/badline/sid/waveform/fast_forward.rb +105 -0
- data/lib/badline/sid/waveform/noise_writeback.rb +82 -0
- data/lib/badline/sid/waveform.rb +289 -0
- data/lib/badline/sid.rb +274 -7
- data/lib/badline/status.rb +251 -23
- data/lib/badline/storage/crt_file.rb +18 -2
- data/lib/badline/storage/d64_image.rb +6 -0
- data/lib/badline/storage/d71_image.rb +4 -0
- data/lib/badline/storage/d81_image.rb +4 -0
- data/lib/badline/storage/disk_image.rb +99 -13
- data/lib/badline/storage/host_directory.rb +30 -4
- data/lib/badline/storage/sid_file.rb +274 -0
- data/lib/badline/storage/song_lengths.rb +65 -0
- data/lib/badline/storage/t64.rb +67 -0
- data/lib/badline/storage/tap.rb +68 -0
- data/lib/badline/storage.rb +19 -0
- data/lib/badline/time_of_day.rb +105 -52
- data/lib/badline/version.rb +1 -1
- data/lib/badline/vic/bank.rb +14 -6
- data/lib/badline/vic/border_mask.rb +76 -0
- data/lib/badline/vic/collisions.rb +104 -0
- data/lib/badline/vic/color_patches.rb +33 -0
- data/lib/badline/vic/display_state.rb +92 -29
- data/lib/badline/vic/graphics_mode.rb +78 -53
- data/lib/badline/vic/graphics_shifter.rb +140 -0
- data/lib/badline/vic/register_log.rb +63 -0
- data/lib/badline/vic/registers.rb +3 -0
- data/lib/badline/vic/sequencer.rb +163 -137
- data/lib/badline/vic/sequencer_output.rb +116 -0
- data/lib/badline/vic/sprite/internal_bus.rb +49 -0
- data/lib/badline/vic/sprite/shifter.rb +129 -0
- data/lib/badline/vic/sprite.rb +275 -65
- data/lib/badline/vic/sprites.rb +220 -58
- data/lib/badline/vic.rb +389 -55
- data/lib/badline.rb +5 -0
- data/vendor/.gitkeep +0 -0
- metadata +104 -4
|
@@ -0,0 +1,1117 @@
|
|
|
1
|
+
# Pinned behaviour
|
|
2
|
+
|
|
3
|
+
These timing rules were derived empirically against a named test, not from
|
|
4
|
+
a datasheet. Each entry states the rule, the test that forced it and, where
|
|
5
|
+
there is one, the fast spec that guards it. Specs carrying a guard say
|
|
6
|
+
`Pinned by <test>` in a comment.
|
|
7
|
+
|
|
8
|
+
The constraint list is the artifact; the implementation is not. When you
|
|
9
|
+
change code a rule governs, re-derive the rule against its pinning test. A
|
|
10
|
+
green suite is not enough, because a suite can stay green while the rule
|
|
11
|
+
breaks: a spec guard catches the rule's own failure mode, and a baseline
|
|
12
|
+
only catches the rows that happen to move.
|
|
13
|
+
|
|
14
|
+
- [CPU interrupt recognition](#cpu-interrupt-recognition)
|
|
15
|
+
- [CPU JAM](#cpu-jam)
|
|
16
|
+
- [CPU unstable stores under DMA](#cpu-unstable-stores-under-dma)
|
|
17
|
+
- [CPU ANE constant](#cpu-ane-constant)
|
|
18
|
+
- [VIC raster IRQ phase](#vic-raster-irq-phase)
|
|
19
|
+
- [VIC mid-line register visibility](#vic-mid-line-register-visibility)
|
|
20
|
+
- [VIC sprite display](#vic-sprite-display)
|
|
21
|
+
- [VIC sprite collisions](#vic-sprite-collisions)
|
|
22
|
+
- [VIC border and idle state](#vic-border-and-idle-state)
|
|
23
|
+
- [VIC bad line and DMA](#vic-bad-line-and-dma)
|
|
24
|
+
- [VIC graphics pipeline](#vic-graphics-pipeline)
|
|
25
|
+
- [VIC phi1 bus](#vic-phi1-bus)
|
|
26
|
+
- [VIC light pen](#vic-light-pen)
|
|
27
|
+
- [CIA 6526 timer pipeline](#cia-6526-timer-pipeline)
|
|
28
|
+
- [CIA serial shift register](#cia-serial-shift-register)
|
|
29
|
+
- [6510 I/O port](#6510-io-port)
|
|
30
|
+
- [SID oscillator](#sid-oscillator)
|
|
31
|
+
- [SID register writes](#sid-register-writes)
|
|
32
|
+
- [SID data bus](#sid-data-bus)
|
|
33
|
+
- [SID envelope](#sid-envelope)
|
|
34
|
+
- [`.sid` tune banking](#sid-tune-banking)
|
|
35
|
+
|
|
36
|
+
## CPU interrupt recognition
|
|
37
|
+
|
|
38
|
+
- `poll` runs at the head of every `CPU#cycle!`, before that cycle's
|
|
39
|
+
micro-operation, and shifts `irq && !I` and the NMI latch through a
|
|
40
|
+
two-stage pipeline (`pending ← sample ← line`). The instruction boundary
|
|
41
|
+
consumes `pending` *before* its own poll, which is the
|
|
42
|
+
second-to-last-cycle sample (64doc: "2 or more cycles before the end").
|
|
43
|
+
`end_sequence` latches that value into `@boundary_irq`/`@boundary_nmi` as
|
|
44
|
+
the instruction ends. That is the same read, because nothing but `poll`
|
|
45
|
+
moves `pending`.
|
|
46
|
+
- A taken same-page branch sets `@skip_poll`, skipping one poll, so the
|
|
47
|
+
interrupt must arrive before clock 1. The CLI/SEI/PLP delays fall out of
|
|
48
|
+
sampling `!I` at poll time.
|
|
49
|
+
- An NMI before cycle 4 hijacks a BRK or IRQ sequence: the vector swaps at
|
|
50
|
+
the `@nmi_pending` check before the vector fetch, and the B flag stays on
|
|
51
|
+
the stack. Interrupt sequences, BRK included, clear the pipeline at their
|
|
52
|
+
end, so the handler's first instruction always runs.
|
|
53
|
+
- The sequence is a true 7 cycles. The CPU does *not* clear `@irq` on
|
|
54
|
+
service, because the line belongs to the device.
|
|
55
|
+
- Pinned by Lorenz `irq` and `nmi` (`nmi` subtest `00/5` for the pipeline
|
|
56
|
+
clear).
|
|
57
|
+
- A cycle the VIC stalls through BA (`CPU#stall!`) keeps sampling the lines
|
|
58
|
+
but doesn't advance the pipeline: `irq_sample ||= irq && !I` and
|
|
59
|
+
`nmi_sample ||= nmi`, OR-ed with the sample it held, while `pending`
|
|
60
|
+
doesn't shift and `@skip_poll` stays owed to the next real cycle. The I
|
|
61
|
+
used is the one the stalled step leaves when it comes from the opcode: a
|
|
62
|
+
stalled CLI execute cycle masks with I = 0 and a stalled SEI with I = 1.
|
|
63
|
+
Every other step uses the current I. PLP too: its pulled I only arrives
|
|
64
|
+
with the stalled stack read.
|
|
65
|
+
- Each piece is forced by an `interrupts/irqdma` case. A NOP stalled on
|
|
66
|
+
its last cycle with the IRQ rising in the stall is taken after that NOP
|
|
67
|
+
(test 1). An SEI whose fetch sampled the IRQ, stalled on its execute
|
|
68
|
+
cycle, is still taken after the SEI (`||=`), while an IRQ rising in the
|
|
69
|
+
SEI stall is masked (test 7, `$d015=03`). A CLI stalled with the IRQ
|
|
70
|
+
rising is taken after the CLI (test 7, `$d015=01`). A PLP stalled on its
|
|
71
|
+
stack read pulling I = 1 is taken after the PLP (test 6, `$d015=40`
|
|
72
|
+
offset 106). Consuming `@skip_poll` in the stall breaks test 5.
|
|
73
|
+
- Pinned by `interrupts/irqdma` (all 16 rows) and Lorenz `irq` and `nmi`.
|
|
74
|
+
- Spec guard: *interrupts sampled while stalled by the VIC* in
|
|
75
|
+
[`cpu_spec.rb`](../spec/badline/cpu_spec.rb), one example per piece.
|
|
76
|
+
- Spec guard: the *interrupt recognition timing* group in
|
|
77
|
+
[`cpu_spec.rb`](../spec/badline/cpu_spec.rb), one example per quirk.
|
|
78
|
+
|
|
79
|
+
## CPU JAM
|
|
80
|
+
|
|
81
|
+
- JAM reads the opcode, a dummy byte, `$FFFF`, `$FFFE` and `$FFFE`, then
|
|
82
|
+
`$FFFF` on every cycle until reset. It never reaches an instruction
|
|
83
|
+
boundary, so a pending IRQ or NMI is never taken.
|
|
84
|
+
- Pinned by `CPU/cpujam` (the halt) and `jamirq`/`jamnmi`.
|
|
85
|
+
- Spec guard: the *JAM* group in
|
|
86
|
+
[`cpu_spec.rb`](../spec/badline/cpu_spec.rb).
|
|
87
|
+
|
|
88
|
+
## CPU unstable stores under DMA
|
|
89
|
+
|
|
90
|
+
- A cycle the VIC holds the CPU through BA is not a CPU cycle.
|
|
91
|
+
`Computer#cycle!` calls `CPU#stall!` instead of `CPU#cycle!`, which
|
|
92
|
+
records `@cycles`. The interrupt pipeline's `pending` stage doesn't
|
|
93
|
+
advance, but the line sample does (see *CPU interrupt recognition*).
|
|
94
|
+
- SHA, SHX, SHY and SHS/TAS drop the `& (H+1)` from the stored value when
|
|
95
|
+
the CPU is stalled **immediately before the dummy read**, the
|
|
96
|
+
second-to-last cycle (VICE x64sc's `LOAD_CHECK_BA_LOW_DUMMY`). A stall at
|
|
97
|
+
any earlier cycle leaves it in. The high byte of a page-crossing target
|
|
98
|
+
is still `value & (H+1)` either way.
|
|
99
|
+
- The rule is that one cycle, not "RDY went low during the instruction".
|
|
100
|
+
In the `*4`/`*5` timing tables the drop falls exactly one position after
|
|
101
|
+
each cycle-steal dip and on no other position. A whole-instruction rule
|
|
102
|
+
would drop on the positions after it too.
|
|
103
|
+
- Pinned by `CPU/sha`, `CPU/shxy` and `CPU/shs`, variants 2–5, with
|
|
104
|
+
variant 1 as the stall-free guard. They measure against VIC BA timing:
|
|
105
|
+
the `*2`/`*3` variants (sprite DMA) and `shxy4`/`shyx4`/`shx-test`
|
|
106
|
+
need sprite BA at `54 + 2n`, and the `*4`/`*5` variants (sprite and
|
|
107
|
+
character DMA) also need the bad-line and sprite-DMA-end BA to match.
|
|
108
|
+
- Spec guard: *SHX stalled by the VIC* in
|
|
109
|
+
[`cpu_spec.rb`](../spec/badline/cpu_spec.rb).
|
|
110
|
+
|
|
111
|
+
## CPU ANE constant
|
|
112
|
+
|
|
113
|
+
- ANE computes `A = (A | CONST) & X & imm`. `CONST` varies from chip to
|
|
114
|
+
chip; `CPU.new` takes it as `ane_constant:` and defaults to the C64
|
|
115
|
+
6510's `$EF`, VICE's value. A stall between the opcode and operand
|
|
116
|
+
fetches clears bits 0 and 4 of it (`CONST & $EE`).
|
|
117
|
+
- Pinned by `CPU/ane` (`ane`, `ane-border` and `ane-none`), which fails any
|
|
118
|
+
constant without bits 0 and 1 set and a high nybble of `$4`, `$5`, `$E`
|
|
119
|
+
or `$F`. The stall variant follows VICE: the testprog only displays the
|
|
120
|
+
RDY-cycle result, so no exit code pins it.
|
|
121
|
+
- SingleStepTests recorded an NMOS 6502 whose constant is `$EE`, so
|
|
122
|
+
`test/test_cpu.rb` builds its CPU with `ane_constant: 0xee` and checks
|
|
123
|
+
`$8B` as strictly as every other opcode.
|
|
124
|
+
- Spec guard: *ANE* in [`cpu_spec.rb`](../spec/badline/cpu_spec.rb).
|
|
125
|
+
|
|
126
|
+
## VIC raster IRQ phase
|
|
127
|
+
|
|
128
|
+
- The raster compare happens at the line wrap (the end of the old line's
|
|
129
|
+
last cycle), except on line 0, which compares at column 0. This is Bauer
|
|
130
|
+
3.12's "cycle 0 of every line, cycle 1 of line 0".
|
|
131
|
+
- The bad line compare picks up the same wrap (see
|
|
132
|
+
[VIC bad line and DMA](#vic-bad-line-and-dma)). It was re-derived against
|
|
133
|
+
the tests below when that landed.
|
|
134
|
+
- Pinned by `greydot`, `ss-*-color` and `den01-49-*`, with `denrsel-s0` as
|
|
135
|
+
the guard that must keep passing. This rule is coupled to
|
|
136
|
+
[CPU interrupt recognition](#cpu-interrupt-recognition), so recalibrate
|
|
137
|
+
the two together.
|
|
138
|
+
- Spec guard: *with the compare at the line wrap* and *with line 0 as the
|
|
139
|
+
target* in [`vic_spec.rb`](../spec/badline/vic_spec.rb). They are the
|
|
140
|
+
only examples that separate this phase from a uniform column-0 compare.
|
|
141
|
+
- `$d011` bit 7 and `$d012` follow the same phase. Every line reads as the
|
|
142
|
+
new line from the CPU cycle paired with column 62 of the old one. Line 0
|
|
143
|
+
reads a cycle later: that cycle still reads 311. VICE resets the counter
|
|
144
|
+
at cycle 2 of line 0 but increments it at cycle 1 of every other line.
|
|
145
|
+
- Pinned by `split-tests/lightpen`, whose `$d011`/`$d012` pages match
|
|
146
|
+
`dump6569` byte for byte only with the delay. Without it, index `$47`
|
|
147
|
+
reads `$1b`/`$00` where the chip reads `$9b`/`$37`, and the test exits
|
|
148
|
+
`$ff`.
|
|
149
|
+
- Spec guard: *when the counter wraps to line 0* in
|
|
150
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
151
|
+
- The compare latches the flag only on a change from no match to match,
|
|
152
|
+
and it runs again after every `$d011`/`$d012` write as well as at each
|
|
153
|
+
line step (VICE x64sc compares in every cycle). A write after column 61
|
|
154
|
+
is left to the line step, so a target moved to the next line there
|
|
155
|
+
still matches when the line steps: the match holds and nothing latches.
|
|
156
|
+
A write that moves the target onto the current line latches at once.
|
|
157
|
+
- Pinned by `rasterirq_hold` (11672 px → pass). It goes back to 11672 px
|
|
158
|
+
when the line step latches on any match, and to 345 px when writes
|
|
159
|
+
don't run the compare, which also breaks `greydot` (1576 px), because
|
|
160
|
+
the stale match state then swallows a later edge.
|
|
161
|
+
- The latch on a write that moves the target onto the current line is
|
|
162
|
+
VICE's behaviour. No test pins it: `rasterirq_hold` and `greydot` still
|
|
163
|
+
pass when a write only updates the match state.
|
|
164
|
+
- Spec guard: *with the target stepped along with the raster line* and
|
|
165
|
+
*when a write moves the target onto the current line* in
|
|
166
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
167
|
+
|
|
168
|
+
## VIC mid-line register visibility
|
|
169
|
+
|
|
170
|
+
- Sprite registers written mid-line are logged against
|
|
171
|
+
`(write cycle + 1) * 8` and take hold after a per-signal delay: **+1 px**
|
|
172
|
+
for the colors ($d025/$d026/$d027–$d02e), **+6 px** for priority
|
|
173
|
+
($d01b) and **+7 px** for the sequencer inputs ($d000–$d010, $d01c,
|
|
174
|
+
$d01d). The color delay is the same +1 px that `greydot` pins for
|
|
175
|
+
$d020–$d024 below.
|
|
176
|
+
- Pinned by the `spritesplit` staircases. `ss-hires-color`/`ss-mc-color*`
|
|
177
|
+
fix the color delay and `ss-pri*` the priority one (only the bands where
|
|
178
|
+
the sprite sits on an odd X can tell 6 from 7).
|
|
179
|
+
`ss-hires-mc`/`ss-mc-hires`/`ss-*exp*` fix the sequencer one.
|
|
180
|
+
- These were +9/+14/+15 until the sprite BA windows moved a column
|
|
181
|
+
earlier (see *VIC sprite display*). `spritesplit` syncs on a raster IRQ
|
|
182
|
+
whose line starts a sprite DMA. With the window where `spritesteal` puts
|
|
183
|
+
it, that line's stall catches a read it used to miss, so the CPU loses 5
|
|
184
|
+
cycles there, not 4, and every write in the staircase lands one cycle
|
|
185
|
+
later. The old delays were 8 px of that phase error on top of the real
|
|
186
|
+
delay.
|
|
187
|
+
- Spec guard: *mid-line write delays* in
|
|
188
|
+
[`vic/sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb), one example
|
|
189
|
+
per path, each failing on a one-pixel change.
|
|
190
|
+
- $d020–$d024 writes become visible 1 px into the next column.
|
|
191
|
+
`ColorPatches` restores the boundary pixel at `finish_line`.
|
|
192
|
+
- Pinned by `greydot`.
|
|
193
|
+
- Spec guard:
|
|
194
|
+
[`vic/color_patches_spec.rb`](../spec/badline/vic/color_patches_spec.rb).
|
|
195
|
+
- `apply_border` restores a pre-composite snapshot (`BorderMask`) instead of
|
|
196
|
+
repainting with the end-of-line $d020, so mid-line border splits survive
|
|
197
|
+
sprite compositing.
|
|
198
|
+
|
|
199
|
+
## VIC sprite display
|
|
200
|
+
|
|
201
|
+
- Sprite display starts at **Y+1**: the Y match turns DMA on, and the first
|
|
202
|
+
row renders on the next line.
|
|
203
|
+
- DMA compares at cycles 55/56 (**VIC columns 53/54**, with BA rebuilt
|
|
204
|
+
mid-line). Display turns on at cycle 58 (column 57), **but only while both
|
|
205
|
+
MxE and Y still match**. That is Bauer §3.8 rule 6 plus the enable bit his
|
|
206
|
+
wording omits. A write to either register between the compares and cycle
|
|
207
|
+
58 keeps DMA running invisibly.
|
|
208
|
+
- Pinned by all five `spriteenable` rows. `3` and `5` move Y and `4`
|
|
209
|
+
clears MxE. `1` and `2` write $d015 *between* the two compares, which
|
|
210
|
+
only lands on that column pair.
|
|
211
|
+
- Each sprite's BA window is five columns, three ahead of its two s-access
|
|
212
|
+
columns, stepping two columns per sprite from column 54
|
|
213
|
+
(`SPRITE_BA_WINDOWS`). `ba_low?` is asked after the VIC has advanced, so
|
|
214
|
+
the CPU cycle that follows VIC column 53 is the first one sprite 0 halts.
|
|
215
|
+
From sprite 3 on, the window runs past the end of the line and splits: the
|
|
216
|
+
tail falls on the line whose compare started the fetch, and the head on
|
|
217
|
+
the next one. One sprite therefore costs the CPU 5 cycles, and sprites 0–3
|
|
218
|
+
together cost 11. A write cycle still completes under BA.
|
|
219
|
+
- Pinned by `spritesteal`, whose `sprite_steal_table` (`core.asm:759`)
|
|
220
|
+
lists the stolen cycles for each sprite alone and all eight together,
|
|
221
|
+
one cycle at a time, on the line that starts the DMA.
|
|
222
|
+
- The `spriteenable` raster markers land on their printed `x`/`y` columns
|
|
223
|
+
only with these windows *and* the bad-line stall in *VIC bad line and
|
|
224
|
+
DMA*: on their line 51, a bad line meets the head of the sprite 3
|
|
225
|
+
window, and the CPU gets 9 cycles between the two.
|
|
226
|
+
- Spec guard: *sprite DMA cycle stealing (#ba_low?)* in
|
|
227
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
228
|
+
- A DMA started on the *second* compare leaves sprite 0 a column short of
|
|
229
|
+
AEC, because sprite 0 alone has its accesses immediately after the
|
|
230
|
+
compares. The first of its three s-accesses therefore reads back the $ff
|
|
231
|
+
the CPU is still driving.
|
|
232
|
+
- Pinned by `spriteenable2`.
|
|
233
|
+
- Spec guard: *the first s-access of a new DMA* in
|
|
234
|
+
[`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
|
|
235
|
+
- Rows come from MCBASE/MC, not from a line counter (Bauer §3.8). MC
|
|
236
|
+
steps once per s-access, so it stands three past MCBASE after a row's
|
|
237
|
+
fetch. At Bauer cycle 16 (**VIC column 14**) MCBASE takes MC while the
|
|
238
|
+
expansion flip-flop is set, and MC reloads from MCBASE at cycle 58. The
|
|
239
|
+
end-of-sprite compare runs a column later, at column 15, where the BA
|
|
240
|
+
columns are rebuilt. The sprite ends only when MCBASE lands on
|
|
241
|
+
**exactly** 63, so a crunched sprite steps over it and runs on through
|
|
242
|
+
the rest of its block.
|
|
243
|
+
- Pinned by `spritecrunch2-09`, whose $d017 clears land in the CPU cycle
|
|
244
|
+
after column 14. The reference repeats one row for 48 lines, so MCBASE
|
|
245
|
+
has already moved when the flip-flop is set. Moving MCBASE with the
|
|
246
|
+
compare at column 15 fails it (804 px).
|
|
247
|
+
- The expansion flip-flop is set by the Y match that starts the DMA, and
|
|
248
|
+
set **at once** by a $d017 write that clears MxYE, not at a column hook
|
|
249
|
+
(VICE `d017_store`). At Bauer cycle 56 (**VIC column 54, after the second
|
|
250
|
+
Y compare**) it inverts for each sprite with DMA running and MxYE set.
|
|
251
|
+
- Pinned by `spritecrunch2-25`–`29`. Their $d017 sets land from the CPU
|
|
252
|
+
cycle after column 48 to the one after column 55, one column later
|
|
253
|
+
every 8 lines, so they straddle the inversion. Inverting at column 53
|
|
254
|
+
fails all five (228–472 px), and inverting ahead of the compare breaks
|
|
255
|
+
`spritedma/d017-54` and `d017-57` as well.
|
|
256
|
+
- **Sprite crunch**: a $d017 write that clears MxYE in Bauer cycle 15 (the
|
|
257
|
+
CPU cycle after VIC column 13) while the flip-flop is reset steps MC to
|
|
258
|
+
`(0x2a & (MCBASE & MC)) | (0x15 & (MCBASE | MC))`, which column 14 then
|
|
259
|
+
hands to MCBASE. From MCBASE $00 that is $01, from $01 or $04–$06 it is
|
|
260
|
+
$05, from $03 it is $07, as the `spritecrunch` readme's table has it.
|
|
261
|
+
- Pinned by `spritecrunch-3b/3c/3d-00` (the same program, whose clear
|
|
262
|
+
lands in that cycle once), `spritecrunch2-08` and `sequencer-bug`. There
|
|
263
|
+
the crunch on line 52 steps MCBASE from $03 to $07, off the multiples of
|
|
264
|
+
three, so the expanded sprites wrap through their block and run for 84
|
|
265
|
+
lines instead of 42. Without the formula, or with the window a column either side,
|
|
266
|
+
all five fail (`sequencer-bug` 8064 px, 384 px a column early).
|
|
267
|
+
- `spritedma/d017-54` and `d017-57` pass with and without it.
|
|
268
|
+
- Spec guard: *sprite crunch* in
|
|
269
|
+
[`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb), one example
|
|
270
|
+
per row of the readme's table.
|
|
271
|
+
- Sprite pixels come from a per-pixel sequencer, not from a decoded row. A
|
|
272
|
+
live X comparator fires one pixel before the sprite's first pixel, the
|
|
273
|
+
expansion flip-flop gates the shift, and in multicolor a two-bit latch
|
|
274
|
+
reloads on every second shift. Both flip-flops idle while their register
|
|
275
|
+
bit is clear, so the first pixel after a mid-sprite $d01c/$d01d change
|
|
276
|
+
repeats the last latched value, and the pairs restart on the pixel after
|
|
277
|
+
that.
|
|
278
|
+
- Pinned by all 17 `spritesplit` tests, and `ss-xpos` for the comparator
|
|
279
|
+
in particular.
|
|
280
|
+
- X coordinates ≥ $1f8 never match the VIC's 504-step X counter, so those
|
|
281
|
+
sprites stay dark. The `spritegap` dumps show this boundary.
|
|
282
|
+
- Spec guard: *X comparator* in
|
|
283
|
+
[`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
|
|
284
|
+
- Each sprite's s-accesses **reload its shift register** at raster pixel
|
|
285
|
+
K = 459 + 16*m (mod 504). That is late in the line for sprites 0–2 and
|
|
286
|
+
early in the next one for 3–7.
|
|
287
|
+
- A sprite still shifting at K loses the rest of its row. Its output
|
|
288
|
+
holds its last pixel from K through **K+6**, then goes dark.
|
|
289
|
+
- A comparator hit in K..K+11 shows nothing.
|
|
290
|
+
- A hit from K+12 on shows the row the reload brought. For sprites 0–2
|
|
291
|
+
that is the *next* line's row, a line early. For sprites 3–7 it is the
|
|
292
|
+
current row, and a hit ahead of their K shows the previous line's row.
|
|
293
|
+
- The row can be shown once on each side of K, so a sprite moved past
|
|
294
|
+
the beam fires a second time on the same line.
|
|
295
|
+
- Pixels that run past the end of the line are drawn at the start of the
|
|
296
|
+
next one.
|
|
297
|
+
- Pinned by `split-tests/spritescan`, a byte-exact dump over all eight
|
|
298
|
+
sprites, 512 X positions and four patterns. `spritex/testsuite`
|
|
299
|
+
(entries 14–16: 469/470 dead, 471 = K+12 fires again),
|
|
300
|
+
`spritex/demusinterruptus` and `spritegap2` pass on it too.
|
|
301
|
+
- Spec guard: *the reload* in
|
|
302
|
+
[`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
|
|
303
|
+
- The hold's length is pinned by `spritefetchbug/test-136-2a`, whose
|
|
304
|
+
X-expanded sprite 0 sits at X = $136 and is still shifting at 459. Its
|
|
305
|
+
reference holds the last pixel through 465. Holding it for one pixel
|
|
306
|
+
fails the test by 134 px. `spritescan` passes either way, because it
|
|
307
|
+
records only whether a collision happened at each X position.
|
|
308
|
+
- A multicolor pair that the latch loads on **K−1**, the last pixel before
|
|
309
|
+
the reload, keeps only its high bit, as a hi-res pixel does: %11 shows the
|
|
310
|
+
sprite's own color and %01 is transparent. The hold then repeats that
|
|
311
|
+
pixel.
|
|
312
|
+
- Pinned by `spritefetchbug/test-136-2a`. Its X-expanded multicolor
|
|
313
|
+
sprite at $136 loads its last pair on pixel 458. Without the rule the
|
|
314
|
+
test fails by 168 px. The readme lists 136, 13a, 13e, 142, 146 and 14e
|
|
315
|
+
as the positions where the bug stops being multicolor. Those are the X
|
|
316
|
+
positions ≡ 2 mod 4, where an X-expanded sprite's pair loads land on
|
|
317
|
+
458.
|
|
318
|
+
- Spec guard: *a multicolor pair loaded on the pixel before the reload*
|
|
319
|
+
in [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
|
|
320
|
+
- Sprites 3–7 run their s-accesses at the start of the line, so on the line
|
|
321
|
+
whose compare starts their DMA, those accesses ran with the DMA still
|
|
322
|
+
off. The shift register loads what the VIC saw anyway. The first and third
|
|
323
|
+
bytes come from the VIC's internal bus in phi2, which reads $ff unless
|
|
324
|
+
the CPU reads or writes a VIC register in that cycle, in which case it
|
|
325
|
+
holds that byte. The middle byte is the idle phi1 fetch at $3fff. A hit
|
|
326
|
+
after the display turns on at cycle 58 (raster pixel 460 on, X ≥ $164)
|
|
327
|
+
shows this row on the same line.
|
|
328
|
+
- Pinned by `sbsprf24-164`. Its sprite 6 at $164 shows `BYTE_S0`, the
|
|
329
|
+
ghost byte and `BYTE_S2` on line $7a, which the program puts on the bus
|
|
330
|
+
with the dummy read and the write of `sta $d000,y` in Bauer cycles 7
|
|
331
|
+
and 8. Without the rule the test goes back from 42 to 51 px. With $ff
|
|
332
|
+
in place of the bus bytes it is 49 px, and with $ff in place of the
|
|
333
|
+
ghost byte, 46. `sbsprf24-163`, one pixel to the left, shows nothing
|
|
334
|
+
on that line.
|
|
335
|
+
- `Sprite::InternalBus` samples $3fff as the line starts rather than in
|
|
336
|
+
the sprite's own cycle, which `sbsprf24` can't tell apart.
|
|
337
|
+
- Spec guard: *the s-accesses before the DMA starts* in
|
|
338
|
+
[`vic/sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb).
|
|
339
|
+
- On the line that shows its last row, where MCBASE reached 63 and the DMA
|
|
340
|
+
ended at cycle 16, the sprite loses its display at cycle 58: no hit from
|
|
341
|
+
raster pixel **460** on starts it, though one already shifting runs on,
|
|
342
|
+
and it shows no row on the line after. VICE x64sc does the same, pending
|
|
343
|
+
bits cleared at xpos $164.
|
|
344
|
+
- Pinned by `spritegap3`'s collision log, where every pair stops at X =
|
|
345
|
+
$164 whatever the lower sprite.
|
|
346
|
+
- MCBASE reaching 63 at cycle 16 stops only the **DMA**. The display turns
|
|
347
|
+
off at cycle 58, and only if the DMA is still off. A Y match at the
|
|
348
|
+
cycle 55/56 compare on the last row's line restarts the DMA under a
|
|
349
|
+
display that is still on, so the new run shows even though Y no longer
|
|
350
|
+
matches at cycle 58.
|
|
351
|
+
- Pinned by `spriterestart`.
|
|
352
|
+
- Spec guard: *when Y matches only at the compare on the last row's line*
|
|
353
|
+
in [`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
|
|
354
|
+
- The DMA end also drops the sprite's BA tail on that line, because the
|
|
355
|
+
BA columns are rebuilt at cycle 16. Pinned by `CPU/sha*`, `shs*` and
|
|
356
|
+
`shxy*`, variants 4 and 5.
|
|
357
|
+
- Spec guard: *sprite BA on the line its DMA ends* in
|
|
358
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
359
|
+
- The Y comparator is **eight bits** wide, so a coordinate of 0–55 matches a
|
|
360
|
+
second time on PAL lines 256–311 and starts a second DMA run there.
|
|
361
|
+
- Pinned by `spritey`, whose reference collides on every one of the 312
|
|
362
|
+
lines.
|
|
363
|
+
- Spec guard: *the eight-bit Y compare* in
|
|
364
|
+
[`vic/sprite_spec.rb`](../spec/badline/vic/sprite_spec.rb).
|
|
365
|
+
|
|
366
|
+
## VIC sprite collisions
|
|
367
|
+
|
|
368
|
+
- $d01e/$d01f latch **as the beam crosses each sprite pixel**, not at the
|
|
369
|
+
end of the line. A read reports the pixels drawn before its own cycle and
|
|
370
|
+
nothing after them. The VIC runs ahead of the CPU inside a machine cycle,
|
|
371
|
+
so the column the read lands on has not latched yet.
|
|
372
|
+
- Pinned by `sprite-sprite-collision-cycle` and
|
|
373
|
+
`sprite-gfx-collision-cycle`, which step the sprite one pixel per
|
|
374
|
+
subtest across that boundary.
|
|
375
|
+
- The reset a read asserts outlives the read by **12 pixels**. Pixels drawn
|
|
376
|
+
under it never reach the register, so two reads four cycles apart report
|
|
377
|
+
a 20-pixel window instead of the 32 pixels between them. The two
|
|
378
|
+
registers reset independently.
|
|
379
|
+
- Pinned by `spritevssprite`'s 20-column bands, which place the boundary
|
|
380
|
+
to the pixel.
|
|
381
|
+
- The comparator runs wherever the sprites do, including the vertical
|
|
382
|
+
blank, where there is no line to paint them over.
|
|
383
|
+
- Pinned by `spritey`. Its sprites carry a single lit pixel on their first
|
|
384
|
+
row, so they only collide on the line after the Y match: line 1 for a
|
|
385
|
+
coordinate of 0.
|
|
386
|
+
- The $D019 collision bits rise on the **cycle that draws the colliding
|
|
387
|
+
pixel**, on the edge out of an empty register, whether or not $D01A
|
|
388
|
+
enables them. The fold keeps up with the beam while an enabled collision
|
|
389
|
+
IRQ is unlatched, and a $D019 read folds first, so neither waits for the
|
|
390
|
+
end of the line.
|
|
391
|
+
- Pinned by `irq-ack-vicii`'s sprite-sprite half. Its `STA $D019` row
|
|
392
|
+
acknowledges the flag before the CPU takes the IRQ (`-`) at exactly the
|
|
393
|
+
fourth of six delays. Folding a cycle early moves the `-` to the third,
|
|
394
|
+
folding a cycle late moves it to the fifth, and folding only at the end
|
|
395
|
+
of the line loses it. The read fold matters only with the IRQ disabled,
|
|
396
|
+
which no testprog covers; it follows VICE, which sets the bit
|
|
397
|
+
regardless of $D01A.
|
|
398
|
+
- Spec guard: *when the beam crosses the colliding pixel* in
|
|
399
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
400
|
+
- Spec guard: *#collide_upto* in
|
|
401
|
+
[`vic/sprites_spec.rb`](../spec/badline/vic/sprites_spec.rb). Its first
|
|
402
|
+
four examples each fail on a one-pixel change.
|
|
403
|
+
|
|
404
|
+
## VIC border and idle state
|
|
405
|
+
|
|
406
|
+
- The vertical border compares run in every cycle of a line, as VICE x64sc
|
|
407
|
+
runs them. The top compare (line 51 with RSEL set, 55 with it clear, and
|
|
408
|
+
DEN set) resets the flip-flop at once. The bottom compare (251 or 247)
|
|
409
|
+
only arms it, and the armed state takes hold at the line's first cycle
|
|
410
|
+
and at the left window edge (Bauer §3.9 rules 2–5). Between writes
|
|
411
|
+
nothing changes, so the VIC compares at each line's first cycle and after
|
|
412
|
+
each `$d011` write.
|
|
413
|
+
- The line's first cycle is the previous line's column 62 (Bauer cycle 1,
|
|
414
|
+
the column the bad line compare also gives to the next line), and it
|
|
415
|
+
compares the line about to start. A `$d011` write after column 61 is
|
|
416
|
+
compared there, against the next line. A write after column 62 has
|
|
417
|
+
missed it.
|
|
418
|
+
- Pinned by `denrsel-s0`/`-s1`: s1 clears RSEL and DEN one cycle later
|
|
419
|
+
than s0, just after line 51's first-cycle compare, so the border
|
|
420
|
+
opens. Pinned with them by `denrsel-1`/`-2`/`-s2`, `den10-51-1` (all
|
|
421
|
+
64000–87040 px → pass), `vborder-33-08`/`-09`, `vborder2-22` and
|
|
422
|
+
`vborder2-64`, which fail again when column 62 compares the line
|
|
423
|
+
ending. `border-bm-idle` (8057 → 9 px) goes back too.
|
|
424
|
+
- A write is compared in the next column, so RSEL touching a bottom
|
|
425
|
+
compare line mid-line closes the border from the next line.
|
|
426
|
+
- Pinned by `vborder2-63` and `vborder-32-08`/`-09`, which fail when
|
|
427
|
+
only the line start and the left edge compare, and `vborder-33-08`
|
|
428
|
+
and `vborder2-36`, which move further.
|
|
429
|
+
- The 40-column left compare runs in column 15 (Bauer 17), a column
|
|
430
|
+
before the column that draws its pixel, so a `$d011` write after
|
|
431
|
+
column 15 misses it. The 38-column one (Bauer 18) runs in column 16.
|
|
432
|
+
- Pinned by `vborder2-36` (320 px → pass), which fails alone when the
|
|
433
|
+
compare sees `$d011` as of column 16. `vborder2-35` is the other side
|
|
434
|
+
of the pair.
|
|
435
|
+
- Spec guard: *vertical border flip-flop* in
|
|
436
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb), one context per rule,
|
|
437
|
+
each failing under its knock-out, and *vertical border flip-flop* in
|
|
438
|
+
[`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb) for
|
|
439
|
+
the armed bottom compare and the 40-column left compare.
|
|
440
|
+
- The side border compares see CSEL a pixel late, as the colour registers
|
|
441
|
+
do. The 40-column compares fall on the first pixel of their group (raster
|
|
442
|
+
x 128 on the left, 448 on the right), and that pixel still sees CSEL as
|
|
443
|
+
the column before had it. The 38-column compares fall on a group's last
|
|
444
|
+
pixel (135 and 439), so they see a write made in the CPU cycle before
|
|
445
|
+
the group. A CSEL clear in the CPU cycle after column 55 therefore misses
|
|
446
|
+
the 38-column compare and still meets the 40-column one: the border
|
|
447
|
+
closes.
|
|
448
|
+
- Pinned by `vicii_reg_timing` (71 px → pass, and 78 → 7 px for `-a5`
|
|
449
|
+
and `-ff`), whose line 232 clears CSEL there and shows a closed right
|
|
450
|
+
border. Lagging the 38-column compares as well breaks `border-bm-ysh`,
|
|
451
|
+
`border-bm-ysh2`, `border-mcbm` and `hvborder1` (71–73 px each), which
|
|
452
|
+
clear CSEL in the cycle after column 53 and close at 439.
|
|
453
|
+
- Spec guard: *closes the right border when CSEL clears in the compare's
|
|
454
|
+
column* and *closes the right border at the 38-column compare in the
|
|
455
|
+
column CSEL clears* in
|
|
456
|
+
[`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
|
|
457
|
+
- The border colour shows where the **main** flip-flop is set, and only
|
|
458
|
+
there. The vertical flip-flop keeps the main one from clearing at the
|
|
459
|
+
left compare and withholds the graphics data, but it does not paint
|
|
460
|
+
border itself. So with the side border held open, the lines inside the
|
|
461
|
+
vertical border show zero data in the latched colours (VICE x64sc
|
|
462
|
+
`draw_border8`).
|
|
463
|
+
- Pinned by `hvborder2`, `border-bm-ysh`, `border-bm-ysh2` and
|
|
464
|
+
`border-mcbm`, which go back to 8134, 5575, 4598 and 5595 px when
|
|
465
|
+
either flip-flop paints border (knock-outs measured before the sprite
|
|
466
|
+
shifter landed; all four pass since the
|
|
467
|
+
[graphics pipeline](#vic-graphics-pipeline) rules).
|
|
468
|
+
- Spec guard: *shows the vertical border only through the main
|
|
469
|
+
flip-flop* in
|
|
470
|
+
[`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
|
|
471
|
+
- An idle-state g-access reads $3fff, or $39ff with ECM set, and the byte
|
|
472
|
+
is painted through the current mode with a zero screen byte and colour
|
|
473
|
+
nibble (foreground black, background per mode). A guard keeps
|
|
474
|
+
closed-border lines on the bulk path.
|
|
475
|
+
- Pinned by `ss-pri*`, whose diffs collapsed from ~85k px to 220–440 px.
|
|
476
|
+
- Spec guard: *renders the idle byte at $3fff in black* in
|
|
477
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
478
|
+
- A column with no g-access (outside columns 14–53), or one whose
|
|
479
|
+
g-access falls while the vertical border stays closed for the rest of
|
|
480
|
+
the line, shifts out **zero data**. It is painted through the current
|
|
481
|
+
mode with the screen byte and colour nibble the last g-access latched:
|
|
482
|
+
a display-state one latches its buffer cell, an idle one latches 0/0,
|
|
483
|
+
and the value carries across lines. So a zero pixel is $d021 in the text
|
|
484
|
+
and multicolour bitmap modes, the kept screen byte's low nibble in
|
|
485
|
+
standard bitmap, the ECM background it selects, and black in the
|
|
486
|
+
invalid modes (VICE x64sc `draw_graphics8`).
|
|
487
|
+
- Pinned by `sbsprf24-163`/`-164` (401/428 → 34/51 px),
|
|
488
|
+
`spritefetchbug` (302 → 174), `hvborder1` (191 → 43) and
|
|
489
|
+
`vicii_reg_timing` (8865 → 1773), which decoded memory at VC there
|
|
490
|
+
before. The kept colours are pinned by
|
|
491
|
+
`border-bm-ysh`/`-ysh2`, which go back to 5580/4741 px with 0/0
|
|
492
|
+
instead.
|
|
493
|
+
- The top compare line counts as open for the whole line, because the
|
|
494
|
+
vertical flip-flop only clears at the left compare, two columns after
|
|
495
|
+
the first g-accesses are sampled. Without that, every picture loses
|
|
496
|
+
the start of its first line (`greydot`, `dmadelay`, `dentest`).
|
|
497
|
+
- Spec guard: *a column with no g-access* in
|
|
498
|
+
[`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
|
|
499
|
+
- The XSCROLL bleed at column 0 takes its pixels from the group before
|
|
500
|
+
it, the zero-data group, rather than filling with $d021. Only
|
|
501
|
+
`border-bm-ysh2` separates the two (+6 px).
|
|
502
|
+
|
|
503
|
+
## VIC bad line and DMA
|
|
504
|
+
|
|
505
|
+
- The display state counts columns two ahead of Bauer's cycles: column
|
|
506
|
+
`c` is his cycle `c + 2`. This is the frame the CPU sees, where the
|
|
507
|
+
bad-line stall `bascan` pins starts in the CPU cycle after column 10
|
|
508
|
+
(Bauer 12). It is also VICE x64sc's order, where the VIC's logic for a
|
|
509
|
+
cycle runs before the CPU's bus access in it. A `$d011` write in Bauer
|
|
510
|
+
cycle `n` runs after column `n − 2` and is first compared in column
|
|
511
|
+
`n − 1`, which is Bauer `n + 1`.
|
|
512
|
+
- The bad line condition is compared in **every** column. The last column
|
|
513
|
+
of a line (Bauer cycle 1 of the next) compares against the line about to
|
|
514
|
+
start.
|
|
515
|
+
- A match enters display state **in its own column**, not when AEC falls.
|
|
516
|
+
- Pinned by 12 of the `dmadelay` rows, `screenpos`, `fldscroll-20-60`
|
|
517
|
+
and `colorfetchbug/bitmap`, which all fail when display state waits
|
|
518
|
+
for AEC.
|
|
519
|
+
- The first match in columns 10–52 (Bauer 12–54) pulls BA low, and the VIC
|
|
520
|
+
owns the bus three columns later. The c-accesses run in columns 13–52
|
|
521
|
+
(Bauer 15–54) for as long as the condition stands, so a condition
|
|
522
|
+
withdrawn mid-line stops them. The ones before AEC read `$ff` off a bus
|
|
523
|
+
the CPU still drives.
|
|
524
|
+
- Pinned by `flibug/blackmail*`. Their `$d011` writes land in column 12
|
|
525
|
+
(Bauer 14), so the match comes in column 13 and three cells read
|
|
526
|
+
`$ff`, as in the reference.
|
|
527
|
+
- The g-accesses run in columns 14–53 (Bauer 16–55), in the first half
|
|
528
|
+
of the column, ahead of that column's compare. Their pixels leave the
|
|
529
|
+
sequencer two columns later (`VIC::GRAPHICS_DELAY`), so cell 0 still
|
|
530
|
+
draws at column 16.
|
|
531
|
+
- Pinned by `dmadelay` `test*-18`/`-1a`, `screenpos` and
|
|
532
|
+
`colorfetchbug/bitmap`, which fail when the compare runs first.
|
|
533
|
+
- VC and VMLI reload in column 12 (Bauer 14). The row counter resets there
|
|
534
|
+
**only** if the condition stands in that column, so a row opened later
|
|
535
|
+
keeps the row counter it had. It steps in column 56 (Bauer 58). This
|
|
536
|
+
replaces the curve-fitted 11–15 reset window, and matches Bauer and
|
|
537
|
+
VICE.
|
|
538
|
+
- Pinned by `colorfetchbug` (its bad lines start at Bauer 17), the
|
|
539
|
+
`dmadelay` `test*-17`/`-18`/`-19`/`-1a` rows, `flibug/blackmail*` and
|
|
540
|
+
`screenpos`, which all fail when a later match resets the counter.
|
|
541
|
+
- The CPU halts on the bad line condition **as it stands**, not on the
|
|
542
|
+
latched match. `ba_low?` holds it for the columns BA covers, 10–52, which
|
|
543
|
+
it sees as `@column` 11–53 because it is asked after the VIC advances.
|
|
544
|
+
That is 43 cycles, the gap Bauer leaves between bad-line BA (cycle 12)
|
|
545
|
+
and sprite 0's BA (cycle 55). A condition that goes away mid-line
|
|
546
|
+
releases the CPU.
|
|
547
|
+
- Pinned by `split-tests/bascan`, a per-cycle dump of when the stall
|
|
548
|
+
first catches a CIA timer read. Its `$d012` reads are the same dump's
|
|
549
|
+
check that the raster sync itself did not move.
|
|
550
|
+
- The end is pinned too. Re-measured in this frame, a 45-cycle stall
|
|
551
|
+
(CPU halted through the cycle after column 54) breaks `bascan`
|
|
552
|
+
itself, all five `colorfetchbug` rows, `flibug/blackmail*`,
|
|
553
|
+
`spriteenable3`–`5` and several `vborder*` rows.
|
|
554
|
+
- Spec guard: *#ba_low?* in [`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
555
|
+
- The DEN latch is level-sensitive across the raster counter's increment,
|
|
556
|
+
so its window runs from the last column of line 47 through the last
|
|
557
|
+
column of line 48. The wrap column counts for both the line ending and
|
|
558
|
+
the line starting. Derived against `dentest`'s `den01-48-*`, `den01-49-*`
|
|
559
|
+
and `den10-48-*`, which bracket both edges one cycle at a time.
|
|
560
|
+
- A condition still standing at column 56 puts the logic straight back
|
|
561
|
+
into display state after the counter wraps (Bauer 3.7.2 step 5), so RC
|
|
562
|
+
rolls 7 → 0 instead of leaving the line idle. This is what holds an FLI
|
|
563
|
+
picture together.
|
|
564
|
+
- VC and VMLI advance per g-access in display state, and VCBASE takes VC at
|
|
565
|
+
the wrap. A row that opens late therefore carries its shortfall into the
|
|
566
|
+
next one instead of a fixed +40.
|
|
567
|
+
- All 21 `dmadelay` rows pass under these rules, and they sweep the match
|
|
568
|
+
across the whole line. `D011Test/disable-bad` pins the too-late match.
|
|
569
|
+
- Spec guard: [`vic/display_state_spec.rb`](../spec/badline/vic/display_state_spec.rb),
|
|
570
|
+
one group per rule.
|
|
571
|
+
- The colour nibble of a c-access before AEC is the low nibble of the byte
|
|
572
|
+
at the CPU's PC, which is the opcode it is halted on, as VICE reads it.
|
|
573
|
+
`Computer` hands the VIC a lambda for it (`VIC#open_bus=`), called only
|
|
574
|
+
on those accesses. A bare VIC reads colour RAM instead.
|
|
575
|
+
- Pinned by `flibug/blackmail*` and `colorfetchbug/main*`, whose bug
|
|
576
|
+
cells take their colour from the halted opcode.
|
|
577
|
+
- Spec guard: *an FLI match in column 13* in
|
|
578
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
579
|
+
- These rows can't be read as pixel counts. The sweep became readable by
|
|
580
|
+
OCRing each reference PNG against `lib/badline/roms/character.rom` and
|
|
581
|
+
matching every display row back to its offset in screen memory, so a diff
|
|
582
|
+
reads as "row 0 starts 40 cells in" instead of "11,376 px". Rebuild that
|
|
583
|
+
as a scratch script before touching these tests again.
|
|
584
|
+
|
|
585
|
+
## VIC graphics pipeline
|
|
586
|
+
|
|
587
|
+
The column frame is the one in [VIC bad line and DMA](#vic-bad-line-and-dma).
|
|
588
|
+
A register write in the CPU cycle after column `c - 1` is seen by column
|
|
589
|
+
`c`, and a g-access in column `c` draws in column `c + 2`. The rules follow
|
|
590
|
+
VICE x64sc's `vicii_fetch_graphics` and `draw_graphics8` for the 6569.
|
|
591
|
+
|
|
592
|
+
- The g-access reads its byte **in its own column**, through the mode,
|
|
593
|
+
`$d018` and VIC bank of that cycle, not when the sequencer draws it two
|
|
594
|
+
columns later. `VIC#fetch_graphics` reads it, and `GraphicsMode` only
|
|
595
|
+
paints.
|
|
596
|
+
- Pinned by `gfxfetch` (224 px → pass), which flips the character data
|
|
597
|
+
between the g-access and the draw, and `fetchsplit` (2939 → 2890 px
|
|
598
|
+
without it), which splits `$d018` and `$dd00` mid-line.
|
|
599
|
+
- Spec guard: *reads the byte in its own column* in
|
|
600
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
601
|
+
- The g-access still sees BMM for one column after it falls: it addresses
|
|
602
|
+
with `$d011` OR-ed with the BMM bit of the column before
|
|
603
|
+
(`VIC::FETCH_HOLD`). When BMM changes and the access moves from RAM onto
|
|
604
|
+
the character ROM, the low address byte comes from the old mode and the
|
|
605
|
+
rest from the new one.
|
|
606
|
+
- The hold is pinned by `vicii_reg_timing` (pass → 56 px without it, and
|
|
607
|
+
7 → 63 px for `-a5` and `-ff`). The address mix is pinned by
|
|
608
|
+
`modesplit` (48 → 202 px) and `videomode-v`, `-x` and `-y` (5/10/1 →
|
|
609
|
+
13/14/9 px).
|
|
610
|
+
- Spec guard: *addresses with a BMM that fell in the same column* and
|
|
611
|
+
*mixes the addresses when BMM falls onto the character ROM* in
|
|
612
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
613
|
+
- ECM is held for that column too, but only when the access it leaves,
|
|
614
|
+
addressed through the old mode, read the character ROM
|
|
615
|
+
(`VIC::FETCH_HOLD_ROM`). A falling ECM reaches a RAM access at once.
|
|
616
|
+
- Pinned by `modesplit` (348 → 48 px). Its section 2 drops ECM in text
|
|
617
|
+
mode with the characters in the ROM, and its section 1 goes from
|
|
618
|
+
ECM text on the ROM to bitmap in RAM. Both need the mask held.
|
|
619
|
+
`videomode-v` makes the second move too (6 → 5 px).
|
|
620
|
+
- The RAM side is pinned by `vicii_reg_timing`, whose ECM row drops ECM
|
|
621
|
+
in text mode with the characters in RAM: holding ECM there as well
|
|
622
|
+
takes it from pass to 32 px (7 → 39 px for `-a5` and `-ff`), and
|
|
623
|
+
`videomode-z`, a `$7b` → `$3b` fall in RAM, from pass to 3 px.
|
|
624
|
+
`videomode-x` makes the same fall in RAM and would prefer the hold
|
|
625
|
+
(10 → 2 px), but its readme says its reference doesn't match every 6569
|
|
626
|
+
capture. VICE holds BMM only.
|
|
627
|
+
- Spec guard: *drops a falling ECM at once when the access left RAM* and
|
|
628
|
+
*holds a falling ECM when the access left the character ROM* in
|
|
629
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
630
|
+
- The byte a group draws loads into the shift register at pixel XSCROLL,
|
|
631
|
+
and that XSCROLL is the one the **column before** saw. It is latched in
|
|
632
|
+
each g-access column with the vertical border open (VICE
|
|
633
|
+
`xscroll_pipe`), so a `$d016` write shows a column later than a colour
|
|
634
|
+
or mode write in the same cycle.
|
|
635
|
+
- Pinned by `sbsprf24-163`/`-164` (34/42 px → pass, 40/44 without it),
|
|
636
|
+
`modesplit` (48 → 144) and `vicii_reg_timing` (pass → 720), and by
|
|
637
|
+
`border-bm-idle`, `border-bm-ysh` and `border-mcbm`.
|
|
638
|
+
- Spec guard: *loads the byte at the XSCROLL the column before saw* in
|
|
639
|
+
[`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
640
|
+
- A mode change takes hold **inside** the group. ECM and BMM rising show
|
|
641
|
+
at pixel 4 and falling at pixel 6, so a change that clears one bit while
|
|
642
|
+
it sets the other shows the invalid mode's black on pixels 4 and 5. MCM
|
|
643
|
+
changes the colour lookup at pixel 4 but how the shift register is read
|
|
644
|
+
only at pixel 7, where a rising MCM also resets the multicolour
|
|
645
|
+
flip-flop. The mode applies to the pixels as they leave the shift
|
|
646
|
+
register, so the boundaries stay put whatever XSCROLL is.
|
|
647
|
+
- `VIC::GraphicsShifter` runs these groups pixel by pixel: a group where
|
|
648
|
+
the mode or the load point changes, and the group after it. Every other
|
|
649
|
+
group paints whole bytes, which comes to the same pixels.
|
|
650
|
+
- Pinned by `modesplit` (48 → 1222 px painting whole groups, 428 with
|
|
651
|
+
MCM read at pixel 4), the `videomode` rows and `vicii_reg_timing`
|
|
652
|
+
(pass → 274).
|
|
653
|
+
- `videomode2` and `videomode-y` disagree on where a falling BMM shows:
|
|
654
|
+
pixel 6 in `videomode2`, pixel 5 in `videomode-y` and in `modesplit`'s
|
|
655
|
+
ECM+BMM → ECM split. The readme says these delays vary with the chip
|
|
656
|
+
and its temperature. badline keeps VICE's pixel 6, which passes
|
|
657
|
+
`videomode2` and leaves `videomode-y` 1 px off and all 48 px left in
|
|
658
|
+
`modesplit`, one pixel on each of its first-section lines.
|
|
659
|
+
- An MCM that falls out of the invalid ECM+MCM text mode is a pixel
|
|
660
|
+
later on both counts. The lookup stays black through pixel 4 and
|
|
661
|
+
changes at pixel 5, and the pairs are read through pixel 7, with
|
|
662
|
+
hi-res reads starting at the next group's pixel 0.
|
|
663
|
+
- Pinned by `videomode1` and `videomode-z` (2 px each → pass), whose
|
|
664
|
+
illegal → ECM text split drops MCM. With the fall at pixels 4 and 7,
|
|
665
|
+
the reference's black pixel 4 and last-pair foreground on pixel 7
|
|
666
|
+
are both lost. Applying the late timing to every falling MCM breaks
|
|
667
|
+
`videomode2`, `vicii_reg_timing`, `modesplit` and `videomode-v`,
|
|
668
|
+
`-w`, `-x` and `-y`.
|
|
669
|
+
- Spec guard: *takes an MCM falling out of ECM+MCM a pixel late* in
|
|
670
|
+
[`vic/graphics_shifter_spec.rb`](../spec/badline/vic/graphics_shifter_spec.rb).
|
|
671
|
+
- Spec guard: [`vic/graphics_shifter_spec.rb`](../spec/badline/vic/graphics_shifter_spec.rb),
|
|
672
|
+
one example per pixel, and *a mode change inside a group* in
|
|
673
|
+
[`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
|
|
674
|
+
- The pixels XSCROLL keeps from the previous byte take the **current**
|
|
675
|
+
colour registers: a `$d021`–`$d024` write repaints that byte before it
|
|
676
|
+
shows. The `ColorPatches` +1 px still applies on top.
|
|
677
|
+
- Pinned by `modesplit` (48 → 114 px without it) and
|
|
678
|
+
`vicii_reg_timing` (pass → 212). `colorsplit` (64 px → pass) and
|
|
679
|
+
`spritefetchbug/test-136-2a` (8 px → pass) go back only when both this
|
|
680
|
+
and the XSCROLL latch are knocked out.
|
|
681
|
+
- Spec guard: *a background write under XSCROLL* in
|
|
682
|
+
[`vic/sequencer_spec.rb`](../spec/badline/vic/sequencer_spec.rb).
|
|
683
|
+
|
|
684
|
+
## VIC phi1 bus
|
|
685
|
+
|
|
686
|
+
- A CPU read of open I/O ($DE00–$DFFF) returns the byte the VIC fetched in
|
|
687
|
+
the phi1 half of the same cycle, and a colour RAM read takes its upper
|
|
688
|
+
nibble from it. `VIC#phi1_data` works the byte out when it is asked for,
|
|
689
|
+
from the VIC's state at that moment. The CPU cycle after column `c` is
|
|
690
|
+
Bauer cycle `c + 2` (the display-state frame in
|
|
691
|
+
[VIC bad line and DMA](#vic-bad-line-and-dma)), which is `@column + 1`
|
|
692
|
+
once the VIC has advanced. Each Bauer cycle has a fixed access, as in
|
|
693
|
+
VICE `cycle_phi1_fetch`:
|
|
694
|
+
- 1–10 and 58–63: two cycles per sprite from sprite 0 at 58, the
|
|
695
|
+
p-access at `screen_base + $3f8 + n` and then the middle s-access,
|
|
696
|
+
`pointer * 64 + MC + 1` with DMA on and `$3fff` with it off.
|
|
697
|
+
- 11–15: refresh at `$3f00 | REF`. REF is `$ff` at line 0 and steps
|
|
698
|
+
down once per refresh access, five per line.
|
|
699
|
+
- 16–55: the g-access of the column just run, read at column time, not
|
|
700
|
+
when the sequencer draws it two columns later. In display state that
|
|
701
|
+
is the address the sequencer uses, with the VC and VMLI the access
|
|
702
|
+
stepped past (`& $39ff` with ECM). In idle state it is `$3fff`, or
|
|
703
|
+
`$39ff` with ECM, whatever the vertical border does.
|
|
704
|
+
- 56–57: `$3fff`, with or without ECM.
|
|
705
|
+
- Pinned by `phi1timing`, which reads $DEAD once per cycle across a
|
|
706
|
+
line in idle state with ECM set. A one-cycle shift of the frame either
|
|
707
|
+
way fails it on 19 of its 63 columns. The refresh counter, display-state
|
|
708
|
+
g-accesses and the DMA s-access follow VICE: `phi1timing` fills
|
|
709
|
+
$3f00–$3ffe with one value and runs with DEN clear and no sprites.
|
|
710
|
+
- Spec guard: *#phi1_data* in [`vic_spec.rb`](../spec/badline/vic_spec.rb).
|
|
711
|
+
|
|
712
|
+
## VIC light pen
|
|
713
|
+
|
|
714
|
+
- CIA1 PB4 level changes (`CIA#on_port_b4_change`) drive
|
|
715
|
+
`VIC#lightpen_level`. The first falling edge per frame latches LPX/LPY one
|
|
716
|
+
cycle later (plus the 6569's 2 half-pixel offset) and raises the `$D019`
|
|
717
|
+
bit 3 IRQ.
|
|
718
|
+
- Triggers on the last line are consumed without latching (except at cycle
|
|
719
|
+
0). A line held low across frame start retriggers with a fixed LPX of
|
|
720
|
+
`$d1`.
|
|
721
|
+
- Calibrated byte-exact against the `split-tests/lightpen` `dump6569`
|
|
722
|
+
reference, as `makeref` corrects it: the test fixes up the tail bytes of
|
|
723
|
+
pre-R03 dumps before comparing. All five pages match, the raster-read
|
|
724
|
+
pages included (see [VIC raster IRQ phase](#vic-raster-irq-phase)).
|
|
725
|
+
- The 6569's offset is 2 half-pixels and the 8565's is 1, so
|
|
726
|
+
`lp-trigger/test2new`, which wants the 8565, would fail by design.
|
|
727
|
+
badline models only the 6569, and `bin/testbench` skips `vicii-new` rows.
|
|
728
|
+
- Pinned by `lplatency`, `lp-trigger`, and the `fldscroll` tests, which sync
|
|
729
|
+
through the light pen instead of the double IRQ.
|
|
730
|
+
|
|
731
|
+
## CIA 6526 timer pipeline
|
|
732
|
+
|
|
733
|
+
- Start and stop go through two stages. A force load lands one tick after
|
|
734
|
+
it becomes visible, then swallows a pulse and lands after that tick's
|
|
735
|
+
count. The timer underflows on reaching zero, with the reload on the next
|
|
736
|
+
tick. Other rules: a zero counter underflows prematurely; draining to zero
|
|
737
|
+
on stop does not raise the flag; zero latches chain; a one-shot lingers
|
|
738
|
+
when cleared at t-1; PB6/PB7 toggle only on a start transition.
|
|
739
|
+
- Old-CIA IR delay: IR rises 1 cycle after the flag, or 2 cycles after a
|
|
740
|
+
mask write hits a pending flag, and an ICR read cancels the pending
|
|
741
|
+
assert.
|
|
742
|
+
- Old-CIA acknowledge: an ICR read releases the interrupt line at once, but
|
|
743
|
+
its IR acknowledge lands a cycle late. A read on the next cycle still
|
|
744
|
+
sees IR (`$80`) if it was set at the first read, or was due to rise on
|
|
745
|
+
the next cycle. The line stays released either way, and the cycle after
|
|
746
|
+
that IR reads clear. Pinned by `CIA/dd0dtest/dd0dtest` tests 0c, 0d
|
|
747
|
+
and 0e: the dummy read of `inc $dd0d,x` acknowledges, and the real read
|
|
748
|
+
one cycle later sees `$80`, so the RMW writes `$80`/`$81` back and the
|
|
749
|
+
mask survives, where `$00`/`$01` would clear timer A's mask bit.
|
|
750
|
+
- Old-CIA mask cancel: a write that masks every pending source on the
|
|
751
|
+
cycle the flag rises cancels the IR assert only if an ICR read happened
|
|
752
|
+
two cycles earlier. Without that read, IR still rises. Pinned by
|
|
753
|
+
`dd0dtest` test 11 (`inc $dd0d,x` reads at F-2 and writes the clearing
|
|
754
|
+
`$01` at F). This is VICE `ciacore.c`'s `CIA_IRQ_ACK_1` branch (its
|
|
755
|
+
NOTE_1).
|
|
756
|
+
- Timer B bug (6526 only): a timer B underflow on the cycle right after an
|
|
757
|
+
ICR read raises the flag, and IR if armed, but the next ICR read drops
|
|
758
|
+
the TB bit unseen. Timer A has no such bug. Pinned by
|
|
759
|
+
`CIA/ciavarious/cia3` K/L, `cia3a` D/H, `cia4` X, `cia8` A/C/F/J/L
|
|
760
|
+
(the readme's old-versus-new CIA cells) and `CIA/cia-timer/cia-timer-oldcias`,
|
|
761
|
+
and ported from VICE's `CIA_IM_TBB`.
|
|
762
|
+
- The modelled revision is the **6526**, not the 6526A, matching
|
|
763
|
+
`Lorenz.d81`. That is all the `(*1)` cells of `cia1ta`/`cia1tb` measure,
|
|
764
|
+
and it is an ICR difference, not a counter one. Those cells read the ICR
|
|
765
|
+
on the very cycle the underflow flag rises, so the source bit is up while
|
|
766
|
+
IR is not: they read `$01`/`$02`, where a 6526A reads `$81`/`$82`. Counter
|
|
767
|
+
readback is identical on both revisions. `Lorenznew.d81` expects the
|
|
768
|
+
6526A and must not be mixed in.
|
|
769
|
+
- Timer B's cascade decodes CRB, not CRA. Every count source (ø2, a CNT
|
|
770
|
+
edge, a cascaded timer A underflow) drives the same two-stage
|
|
771
|
+
count-enable pipeline, so a timer A underflow decrements timer B two
|
|
772
|
+
cycles later, and switching the source mid-flight leaves up to two
|
|
773
|
+
enables in the pipe. `cia1tab` is what rules out feeding the underflow
|
|
774
|
+
straight in. With both latches at 2, it wants timer B to read `00` for
|
|
775
|
+
exactly two cycles, with the flag, the PB7 toggle and the reload all
|
|
776
|
+
landing on the cycle after: the premature underflow of a zero counter,
|
|
777
|
+
one pipeline stage ahead of the decrement.
|
|
778
|
+
- Pinned by the Lorenz `irq` header, `cia1tb123`, `cia2tb123`, `cia1pb6`,
|
|
779
|
+
`cia1pb7`, `cia2pb6`, `cia2pb7`, `flipos`, `oneshot`, `cntdef`, `loadth`,
|
|
780
|
+
`icr01`, `cia1tab`, `imr`, `cputiming`, `cia1ta`, `cia1tb`, `cia2ta` and
|
|
781
|
+
`cia2tb`.
|
|
782
|
+
- Both timers power on with latch and counter at `$ffff`, as VICE's
|
|
783
|
+
`ciat_reset` does. The KERNAL never writes CIA2's timer latches, so a
|
|
784
|
+
program that writes only the high byte while the timer is stopped loads
|
|
785
|
+
the counter with `$00ff`, not zero. A zero there underflows as soon as the
|
|
786
|
+
timer starts and raises a spurious NMI. Pinned by
|
|
787
|
+
`interrupts/branchquirk/branchquirk-nmiold` (first cell only) and
|
|
788
|
+
`CPU/Acid800/cpu_bugs` (NMI lands before the BRK instead of hijacking it).
|
|
789
|
+
- Spec guard: [`cia/timer_spec.rb`](../spec/badline/cia/timer_spec.rb) runs
|
|
790
|
+
the eight `(*1)` cells and the `cia1tab` table. It fails if the IR delay
|
|
791
|
+
is dropped. Its *power-on state* group guards the `$ffff` reset.
|
|
792
|
+
[`cia_spec.rb`](../spec/badline/cia_spec.rb)'s *6526 interrupt
|
|
793
|
+
acknowledge* and *6526 timer B bug* groups guard the three ICR rules
|
|
794
|
+
above, on a bare CIA.
|
|
795
|
+
|
|
796
|
+
## CIA serial shift register
|
|
797
|
+
|
|
798
|
+
- The register counts itself empty on the 15th timer A underflow, when the
|
|
799
|
+
eighth bit reaches SP, not on the 16th that raises CNT over it. The
|
|
800
|
+
serial ICR flag rises 4 cycles after that 15th underflow. VICE waits for
|
|
801
|
+
the 16th, which is the "4 cycle delay" in `cia-sdr-delay`'s readme.
|
|
802
|
+
- A byte waiting in the data register loads from that same point
|
|
803
|
+
(`@steps <= 1`) and goes out on the next underflow, so CNT stays low
|
|
804
|
+
between the two bytes. Waiting for the 16th costs a whole timer period,
|
|
805
|
+
the 49 cycles `cia-sdr-load` measured.
|
|
806
|
+
- An in-flight level runs through a delay line. It rises 1 cycle after a
|
|
807
|
+
bit lands on SP, drops at 2 and rises again at 3. The eighth bit skips
|
|
808
|
+
straight to 3. The level drops 4 cycles after CNT rises over the bit.
|
|
809
|
+
- A CRA bit 6 change is not symmetric. Switching the port to input tears
|
|
810
|
+
the transmission down. If the register is busy, from 4 cycles after the
|
|
811
|
+
first bit reaches SP until it reports empty over the eighth, the byte is
|
|
812
|
+
flagged gone. The same change latches the in-flight level, and switching
|
|
813
|
+
back to output flags the byte gone if the latch is set. Reading the live
|
|
814
|
+
level at the output change instead leaves it stuck high whenever a byte
|
|
815
|
+
never finishes, and the single-baud `cia?-sdr-icr-*` rows catch that.
|
|
816
|
+
- A zero timer A latch holds the underflow line asserted rather than
|
|
817
|
+
pulsing it. A waiting byte is still picked up on the level, but every
|
|
818
|
+
later half-step needs a fresh edge, so the byte stops after one bit, CNT
|
|
819
|
+
stays low and the flag never rises.
|
|
820
|
+
- Pinned by the `CIA/shiftregister` rows below. Each was checked by
|
|
821
|
+
breaking the rule and rerunning the rows:
|
|
822
|
+
- Empty at the 15th: `cia-sdr-delay`, `cia-sdr-init`, `cia-sdr-load`,
|
|
823
|
+
`cia-sp-test-oneshot-old` and the `-3`/`-19`/`-39` `cia-sdr-icr` rows.
|
|
824
|
+
- Loading from the same point: `cia-sdr-load` alone.
|
|
825
|
+
- The in-flight delay line: the `-3`/`-19`/`-39` `cia-sdr-icr` rows.
|
|
826
|
+
- The latched level: every single-baud `cia-sdr-icr` row, `-0`
|
|
827
|
+
included.
|
|
828
|
+
- The busy report on the switch to input: only
|
|
829
|
+
`cia1-sdr-icr-test2-0_7f` and `cia2-sdr-icr-test2-0_7f`.
|
|
830
|
+
- The zero-latch stall: the `-0` `cia-sdr-icr` rows.
|
|
831
|
+
- Nothing pins whether the delay line keeps moving while timer A is
|
|
832
|
+
stopped. It is clocked every cycle, and freezing it passes every row too.
|
|
833
|
+
- Don't read `cia-sdr-icr/generate.c` as the spec. Its `reset1` sets
|
|
834
|
+
`delaysetsdr1 = baud` for baud > 3, but `cia-sdr-icr-v3.asm`, which is
|
|
835
|
+
what runs, leaves it at 3 for every baud ≥ 3 (`lda #$03 / cpx #$03 /
|
|
836
|
+
bcs +`). That byte is the difference between the first rule and VICE's
|
|
837
|
+
behaviour. `generate-test2.c` states the rule in a comment: "SP INT is
|
|
838
|
+
raised 4 cycles after TA INT".
|
|
839
|
+
- The twelve `-4485` rows other than `-4485-0` are `expect:error`: they
|
|
840
|
+
pass because the model does not match the 4485-batch CIA's reference.
|
|
841
|
+
Baud 0 behaves the same on that batch, so the two `-4485-0` rows must
|
|
842
|
+
pass outright.
|
|
843
|
+
- An offline replay that starts each baud on a fresh CIA cannot see the
|
|
844
|
+
state one baud carries into the next through the mode-change latch. Run
|
|
845
|
+
the test2 sweeps for real (about 5 min each) after changing that path.
|
|
846
|
+
- Spec guard: the "serial port in output mode" block in
|
|
847
|
+
[`cia_spec.rb`](../spec/badline/cia_spec.rb) covers the flag timing, the
|
|
848
|
+
waiting byte, both mode-change reports and the zero-latch stall.
|
|
849
|
+
|
|
850
|
+
## 6510 I/O port
|
|
851
|
+
|
|
852
|
+
- DDR at `$00`/`$01`: inputs are pulled up, bit 5 reads low, and bits 3, 6
|
|
853
|
+
and 7 float.
|
|
854
|
+
- Pinned by Lorenz `mmu` and `cpuport`.
|
|
855
|
+
- DDR and data both power on at `$00`, so `$00` reads `$00` and `$01` reads
|
|
856
|
+
`$17` until the KERNAL sets them.
|
|
857
|
+
- Pinned by `CPU/cpuport/initvalue.crt`, which runs from a cartridge
|
|
858
|
+
before the KERNAL does.
|
|
859
|
+
- Spec guard: *when powered on* in
|
|
860
|
+
[`address_bus_spec.rb`](../spec/badline/address_bus_spec.rb).
|
|
861
|
+
|
|
862
|
+
## SID oscillator
|
|
863
|
+
|
|
864
|
+
- The phase accumulator powers on at `$555555` (all bits high, with the odd
|
|
865
|
+
ones stored inverted) and survives reset. `SID/osc3-wave0` only reads the
|
|
866
|
+
documented `$00`/`$ff` because of it.
|
|
867
|
+
- Pinned by `SID/oscinit` (all three).
|
|
868
|
+
- Spec guard: *leaves the SID's accumulators alone* in
|
|
869
|
+
[`computer_spec.rb`](../spec/badline/computer_spec.rb). The testbench
|
|
870
|
+
only runs `SID/oscinit` from power-on, so only the spec catches a reset
|
|
871
|
+
that rebuilds the voices.
|
|
872
|
+
- Ring modulation substitutes the triangle's MSB with
|
|
873
|
+
`!Saw & ((!V3 & Ring) ^ bit23)`, where `V3` is the modulating voice's MSB.
|
|
874
|
+
That is an XNOR where reSID uses an XOR.
|
|
875
|
+
- Pinned by `SID/ringmod`, which expects OSC3 to read `$ff` with both
|
|
876
|
+
oscillators stopped at zero.
|
|
877
|
+
- The noise LFSR is 23 bits wide. It feeds bit 0 back from bits 22 ^ 17.
|
|
878
|
+
Its eight output taps are bits 20, 18, 14, 11, 9, 5, 2 and 0, driving
|
|
879
|
+
waveform bits 11 down to 4 (Dag Lem's diagram in `SID/noise-reset_new`).
|
|
880
|
+
It powers on at `$7ffffe`, which is what makes `SID/oscinit`'s
|
|
881
|
+
`noiseinit` read `$fe`.
|
|
882
|
+
- Each rise of accumulator bit 19 shifts the LFSR two cycles later, in two
|
|
883
|
+
phases (libresidfp's shift pipeline). The cycle after the rise is phase
|
|
884
|
+
1: the register bits float, so a combined waveform pulls nothing down and
|
|
885
|
+
its output is latched instead. The cycle after that is phase 2: the
|
|
886
|
+
latched output is written over the taps, then the register shifts.
|
|
887
|
+
Phase 2 writes back by the test bit release rule below, with the old and
|
|
888
|
+
new waveform the same: noise combined with anything but pulse alone.
|
|
889
|
+
Setting the test bit drops a shift in flight.
|
|
890
|
+
- Pinned by `SID/noisewriteback`'s `noise_writeback_test2` (both chips).
|
|
891
|
+
It releases the test bit into noise+triangle, which pulls every tap low,
|
|
892
|
+
then sets the frequency to `$ffff`. Bit 19 rises on the 9th cycle and
|
|
893
|
+
the read lands on the 11th, the first output after the shift has filled
|
|
894
|
+
the taps from the bits below: `$14` on the 6581 and `$12` on the 8580,
|
|
895
|
+
whose OSC3 reads the triangle a cycle late. Shifting on the rise itself
|
|
896
|
+
lets the triangle pull the new taps down first, and the read is `$10`.
|
|
897
|
+
The test pins the delay only. Phase 1's float differs from pulling down
|
|
898
|
+
only when pulse+noise is selected across it, and nothing pins that.
|
|
899
|
+
- Spec guard: *two cycles after accumulator bit 19 rises* and *around a
|
|
900
|
+
shift* in [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb),
|
|
901
|
+
and *carries a noise shift pending across a span edge* in
|
|
902
|
+
[`sid_spec.rb`](../spec/badline/sid_spec.rb): a catch-up that
|
|
903
|
+
fast-forwards across a rise carries the shift still in flight into the
|
|
904
|
+
next span.
|
|
905
|
+
- The test bit does not clear the LFSR. It stalls it halfway through a
|
|
906
|
+
shift with bit 22 forced high. While the bit is held, every bit bleeds up
|
|
907
|
+
to `$7fffff`: after `$950000` cycles on the 8580 (`SID/bitfade`'s
|
|
908
|
+
`delaynoise` on a real 8580; VICE's `~8000` there is its own emulation)
|
|
909
|
+
and after `$80000` on the 6581. On release, one bit clocks in.
|
|
910
|
+
- Pinned by `SID/resid-test`'s `oscsample0`/`oscsample1` (both chips).
|
|
911
|
+
They hold the test bit about `$8800` cycles between their eight noise
|
|
912
|
+
runs, and the real chips' dumps read each run on from the register the
|
|
913
|
+
last one left, so the bleed has to take longer. The old `$8000` fails
|
|
914
|
+
all four rows. The 6581 value is not measured anywhere: it only has to
|
|
915
|
+
fall between that hold and the second `SID/noise-reset_old` allows the
|
|
916
|
+
6581 (60 frames, about `$120000` cycles), and `$80000` sits between
|
|
917
|
+
them. The scored tests that wait for the bleed hold the bit far longer
|
|
918
|
+
(`resid-test/noisetest` about `$1680000`, `waveforms-80` and
|
|
919
|
+
`noise_writeback_test1` about `$f60000`), so the 8580's `$950000`
|
|
920
|
+
passes them too. On a real chip the bits rise one at a time, at a rate
|
|
921
|
+
that varies with the chip's temperature (`SID/wf12nsr`'s
|
|
922
|
+
`quicktest.prg` on a 6581), which nothing scored depends on.
|
|
923
|
+
- Spec guard: *bleeding through a held test bit* in
|
|
924
|
+
[`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb). Before it does, the old waveform's output is
|
|
925
|
+
written back only for some waveform changes. Noise has to have been
|
|
926
|
+
combined before the release and still be selected after it. A change to
|
|
927
|
+
noise alone writes nothing back unless all four waveforms were selected
|
|
928
|
+
before. A change to pulse+noise writes nothing back, nor does a change
|
|
929
|
+
from pulse+noise to sawtooth+noise. On the 6581, trading triangle for
|
|
930
|
+
sawtooth or back writes nothing back. The rule follows libresidfp's
|
|
931
|
+
`do_writeback`. What is written is the writeback shape below, not the
|
|
932
|
+
OSC3 read. One more case is not in that rule: on the 6581, a change that
|
|
933
|
+
keeps only pulse+noise of the old waveform (`D`/`E`/`F`→`C`, `D`→`E`)
|
|
934
|
+
writes the three lowest noise lines low (`$f8`).
|
|
935
|
+
- Pinned by `SID/wb_testsuite` (the `9`/`A`/`D`/`E`→`8` rows on both
|
|
936
|
+
chips, and the 6581's `9`↔`A`, `9`/`A`→`C` and `D`→`A` rows) and by
|
|
937
|
+
`SID/noisewriteback`'s `noise_writeback_test1`. The 6581's `$f8` case
|
|
938
|
+
is pinned by its `D`→`C`, `D`→`E`, `E`→`C` and `F`→`C` rows, and
|
|
939
|
+
dropping it fails all four. The 8580's `C`→`A` row pins the
|
|
940
|
+
pulse+noise to sawtooth+noise exception: without it the release writes
|
|
941
|
+
the 8580's `$fc` and the row fails.
|
|
942
|
+
- Known conflict: the 6581's `F`→`8` row expects no writeback, but
|
|
943
|
+
`SID/noiselfsrinit`'s `simple` and `scan` zero the register with
|
|
944
|
+
`$f8`/`$80` pairs, which needs `F`→`8` to write back. The row stays
|
|
945
|
+
failing.
|
|
946
|
+
- Spec guard: *as the test bit falls* in
|
|
947
|
+
[`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
|
|
948
|
+
- A combined waveform shorts the shapers onto the lines the oscillator reads
|
|
949
|
+
back. A low top bit reaches the accumulator MSB through the sawtooth
|
|
950
|
+
switch and clears it (`SID/osc_topbit`, all three). With noise selected,
|
|
951
|
+
the result is written into the LFSR, where a bit pulled low never comes
|
|
952
|
+
back. Together these run Dag Lem's fast LFSR reset exactly as documented:
|
|
953
|
+
three `$b8`/`$b0` pairs zero the register, and 18 `$88`/`$80` pairs set
|
|
954
|
+
bits 0–17.
|
|
955
|
+
- The shape of a combined waveform without noise comes from a fitted model
|
|
956
|
+
(`SID::Waveform::Combined`), not from ANDing the shapers: each line is
|
|
957
|
+
high when its own drive and its neighbours', weighted by distance, clear
|
|
958
|
+
a threshold. `bin/sidwavefit` fits it to `SID/resid-test`'s oscsample
|
|
959
|
+
dumps and `bin/sidwavecheck` scores it. A low pulse grounds every line.
|
|
960
|
+
Noise is ANDed over triangle and sawtooth mixes; pulse+noise is below.
|
|
961
|
+
- Not pinned by an exit code: oscsample only scores the single
|
|
962
|
+
waveforms, and no scored test reads a combined shape without noise.
|
|
963
|
+
- Spec guard: [`sid/waveform/combined_spec.rb`](../spec/badline/sid/waveform/combined_spec.rb)
|
|
964
|
+
checks spot values against the dumps.
|
|
965
|
+
- Noise in a combined waveform writes its lines back into the LFSR by a
|
|
966
|
+
lower threshold than OSC3 reads them by. Two rules follow, both fitted to
|
|
967
|
+
the tests' own reference data (`SID::Waveform::NoiseWriteback`):
|
|
968
|
+
- With triangle or sawtooth, OSC3 reads the plain AND, but a line left
|
|
969
|
+
high between two low ones is written low. `SID/noisewriteback`'s
|
|
970
|
+
`noise_writeback_test2` reads such lone lines (`$14` is output bits 8
|
|
971
|
+
and 6), and `SID/wf12nsr`'s noise+triangle and noise+sawtooth rows need
|
|
972
|
+
them written low: at the end of the `$ffff` period the real register
|
|
973
|
+
holds `$020100`, and a writeback of the plain AND leaves `$024100`.
|
|
974
|
+
- Pulse+noise with the pulse high loses its lowest noise lines, next to
|
|
975
|
+
the four below them that nothing drives. The 8580 reads `$f8` of a full
|
|
976
|
+
register and writes `$fc` back, the only pair among `$f8`, `$fc` and the
|
|
977
|
+
full noise that passes `SID/wf12nsr`'s pulse+noise row. That `$fc` is what
|
|
978
|
+
`SID/wb_testsuite`'s `C`→`9` and `C`→`E` rows need written at release.
|
|
979
|
+
The 6581 reads `$fc` (the readme of `SID/wf12nsr`, VICE bug #1037,
|
|
980
|
+
unscored) and writes nothing back: its `8`/`9`/`A`/`B`→`C` rows run
|
|
981
|
+
pulse+noise and leave the register alone.
|
|
982
|
+
- Pinned by `SID/wf12nsr` (wf9/wfa on both chips, wfc on the 8580) and
|
|
983
|
+
the `SID/wb_testsuite` rows above. Knock-outs: writing back the plain
|
|
984
|
+
AND fails wf9 and wfa on both chips; on the 8580, reading and writing
|
|
985
|
+
`$fc` fails wfc, and reading and writing `$f8`, or the full noise,
|
|
986
|
+
fails wfc and the `C`→`9`/`C`→`E` rows; applying the lone-line rule to
|
|
987
|
+
the read as well makes `noise_writeback_test2` read `$00` on both
|
|
988
|
+
chips.
|
|
989
|
+
- Not fitted: the 6581's pulse+noise row of `SID/wf12nsr`. The program
|
|
990
|
+
never sets voice 3's pulse width, and the 6581 reference reads as if
|
|
991
|
+
the pulse was low when the test bit fell, grounding every line. From
|
|
992
|
+
power-on the width is zero and the pulse high. Forcing a width of
|
|
993
|
+
`$800` before the row makes it pass.
|
|
994
|
+
- Spec guard: *writes a lone line of a noise combination back low*, *with
|
|
995
|
+
pulse+noise on the 8580* and *as the test bit falls* in
|
|
996
|
+
[`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
|
|
997
|
+
- The pulse comparator's output reaches the lines a cycle after the
|
|
998
|
+
accumulator it compared, on both chips. Setting the test bit forces it
|
|
999
|
+
high at once.
|
|
1000
|
+
- Pinned by `SID/waveforms`' `waveforms-40` (both chips) and the pulse
|
|
1001
|
+
rows of `SID/resid-test`'s `oscsample1` dumps, where the width of `$100`
|
|
1002
|
+
first reads high at phase `$101` on the 6581, and at `$100` on the 8580
|
|
1003
|
+
whose OSC3 reads the phase itself a cycle late. Comparing on time fails
|
|
1004
|
+
both `waveforms-40` rows.
|
|
1005
|
+
- Spec guard: *reaches the output a cycle after the accumulator it
|
|
1006
|
+
compared* in [`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb).
|
|
1007
|
+
- Waveform 0 leaves the DAC input floating. It holds the last value a shaper
|
|
1008
|
+
drove onto it and drains to `$000` after `$4000` cycles.
|
|
1009
|
+
- Pinned by `SID/osc3-wave0`. `SID/oscinit`'s `allinit` pins the power-on
|
|
1010
|
+
`$00`, before anything has driven the line.
|
|
1011
|
+
- The 8580 delays the triangle and sawtooth shapers by half a cycle. OSC3
|
|
1012
|
+
latches in the first phase of the clock, so it reads them a whole cycle
|
|
1013
|
+
late, while pulse and noise still reach the lines on time (the pulse with
|
|
1014
|
+
its own cycle of lag, above). The audio output is not delayed. This
|
|
1015
|
+
follows libsidplayfp's `tri_saw_pipeline`.
|
|
1016
|
+
- Pinned by `SID/detect`'s `detect-2-new`. It releases the test bit into
|
|
1017
|
+
a `$ffff` sawtooth and reads OSC3 four cycles later: `3` on the 6581,
|
|
1018
|
+
`2` on the 8580.
|
|
1019
|
+
- Spec guard: *#osc3 on the 8580* in
|
|
1020
|
+
[`sid/waveform_spec.rb`](../spec/badline/sid/waveform_spec.rb) and
|
|
1021
|
+
*the 8580* in [`sid_spec.rb`](../spec/badline/sid_spec.rb).
|
|
1022
|
+
|
|
1023
|
+
## SID register writes
|
|
1024
|
+
|
|
1025
|
+
- The 6581 latches a register write one cycle late. That falls out of the
|
|
1026
|
+
bus order, not from any delay line inside `SID`. `Computer#cycle!` clocks
|
|
1027
|
+
the SID ahead of the CPU, so a write on cycle N first reaches the DSP on
|
|
1028
|
+
cycle N+1, while a read on cycle N sees N cycles of clocking. Moving
|
|
1029
|
+
`sid.cycle!` after `cpu.cycle!` breaks this.
|
|
1030
|
+
- Pinned by `SID/writedelay`, which reads OSC3 four cycles after releasing
|
|
1031
|
+
the test bit and expects the pulse already high.
|
|
1032
|
+
- Spec guard: *SID writes against the clock order* in
|
|
1033
|
+
[`computer_spec.rb`](../spec/badline/computer_spec.rb). It runs an
|
|
1034
|
+
`STA $d400` and checks that the DSP has not clocked the new frequency on
|
|
1035
|
+
the store's own cycle, over both the synthesizing and the idle-replay
|
|
1036
|
+
paths.
|
|
1037
|
+
|
|
1038
|
+
## SID data bus
|
|
1039
|
+
|
|
1040
|
+
- Reading a write-only or unconnected register returns the latch without
|
|
1041
|
+
draining it. reSID halves what is left of the charge on such a read,
|
|
1042
|
+
which would cut the 6581 measurement below to `$52`–`$7d`.
|
|
1043
|
+
- Pinned by `SID/bitfade`'s `delayfrq0`, which polls `$d400` every nine
|
|
1044
|
+
cycles while it waits and still measures the full TTL: `$1d02` here
|
|
1045
|
+
against `~$01d00` on a real 6581, and `$a2005` for the 8580 against
|
|
1046
|
+
VICE's `~$a2000`.
|
|
1047
|
+
|
|
1048
|
+
## SID envelope
|
|
1049
|
+
|
|
1050
|
+
- The envelope follows reSID's rate-counter model: a 15-bit counter compared
|
|
1051
|
+
against a per-nibble period, and a second level-dependent divider (`$ff`→1,
|
|
1052
|
+
`$5d`→2, `$36`→4, `$1a`→8, `$0e`→16, `$06`→30) that the attack phase
|
|
1053
|
+
bypasses and resets. Zero freezes the envelope until the next gate edge.
|
|
1054
|
+
- Lowering the rate period below the running counter sends the counter the
|
|
1055
|
+
long way round through `2^15`. Hard restarts depend on this ADSR delay
|
|
1056
|
+
bug.
|
|
1057
|
+
- Pinned by `SID/envelope` (`testADSRDelayBug`, `testFlip00toFF`,
|
|
1058
|
+
`testFlipFFto00`, `lft-adsr-test`) and `SID/exp_counter_reset`.
|
|
1059
|
+
- Each stage runs a cycle or more behind the one feeding it, after reSID
|
|
1060
|
+
1.0's single-cycle pipeline (VICE's `resid/envelope.h`), not reSID 0.16's
|
|
1061
|
+
step-on-match:
|
|
1062
|
+
- The rate counter compares against `PERIODS` (8, 31, 62, …) *before* it
|
|
1063
|
+
counts. A match holds the counter for a cycle and resets it to 0 on the
|
|
1064
|
+
next, so the period is one cycle longer than the comparison value.
|
|
1065
|
+
- An attack step lands two cycles after that reset. A decay or release
|
|
1066
|
+
step with the divider at 1 also lands two cycles after it, through a
|
|
1067
|
+
one-cycle divider stage. With the divider above 1, it takes one cycle
|
|
1068
|
+
more.
|
|
1069
|
+
- ENV3 reads the counter as it stood at the start of the cycle, one cycle
|
|
1070
|
+
behind the audio path.
|
|
1071
|
+
- A rising gate runs the decay state and decay rate for one cycle and
|
|
1072
|
+
enters attack on the second. A step already set off by a pending reset
|
|
1073
|
+
or divider still lands, as an attack step, two cycles after the edge
|
|
1074
|
+
(four with the divider above 1), and a divider one cycle from landing
|
|
1075
|
+
holds the attack off a cycle more.
|
|
1076
|
+
- A falling gate switches the rate over on its second cycle, or its third
|
|
1077
|
+
with a step in flight. Out of decay it switches a cycle sooner.
|
|
1078
|
+
- Pinned by `SID/env_test`, all seven. The readme says they pass on a
|
|
1079
|
+
real 6581 and 8580. Knock-outs, each failing the listed tests:
|
|
1080
|
+
attack step one cycle after the reset (all seven), divider stage
|
|
1081
|
+
always one cycle (all seven), ENV3 reading the live counter (all
|
|
1082
|
+
seven), a rising gate ignoring the pending step (`ra_0000`, `ra_0100`,
|
|
1083
|
+
`adra_1`, `adra_2`), attack on the first cycle after the edge
|
|
1084
|
+
(`ra_0100`, `adra_1`, `adra_2`), no decay rate on that first cycle
|
|
1085
|
+
(`ra_0100`), and a falling gate always taking two cycles (`ar_1`,
|
|
1086
|
+
`ar_2`). `SID/envelope`, `SID/exp_counter_reset` and the four
|
|
1087
|
+
`resid-test/env*` programs pass either way.
|
|
1088
|
+
- Spec guard: *gate edge*, *attack*, *#env3*, *exponential divider*,
|
|
1089
|
+
*gate falling with a step in flight* and *gate rising as the rate
|
|
1090
|
+
counter matches* in
|
|
1091
|
+
[`sid/envelope_spec.rb`](../spec/badline/sid/envelope_spec.rb). Its
|
|
1092
|
+
*#fast_forward* examples hold the batched catch-up to the same timing:
|
|
1093
|
+
between steps a span jumps from one match to the next, and the cycles
|
|
1094
|
+
around each step run whole.
|
|
1095
|
+
|
|
1096
|
+
## `.sid` tune banking
|
|
1097
|
+
|
|
1098
|
+
- A tune's init and play routines must run with the ROMs banked to match
|
|
1099
|
+
the address they live at, following libsidplayfp's iomap: `$37` below
|
|
1100
|
+
`$a000`, `$36` under BASIC, `$34` in the `$d000` I/O window, `$35` under
|
|
1101
|
+
the KERNAL. Restore `$01` afterwards so the caller's banking survives.
|
|
1102
|
+
This entry is the rule, and it should outlive whichever code carries it.
|
|
1103
|
+
- Derived against six OneLoad64 tunes (Galway, Tel, Gray, Dunn, Cooksey).
|
|
1104
|
+
`SIDFile#bank_for` is the one copy of the map: `Driver` wraps both calls
|
|
1105
|
+
in a `$01` save/bank/restore, and `BarePlayer#dispatch` pokes it before
|
|
1106
|
+
handing the CPU the stub.
|
|
1107
|
+
- Spec guard, at both ends: *a tune living under the BASIC ROM* in
|
|
1108
|
+
[`audio/renderer_spec.rb`](../spec/badline/audio/renderer_spec.rb) (a
|
|
1109
|
+
fixture at `$a000` that reads peak=0 without the `$01` write), and the
|
|
1110
|
+
*#driver for a tune under BASIC* and *#bank_for* groups in
|
|
1111
|
+
[`storage/sid_file_spec.rb`](../spec/badline/storage/sid_file_spec.rb).
|
|
1112
|
+
- The boot stub never returns to its caller: after `cli` it spins on
|
|
1113
|
+
`jmp *` (PSID and RSID). BASIC's READY loop reuses zero page `$19`–`$21`,
|
|
1114
|
+
which a tune owns. Wizball (Ocean Loader 1) keeps its music-enable flag
|
|
1115
|
+
in `$19` and goes silent when BASIC writes `$0a` there.
|
|
1116
|
+
- Spec guard:
|
|
1117
|
+
[`storage/sid_file/driver_spec.rb`](../spec/badline/storage/sid_file/driver_spec.rb).
|