A downloadable project

The ULAplus™ specification describes an enhanced ULA for the Sinclair ZX Spectrum family of computers. It can be implemented as a plug-in replacement for the ULA such as SLAM+, an external interface such as ZXHD or MB03+, in emulators, or in modern clones such as the ZX-Uno. It is designed for maximum compatibility with existing software. This information supersedes all existing versions of the specification, including those with later version numbers.

The latest official version of the specification is version 1.1d.

Revision History

Version 1.1e

Converted specification to markdown and transferred ownership to ZX Design and Media.

Version 1.1d

Restored missing information about palettes in Timex hi-res mode.

Version 1.1c

Revised to clarify that unused modes and groups are reserved for future use.

Version 1.1b

Revised to clarify the values returned when reading from the ports. Added quick palette set example.

Version 1.1a

Revised for the release of Fuse 1.2, incorporating changes from Mark Smith's ULAplus™ implementation. This version deprecates grayscale mode and adds the Timex screen mode control register to the SZX palette block.

Version 1.1

Revised for the release of ZXDS 1.3, incorporating changes from the OpenCores ULAplus™ implementation. This version rationalizes the specification. Software written for the standard mode of the new specification will run unmodified on earlier implementations.

  • Includes the Timex video modes (optional).
  • Changes the preferred way of multiplexing the 2-bit blue value to obtain a 3-bit blue value.
  • Adds 256 grayscale support (optional).
  • Deprecated HSL and CMYK support.

Version 1.0

First published version of the specification.

I/O ports

ULAplus™ is controlled by two ports. Port BF3B is the register port (write only). The byte output is interpreted as follows:

  • Bits 0-5: Select the register sub-group
  • Bits 6-7: Select the register group.

Two groups are available:

  • 00 - Palette Group. When this group is selected, the sub-group determines the entry in the palette table (0-63).
  • 01 - Mode Group. The sub-group is (optionally) used to mirror the video functionality of Timex port FF and the framebuffer select of port 7FFD as follows:
    • bits 0-2: Screen Mode
      • 000: screen 0 (bank 5)
      • 001: screen 1 (bank 5)
      • 010: hi-color (bank 5)
      • 110: hi-res (bank 5)
      • 100: screen 0 (bank 7)
      • 101: screen 1 (bank 7)
      • 011: hi-color (bank 7)
      • 111: hi-res (bank 7)
    • bits 3-5: Screen Color in Hi-Res Mode (foreground/background)
      • 000: 0 / 7
      • 001: 1 / 6
      • 010: 2 / 5
      • 011: 3 / 4
      • 100: 4 / 3
      • 101: 5 / 2
      • 110: 6 / 1
      • 111: 7 / 0

Port FF3B is the data port (read/write). When the palette group is selected, the byte written will describe the color. When the mode group is selected, the byte output will be interpreted as follows:

bit 0: ULAplus™ palette on (1) or off (0)

Reading from port FF3B returns the last data byte written to the currently selected register. This can be used to read back the current palette or determine if palette mode is active.

Implementations that support the Timex video modes use port FF as the primary means to set the video mode, as per the Timex machines. It is left to the individual implementations to determine if reading the port returns the previous write or the floating bus.

Note: Modes 2 to 255 are reserved for future use. Groups 10 and 11 are reserved for future use.

GRB Palette Entries

For a device using the GRB color space, the palette entry is interpreted as follows:

bits 0-1: Blue Intensity
bits 2-4: Red Intensity
bits 5-7: Green Intensity

This color space uses a sub-set of 9-bit GRB. The missing lowest blue bit is set to OR of the other two blue bits (Bbbecomes 000 for 00, and Bb1 for anything else). This gives access to a fixed half the potential 512 color palette. This reduces the jump in intensity in the lower range in the earlier version of the specification. It also means the standard palette can now be represented by the ULAplus™ palette.

Grayscale Palette Entries (deprecated)

In grayscale mode, each palette entry describes an intensity from zero to 255. This can be achieved by simply removing the colour from the output signal.

Limitations

Although in theory 64 colors can be displayed at once, in practice this is usually not possible except when displaying color bars, because the four CLUTs are mutually exclusive; it is not possible to mix colors from two CLUTs in the same cell. However, with software palette cycling it is possible to display all 256 colors on screen at once.

Emulation

The 64 color mode look-up table is organized as four palettes of 16 colors.

Bits 7 and 6 of each attribute byte, normally used for FLASH and BRIGHT, are used as an index value (0-3) to select one of the four color palettes.

Each color palette has 16 entries: eight for INK, and eight for PAPER. Bits 0-2 (INK) and 3-5 (PAPER) of the attribute byte are used as indexes to retrieve color data from the selected palette.

With the standard Spectrum display, the BORDER color is the same as the PAPER color in the first CLUT. For example BORDER 0 would set the border to the same color as PAPER 0(with the BRIGHT and FLASH bits not set).

