eZX

eZX is the FPGA at the heart of the eZX Spectrum — the same relationship FireStorm has to the Ant64. Inside it: the FireStorm Execution Engine (EE) — the identical RV64GC CPU the Ant64 runs — and the eZX primary chipset described in this document, a clean-slate Spectrum-lineage display, audio, and I/O design. "eZX" names the whole FPGA; the CPU inside it is properly called the EE, and doesn't have a separate identity of its own. See cpu for the eZX-board-specific CPU integration and spec for the hardware platform.

Applies to the eZX primary FPGA image. For the Next-compatible image's chipset, see the upstream Next documentation.


Overview

The eZX primary chipset is a clean-slate design — Spectrum-lineage in feel, but not Spectrum-compatible at the hardware register level. The brief: what a Spectrum-lineage machine could reasonably have been a few years after the last commercial Spectrum, given modern implementation freedom and the GW5AST-LV138 FPGA (138K LUT4, the same part the Ant64 uses for FireStorm). NextNano fits a complete ZX Spectrum Next in roughly 20k LUT4, so the eZX has ample room for the eZX CPU, the chipset described here, and substantial headroom besides.

It is organised around the standard subsystems of an early-90s home computer or console — multi-layer display with hardware sprites, palette and copper-style raster effects, a polyphonic audio engine with both waveform synthesis and sample playback, a 2D blitter, DMA, timers, and a memory-mapped register space accessed directly by the eZX CPU.

The Spectrum feel comes through in the defaults rather than the architecture: a native 256×192 display mode, a 16-colour Spectrum-inspired default palette, an attribute-style tilemap layer that honours the 8×8 cell colour grid, and a border colour register that still does what it has always done.


Memory map

The eZX primary image runs the same FireStorm EE the Ant64 uses — see cpu and EE for the CPU architecture and its generic memory-tier model (narrow HyperRAM, wide DDR3/36-bit SRAM). What follows is an illustrative sketch of the physical address layout on the eZX board specifically, not a bit-exact final map — see spec § Memory for the authoritative per-tier capacity and access-strategy detail.

