Full picture
11

Video Configuration

11.1 Overview

Zeal Video Board presents some registers that can be used to get the current state or configure the video output. These registers include setting the current video mode, scrolling, beam position or even current interrupt state.

This configuration area's base address on the I/O bus is 0x90, it is never mapped out, so it is available at any time.

11.2 Virtual Beam

Rendering

The Zeal Video Board implements the VGA protocol to generate a video signal. This signal can be imagined as a virtual beam that scans the screen from the top-left corner to the bottom-right corner. The rendered resolution is fixed at 640x480 pixels, regardless of the current mode, with a pixel clock frequency of 25 MHz.

Although the visible area is 640x480, the virtual beam actually traverses a larger region. Each horizontal line includes an additional 160-pixel-wide non-visible section known as the horizontal blanking (or H-blank).

Likewise, after rendering the 480 visible lines, the beam continues through 42 extra non-visible lines, referred to as the vertical blanking (or V-blank).

As a result, the virtual beam coordinates range from 0 to 799 horizontally and from 0 to 524 vertically, including both visible and blanking regions.

VGA screen
VGA complete rendering area

With these information, it is possible to calculate how long the beam will stay in these blanks intervals.

The horizontal blanking period consists of 160 pixels, and with a pixel clock of 25 MHz (i.e., each pixel takes 40 ns), the duration is:

\( t = 160 \text{ px} \times 40\,\text{ns} = 6400\,\text{ns} = 6.4\,\mu\text{s} \)

The vertical blanking consists of 42 lines, and each line consists of 800 pixels (640 visible + 160 blank). So:

\( t = 42 \text{ lines} \times 800 \text{ px} \times 40\,\text{ns} = 1344000\text{ns} = 1.344\,\text{ms} \)

In theory, the standard 640x480 VGA resolution requires generating 525 lines with a pixel clock of 25.125MHz, however, the Zeal Video Board has a master clock of 50MHz, and thus uses a pixel clock of 25MHz. To compensate for this difference, only 524 lines are generated.

320x240 mode

The 320×240 mode is purely virtual, it behaves like a 640×480 display that is 2× zoomed into the top-left quadrant. In other words, each pixel in the 320×240 space is rendered as a 2×2 block of pixels on the screen.

The VGA signal remains unchanged and still outputs a 640×480 image. The beam position values are unaffected, only the rendering logic is modified to double each pixel in both horizontal and vertical direction.

Getting the position

The virtual beam position is represented as two 16-bit values, one for the vertical position, and one for the horizontal position. As such, four registers are provided to read these values, V_CURSOR_LOW and V_CURSOR_HIGH for the verical position, H_CURSOR_LOW and H_CURSOR_HIGH for the horizontal position. Check the Register Summary section for the addresses.

Just like other 16-bit values on ZVB, reading the low byte firt is mandatory to latch the upper bytes in the higger byte registers.

Another possibility to quickly check whether is currently in an h-blank or v-blank interval is to read the VIDEO_STATUS registers, which presents two bits specifically for that. Thanks to this register the CPU the ZVB is connected to doesn't need to read the 16-bit values and perform a comparison manually.

11.3 Screen Scrolling

As described in the Tilemap chapter, the ZVB features two tilemap layers: layer0 and layer1. Each layer stores 80×40 tiles in memory. With a fixed tile size of 16×16 pixels, this results in a virtual screen resolution of 1280×640 pixels per layer.

However, as explained above, the actual video output resolution is fixed at 640×480 pixels. This means only a portion of the full tilemap can be visible on screen at any given time.

This is where scrolling comes in. Scrolling allows you to shift the visible portion of the tilemap, in both X and Y, effectively controlling which part is displayed within the 640×480 viewport. In other modes, scrolling may also define the starting position from which rendering begins. Scrolling uses modular arithmetic, so values beyond the tilemap dimensions wrap around automatically, creating a continuous scrolling effect.

Layer0 and layer1 can be scrolled independently, with pixel-level granularity. For example, this can be used to implement depth or parallax effects by scrolling layer0 (background) faster than layer1 (foreground):

Scrolling
Scrolling example in 8-bit 640x480 mode

Again, the 320x240 mode is purely virtual, so the principle here is the same, the only difference being that the screen view is smaller. The scrolling values are not affected, they are still comprised between $[0,1279]$ and $[0,799]$ for X and Y respectively.