The complete index can be calculated as:

  • ink color = (FLASH * 2 + BRIGHT) * 16 + INK
  • paper color = (FLASH * 2 + BRIGHT) * 16 + PAPER + 8

When scaling 3-bits of color data to more bits for emulators that operate in high color mode, simply concatenate the bits repeatedly and then truncate to as many bits as needed. For example, for 8-bits the following conversion should be used:

  • 76543210
  • hmlhmlhm

where h is the high bit, m is the middle bit, and l is the low bit of the original 3-bit value.

With the Timex hi res display, the BORDER color is the same as the PAPER color in the second CLUT. Bits 3-5 of port FF set the INKPAPER, and BORDER values to the following ULAplus™ palette registers:

BITS   INK   PAPER   BORDER
000     24     31     31
001     25     30     30
010     26     29     29
011     27     28     28
100     28     27     27
101     29     26     26
110     30     25     25
111     31     24     24

Extension to the ZX-State (SZX) Format

ZXSTPALETTE

The state of the ULA registers found in the 64 color replacement ULA. This block may be present for any machine.

// Palette Block flags
#define ZXSTPALETTE_DISABLED 0
#define ZXSTPALETTE_ENABLED 1
// Palette Block (contains the palette register values)
typedef struct _tagZXSTPALETTEBLOCK
{  ZXSTBLOCK blk;  BYTE chFlags;  BYTE chCurrentRegister;  BYTE chPaletteRegs[64];  BYTE chFf;
} ZXSTPALETTEBLOCK, *LPZXSTPALETTEBLOCK;

Members

blk

The block header. The block id is ZXSTBID_PALETTE ('P', 'L', 'T', 'T').

chFlags

A flags that indicates if the palette is enabled or if the normal display mode is in use. This can be one of:

  • ZXSTPALETTE_DISABLED / Normal palette mode with BRIGHT and FLASH
  • ZXSTPALETTE_ENABLED / 64 color palette mode

chCurrentRegister

The currently selected palette register (0-63).

chPaletteRegs

The current values of the palette registers.

chFf

The current value of port FF which controls the Timex screen mode, and high resolution colors. Added in v1.1aof the specification.

Extension to the SCR format

A 6912 byte .SCR file contains a standard Spectrum screen.

A 6976 byte .SCR file contains a standard Spectrum screen followed by 64 color registers.

A 12288 byte .SCR file contains a Timex hi-color screen.

A 12352 byte .SCR file contains a Timex hi-color screen followed by 64 color registers.

A 12289 byte .SCR file contains a Timex hi-res screen.

A 12353 byte .SCR file contains a Timex hi-res screen followed by the hi-res color information that was dumped from port 255, followed by 64 color registers.

Palette File Format

The palette format doubles as the BASIC patch loader. This enables you to edit patches produced by other people.

; 64 color palette file format (internal) - version 1.0
; Copyright (c) 2009 ZX Design and Media
;
; The palette file is stored as a BASIC program with embedded machine code
header:    defb 0x00;                           program file    defb 0x14, 0x01, "64colour";         file name    defw 0x0097;                         data length    defw 0x0000;                         autostart line    defw 0x0097;                         program length
; 0 RANDOMIZE USR ((PEEK VAL "2
; 3635"+VAL "256"*PEEK VAL "23636"
; )+VAL "48"): LOAD "": REM
basic:    defb 0x00, 0x00, 0x93, 0x00, 0xf9, 0xc0, 0x28, 0x28    defb 0xbe, 0xb0, 0x22, 0x32, 0x33, 0x36, 0x33, 0x35    defb 0x22, 0x2b, 0xb0, 0x22, 0x32, 0x35, 0x36, 0x22    defb 0x2a, 0xbe, 0xb0, 0x22, 0x32, 0x33, 0x36, 0x33    defb 0x36, 0x22, 0x29, 0x2b, 0xb0, 0x22, 0x34, 0x38    defb 0x22, 0x29, 0x3a, 0xef, 0x22, 0x22, 0x3a, 0xea
start:    di;             disable interrupts    ld hl, 38;      HL = length of code    add hl, bc;     BC = entry point (start) from BASIC    ld bc, 0xbf3b;  register select    ld a, 64;       mode group    out (c), a;    ld a, 1;    ld b, 0xff;     choose register port    out (c), a;     turn palette mode on    xor a;          first register
setreg:    ld b, 0xbf;     choose register port    out (c), a;     select register    ex af, af';     save current register select    ld a, (hl);     get data    ld b, 0xff;     choose data port    out (c), a;     set it    ex af, af';     restore current register    inc hl;         advance pointer    inc a;          increase register    cp 64;          are we nearly there yet?    jr nz, setreg;  repeat until all 64 have been done    ei;             enable interrupts    ret;            return
; this is where the actual data is stored.; The following is an example palette.
registers:    defb 0x00, 0x02, 0x18, 0x1b, 0xc0, 0xc3, 0xd8, 0xdb; INK    defb 0x00, 0x02, 0x18, 0x1b, 0xc0, 0xc3, 0xd8, 0xdb; PAPER    defb 0x00, 0x03, 0x1c, 0x1f, 0xe0, 0xe3, 0xfc, 0xff; +BRIGHT    defb 0x00, 0x03, 0x1c, 0x1f, 0xe0, 0xe3, 0xfc, 0xff;    defb 0xdb, 0xd8, 0xc3, 0xc0, 0x1b, 0x18, 0x02, 0x00; +FLASH    defb 0xdb, 0xd8, 0xc3, 0xc0, 0x1b, 0x18, 0x02, 0x00;    defb 0xff, 0xfc, 0xe3, 0xe0, 0x1f, 0x1c, 0x03, 0x00; +BRIGHT/    defb 0xff, 0xfc, 0xe3, 0xe0, 0x1f, 0x1c, 0x03, 0x00; +FLASH
terminating_byte:    defb 0x0d;

