Skip to content
RetrogamingDeep Dive Published Updated 7 min readViews unavailable

Commodore 64 VIC-II Bad Lines: Raster Fetches and CPU Arbitration

Model C64 VIC-II bad-line conditions, video-matrix fetches, BA/AEC arbitration, and raster interrupts without assuming a constant CPU budget.

The Commodore 64’s VIC-II does not simply read a complete framebuffer. It fetches character and color information as the raster advances, and those reads temporarily take bus time away from the 6510 CPU. A raster effect that works on an ordinary line may drift or miss a deadline on a bad line, where the video chip fetches the character matrix for a new text row. Accurate emulation therefore needs a shared bus-and-raster model, not just a frame renderer that samples memory once per scanline.

The phrase “bad line” refers to a specific VIC-II condition, not a damaged raster or a generic slow frame. During such a line, VIC-II performs matrix fetches that are not all hidden in otherwise available bus cycles. BA and AEC signaling coordinate the CPU’s access to the shared memory bus; the CPU is stalled while the video chip performs required fetches. This interaction affects CPU instruction timing and software that uses cycle-counted raster effects, fast loaders, or tight I/O loops.

The bad-line condition

The commonly documented bad-line test combines the vertical display region, display enable, and vertical scroll state. A bad line occurs in the visible text region when the raster line’s low three bits match the vertical scroll value and display enable is active under the chip’s frame-latched rules. In programmer terms, $D011 carries display-enable and vertical-scroll bits, while raster state determines the current line. Exact latch timing matters: sampling the live display-enable bit at an arbitrary later cycle can differ from the condition the chip latched at the beginning of the display window.

On a bad line, VIC-II fetches video-matrix data for the next character row. The screen RAM provides character codes, and color RAM provides associated color values; character or bitmap data fetches then contribute pixels according to the mode. This work requires many bus accesses in a confined part of the raster line. The CPU loses roughly forty cycles of useful progress on a typical bad line, with exact timing and surrounding cycles depending on VIC-II variant and raster model.

PAL and NTSC C64 variants differ in raster timing and cycles per line. An emulator must not hard-code one line length or one display-window range for every model. Use the correct VIC-II variant, timing constants, and register behavior for the target machine, and label unverified clone behavior separately.

BA, AEC, and CPU-visible stalls

BA (Bus Available) warns the CPU that the VIC-II will need bus cycles; AEC (Address Enable Control) indicates when the CPU’s bus access is disabled. These pins explain why software sees a stall rather than merely a lower video frame rate. The 6510’s instruction stream is delayed by bus arbitration, so the number of executed CPU cycles between two raster positions is not constant.

For an emulator, a plausible model can use a cycle scheduler with explicit VIC-II fetch slots and CPU wait behavior. It must preserve ordering: if VIC-II reads a screen byte before the CPU writes a new value, the current line uses the prior byte; if the write precedes the fetch, the new byte can be visible. A line-level renderer that takes a snapshot of all memory at frame start cannot reproduce this.

Fast loaders and timing demos are useful stress tests because they can rely on exact bus availability. A model that only inserts a lumped delay once per bad line may pass simple text output but fail software that observes BA warning cycles or performs stores near the arbitration window. Start with documented functional timing and refine the cycle schedule only when tests reveal a measurable discrepancy.

Raster interrupts are a separate mechanism

The raster interrupt compare is distinct from bad-line bus arbitration. $D012 holds the lower raster compare bits and $D011 contributes a high raster bit; $D01A enables interrupt sources and $D019 exposes/acknowledges pending flags. Software can program a compare and receive an interrupt at a particular raster line, then use a handler to change colors, scroll, or pointers.

An interrupt’s CPU response is still subject to the machine’s current execution and bus timing. A compare can become pending while the CPU is stalled or servicing another interrupt. An emulator should model the flag and interrupt line separately from the bad-line state, then let the CPU core accept it according to its interrupt rules. Clearing a flag must follow the hardware’s write-one-to-clear behavior; a generic read-modify-write on the register may acknowledge sources unintentionally.

Reproduce and inspect raster drift

Use a small test program that writes one color or border value at a fixed cycle position on every line. Compare output on lines with and without matrix fetches, then vary vertical scroll and the display-enable latch timing. A second test can set a raster compare around the same region and log interrupt entry time. Preserve the assembled binary, target VIC-II model, emulator version, and expected raster/cycle position.

When a screen split is off by a few pixels, first identify whether the error is in CPU execution time, interrupt timing, line fetch order, or host video presentation. Add an emulator trace for the relevant register writes and VIC-II fetch cycles. Do not compensate by adding a shader offset or frame-delay tweak; those affect the presentation path, not the bus event that caused the split to drift.

Test character and bitmap modes separately because the VIC-II’s fetch pattern and memory interpretation differ. Test bad lines across the full vertical scroll range and near top/bottom display-window boundaries. Include raster compare values that cross the 8-bit $D012 range so the high bit in $D011 is exercised. Use a known PAL and NTSC configuration where the emulator claims both.

A cycle-level test should place writes just before and after the matrix-fetch window, then verify which value the VIC-II captures. Include stores to screen RAM, color RAM, and scroll/control registers because their access paths and sampling rules differ. Also test an instruction that spans the transition into a bus-steal interval; an instruction-level scheduler that only checks the raster at instruction boundaries may need to represent partial progress or defer bus cycles precisely. Keep the test small enough that a trace can show each CPU bus opportunity and VIC-II fetch without requiring a full game capture.

When comparing output, separate the VIC-II’s internal raster from host presentation. Record the emulated raster and cycle of each register write, then inspect the generated line before applying scaling, filtering, or CRT shaders. A one-pixel host crop can resemble a one-cycle raster error, so first compare unscaled native output and only then add the display pipeline back into the test.

Common modeling mistakes

Common mistakes include using one constant CPU budget per line, treating every visible line as a bad line, checking the current $D011 bit without modeling its latch, and rendering all pixels after the CPU has run the entire frame. Another error is treating the raster compare interrupt as if it caused the bad-line fetch. They are separate hardware behaviors that can occur near each other but should be tested independently.

Do not assume an arbitrary third-party test ROM is authoritative merely because it passes one emulator. Use the VIC-II timing article and other primary hardware material for signal behavior, and compare multiple implementations only as supporting evidence. Where the PAL/NTSC revisions or VIC-II chip revisions behave differently, state the tested variant rather than claiming a universal C64 rule.

Acceptance criteria

A timing implementation should reproduce the documented bad-line condition, matrix reads, CPU stalls, and raster interrupt flags across the claimed machine variants. Acceptance should include pixel output, CPU cycle position, register traces, and a workload that performs writes on both sides of a VIC-II fetch. A game booting to its title screen does not establish correct raster arbitration.

VIC-II bad lines connect display generation directly to CPU execution. The accurate mental model is a video device taking scheduled bus cycles, not a renderer painting a completed frame beside an independent CPU. Once the bus boundary is explicit, raster-effect failures can be measured and corrected with evidence.

Related:

Sources:

Comments