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 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:
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 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.
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)