In graphics mode, eigth registers are provided to get or set the current scrolling values, in both X and Y:

Here again, the registers are latched when reading the lowest byte, and written values are applied when writing the highest byte.

In text mode, the registers above are ignored and won't affect the output as the scrolling is delegated to the Text Controller's SCROLL_X and SCROLL_Y registers.

Layer1 scrolling values will be ignored in 4-bit graphics mode since this layer is use for layer0 tiles attributes. These scrolling registers will only affect the video output when in tiled graphics mode. They will be ignored by the hardware in any other video mode.

11.4 Disabling Screen Output

In some cases, it is useful to disable screen output to prevent visual artifacts, such as when loading a new tileset or updating a tilemap. These operations may temporarily corrupt the on-screen image, especially if they take longer than a frame to complete.

To handle this, the VIDEO_STATUS register includes a read-write ENABLE bit. When this bit is cleared, the screen output is disabled and black pixels are rendered for the rest of the current frame and next frames. When the bit is set again, normal video output resumes, using the current contents of video memory.

To avoid visible glitches, it is recommended to disable screen output during the vertical blanking period (V-blank) whenever possible.

11.5 Registers Summary

Name Description Base-relative address Access
V_CURSOR_LOW Vertical cursor LSB (read this first to latch the full 11-bit value) 0x00 RO
V_CURSOR_HIGH Vertical cursor MSB (read after V_CURSOR_LOW to get full 11-bit value) 0x01 RO
H_CURSOR_LOW Horizontal cursor LSB (read this first to latch the full 11-bit value) 0x02 RO
H_CURSOR_HIGH Horizontal cursor MSB (read after H_CURSOR_LOW to get full 11-bit value) 0x03 RO
LAYER0_SCROLL_Y_LOW Lower 8 bits of Layer 0 vertical scroll. Must be written before writing the HIGH register to latch a full value. 0x04 WO
LAYER0_SCROLL_Y_HIGH Upper bits of Layer 0 vertical scroll. Writing this completes the atomic update using previously latched LSB. 0x05 WO
LAYER0_SCROLL_X_LOW Lower 8 bits of Layer 0 horizontal scroll. Must be written before writing the HIGH register to latch a full value. 0x06 WO
LAYER0_SCROLL_X_HIGH Upper bits of Layer 0 horizontal scroll. Writing this completes the atomic update using previously latched LSB. 0x07 WO
LAYER1_SCROLL_Y_LOW Lower 8 bits of Layer 1 vertical scroll. Must be written before writing the HIGH register to latch a full value. 0x08 WO
LAYER1_SCROLL_Y_HIGH Upper bits of Layer 1 vertical scroll. Writing this completes the atomic update using previously latched LSB. 0x09 WO
LAYER1_SCROLL_X_LOW Lower 8 bits of Layer 1 horizontal scroll. Must be written before writing the HIGH register to latch a full value. 0x0A WO
LAYER1_SCROLL_X_HIGH Upper bits of Layer 1 horizontal scroll. Writing this completes the atomic update using previously latched LSB. 0x0B WO
VIDEO_MODE Select current video mode 0x0C RW
VIDEO_STATUS Current video system state 0x0D varies
VIDEO_INT_STATUS Interrupt status bits 0x0E RO
VIDEO_INT_CLEAR Clear video interrupt flags 0x0F WO

11.6 Registers

