Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/systems/c64/libraries.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ VIC-II fetched during the stall reflects memory before the stalled instruction's

The VIC-II also takes the bus from the CPU as on hardware: 40 cycles on every bad line (BA low
from cycle 12, video matrix fetches in cycles 15-54) and two cycles per sprite with DMA on, BA
low three cycles ahead. A CPU read that falls inside such a window waits until the window ends;
low three cycles ahead (a sprite enabled, or moved onto the line, after the line's two DMA compares does not start there and takes no bus). A CPU read that falls inside such a window waits until the window ends;
writes do not wait. Bad lines follow YSCROLL and the DEN bit as the VIC-II saw it during raster
line $30: clearing DEN before that line switches the display, and its bad lines, off for the whole
frame, clearing it later has no effect until the next frame, and setting it partway through line
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ public ulong StallCyclesForRead(ulong busCycle, out ulong nextCheckBusCycle)
var line = (int)(frameIndex / (ulong)_cyclesPerLine);
var offset = (int)(frameIndex % (ulong)_cyclesPerLine);

BuildWindows(line);
BuildWindows(line, offset);

for (var i = 0; i < _windowCount; i++)
{
Expand Down Expand Up @@ -90,7 +90,7 @@ public ulong StallCyclesForRead(ulong busCycle, out ulong nextCheckBusCycle)
/// the previous line's sprite 3-7 DMA that runs into this line. Windows beyond the line are
/// kept (a stall may run past the line end); overlapping and adjacent windows are merged.
/// </summary>
private void BuildWindows(int line)
private void BuildWindows(int line, int offset)
{
_windowCount = 0;

Expand All @@ -104,7 +104,12 @@ private void BuildWindows(int line)

// The compare in cycle 55 switches DMA on in the same cycle sprite 0's window begins, so
// a sprite about to start counts here; the read that hits the window is re-evaluated there.
var active = (byte)(_vic2.SpriteDmaMask | _vic2.SpriteDmaStartMask(line));
// Only until the second compare has been made: a sprite enabled after it, or whose Y is
// written to the line after it, does not start on this line and takes no bus.
var secondCompareOffset = SpritePointerOffset(0) - 2;
var active = offset < secondCompareOffset
? (byte)(_vic2.SpriteDmaMask | _vic2.SpriteDmaStartMask(line))
: _vic2.SpriteDmaMask;
for (var n = 0; n < 8; n++)
if ((active & (1 << n)) != 0)
AddWindow(SpritePointerOffset(n));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -256,6 +256,23 @@ public void A_y_position_written_after_the_raster_passed_it_does_not_start_dma()
Assert.Equal(0, c64.Vic2.SpriteDmaMask);
}

[Theory]
[InlineData(55, 3)] // enabled in time for the first compare: sprite 0 starts, a read in cycle 57 waits until 60
[InlineData(57, 0)] // enabled after both compares: no DMA on this line, the bus stays free
public void A_sprite_enabled_after_the_compares_takes_no_bus_on_that_line(int enableCycle, int expectedStall)
{
// VICE's spriteenable4 test programs enable sprites 0-2 in cycle 57 of the line their Y
// names and time the code that follows: it is not held up.
var c64 = Build([0xEA]);
c64.Mem.Write(Vic2Addr.SPRITE_0_Y, 100);
PositionAt(c64, 100, enableCycle);
c64.Mem.Write(Vic2Addr.SPRITE_ENABLE, 0x01);
c64.Vic2.AdvanceRaster((ulong)(57 - enableCycle)); // the next bus cycle is cycle 57

Assert.Equal(2 + (ulong)expectedStall, Step(c64));
Assert.Equal(expectedStall == 0 ? 0 : 1, c64.Vic2.SpriteDmaMask);
}

[Fact]
public void Dma_switches_on_when_the_raster_reaches_the_sprite_y()
{
Expand Down
Loading