Quick Palette Set

set_palette:    ld c, 0x3b;           ULAplus™ port    ld de, 0x00bf;        d = data, e = register    ld hl, pal_end;       mode group register    ld a, 65;             becomes 64
palette_loop:    dec a;                next register    ld b, e;              register port    out (c), a;           select register    ld b, d;              data port    outd;                 out bc, (hl); dec hl; dec b    and a;                was that the last register?    jr nz, palette_loop;  set all 64 entries    ret;                  done
palette:    incbin "palette.bin"; 64 bytes of G3R3B2 palette register values
pal_end:    defb 1;               write 1 to register 64 to enable ULAplus mode

Standard Spectrum Palette for ULAplus™

;                %G..R..B.
;standard
    black        equ    %00000000
    blue        equ    %00000010
    red            equ    %00010100
    magenta        equ    %00010110
    green        equ    %10100000
    cyan        equ    %10100010
    yellow        equ    %10110100
    white        equ    %10110110
;bright
    b_black        equ    %00000000
    b_blue        equ    %00000011
    b_red        equ    %00011100
    b_magenta    equ    %00011111
    b_green        equ    %11100000
    b_cyan        equ    %11100011
    b_yellow    equ    %11111100
    b_white        equ    %11111111
;flash
    f_black        equ    %10110110
    f_blue        equ    %10110100
    f_red        equ    %10100010
    f_magenta    equ    %10100000
    f_green        equ    %00010110
    f_cyan        equ    %00010100
    f_yellow    equ    %00000010
    f_white        equ    %00000000
;flash_bright
    fb_black    equ    %11111111
    fb_blue        equ    %11111100
    fb_red        equ    %11100011
    fb_magenta    equ    %11100000
    fb_green    equ    %00011111
    fb_cyan        equ    %00011100
    fb_yellow    equ    %00000011
    fb_white    equ    %00000000
palette:
    defb black, blue, red, magenta
    defb green, cyan, yellow, white
    defb black, blue, red, magenta
    defb green, cyan, yellow, white
    defb b_black, b_blue, b_red, b_magenta
    defb b_green, b_cyan, b_yellow, b_white
    defb b_black, b_blue, b_red, b_magenta
    defb b_green, b_cyan, b_yellow, b_white
    defb f_black, f_blue, f_red, f_magenta
    defb f_green, f_cyan, f_yellow, f_white
    defb f_black, f_blue, f_red, f_magenta
    defb f_green, f_cyan, f_yellow, f_white
    defb fb_black, fb_blue, fb_red, fb_magenta
    defb fb_green, fb_cyan, fb_yellow, fb_white
    defb fb_black, fb_blue, fb_red, fb_magenta
    defb fb_green, fb_cyan, fb_yellow, fb_white

Legal

ULAplus™ is a trademark of ZX Design and Media. ULAplus™ is a royalty-free open format. The official ULAplus™ specification is released under the Creative Commons Attribution-Share Alike License.

Download

Download
ulaplus.md 13 kB
Download
ULAplus alternate logo 12 kB

Comments

Log in with itch.io to leave a comment.

Hello. Nice to see the ULAplus specs here.
There's an issue with the  formatting of the listings which makes reading them quite hard. Would it be possible to correct this please?

The md version formatting is correct.

Indeed, thanks.
Is there any was of cleaning up the online version above?

Not without custom CSS, and that requires itch.io to enable CSS support on the account.

With the code listings on this page, it seems like there's something wrong with the CR/Line feeds.
 
I just did a quick test on a test page here on Itch with code from the ULAplus spec and it shows up normal when creating a code-block.

The only thing that Itch does is remove "empty" lines (ie before the "header:", "basic:" labels, etc.) but that can be easily fixed by putting only a space on those lines.


Nice. I couldn't find a reference to a code style in the documentation, but that looks good.