Range Purpose
$00000000$00FFFFFF HyperRAM (16 MB) — narrow tier, always present. Primary system RAM in the base configuration; ROM tier (OS images, cartridge ROMs, system ROMs) in every configuration
$01000000$0FFFFFFF Reserved
$10000000–… DDR3 SODIMM (when fitted) — wide tier. 1 / 2 / 4 GB user-addressable data supported; 8 GB of address space reserved for the region (theoretical single-rank ceiling at JEDEC's chip-density maximum) — see spec § DDR3 SODIMM details
$E0000000$EFFFFFFF DeMon shared region — OSD, file transfer, debug, AntOS services
$F0000000$F00FFFFF Chipset register space (see below)
$F0100000$F0FFFFFF Reserved for future chipset expansion
$F1000000$F1FFFFFF Sample memory window — 16MB window into DDR3 visible to audio engine
$FFFF0000$FFFFFFFF Boot ROM and reset vector — the FPGA's own flash chip, mapped into the EE's address space (not DeMon-provided; see cpu § Reset and boot on the eZX board)

BSRAM does not appear in this map — it's FPGA-internal storage (prefetch buffers, sprite tables, line buffers, chipset state) and is not a CPU code/data placement target; see spec § On-chip BSRAM.

The chipset register space is laid out by subsystem:

Range Subsystem
$F0000000$F0000FFF System and configuration
$F0001000$F0001FFF Interrupt controller
$F0002000$F0002FFF Timers
$F0003000$F0003FFF Performance counters
$F0010000$F001FFFF Display — layers, palette, scroll, border, modes
$F0020000$F002FFFF Sprite engine — per-sprite attribute registers (AGA-style; bitmap data lives in DDR3)
$F0030000$F003FFFF Copper — display list memory
$F0040000$F004FFFF Audio engine — voice registers and mixer
$F0050000$F005FFFF Blitter
$F0060000$F006FFFF DMA controller
$F0070000$F007FFFF Sticky and Pulse SPI interfaces
$F0080000$F008FFFF NFC interface
$F0090000$F009FFFF DeMon mailbox

All chipset registers are 32-bit. Sub-32-bit fields are accessed via standard lb/lh/lbu/lhu reads and read-modify-write writes; the chipset itself does not implement byte-strobing for narrower writes.


Display

Output

A single display engine produces a fixed HDMI timing — 1280×720 @ 60Hz native — driven to both the main HDMI output and the retro VGA output. The retro path receives a downscaled or progressive 4:3 signal suitable for CRT-style monitors and analogue capture.

Framebuffer logical resolutions are independent of output timing. The display engine upscales the logical resolution to the output with integer pixel doubling where possible and nearest-neighbour scaling otherwise.

Logical mode Use
256 × 192 Classic Spectrum mode (5× horizontal, 3.75× vertical on 1280×720)
320 × 240 4:3 retro
512 × 288 16:9 retro, doubled to 1024×576 (with letterbox)
640 × 360 16:9 modern retro
1280 × 720 Native HD

Switching modes is a single register write — DISP_MODE at $F0010000. The display engine reconfigures on the next vertical blank.

Layers

Up to 8 hardware layers participate in the compositor (register space accommodates 16; 8 used, 8 reserved). Layer composition order is fully runtime-programmable via the DISP_PRIORITY register — a 32-bit value laid out as 8 nibbles, one priority field per layer slot:

 31         28 27         24 23         20 19         16 15         12 11          8 7           4 3           0
+------------+------------+------------+------------+------------+------------+------------+------------+
|     L7     |     L6     |     L5     |     L4     |     L3     |     L2     |     L1     |     L0     |
+------------+------------+------------+------------+------------+------------+------------+------------+

Each nibble:

  • 0 — the layer is off, not composited at all (no bandwidth, no gate-budget cost, no contribution to the output)
  • 1–15 — the layer is on, with the value as its composition priority. Higher value = nearer the viewer (drawn on top of lower-priority layers)

A future chipset revision can grow DISP_PRIORITY to 64 bits (16 nibbles, 16 layers) by adding DISP_PRIORITY_HI at the adjacent register slot — the register block is sized accordingly. Software that only uses the v1 layers can ignore DISP_PRIORITY_HI.

Layer slot assignment

Slot Layer Owner
L0 Border — solid colour, the area outside the framebuffer rectangle eZX
L1 Background tilemap — 8×8 cell tilemap with attribute-style or pixel-style colour eZX
L2 Bitmap — direct pixel layer, palette-indexed or true-colour eZX
L3 Sprites — Amiga AGA-style hardware sprite engine output (see Sprites) eZX
L4 Foreground tilemap — second 8×8 cell tilemap, useful for HUDs and overlays eZX
L5 Reserved eZX
L6 Pulse composer layer — display-list-driven layer fed by Pulse via MIPI RX #0 (the only supervisor still using a MIPI RX hardcell) Platform (Pulse)
L7 DeMon composer layer — display-list-driven layer fed by DeMon via its HDMI link, not MIPI (primary AntOS UI surface) Platform (DeMon)

The nibbles for L6 (Pulse) and L7 (DeMon) are write-protected from the eZX — only the platform itself (Pulse for L6, AntOS via DeMon for L7) can set them. This preserves the platform guarantee: eZX user software cannot disable or reorder the supervisor overlays.

Default priority on reset

On reset the chipset initialises DISP_PRIORITY to a classic back-to-front game ordering, equivalent to what earlier eZX drafts pinned down as fixed:

Layer Default priority
L0 Border 1
L1 Background tilemap 2
L2 Bitmap 3
L3 Sprites 4
L4 Foreground tilemap 5
L5 Reserved 0 (off)
L6 Pulse 6
L7 DeMon 7

Software that doesn't touch DISP_PRIORITY gets this ordering, which matches the Spectrum-lineage intuition (border behind everything, sprites between background and foreground, supervisor UI on top).

Runtime priority changes

Software can:

  • Disable a layer by writing 0 to its nibble — the bandwidth and gate-budget cost of compositing that layer is saved entirely
  • Reorder layers — for example, push sprites behind the foreground tilemap by setting L3's priority below L4's, or move the bitmap layer above the sprites for an overlay-style HUD
  • Change priority mid-frame via the copperDISP_PRIORITY is just another chipset register, so any priority value can be sequenced per-scanline for split-screen effects (game viewport with one layer order, status bar at the top with another)

By default DISP_PRIORITY writes commit at the next vertical blank, avoiding mid-frame tearing. A DISP_PRIORITY_IMMEDIATE flag in the display control register commits writes on the next scanline boundary instead, which is what the copper uses for raster-level reordering.

Compositor rules

The compositor traverses the 8 slots each scanline, takes the enabled ones (nibble ≠ 0), sorts them by priority value (lowest first, highest last), and emits the topmost non-transparent pixel at each output position. Ties between equal priority values are broken by slot index (lower slot first / behind).

Each enabled layer can be independently scrolled (X and Y with fractional sub-pixel components — see Scroll) and palette-shifted (PAL_BANK per layer). The bitmap layer supports two formats — 8 bits per pixel palette-indexed, or 16 bits per pixel direct colour (5R 6G 5B).

The compositor honours per-layer transparency: index 0 in palette modes and a configurable transparent colour in direct-colour modes.

System overlays — Pulse and DeMon composer layers

L6 (Pulse) and L7 (DeMon) are composer-driven display layers. Neither supervisor renders pixels itself or ever has a framebuffer for the eZX to read; instead each emits per-frame display list descriptions — text cell grids, tilemap index maps, and sprite display lists — to the chipset. The physical path differs per supervisor: Pulse's descriptions ride its MIPI uplink; DeMon's ride its HDMI link (DeMon moved from an ESP32-P4 to a Raspberry Pi Compute Module, and its connectivity moved with it — see the data path summary below, and spec § Supervisor ↔ FPGA interconnect for the full physical detail). Either way the chipset's composer-receiving block turns the descriptions into render commands and draws the pixels at scanout using the same sprite, tilemap, and text engines that the native L1–L5 layers use. This is the platform's composer architecture; see ant64.com/composer for the full specification, which the eZX primary chipset implements directly.

What this means concretely:

  • The supervisors own the scene; the chipset owns the pixels. Each supervisor maintains its scene state — sprite positions, animation phases, tilemap state, text cells — in its own memory (Pulse's 32 MB PSRAM; DeMon's CM4/CM5 RAM, 1–8 GB depending on the module fitted). The eZX FPGA never sees that state, only the per-frame description of what's currently in the visible window
  • Visible-window culling at the supervisor. A supervisor might be tracking thousands of sprites or a tilemap far larger than the screen; only the sprites whose bounds intersect the view and the tile rows/columns the view currently covers are sent. FireStorm-style: per-frame cost is bounded by what's visible, not by scene size
  • One sprite engine, two content sources. The chipset has a single hardware sprite engine (see Sprites). The native L3 sprite layer sources its descriptors from eZX-CPU writes to sprite attribute registers; the L6 (Pulse) and L7 (DeMon) layers source their descriptors from MIPI-borne display lists. Same engine, same texture pool, same compositing pipeline — only the source of the descriptors differs. There is no separate "supervisor sprite path"; composer-driven sprites are first-class chipset sprites
  • Shared 4bpp 16×16 texture format. All sprites and tiles — native L1/L3/L4 and composer-driven L6/L7 — observe the platform texture contract: 4bpp packed, 16×16 cells (2 px/byte, 128 bytes per cell), 16 palette banks × 16 entries each, index 0 = colour key. The texel nibble plus the bank index yields a global palette index (b << 4) | v. Assets authored for the Ant64 platform work unchanged on the eZX primary
  • In-view working set in BSRAM. Because the visible window is small, the in-view subset of textures referenced by composer-driven descriptors fits comfortably in the chipset's on-chip BSRAM texture cache for single-cycle scanout fetch. The full asset store stays in DDR3; only the in-view art is promoted each frame. See spec § Composer integration for the BSRAM accounting
  • The chipset is the genlock master. The chipset broadcasts a frame-sync signal that both supervisors pace their description streams to. The link is frame-locked end to end; descriptions arrive deterministically before their target scanout. The chipset's system vsync drives this directly

DeMon composer layer (L7). AntOS owns L7 by default; AntOS UI rides this layer as text cell grids primarily (command line, status indicators, hotkey hints, debug HUD), with optional tilemap descriptions (icon grids, file-browser tile views) and optional sprite display lists (animated cursor, notification badges, recovery overlays). All three description types are accepted on the eZX primary image's full composer-receiving interface. L7 starts at the highest active priority on reset, and the L7 nibble is write-protected from the eZX — only AntOS via DeMon can change L7's priority. This keeps the overlay reliably visible so system messages and recovery prompts remain on screen regardless of what user software is doing.

Pulse composer layer (L6). Pulse owns L6 — typical Pulse content includes a step-sequencer tilemap, mixer-channel-strip tilemaps, VU meter sprite display lists, and waveform thumbnails as sprites. All composer description types accepted; the actual mix of text / tilemap / sprite is up to Pulse firmware per scene.

Pulse's MIPI feed carries more than composer descriptions; DeMon no longer has a MIPI feed to the chipset at all. DeMon's connectivity moved to HDMI + PCIe when it moved from the ESP32-P4 to a Raspberry Pi Compute Module — its MIPI output now drives its own local touchscreen, not the FPGA. Pulse's link is unchanged from the Ant64 pattern: a shared multi-tenant conduit, not a display-only feed. Composer descriptions are the highest-priority traffic class; Pulse's feed also carries secondary traffic at lower priority — controller and peripheral state from Pulse's connected hardware (joystick positions from Sticky, MIDI controller inputs, button presses, and any other peripheral state Pulse aggregates). Composer descriptions take priority; controller updates ride the remaining bandwidth. (Keyboard events on the eZX bypass Pulse entirely — they go from Clicky directly to DeMon over UART, see clicky § DeMon ↔ Clicky Interface.)

The chipset's MIPI receiver demultiplexes Pulse's stream into per-class consumers — the composer block, the controller interface — without anything sitting in the MIPI receiver's critical path. The composer block sees only composer-class packets; non-composer packets are routed past it to the appropriate chipset subsystem. DeMon's network and storage data (WiFi traffic from the ESP32-C5 radio, SD card content for tape/disk image access, debug bulk transfer, file streaming) now rides the PCIe/serdes link rather than a MIPI feed — see below.

Data path summary. DeMon and Pulse connect very differently now — the symmetry that held when both were ESP32-P4-based no longer applies:

  • DeMon — HDMI ×2 + PCIe 1x. Two HDMI outputs to the FPGA — physically 2× LVDS sets of 4 channels each (3 TMDS data pairs + 1 clock pair per link, the standard HDMI physical layer) — one display overlay, one layer/data stream, plus PCIe 1x to the FPGA's serdes interface (hardware debug, and the general-purpose high-bandwidth link for network/storage/telemetry traffic — the exact split of what rides HDMI versus PCIe is still settling). DeMon's earlier MIPI DSI uplink and QSPI register window to the chipset are both gone; DeMon's MIPI output now feeds its own local touchscreen only
  • Pulse — MIPI uplink, all lanes. Pulse's MIPI DSI TX drives FPGA MIPI RX #0 with its full native lane count (2 data lanes + clock) — unreduced, since DeMon no longer occupies the other MIPI RX hardcell at all. Carries composer descriptions (text cell grid, tilemap index map, sprite display list; highest priority), audio buffers (AMY-synthesized voices, tracker engines, classic synth emulators), and secondary traffic (controller and peripheral state) at lower priority
  • Pulse — OPI bus. An 8-bit-wide SPI2 bus to the chipset, serving as both the register-window / control-plane path (parallel to MIPI — audio parameter writes, low-latency control messages, command/response pairs) and the mechanism for Pulse to request data back from Tempest, using an interrupt line so either side can signal the other rather than needing a dedicated streaming return link. Pulse's HS and FS USB OTG controllers are both committed to MIDI 2.0 roles (HS = host, FS = device) and carry no chipset traffic
  • JTAG — DeMon → FPGA for bitstream programming, separate from the PCIe/serdes debug link
  • System vsync — FPGA-driven GPIO signal to both supervisors for genlock; Pulse paces its MIPI uplink description stream to this, DeMon paces its HDMI/PCIe traffic to the same signal via its VBLANK GPIO input

The eZX uses the same GW5AST-LV138 FPGA as FireStorm, but the interconnect is no longer identical to the Ant64's on the DeMon side — DeMon's move to a Compute Module changed its physical connectivity to the chipset. Pulse's side is unchanged: same MIPI RX hardcell, same OPI/QSPI register window, same JTAG-free role (only DeMon has JTAG), same vsync signalling. From Pulse's perspective the contract is identical to the Ant64 — maintain scene state, emit descriptions and audio over MIPI uplink, use the OPI bus for register-class transactions and data requests. DeMon's contract changed with its hardware: HDMI for layer/display data, PCIe/serdes for debug and general chipset traffic. See ant64.com/demon and ant64.com/pulse for the canonical supervisor capability detail, and ant64.com/composer for the architecture.

Attribute-style tilemap mode

The background tilemap layer has a special mode that recreates the Spectrum attribute system in updated form. Each 8×8 cell has:

  • A tile index (selects 8×8 character data, monochrome 1-bit, from BSRAM character ROM)
  • A foreground colour (8 bits → palette entry)
  • A background colour (8 bits → palette entry)
  • A BRIGHT flag
  • A FLASH flag — toggles the FG/BG every 16 frames at the chipset level

This is the Spectrum experience in its native shape, with 256 colours available per cell rather than 8.

The foreground tilemap layer supports pixel-style mode only — 4 or 8 bits per pixel directly per tile, suitable for modern HUDs and overlays.

Palette

A 1024-entry palette, organised as 4 banks of 256 entries. Each entry is 24-bit RGB (8R 8G 8B), with a 9-bit-RGB compatibility mode for retro-feel content. Different layers can reference different banks via a PAL_BANK field in their layer control register.

A default palette is loaded on reset: bank 0 is a 256-entry palette whose first 16 entries match the Spectrum's classic colour set (BLACK, BLUE, RED, MAGENTA, GREEN, CYAN, YELLOW, WHITE, ×2 brightness). Entries 16–255 cover a balanced colour cube for general use.

Border

A traditional Spectrum border colour register (DISP_BORDER at $F0010004). Reads and writes are immediate. The border occupies the area outside the active framebuffer rectangle when the logical mode is smaller than the output mode.

Scroll

Each layer (except border) has horizontal and vertical scroll registers, both with fractional sub-pixel components (8 fractional bits) for smooth scrolling at any logical resolution.

Copper

A copper-style display list engine reads instructions from a 16KB region of BSRAM ($F0030000) on every scanline transition. Three instruction forms:

Form Action
WAIT scan, h Wait until the display reaches scanline scan (and horizontal position h if non-zero)
MOVE addr16, val16 Write val16 to chipset register $F00xxxxx | addr16
MOVE32 addr, val32 Write a 32-bit value to a chipset register (two-instruction encoding)

The copper executes in lockstep with the display, allowing per-scanline palette changes, scroll changes, layer toggles, and any other chipset state to be sequenced for raster effects — the canonical use cases the Z80N copper and the Amiga copper share.


Sprites

The sprite engine is Amiga AGA-inspired and follows the platform's composer texture contract: 4bpp packed 16×16-cell textures (2 px/byte, 128 bytes per cell), 16 palette banks × 16 entries each (index 0 = colour key). Sprites are variable-height multi-cell stacks streamed per-scanline from BSRAM-cached texture pool (with DDR3 as the bulk asset store), copper-multiplexable for screen-wide reuse. Supported transforms are power-of-2 scale (1×/2×/4×/8×), horizontal and vertical flip, and 90° quarter-turn rotations; arbitrary-angle rotation and alpha blending are not part of the chipset — the platform's "classic" sprite type explicitly carries no arbitrary angle. The same engine renders sprites for the native L3 layer (sourced from eZX-CPU writes to attribute registers) and for the composer-driven L6 / L7 layers (sourced from display list descriptions from Pulse and DeMon, over MIPI and HDMI respectively) — see System overlays — Pulse and DeMon composer layers.

Sprite engine summary

  • 128 hardware sprites per scanline — sixteen times the Amiga AGA's 8
  • Width: 16, 32, or 64 pixels per sprite, selectable per-sprite — the widths AGA settled on
  • Height: variable per sprite, set by Y_START and Y_END registers
  • Colour depth: 2 bits per pixel standalone (3 colours + transparent), or 4 bits per pixel by pairing adjacent sprites (15 colours + transparent)
  • Position: 12-bit signed X and Y, easily rewriteable by the copper for mid-screen tricks
  • Bitmap data: streamed per scanline from anywhere in DDR3 — no separate pattern table required

Per-sprite registers

Each sprite has a small register set in BSRAM. Bitmap data lives in DDR3 wherever the CPU or blitter places it.

Field Width Meaning
X 12 bits Signed horizontal pixel coordinate
Y_START 12 bits Sprite first appears on this scanline
Y_END 12 bits Sprite last appears on this scanline
DATA_PTR 24 bits Byte address in DDR3 of the sprite's bitmap data
MOD 12 bits Bytes to advance DATA_PTR per scanline (sprite stride)
WIDTH 2 bits 00 = 16 px, 01 = 32 px, 10 = 64 px, 11 = reserved
ATTACH 1 bit Attach with the next sprite for 4 bpp / 15 colours
PRI 3 bits Priority versus the playfield layers and other sprites
PAL_BANK 4 bits Palette bank selector
ENABLE 1 bit Sprite active this frame

Sprite data format

Each scanline of a 16-wide 2 bpp sprite is 4 bytes (two bitplanes of 2 bytes each, AGA-style). A 32-wide 2 bpp sprite is 8 bytes per line; 64-wide is 16 bytes. Attached pairs double the bitplane count and so double the per-line storage (8 / 16 / 32 bytes for widths 16 / 32 / 64 in 4 bpp).

Animation happens by swapping the data pointer, not by editing a pattern bank. Sprites consume bandwidth and memory only for their actual on-screen footprint — no pre-allocated pattern table to size or maintain.

Attached sprites

Pairing two adjacent sprites (sprite N and sprite N+1, where N is even) with the ATTACH bit set on the lower-numbered sprite combines their bitplanes for a 4 bpp sprite — 15 colours plus transparent. Attached pairs share Y_START, Y_END, and palette bank. Up to 64 attached pairs are possible (using all 128 slots).

Copper multiplexing

The chipset copper (see Copper) can rewrite a sprite's X, Y_START, Y_END, DATA_PTR, and MOD registers between scanlines. A single sprite slot can be reused for distinct objects elsewhere in the frame, exactly the way Amiga programmers reused their 8 hardware sprites for far more on-screen objects. Starting from 128 slots the eZX routinely puts hundreds to thousands of sprite occurrences on screen in a single frame.

Per-scanline limit

128 sprites per scanline is the engine's bandwidth limit. Sprites exceeding the limit are silently dropped from that scanline, lowest-priority-first. PC_SPRITE_DROPPED in the performance counters latches the count per frame so developers see when they're hitting the ceiling.

What about rotation, scaling, and alpha?

The chipset sprite engine — and the composer-driven L6 / L7 layers that share it — supports power-of-2 scale (1×/2×/4×/8×), horizontal and vertical flip, and 90° quarter-turn rotations. Arbitrary-angle rotation and alpha blending are not part of the platform — they're explicitly out of the "classic" sprite type that the composer architecture defines. Three answers depending on what's wanted:

  1. For UI sprites and effects (animated cursors, particle bursts, status indicators): use the composer-driven L7 (DeMon) or L6 (Pulse) layer with the standard sprite display-list mechanism. The supervisors handle the high-level scene logic (animation phases, particle physics, tweening between keyframes) in their own memory (Pulse's PSRAM; DeMon's CM4/CM5 RAM) and emit per-frame sprite descriptors; the chipset renders at scanout. Power-of-2 scale, h/v flip, and 90° turns are available; arbitrary rotation is not.
  2. For pre-rotated sprite frames (the classic 1980s arcade technique): generate rotated bitmaps offline or with the blitter, swap the sprite descriptor's texture index per-frame to switch through pre-rotated frames. Gives arbitrary-angle rotation at the cost of texture-pool footprint per sprite. This works equally well for native L3 sprites and composer-driven L6 / L7 sprites
  3. For BOBs in the Amiga tradition: use the blitter to draw arbitrary rectangles with logical operations and masking directly into the bitmap layer. The blitter handles arbitrary sizes, arbitrary colour depths, and any logical combination of source and destination — at the cost of writing to a bitmap rather than getting hardware composition.

Audio

The eZX audio architecture has seven sources that meet at the chipset mixer before being driven to the WM8960 codec:

  1. eZX chipset voice engine — 32 polyphonic voices in FPGA fabric (described below)
  2. Pulse-side synthesis — AMY and other software synths running on Pulse
  3. DeMon Triple SID engine — 9 SID voices (3× MOS 6581/8580 emulators, L/C/R panned) hosted on DeMon
  4. DeMon SAM speech synthesizer — formant text-to-speech hosted on DeMon
  5. DeMon Dexed — DX7-style FM synthesis hosted on DeMon (described below)
  6. DeMon AcidBox — TB-303 + TR-808-style synthesis hosted on DeMon (described below)
  7. Application PCM — direct sample playback written by the eZX CPU into a chipset audio buffer

All seven sources sum at the chipset mixer, which applies per-source gain and pan, then routes the final stereo mix through the master EQ, limiter, and reverb send before the codec.

The Pulse stream reaches the chipset over its MIPI bulk path, sharing the link with Pulse's display feed — the same arrangement as on Ant64. DeMon's SID and SAM streams no longer ride a MIPI path — DeMon moved from the ESP32-P4 to a Compute Module and its MIPI output now feeds its own local touchscreen, not the chipset. Whether DeMon's audio rides the HDMI link depends on whether the FPGA's HDMI RX can de-embed audio from the HDMI data stream (HDMI carries audio as an optional embedded channel) — if it can, DeMon's audio rides HDMI alongside the layer/display data; if not, it needs the PCIe/serdes link instead. This isn't yet confirmed either way. The eZX chipset mixer is the destination in place of FireStorm's mixer, but the upstream contract on Pulse and DeMon (render audio, hand it to the chipset mixer) is unchanged regardless of which physical link carries it.

eZX chipset voice engine

A polyphonic voice engine in FPGA fabric producing stereo 16-bit PCM at 48kHz.

Voices

32 polyphonic voices. Each voice has:

Parameter Meaning
Waveform Square, sawtooth, triangle, noise, or sample playback
Frequency 24-bit phase accumulator
Volume 16-bit linear
Pan 8-bit, -128 (full left) to +127 (full right)
ADSR 16-bit attack rate, 16-bit decay rate, 16-bit sustain level, 16-bit release rate
Filter Per-voice low-pass with 16-bit cutoff and 8-bit resonance
Sample base 24-bit address into the sample memory window (for sample-playback voices)
Sample length 24-bit byte count (for sample-playback voices)
Loop start 24-bit offset within the sample (for looping samples)
Sample format 8-bit unsigned, 8-bit signed, 16-bit signed, or 4-bit ADPCM

Sample playback uses linear interpolation between samples. The sample memory window ($F1000000) provides a 16MB sliding window into DDR3, so very large sample libraries can be streamed in.

Waveform-mode voices

Setting a voice's waveform to square, sawtooth, triangle, or noise produces classic-style synth voices. With ADSR and per-voice filter, a square wave voice can recreate AY-3-8912 character; the saw and triangle waves cover the broader 1980s synth aesthetic; the noise generator covers percussion and effects.

Sample-mode voices

Setting a voice to sample mode plays back PCM data from DDR3, with optional looping. Eight voices in sample mode at 22kHz with 16-bit samples consume roughly 2.8MB/s of DDR3 bandwidth — comfortable within the available headroom.

Pulse-side synthesis

Pulse runs its own audio engines on the ESP32-P4, accelerated by the PIE extensions, and streams the output as a stereo PCM buffer in DDR3 for the chipset mixer to read. Pulse-side audio includes:

  • AMY — a multi-paradigm synthesis engine (additive, FM, PCM, partials, wavetable, Karplus-Strong) with ~180 oscillators, used both as a sound source and as Pulse's sequencer engine
  • Tracker engines — MOD, S3M, IT and similar formats, with pattern data in Pulse's 32MB PSRAM
  • Classic synth emulators — specific emulations beyond the AMY model

On Ant64 Pulse streams to FireStorm's mixer via MIPI. On the eZX the destination is the chipset's mixer instead — the MIPI path itself is unchanged because the eZX uses the same GW5AST-LV138 FPGA as FireStorm. Pulse on the eZX has no jog dials, so patches that depend on jog-dial parameter control (TB-303-style basslines and similar) are driven from MIDI or keyboard input on the eZX. With that constraint in mind, see ant64.com/pulse for the Pulse audio subsystems in detail.

DeMon Triple SID engine

DeMon hosts a software emulator of three MOS 6581/8580 SID chips, panned Left / Centre / Right for a wide stereo image — nine SID voices in total, running as native software on the CM4/CM5's ARM cores (no dedicated acceleration needed — the compute headroom over the earlier ESP32-P4 is substantial). Voice character (6581 muddy versus 8580 cleaner) is selectable per instance. The engine can play classic .sid files directly, so AntOS doubles as a SID music player out of the box. Triggers come from AntOS scripts, from the eZX CPU via a chipset mailbox, or from Pulse over the SPI peer link.

On Ant64 the SID audio reached FireStorm's mixer over DeMon's MIPI bulk path. That path no longer exists — DeMon moved from the ESP32-P4 to a Compute Module, and its MIPI output now feeds its own local touchscreen rather than the chipset. Whether SID audio now rides the HDMI link depends on whether the FPGA's HDMI RX can de-embed the audio channel HDMI optionally carries alongside video — if it can, SID audio rides HDMI; if not, it needs the PCIe/serdes link. Not yet confirmed either way. The engine itself is unchanged. See ant64.com/demon for the SID engine in detail.

DeMon SAM speech synthesizer

DeMon also hosts a formant speech synthesizer in the tradition of Software Automatic Mouth (1982) — the iconic robotic voice of the Commodore 64, Atari 8-bit, and Apple II era. Runs as native software on the CM4/CM5's ARM cores. Used for AntOS voice prompts, accessibility, system narration, and as a musical instrument when driven by Pulse (vocoder-style leads, robotic dialogue tracks, sequencer-driven phoneme streams).

On Ant64 SAM streamed to FireStorm's mixer over DeMon's MIPI bulk path. That path no longer exists — DeMon moved from the ESP32-P4 to a Compute Module, and its MIPI output now feeds its own local touchscreen rather than the chipset. Whether SAM audio now rides the HDMI link or the PCIe/serdes link is not yet confirmed, matching the SID engine above. The engine itself is unchanged. See ant64.com/demon for SAM in detail.

DeMon Dexed

DeMon hosts a DX7-style FM synthesizer, built on Synth_Dexed (or the underlying msfa engine) rather than MiniDexed itself — MiniDexed is a bare-metal application built on Circle and can't coexist with Linux and AntOS, so only the embeddable synthesis library is used, with AntOS providing the multi-timbral tone-generator layer, patch management, and UI around it. Licensing shapes the choice of engine: Dexed and MiniDexed are GPL v3, but msfa is Apache 2.0, making it the safer base for a commercial product. Runs as native software on the CM4/CM5's ARM cores — comfortably inside the CM's headroom (a Raspberry Pi 3 already runs eight 16-voice tone generators). Triggered via MIDI, the sequencer, or AntOS scripts, alongside the other DeMon-hosted engines.

DeMon AcidBox

DeMon also hosts a TB-303 + TR-808-style synthesizer based on AcidBox (MIT licensed), providing the acid-bassline 303 voice and 808-style drums with an FX chain. The distinctive value over the chipset's own AMY-adjacent synthesis is the 303 filter and its accent/env-mod/slide interaction — a specific, hard-to-replicate character. Runs as native software on the CM4/CM5's ARM cores. Triggered via MIDI, the sequencer, or AntOS scripts, alongside the other DeMon-hosted engines.

Application PCM

The eZX CPU can also produce audio directly — mod players, software synthesis, file-based playback, anything that suits direct PCM rendering. The CPU writes into a chipset audio buffer (visible in the chipset register space) and the mixer picks it up alongside the other sources. This is the same role FireStorm EE application audio plays on Ant64.

Global mixer

The mixer combines all seven audio sources into a stereo output bus, fully implemented in FPGA fabric. Each source has independent gain and pan controls. The output bus has:

  • Master volume (16-bit per side)
  • Soft-clipping limiter
  • A 3-band EQ (low, mid, high shelf)
  • A simple stereo reverb (delay length and feedback)

Per-source register changes commit on the next sample boundary. No CPU overhead for routine mixing — the eZX only intervenes when source levels or routing change.

Retro modes (chipset voices)

A small bank of pre-configured chipset voice setups recreates the audio character of earlier machines:

Mode Setup
Beeper One square-wave voice, no ADSR, fixed volume
AY-style Three square-wave voices with envelope generator, one noise voice
Spectrum 128 stereo Six AY-style voices arranged as TurboSound

These are chipset-side voice presets — not chip-accurate emulations. They give a quick approximation of the original AY-3-8912 character. For hardware-faithful AY playback, use the Next-compatible image. For SID character, use the DeMon Triple SID engine — it is a real software emulation, not a chipset preset.

MIDI

USB MIDI is supported by both images via DeMon's USB stack. Hardware DIN MIDI (In, Out, Thru) is provided through the MIDI/audio expansion daughter-board — see Expansion § MIDI/audio expansion for the unified overview and spec for the connectivity detail.


Blitter

A 2D blitter capable of moving and combining rectangular regions of memory. Each blit operation has:

Register Meaning
BLIT_SRC Source base address (DDR3 or chipset memory)
BLIT_DST Destination base address
BLIT_SRC_PITCH Source row stride in bytes
BLIT_DST_PITCH Destination row stride in bytes
BLIT_WIDTH Rectangle width in pixels
BLIT_HEIGHT Rectangle height in pixels
BLIT_BPP Bits per pixel (1, 2, 4, 8, 16)
BLIT_FLAGS Operation flags
BLIT_MASK Source colour value for transparency
BLIT_BLEND Blend factor for alpha blending

BLIT_FLAGS selects the operation:

  • COPY — straight copy
  • MASK — copy where source pixel ≠ BLIT_MASK
  • AND, OR, XOR — logical combination of source and destination
  • BLEND — alpha blend using BLIT_BLEND factor (for 16-bit modes)
  • FILL — fill destination with BLIT_MASK value

Writing to BLIT_FLAGS starts the operation. The blitter runs autonomously, sharing DDR3 bandwidth with the display engine via the DMA arbiter. A status bit indicates completion; an optional interrupt fires when an operation finishes.

Typical performance at 60% memory bandwidth allocation: ~200 megapixels per second for COPY blits at 8 bits per pixel. Fast enough for full-screen redraws every frame at 256×192, with headroom to spare.


DMA controller

Eight independent DMA channels. Each channel has:

Register Meaning
DMA_SRC Source address
DMA_DST Destination address
DMA_COUNT Byte count
DMA_STRIDE Stride per element (signed; -1, 0, 1, larger)
DMA_CTRL Element width (1, 2, 4 bytes), increment mode, trigger, interrupt enable

Channels can be configured as:

  • Memory-to-memory — bulk data movement
  • Memory-to-peripheral — feed audio sample streams, sprite tables, palette updates
  • Peripheral-to-memory — capture audio input, NFC reads, etc.

Channels are prioritised by index — channel 0 has highest priority. The display engine and blitter are bus masters in their own right; the DMA controller and these masters share DDR3 bandwidth under an arbiter that guarantees display scanout is never starved.

A DMA channel can be chained to start on the completion of another channel, enabling scatter-gather operations and pipelined effects.


Interrupt controller

A lightweight interrupt controller routes chipset events to the CPU's external interrupt line. Source events:

Source Trigger
IRQ_VBLANK Vertical blank — start of vertical retrace
IRQ_HBLANK Horizontal blank — programmable scanline match
IRQ_TIMER0..IRQ_TIMER3 Timer expired
IRQ_BLITTER Blitter operation complete
IRQ_DMA0..IRQ_DMA7 DMA channel complete
IRQ_AUDIO_BUF Audio buffer underrun (sample-mode voice)
IRQ_NFC NFC card event
IRQ_STICKY Sticky input event (keyboard or joystick)
IRQ_PULSE Pulse MIDI event
IRQ_DEMON DeMon mailbox message
IRQ_COPPER Copper trigger (programmable from copper list)

Each source has an enable bit, a pending bit, and a priority field (4 bits). The CPU receives a single external interrupt; reading IRQ_PENDING returns the highest-priority pending source.

For modest interrupt loads this works exactly like a classic 8-bit machine — IRQ_VBLANK is the only enabled source, the handler runs once per frame. For more elaborate setups the priority field allows reasonable nesting.


Timers

Four general-purpose 32-bit countdown timers. Each has:

Register Meaning
TMR_RELOAD Reload value (cycles)
TMR_VALUE Current value (decrements each chipset cycle)
TMR_CTRL Run, restart on expire, interrupt enable, prescaler

The chipset clock is 50MHz, giving a 20ns timer resolution. The prescaler divides this by 1, 8, 64, 256, or 1024 for slower events. Timer expiry can fire an interrupt and/or be observed via polling.


Performance counters

A small set of hardware counters that give software a precise view of where time is being spent. Counters update continuously and can be sampled or latched-on-event.

Counter Measures
PC_CPU_CYCLES Total CPU clock cycles
PC_CPU_INSTR Instructions retired
PC_CPU_STALL Pipeline stall cycles
PC_MEM_DDR3 DDR3 transactions issued
PC_MEM_STALL DDR3 stall cycles (any master)
PC_BLITTER Blitter pixels processed
PC_SPRITE_DRAWN Sprites drawn (per frame, latched at VBLANK)
PC_SPRITE_DROPPED Sprites dropped due to per-scanline limit
PC_AUDIO_VOICES Active voice count (instantaneous)
PC_DMA_BYTES Total DMA bytes moved

Counters are 32-bit and wrap. A pair of additional 64-bit counters (PC_TIME and PC_VBLANKS) provide a never-wrapping time reference for normalising the others.


DeMon interface

DeMon is the supervisor — it owns the FPGA configuration, manages USB and SD storage, runs AntOS, and provides the host debug path. The chipset exposes DeMon to running software through a mailbox region at $F0090000:

  • DEMON_TX — outgoing message buffer (CPU writes, DeMon reads)
  • DEMON_RX — incoming message buffer (DeMon writes, CPU reads)
  • DEMON_STATUS — handshake bits
  • DEMON_IRQ_MASK — which events from DeMon raise IRQ_DEMON

The mailbox is the primary way running software requests services from AntOS — file I/O, network access, OSD updates, debug events. Detail is documented separately in the AntOS integration notes.


Sticky and Pulse interfaces

Sticky scans the keyboard matrix and joystick ports. Pulse handles MIDI events and the music sequencer (when present). Both connect to the chipset via SPI bridges visible at $F0070000:

  • STICKY_DATA — input event queue
  • STICKY_CTRL — configuration (debounce, repeat rate, joystick mode)
  • PULSE_DATA — MIDI/sequencer event queue
  • PULSE_CTRL — configuration

Software typically subscribes to interrupts (IRQ_STICKY, IRQ_PULSE) and drains the event queues on each interrupt.

For the Sticky and Pulse subsystems themselves, see ant64.com — note the eZX-specific differences: no Crank (Ant64's dedicated jog-dial controller), no jog dials of any kind, and the addition of Clicky (eZX-only AVR for keyboard matrix scanning, buzzer, and the unified WS2812 NeoPixel chain). Clicky talks UART directly to DeMon and isn't part of the Pulse-aggregated peripheral stream described above.


Status and open questions

This document is a first specification of the eZX primary image chipset. Open items:

  • Gate budget validation — the feature set targets the GW5AST-LV138 with considerable headroom. NextNano demonstrates a complete ZX Spectrum Next fits in ~20k LUT4 on a similar Gowin part, leaving well over 100k LUT4 of headroom on the GW5AST-LV138 for the eZX CPU and the chipset above. Headline numbers (128 sprites/scanline, 32 voices, 8 DMA channels) will firm up at synthesis with substantial room to spare; the question is whether to add features (Mode-7-style background affine, additional bitmap modes, larger sprite engine) rather than scale back.
  • Affine background transforms — Mode-7-style affine transforms on the tilemap and bitmap layers are an obvious extension. Arbitrary-angle sprite rotation and alpha are deliberately not part of the chipset sprite engine — the AGA-style architecture trades them away for sprite count, simplicity, and the platform's "classic" sprite-type contract that all composer-driven sprites also follow (power-of-2 scale, h/v flip, 90° quarter-turns only). Software wanting arbitrary-angle rotation uses pre-rotated frames or the blitter (see What about rotation, scaling, and alpha?). Whether background affine lands in v1 depends on synthesis findings, not gate availability.
  • HDMI audio packetisation — HDMI audio is supported by the codec; the chipset side of the audio packet generation is straightforward but not detailed here.
  • Direct true-colour bitmap modes above 16bpp — 24- or 32-bpp bitmap modes are possible but not currently allocated a layer.
  • Hardware debug system — the chipset side of the debug subsystem (overlay text layer, trace ring buffer, performance latching at NMI boundary) is being redesigned. The performance counters above are the part that is settled.
  • Border timing tricks — the Spectrum's iconic border raster effects need a corresponding mechanism in the eZX. The copper covers most cases but specific "border colour mid-scanline" semantics deserve their own pass.
  • Sample memory window placement — the 16MB window may grow if audio workloads need it; the address allocation has room.

Important: The eZX Spectrum board is at early design/prototype stage, everything you see here is subject to change.