V_CURSOR_LOW (0x00)
Vertical cursor LSB (read this first to latch the full 11-bit value)
V_CURSOR_LSB
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
V_CURSOR_LSB
Lower 8 bits of the vertical cursor position. Reading this register latches the upper bits into an internal shadow register for an atomic vertical cursor read. (RO)
V_CURSOR_HIGH (0x01)
Vertical cursor MSB (read after V_CURSOR_LOW to get full 11-bit value)
(reserved)
V_CURSOR[10:8]
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
V_CURSOR[10:8]
Upper 3 bits of the latched vertical cursor position. Must be preceeded by a `V_CURSOR_LOW` read. (RO)
H_CURSOR_LOW (0x02)
Horizontal cursor LSB (read this first to latch the full 11-bit value)
H_CURSOR_LSB
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
H_CURSOR_LSB
Lower 8 bits of the horizontal cursor position. Reading this register latches the upper bits into an internal shadow register for an atomic horizontal cursor read. (RO)
H_CURSOR_HIGH (0x03)
Horizontal cursor MSB (read after H_CURSOR_LOW to get full 11-bit value)
(reserved)
H_CURSOR[10:8]
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
H_CURSOR[10:8]
Upper 3 bits of the latched horizontal cursor position. Must be preceeded by an `H_CURSOR_LOW` read. (RO)
LAYER0_SCROLL_Y_LOW (0x04)
Lower 8 bits of Layer 0 vertical scroll. Must be written before writing the HIGH register to latch a full value.
SCROLL_Y_LSB
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
SCROLL_Y_LSB
Lower 8 bits of Y scroll value (write latch). Writing this alone has no effect until the HIGH register is written. (WO)
LAYER0_SCROLL_Y_HIGH (0x05)
Upper bits of Layer 0 vertical scroll. Writing this completes the atomic update using previously latched LSB.
(reserved)
SCROLL_Y[9:8]
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
SCROLL_Y[9:8]
Upper 2 bits of Y scroll value. Writing this commits the latched LSB to the actual scroll register. (WO)
LAYER0_SCROLL_X_LOW (0x06)
Lower 8 bits of Layer 0 horizontal scroll. Must be written before writing the HIGH register to latch a full value.
SCROLL_X_LSB
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
SCROLL_X_LSB
Lower 8 bits of X scroll value (write latch). Writing this alone has no effect until the HIGH register is written. (WO)
LAYER0_SCROLL_X_HIGH (0x07)
Upper bits of Layer 0 horizontal scroll. Writing this completes the atomic update using previously latched LSB.
(reserved)
SCROLL_X[10:8]
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
SCROLL_X[10:8]
Upper 3 bits of X scroll value. Writing this commits the latched LSB to the actual scroll register. (WO)
LAYER1_SCROLL_Y_LOW (0x08)
Lower 8 bits of Layer 1 vertical scroll. Must be written before writing the HIGH register to latch a full value.
SCROLL_Y_LSB
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
SCROLL_Y_LSB
Lower 8 bits of Y scroll value (write latch). Writing this alone has no effect until the HIGH register is written. (WO)
LAYER1_SCROLL_Y_HIGH (0x09)
Upper bits of Layer 1 vertical scroll. Writing this completes the atomic update using previously latched LSB.
(reserved)
SCROLL_Y[9:8]
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
SCROLL_Y[9:8]
Upper 2 bits of Y scroll value. Writing this commits the latched LSB to the actual scroll register. (WO)
LAYER1_SCROLL_X_LOW (0x0A)
Lower 8 bits of Layer 1 horizontal scroll. Must be written before writing the HIGH register to latch a full value.
SCROLL_X_LSB
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
SCROLL_X_LSB
Lower 8 bits of X scroll value (write latch). Writing this alone has no effect until the HIGH register is written. (WO)
LAYER1_SCROLL_X_HIGH (0x0B)
Upper bits of Layer 1 horizontal scroll. Writing this completes the atomic update using previously latched LSB.
(reserved)
SCROLL_X[10:8]
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
SCROLL_X[10:8]
Upper 3 bits of X scroll value. Writing this commits the latched LSB to the actual scroll register. (WO)
VIDEO_MODE (0x0C)
Select current video mode
(reserved)
MODE
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
MODE
Select among predefined video modes, check the table above to see all the available options (RW)
VIDEO_STATUS (0x0D)
Current video system state
ENABLE
(reserved)
VBLANK
HBLANK
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
ENABLE
Set to enable video output. Writing 1 to this bit will result in a black screen being rendered. (RW)
VBLANK
Vertical blanking state (1 when active) (RO)
HBLANK
Horizontal blanking state (1 when active) (RO)
VIDEO_INT_STATUS (0x0E)
Interrupt status bits
(reserved)
VBLANK_INT
GP_INT
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
VBLANK_INT
Indicates a pending vertical blank interrupt (RO)
GP_INT
Indicates a pending general-purpose video interrupt (RO)
VIDEO_INT_CLEAR (0x0F)
Clear video interrupt flags
(reserved)
CLR_VBLANK
CLR_GP
7 6 5 4 3 2 1 0
0 0 0 0 0 0 0 0
Reset
CLR_VBLANK
Write 1 to clear vertical blank interrupt (WO)
CLR_GP
Write 1 to clear general-purpose video interrupt (WO)