Computing History · Microprocessor Revolution

Cycle Counting When the Video Circuit Steals the Bus

How display refresh suspends the processor on shared-memory machines, why the stolen cycles are unevenly distributed, and how timing loops are written so the loss is measurable.

A processor that shares memory with a display does not run at its rated clock. The rated clock describes the rate at which the processor completes cycles when it owns the bus; the display owns the bus for part of every scan line, and during those intervals the processor waits. The waiting is not a slowdown factor that can be applied uniformly. It depends on which instruction is executing, because a memory access that falls inside the stolen interval costs cycles that a register operation in the same slot does not.

Why the display has priority

A cathode ray tube draws its image by sweeping an electron beam across the screen. The beam’s position is determined by analogue deflection circuitry and is not under software control. When the beam reaches a position that needs pixel data, the data must be available at that instant. There is no queue and no retry. If the memory does not deliver the byte, the pixel is whatever the video shift register held, which is a visible artefact rather than a delayed one.

The processor has the opposite property: it can be delayed without losing correctness, because its state is held in registers and it resumes where it stopped. Arbitration logic therefore gives the video circuitry unconditional priority and holds the processor off the bus while the video fetches. The mechanism differs by machine. Some processors have a dedicated input that tri-states their address and data pins for a cycle; others are halted by gating the clock, so the cycle in progress simply takes longer.

Because the video’s needs recur at the line rate, the stolen cycles recur at the line rate too. On a display with 312 or 313 lines at roughly 50 Hz, or 262 or 263 lines at roughly 60 Hz, a line takes on the order of 64 microseconds. Within each line the video needs a fixed number of memory fetches, and those fetches are placed at fixed positions in the line.

Where the stolen cycles fall

The distribution is not spread evenly across the line. The video needs bytes only while the beam is drawing visible pixels. During the horizontal blanking interval, when the beam is returning to the left of the next line, the video needs no display data, and the processor has the bus to itself. The same is true during vertical blanking, when the beam returns to the top of the frame. Vertical blanking lasts several lines, which is why routines that must run without interference were written to execute during it.

A machine that maps a character grid rather than a pixel grid has a different pattern. The video fetches a character code and a colour byte for each cell in the line, then fetches the glyph rows from the character generator. The fetches are grouped at the start of the cell’s time slot, so the processor is blocked for a short burst and free for the remainder. A machine with a bitmap display fetches bytes continuously across the visible part of the line, so the blocking is nearly total for the duration of the drawing.

The result is that the processor’s available bandwidth varies by a factor across the line. Near the left and right edges of the visible area the contention may be minimal; across the middle it may be complete. Software that measures elapsed time by counting loop iterations therefore measures an average that depends on where in the frame the loop starts and how long it runs.

Writing a loop whose cost is predictable

The technique for dealing with this is to make the loop’s timing dominated by operations that do not touch memory. A processor with registers and immediate-mode instructions can execute a sequence of decrements and branches entirely from its own internal state, and such a sequence is delayed only if the processor is halted for a whole cycle. Even then, the halt adds cycles rather than changing the instruction sequence.

The complication is that the branch itself is a memory access: the processor fetches the next opcode. On machines where the processor is stopped by clock gating, the fetch is stretched, and the loop runs slower in the contended region than in the blanking interval. On machines where the processor is halted by having its bus taken away, the same applies.

; A delay loop whose body is chosen to be short and memory-light.
; The exact cycle count is documented per instruction; the loop's
; measured duration still varies with bus contention.
        LDX #$00        ; 2 cycles, immediate
delay:  DEX             ; 2 cycles, register only
        BNE delay       ; 3 cycles taken, 2 not taken; opcode fetch each time
        RTS

The loop above runs 256 iterations. Each iteration costs a decrement plus a branch, and the branch’s cost differs between the taken and the final not-taken case. The total is arithmetic on documented per-instruction cycle counts, and it is exact only if no cycles are stolen. Under contention the total grows by the number of cycles during which the processor was held, which the program cannot know in advance without measuring the display’s state.

The usual solution is to synchronise with the display rather than fight it. A program that waits for a specific point in the frame, then runs a loop, starts from a known position in the contention pattern, and the number of stolen cycles in the following interval is then determined by the display’s geometry rather than by chance. The synchronisation itself is done by polling a status register bit that the video circuitry sets at a known point in the frame, or by taking an interrupt that the video circuitry raises.

The raster interrupt as a scheduling primitive

Machines that generate an interrupt at a programmable scan line turn the display’s timing into a scheduling resource. The program sets a register to a line number, and the video circuitry raises an interrupt when the beam reaches it. The handler runs at a known position in the frame, which means the contention it experiences is known, and the code that must complete before the beam returns to the top of the screen has a deadline measured in lines.

This is how split-screen effects were built: the handler changes the display’s base address or a colour register partway down the frame, and the change takes effect on the next line drawn. The technique depends entirely on the handler running before the beam reaches the lines whose appearance is being changed, so the handler’s own duration is a hard constraint. A handler that takes longer than the interval between the interrupt line and the target line produces a visible tear. The handler must also be installed without losing whatever handler it displaces, which is the general problem described in resident programs and reentrancy.

The same mechanism gives a way to measure the stolen cycles. A handler that increments a counter and returns, started at a known line, will have executed a number of times that depends on the frame rate rather than on the contention. A loop inside the handler whose duration is compared against the beam position gives a direct measurement of how much time the processor actually had.

Contention in the other direction

The bus is shared in both directions, and the processor can also interfere with the video. A write to display memory during the visible part of a line can collide with a video fetch. The arbitration logic resolves the collision by delaying one of them, and the processor is the one delayed. From the video’s side, the fetch is delayed by at most a cycle and the pixel appears one pixel-clock late, which is not visible.

The interference is more serious when the processor writes to a register that the video circuitry reads on every line, such as a scroll offset or a colour register. A write that lands partway through a line changes the value for the remainder of that line, producing a horizontal discontinuity. Programs that change such registers mid-frame do so deliberately, for effects, and must account for the exact cycle at which the write lands.

Memory refresh is a third consumer. Dynamic memory needs periodic row accesses, and on machines where the video circuitry performs the refresh as part of its line fetches, the refresh is free. On machines where a separate controller does it, the controller steals cycles on its own schedule, which may or may not align with the video’s. A program timing a loop across a refresh burst sees a delay that has nothing to do with the display. Processors that drive dynamic memory directly can fold refresh into a bus cycle they were not using, which is the arrangement described in the design of a multiplexed bus.

The general principle is that on a shared-bus machine, the processor’s clock rate is an upper bound rather than a description. Any code whose correctness depends on elapsed time must either measure the contention or avoid the bus, and the only operations that avoid the bus entirely are those that work on registers the processor already holds.

References

  1. Commodore 64 Programmer's Reference Guide — Commodore Business Machines, 1982
  2. MOS 6567/6569 Video Interface Chip data sheet — MOS Technology
  3. MCS6500 Family Hardware Manual — MOS Technology, 1976