Developers

Open hardware, open firmware.

The FPGA logic, ARM firmware and schematic are on GitHub; Apple II demos and diagnostics have their own software repository. This page is the map, for people writing Apple II software that targets the card and for people who want to change the card itself.

Source & license

The repository is github.com/hasseily/appletini-one. Releases with a ready-to-flash FIRMWARE.BIN are on the Releases page; the Downloads page points at the latest.

Apple-side demos, diagnostics and their builders now live in github.com/hasseily/appletini-software. This reference covers firmware source through 7b972bf (14 October 2026, F1.3.0). Check a release’s notes for the features included in its downloadable image. The second firmware, Multitini, has its own repository.

DirectoryWhat is in it
hdl/SystemVerilog and VHDL sources, the active source manifest, and board pin and timing constraints under hdl/constraints/. The Apple-side logic lives in hdl/apple/: bus wrapper, cycle capture, soft-switch manager, every virtual card, the W65C02 core and the TransWarp bus engine.
ps_sources/Bare-metal ARM code: the golden updater, the frontend (menu, storage, USB, coprocessors, printer, networking), the CPU1 renderer, and shared libraries.
scripts/Vivado, Vitis, image-generation, programming and regression tools.
software/Firmware ROM inputs, shared test media, renderer fixtures and the ca65 memory API example. Standalone apps moved to appletini-software.
tools/mcp/A serial control server that exposes a live Apple II to MCP clients.
schematics/The Rev. A3 board schematic, as PDF.
third_party/External components with their own license terms.

The project is licensed GPL-3.0. Bundled components keep their own licenses and attribution: the XTulator 8088 CPU core is GPL-2.0-or-later, the USB stack is CherryUSB, and the Super Serial Card and SmartPort ROM sources come from the a2retronet project. Software projects and bundled media in appletini-software carry their own license and provenance notes, including IP65 for the network demos.

Architecture

The card is a Xilinx Zynq-7020: FPGA fabric plus two ARM Cortex-A9 cores at 766 MHz. The split is strict. The fabric (PL) owns everything cycle-sensitive on the Apple bus: capturing every cycle, answering as each virtual card, the soft 65C02 and its posted-write shadow, video timing, the Disk II sequencer, the 6522s and the SSI-263 speech synthesis. The two ARM cores (PS) provide what a cycle does not have to wait for: SD storage, the video renderer and HDMI compositor, USB host services, the menu and configuration, the Z80 and 8088 machines, the ImageWriter interpreter, FTP and the network stack.

Emulation is driven by the real bus, not by a software model of the Apple. That is why timing-sensitive demos, copy protection and raster tricks behave as they do on the original cards. The PS talks to the PL through a small AXI register bank (card-control registers) and DDR-backed shadows of Apple memory; the PL reports its RTL version at runtime alongside the firmware and golden-updater versions so a mixed build is visible immediately.

Virtual slot map

Software sees the card as several original cards in fixed slots. A physical card that conflicts with an enabled virtual slot must be removed. Install Appletini in slot 7, or leave physical slot 7 empty when installing it elsewhere on a legacy host. IIgs use requires physical slot 7 and has a restricted slot policy.

SlotVirtual card
1Super Serial Card driving a color ImageWriter II, and the Uthernet II (W5100S). They share the slot with disjoint decodes and work at the same time.
2Off, Apple Mouse Card, 4Play (four players), or SNES MAX (two players), fed by USB input. One personality at a time.
3TransWarp accelerator, as its configuration presentation. The 80-column firmware is the //e’s own.
4Phasor: dual Mockingboard with dual SSI-263 speech chips, synthesized natively in the FPGA. Slot 4 is slowed down under acceleration while the Phasor is active.
5PCPI Appli-Card (Z80) or ALF AD8088 Plus (8088), one at a time. Physical slot 5 must be empty.
6Disk II controller with two drives, WOZ-accurate.
7The boot menu card at power-on, which hands off to the SmartPort controller, with the text overlay on the slot’s DEVSEL block; or the SuperSprite, which takes slot 7 exclusively and disables both.

Off the slot bus, the card also provides RamWorks-style auxiliary memory (8 MB under acceleration), a no-slot clock, and the VidHD-compatible video controls used for Super Hi-Res and SHR4.

Fresh settings leave TransWarp and the optional slot cards off, including the SSC, Ethernet, slot 2, Phasor, slot 5 and Disk II. SmartPort remains available. Turning Disk II off also changes a Disk II boot target to SmartPort, including in ONE//e.

Detecting the card

The supported way for Apple II software to know it is running with an Appletini is a standard SmartPort STATUS call: status code $03 (GETDIB) to unit $00, the controller, in slot 7. The Device Information Block carries an ID string: Appletini SP from the controller, Appletini HD from a device unit. Compare the first nine characters. No soft-switch probing, no ROM tricks, and the controller answers even with no images mounted.

One caveat: slot 7 presents the SmartPort ROM only after the boot menu hands off. On power-up and after every Ctrl-Reset, slot 7 is the boot-menu card, which advertises itself as a Disk II ($C707 reads $3C) and will not answer GETDIB. Application code loaded from SmartPort or Disk II is past the handoff already; a reset-vector hook is not. Check the ROM signature first, as the sample does. With SuperSprite enabled, SmartPort is off and this detection is unavailable.

AddressValueMeaning
$C701$20SmartPort/ProDOS signature
$C703$00SmartPort/ProDOS signature
$C705$03SmartPort/ProDOS signature
$C707$00SmartPort device ($3C means boot-menu phase, not ready)
$C7FF$0AProDOS entry offset, so the ProDOS entry is $C70A
$C70DSmartPort dispatch entry (ProDOS entry + 3)

The call uses the standard inline-parameter convention: JSR the dispatch address, then the command byte and a pointer to the parameter list. Carry clear is success. The controller DIB puts the ID length ($0C) at offset 8 and the text at offsets 9–20, with firmware major and minor versions at offsets 27 and 28. A device DIB (unit $01 and up) puts the length at offset 4 and the text at offset 5.

The complete routine, in ACME syntax, is examples/detect_appletini/detect_appletini.a65 in appletini-software:

; detect_appletini.a65 -- detect an Appletini from 6502 assembly.
; Returns:  carry CLEAR -> Appletini present
;           carry SET   -> not present / slot 7 not yet handed off
; Assemble: acme -o detect_appletini detect_appletini.a65

!cpu 6502
* = $0800

SLOT        = 7
ROM         = $C000 + (SLOT * $100)   ; $C700  slot-7 ROM
SIG5        = ROM + $05               ; $C705  -> $03 on a SmartPort card
SIG7        = ROM + $07               ; $C707  -> $00 on a SmartPort card
SP_ENTRY    = ROM + $0D               ; $C70D  SmartPort dispatch

SETSLOTCXROM = $C006                  ; IIe: map peripheral-card ROM at $Cx00

buffer      = $0900                   ; DIB result buffer (>= 32 bytes)
SIG_LEN     = 9                       ; compare just "Appletini"

detect
                sta SETSLOTCXROM        ; harmless on a II/II+

                ; 1. Is slot 7 presenting the SmartPort ROM yet?
                lda SIG5
                cmp #$03
                bne not_present
                lda SIG7
                bne not_present         ; must be $00

                ; 2. SmartPort STATUS / GETDIB to unit 0 (controller)
                jsr SP_ENTRY
                !byte $00               ; CMD = STATUS
                !word params
                bcs not_present         ; carry set -> call failed

                ; 3. Compare the controller ID string to "Appletini"
                ldx #0
cmp_loop
                lda buffer + 9, x
                cmp sig_text, x
                bne not_present
                inx
                cpx #SIG_LEN
                bne cmp_loop

found
                clc                     ; carry clear = Appletini detected
                rts

not_present
                sec                     ; carry set = not detected / not ready
                rts

params
                !byte $03               ; parameter count
                !byte $00               ; unit 0 = SmartPort controller
                !word buffer            ; status-list (result) pointer
                !byte $03               ; status code $03 = GETDIB

sig_text
                !text "Appletini"       ; plain low ASCII, as the firmware emits it

Compare against low ASCII; the firmware does not set the high bit. For portability across SmartPort cards, derive the dispatch address from $C7FF rather than hard-coding $C70D. The sta $C006 only matters on the //e family; save and restore INTCXROM if your code must preserve it.

Text overlay (VT100)

The card can draw a text layer above the normal Apple picture from a buffer your program keeps in ordinary Apple RAM. You lay out 16-bit cells (a character byte, then a VGA-style attribute byte) in one linear, row-major block anywhere from $0200 to $BFFF in main or auxiliary memory; the card watches the writes, keeps a private copy, and composites the text into the HDMI output. It never touches Apple RAM, never stops the processor, and leaves the video soft switches alone, so text, hi-res, double hi-res and Super Hi-Res all keep working underneath.

  • VT100 mode: 7-bit ASCII plus the DEC Special Graphics set at $00–$1F; bit 7 underlines. CP437 mode: all 256 IBM glyphs, for ANSI and BBS screens.
  • 8×14 or 8×16 fonts, integer scaling on each axis, any grid up to 255×127, placed anywhere on the canvas (1120×768 in legacy modes, 1280×800 in SHR).
  • The standard 16-color VGA/ANSI palette, blink or 16 background colors, a hardware cursor (block, underline or bar), and an optional transparent background.
  • An ARM → fill → SHOW handoff: ARM latches the layout and clears the card’s copy, you write every cell, SHOW flips at the next frame edge. Two buffers give flicker-free full redraws.

It coexists with SmartPort. A slot has three separate decodes, and the two cards in slot 7 use different ones, so both are present at the same time and never collide. The SmartPort answers only in the slot-ROM space and the expansion-ROM window; the overlay answers only in the slot’s 16-byte DEVSEL block, which the SmartPort does not decode. A program can draw with the overlay while it boots from, reads and writes SmartPort volumes. In the FPGA the overlay is built into the SmartPort card itself, sharing its bus gating, so it comes and goes with SmartPort: both are present after the boot menu hands off, and both step aside when SuperSprite takes slot 7.

Slot-7 decodeRangeAnswered by
IOSEL, slot ROM$C700–$C7FFSmartPort
IOSTROBE, expansion ROM$C800–$CFEF, plus DATA/CTRL/DPOP at $CFF0–$CFF2SmartPort
DEVSEL$C0F0–$C0FFText overlay

Detection is read-only: +$E reads $4C (‘L’), +$F the BCD version, and +$8–+$D spell LINTXT. Never write to a slot that has not passed all three checks.

OffsetNameR/WFunction
+$0INDEXRWSelects one of 256 indirect registers (buffer base, CONFIG, COLS, ROWS, origin, SCALE, cursor, fill values, canvas size, active layout, CAPS).
+$1DATARWReads or writes the selected register.
+$2DATA_INCRWSame, then increments INDEX.
+$3CMDW$00 OFF, $01 ARM, $02 SHOW, $03 HIDE.
+$4STATUSRBit 7 BUSY, 6 STALE, 5 CONFIG_ERROR, 4 FRAME_PENDING, 1 ARMED, 0 VISIBLE.
+$8–+$FMAGIC, SIG, VERRIdentification, fixed regardless of card state.

The interface is written as a standard any Apple II video card can implement, with the Appletini as the reference. The full specification, with every register, the range rules, frame-edge semantics, the detection routine and a worked 80×24 example, is hosted here: Linear RAM Text Overlay Interface 1.0. A demo that uses it is demos/appletini_demos/textoverlay.a65 in appletini-software. Output resolution and picture size scale the overlay with the Apple picture; its logical canvas dimensions and register interface stay the same.

Virtual TransWarp

The accelerator mirrors the bus contract of the physical Applied Engineering TransWarp: a full shadow of Apple memory, sparse bus traffic, and /DMA held while the soft CPU runs. Because the card’s capture, serving and renderer paths were hardened against that exact master, every test run against the real card doubles as the acceptance suite for the virtual one. The one electrical difference is an improvement: bus cycles are driven with ideal 6502 timing, address and R/W asserted early in PHI1 and held through the cycle.

Things software can observe:

  • Under acceleration the machine is always an Enhanced //e. The 16 KB Enhanced //e ROM is loaded into the shadow on every takeover, and $C011–$C01F status reads are served from the tracked switch state.
  • $C019 RDVBLBAR uses the native VBL boundary at line 192, cycle 0, with the AppleWin convention for bit 7.
  • The TransWarp speed register at $C074 is decoded; vtw.c074.ignore turns that off for software that pokes it by accident.
  • Fixed presets: 33 MHz, 26, 13, 7, 3.6, 2.6 and cycle-exact 1 MHz. Config keys: vtw.enabled, vtw.speed.mode, vtw.pace.divider, vtw.disk2.acceleration.disabled, and vtw.slowdown.cycles for the per-slot slowdown window (menu presets 32 through 65535 cycles, default 512; any decimal value up to 65535 is accepted). UART: vtw [status|on|off] or vtw speed full|turbo|1mhz|div <n>. Speed hotkeys show TW: OFF and change nothing without an active accelerator session.
  • Ctrl-Reset cannot tear a running session down; the enable is latched for the session.
  • On a physical //e, a saved vTW session starts while the initial Apple RESET is held, before the boot prompt: CPU0 patches the ARM-loaded C8 boot helper so only the //e identification return waits for vTW. The prompt and menu run at 1 MHz to keep the ROM’s VBL timing, and the saved speed applies at storage handoff without another reset. II/II+, IIgs and unidentified hosts keep the post-handoff start.
  • When Appletini supplies auxiliary RAM, the C8 helper waits for the RAM policy and selects the internal slot-3 ROM before booting, so ProDOS’s slot scan and BASIC.SYSTEM’s PR#3 find the //e 80-column firmware on the first boot.

TURBO execution

TURBO is a separate execution mode, enabled by the saved opt-in vtw.turbo.enabled and selected with vtw.speed.mode=3 or vtw speed turbo. It has no fixed MHz rating. The fixed presets keep the original cycle-exact path; TURBO caches shadow memory and skips safe internal CPU cycles. I/O retains its bus timing, soft-switch ordering and slowdown rules.

Every accepted video or overlay write still reaches Appletini’s renderer. Writes mirrored to motherboard video may combine, and writes to inactive pages may wait until a display change, card access, bank change or memory hold needs them. Use a fixed preset when software depends on intermediate physical display writes. Cache coherence covers self-modifying code, bank changes and ARM memory holds. See README_TURBO.md for the execution, cache and video rules.

The core is a synthesizable, cycle-stepped W65C02S in SystemVerilog with the full 256-entry opcode map, verified against 2.54 million SingleStepTests vectors, the Klaus Dormann functional, extended, decimal and interrupt suites, and 266 directed checks. Design notes: README_VIRTUAL_TRANSWARP.md and README_W65C02_CORE.md.

Copy/fill API 1.0

The ARM memory service copies and fills explicit physical memory ranges while holding the virtual TransWarp CPU. Use F1.1.4 or later with its matching FPGA image: earlier versions could report false DMA timeouts. Active vTW is required at any speed, including TURBO; extended AUX also requires RamWorks. The native Apple CPU is unsupported.

  1. Identify the Appletini SmartPort controller before using its private selector.
  2. Call SmartPort STATUS ($00), unit 0, selector $80, into a 32-byte buffer. Require success, signature AMEM, major version 1, descriptor size 16, the feature bits you need, and availability bit 0.
  3. Build a length-prefixed list of 1–16 descriptors. Call CONTROL ($04), unit 0, selector $80. A successful return confirms execution and CPU-hold release.
  4. STATUS can then report the last result, completed descriptors, confirmed byte count and ARM elapsed microseconds.

Both calls use a nine-byte parameter block: count 3, unit 0, a two-byte CPU buffer pointer, selector $80, then four zero padding bytes. All multibyte fields are little endian. Success returns A=$00, C=0; failure returns A=error, C=1. Treat X and Y as clobbered.

The CONTROL list starts with a two-byte payload length (8 + 16*N), followed by AMEM, version 1, descriptor count, zero flags and a zero reserved byte. Each descriptor is 16 bytes:

OffsetField
0–1Operation: 1 COPY, 2 FILL; flags: bit 0 PRIVATE
2–5Source space, bank, 16-bit address; all zero for FILL
6–9Destination space, bank, 16-bit address
10–11Nonzero byte count
12–15Fill byte (zero for COPY), then three reserved zero bytes

Space 0 is MAIN (bank 0 only); space 1 is AUX (banks 0–126). Endpoints must stay within $0200–$BFFF and cannot wrap. COPY rejects overlap within the same physical bank. The service validates the full list before writing, then executes it in order.

PRIVATE is required for every MAIN or base-AUX destination. These writes update CPU memory and caches without sending renderer records or motherboard display writes. Use them for private working memory. Before displaying that data, publish the whole required range through the normal display path; changing video mode alone does not publish it.

Choose a CPU fallback only when the initial probe says the API is unsupported or unavailable. A failed CONTROL may have changed memory; do not retry with a CPU copy or treat the progress count as a safe resume offset. Keep the caller, stack and return path outside all destination ranges. Normal SmartPort entry also writes workspace byte $07F8, which matters for whole-MAIN snapshots.

Since F1.2.2, a matching FPGA image runs each descriptor in its own copy engine, directly between shadow memory and RamWorks PSRAM; the ARM path remains the fallback when that engine is absent. The request format and results are unchanged.

The full API reference covers capability fields, errors, hold recovery and raw FIFO transport without the ROM workspace write. The firmware repository supplies ca65 constants, command builders and an example.

Character glyph API 1.0

From F1.3.0, Apple II programs can replace the character ROM that Appletini renders with, through private SmartPort selector $81. The service accepts a complete 4096-byte //e video ROM in chunks, checks its CRC32 and swaps it in at a frame boundary. It works from the motherboard CPU or vTW and needs no CPU hold or DMA. Only Appletini’s rendered video changes: the motherboard’s character ROM, composite output, soft switches and the saved video.rom menu choice are untouched. The $80 copy/fill selector keeps its format.

  1. Identify the Appletini SmartPort controller. Never send private selectors to another card’s SmartPort.
  2. Call STATUS ($00), unit 0, selector $81, into a 32-byte buffer. Check AGLY, major version 1, header size 16, ROM size 4096, a maximum chunk of at least your chunk size, and the AVAILABLE flag. Older firmware returns $21 (BADCTL).
  3. Wait until PENDING clears, and note the applied generation.
  4. Call CONTROL ($04), unit 0, selector $81: BEGIN with a nonzero transaction ID, WRITE all 4096 bytes (eight 512-byte chunks, in any order), then COMMIT with the CRC32 of the whole ROM. Partial uploads never change the active ROM.
  5. Poll STATUS until PENDING clears, the generation changes and CUSTOM_ACTIVE is set.
  6. On exit, ABORT any unfinished upload, then RESTORE with ID zero to return to the menu-selected ROM.

The parameter list matches the copy/fill API: count 3, unit 0, a two-byte buffer pointer, selector $81 and four zero bytes. Each CONTROL buffer starts with a two-byte payload length, then a 16-byte AGLY header (version, operation, transaction ID, WRITE offset and count) and the WRITE data or COMMIT CRC. Operations are BEGIN 1, WRITE 2, COMMIT 3, ABORT 4 and RESTORE 5; errors run from $60 UNAVAILABLE to $67 CHECKSUM, and BUSY ($66) means an earlier change still awaits the renderer.

Upload the full //e video-ROM layout. The bytes are inverted pixel rows, as in a physical ROM dump such as 342-0265-A, not an ASCII bitmap. Keep all 4096 bytes meaningful: the PAL Accurate renderer also reads ROM addresses during graphics scanout. $C00E/$C00F still choose primary or alternate (MouseText) characters from the uploaded ROM.

Apple reset discards an unfinished upload but keeps the active ROM; a menu ROM change ends any upload and wins. The full README_GLYPH_API.md has every field, flag and error, and software/glyph_api/glyph_api.inc supplies the constants. A complete client is demos/glyph_lab in appletini-software.

USB paddles

On a physical Apple, USB paddle input requires active vTW. ONE//e uses its private input path with the same saved mappings. Up to eight shared USB input slots supply four independently mapped paddles; the lowest connected joystick supplies PB0–PB2. Device numbers can change with connection order. The user guide describes supported controllers and mapping controls.

The vTW bridge serves paddles at $C064–$C067 (mirrored at $C06C–$C06F) and buttons at $C061–$C063 (mirrored at $C069–$C06B). Each $C070–$C07F access snapshots the axes and restarts all four timers; an axis value lasts 4 + 11 * value native Apple cycles. Only $C070 completes privately; the other aliases preserve their physical RamWorks and speed-register effects.

Actual USB paddle measurement runs at native speed even in TURBO or with Slow Paddles/joystick disabled. Timers continue while the CPU is paused. On a //e, USB buttons combine with the physical Apple keys; disconnecting all controllers restores the physical connector.

Settings use usb.joystick.paddle.0 through usb.joystick.paddle.3, including each paddle’s .device. The ARM bridge stages PDL0–3 at $400002AC and commits axes, presence and buttons at $400002B0; check its $4A signature in bits 31:24 and physical-vTW enable bit 8 before publishing. Full register, mapping and USB diagnostics: README_USB_JOYSTICK.md. UART usb1 status reports connected inputs and driver state.

4Play & SNES MAX

Slot 2 selects Off, Mouse, 4Play or SNES MAX. The gamepad cards work through the physical Apple bus and ONE//e without TransWarp. They require the updated frontend and FPGA. Neither provides slot ROM, expansion ROM, IRQ or no-slot-clock access. On a physical IIgs, slot 2 remains disabled.

4Play

Read $C0A0–$C0A3 for players 1–4. The ports repeat across $C0A0–$C0AF; writes do nothing. Controls are active high:

BitMeaning
0, 1, 2, 3Up, Down, Left, Right
4Trigger 3
5Always high
6, 7Trigger 2, Trigger 1

An idle or disconnected player reads $20.

SNES MAX

Write $C0A0 to latch both players, then read D7/D6 for players 1/2. Write $C0A1 to advance both streams; reads do not advance them. Controls are active low in this order: B, Y, Select, Start, Up, Down, Left, Right, A, X, L, R, followed by four high bits. Position 16 reads low for a connected pad and high for an empty port; further clocks stay at that presence value.

A latch snapshots buttons and presence together. All even write offsets in the slot’s I/O block latch, all odd offsets clock, and all sixteen offsets can be read. Games fixed to slot 4 need a slot-2 setting, autodetection or a patch.

Settings & ARM interface

slot2.card saves OFF, MOUSE, FOUR_PLAY or SNES_MAX. slot2.player1.device through slot2.player4.device save AUTO, 1–8 or OFF. Auto retains each player assignment while connected. Old mouse.slot2.enabled settings still load; an explicit slot2.card wins. New settings default to Off and Auto.

Stage player pairs at $400002B8 and $400002BC; a byte-0 write to CONTROL at $400002B4 commits the staged buttons, card choice and presence together. Check CONTROL’s $5332 signature in bits 31:16 first. An older FPGA supports Mouse only. Full protocol, button mapping, register fields and checks: README_SLOT2_GAMEPADS.md.

Z80 Appli-Card

Slot 5 can be a PCPI Appli-Card. The Apple-visible latch protocol is in the fabric; the Z80 and its 2 MB of private memory (32 banks of 64 KB) run on the ARM side. There is no slot ROM and no shared memory, exactly as on the original: both processors talk only through the latches, so ARM execution latency never changes Apple-visible timing. The embedded 2 KB v9 boot ROM is CRC-checked at start, and the Z80 interpreter passes ZEXALL, undocumented behavior included.

AddressReadWrite
$C0D0Z80-to-6502 byte; clears its pending flagIgnored
$C0D1Read back the 6502-to-Z80 latchWrite a byte and set its pending flag
$C0D2Bit 7: byte pending for the Z80Ignored
$C0D3Bit 7: byte pending for the 6502Ignored
$C0D4$FFIgnored
$C0D5Reset the Z80; returns $FFReset the Z80
$C0D6$FFIgnored (the CTC socket is unpopulated)
$C0D7Pulse Z80 NMI; returns $FFPulse Z80 NMI
$C0D8–$C0DF$FFIgnored

On the Z80 side, each function is mirrored across a 32-port range: $00–$1F the Z80-to-6502 latch, $20–$3F the 6502-to-Z80 latch, $40–$5F handshake flags, $60–$7F map or unmap the boot ROM, and $C0–$DF the bank and common-area register (bits 1–3 keep GZ/80S behavior, bits 4–5 select the extra banks, bit 6 maps the upper 32 KB common area to bank 0).

Config keys: applicard.slot5.enabled, applicard.resource.max. UART: z80 status|on|off|reset, z80 budget <tstates>, z80 wall <microseconds>, z80 dump <addr> [len]. Demo media and validation programs are now in appletini-software/diagnostics/applicard/. The embedded software/applicard/APPLICARD.ROM remains in the firmware repository. Full notes: README_APPLICARD.md.

ALF AD8088 Plus

The other slot-5 personality is a 640 KB ALF AD8088 Plus, a compatibility superset of the original Processor Card including the AD128K range. The fabric implements the sixteen I/O ports at $C0D0–$C0DF with the port-0 bit-7 handshake; the ARM side runs an 8088-compatible XTulator core and a clean-room monitor in a dedicated 1 MB DDR arena.

  • $00000–$0FFFF 64 KB local RAM
  • $10000–$1FFFF 64 KB Apple shared-memory window
  • $20000–$2FFFF 64 KB expansion RAM
  • $30000–$3FFFF unpopulated (reads $FF)
  • $40000–$BFFFF 512 KB expansion RAM; $40000–$5FFFF is the AD128K-compatible range
  • $C0000–$FEFFF unpopulated ROM space (reads $FF)

The monitor implements integer commands 29–32, floating-point 33–47, user commands 48–247, SEQUENCE, RANDOM, SET MEMORY, MOVE DATA and far CALL. The original ROM’s graphics and MET commands 1–28 are accepted as no-ops; software that depends on them is outside the current scope. Single-byte accesses use the normal DMA path; fills and copies move four bytes per DMA hold and release the bus between batches, keeping every hold under the 6502’s 10 µs register-retention limit. The 8088 and the virtual TransWarp cannot run together, since both own the bus.

In appletini-software, diagnostics/ad8088/tools/build_ad8088_test_disks.py generates DOS 3.3 and ProDOS test disks that exercise the mailbox, a monitor command, the AD128K range, and real 8088 code writing straight into the Apple text page. UART 8088 status reports instruction counts and instructions per second. Full notes: README_AD8088.md.

Printer

The virtual Super Serial Card runs the real 1981 Apple SSC ROM with its DIP switches hardwired to the classic printer setup: 9600 baud, printer mode, no delays, no width formatting, LF after CR for the BASIC entry. Ctrl-I commands work as on the real card. The virtual 6551 always reports transmit-ready, and every byte goes into a 2 KB FIFO the ARM drains through the card-control registers.

Slot-1 resourceOwner
$C0n1/$C0n2 DIP readback, $C0n8–$C0nF 6551 ACIASuper Serial Card
$C0n4–$C0n7 W5100 windowUthernet II
$C100–$C1FF slot ROM, $C800–$CFFF expansion ROMSuper Serial Card

The ImageWriter II interpreter renders draft text in all eight pitches, bold, underline, half-height, super- and subscript and double width; ESC G/S/g/V dot graphics at the per-pitch densities; ESC T/A/B line spacing; ESC F/L positioning; ESC H form length; and ESC K 0–6 for black, yellow, magenta, cyan, orange, green and purple on the four-color ribbon, with overlapping strikes mixing their ink. LQ-only commands, MouseText and user-defined characters are consumed and ignored. Each page is a US Letter PNG at 144 dpi. Config key: printing.ssc.enabled (off in fresh settings). Full notes: README_PRINTER.md.

Ethernet

The Uthernet II-compatible W5100S sits in slot 1 next to the SSC. The user is expected to have configured it first, from the Ethernet tab of the config menu: MAC, IP, subnet and gateway, static or DHCP. The card writes those values to the chip at boot, so software that reads them from the W5100 gets a working setup without carrying its own network configuration. Anything written for the Uthernet II should work unchanged.

In appletini-software, demos/appletini_demos/appletini_webserver/ holds two ProDOS system programs built with cc65 and the IP65 stack: A2WEBSRV.SYSTEM, a small HTTP/1.0 status server, and A2BROWSE.SYSTEM, the text browser on the demo disk. Both follow Contiki’s design (one MACRAW socket, ARP/IPv4/TCP on the 6502), run on an NMOS 6502, and ask the gateway to resolve names since the W5100 has no DNS register.

Demo sources

The appletini-software repository is the Apple-side toolbox. Its demos/appletini_demos/ project contains the 32 MB showcase disk, launcher, raster and speed demos, SHR/SHR4 viewers, SuperSprite and text-overlay examples, network apps and the MS-DOS demo. AUX-memory, AD8088 and Appli-Card diagnostics are under diagnostics/; the detection example is under examples/detect_appletini/.

Build and check the demo disk from that repository’s root:

python demos/appletini_demos/tools/build_appletini_demo_disk.py
python demos/appletini_demos/tools/test_demo_disk.py
python demos/appletini_demos/tools/test_demo_disk.py --disk demos/appletini_demos/build/Appletini_Demos.po

The builder needs Python 3.10+, Java, AppleCommander, ACME and cc65. Generated output goes under demos/appletini_demos/build/. See the project README for tool paths and network-app tests. The demo disk remains a free download.

libtini C SDK

libtini is a C game SDK for an enhanced Apple //e with the Appletini, for cc65. It draws Super Hi-Res screens, keeps large data in RamWorks banks, plays Mockingboard or Phasor music and SSI-263 speech, reads the keyboard and joystick, and loads and saves ProDOS files. It uses only interfaces that the firmware has. libtini has the MIT License.

The libtini reference has first steps, guides with sample code and the full API. The example game, STARFALL, also plays in your browser. The source is in libtini/ in appletini-software.

Display output

The frontend supports six HDMI outputs: 1024×768, 1200×800, 1280×1024, 1360×768, 1680×1050 and 1920×1080. Settings and profiles save video.resolution as that size string and video.size_multiplier as max, 1 or 2. Missing or invalid values use 1920×1080 and Max. The user guide lists picture sizes and display requirements.

Resolution switching requires matching PL support; an older FPGA stays at 1080p. The PL drains framebuffer reads, changes the pixel clock and commits dimensions with the first frame address. A failed clock change tries to restore 1080p; composition remains paused if scanout stays held or unlocked. The three RGB565 output buffers retain their 4 MiB spacing with a packed stride of width × 2 bytes.

Size changes affect composition without changing the output clock. The text-overlay canvas and Apple-only screenshot format stay fixed; full-output captures use the current resolution. Bezels draw at native size and must match the output width without exceeding its height. No EDID mode selection is implemented. Full timing tables and tests: README_DISPLAY_RESOLUTIONS.md.

Legacy monochrome shaping saves video.dot.bleed as OFF, LIGHT, MEDIUM or STRONG, with Light as the default. See README_VIDEO_MONO.md for filter and rendering details.

The pixel mask is applied by the FPGA during scanout and saved as video.pixel_mask: OFF (default), APERTURE, SHADOW or LCD. Masked channels drop to 75%. Aperture keeps one of R, G or B per output column in a three-column cycle, Shadow shifts that cycle one column on alternate rows, and LCD dims every third row and column.

Screenshots go to 0:/screenshots/<timestamp>-<a2|1080p|WxH>-<serial>.png. a2 captures keep raw renderer colors at the fixed Apple size (1120×768 legacy, 1280×800 SHR) with scanlines; full-output captures copy the completed output slot’s pixels, effects and mask before yielding, then write PNG rows in batches.

IIgs host policy

IIgs support remains experimental. Use physical slot 7 configured as Your Card. The boot ROM reports host identity before optional bus ownership is allowed. A confirmed IIgs host permits the boot menu and SmartPort in external slot 7, with SuperSprite using polling; passive capture and video output remain available.

All Appletini-emulated cards in logical slots 1–6 stay blocked for the entire physical IIgs boot. This is a slot-ownership restriction, not a per-card compatibility list: the PCB has live DEVSEL only for physical slot 7, and a cached $C02D report cannot establish ownership of other slots after GS/OS changes their mapping. Both boot_menu_service_slot_allowed() and the FPGA machine policy enforce this limit. It does not disable separate cards installed in the IIgs's other physical slots.

Memory replacement, RamWorks, vTW, AD8088, PS host-memory operations and fake-SHR $C029 writes stay blocked. Physical IRQ, INH, DMA and address/RW drive stay disabled. Slot-7 I/O uses live DEVSEL and active-low M2SEL qualification. A separate LINTXT path can serve overlay I/O when another slot boots, without granting SmartPort or bus ownership.

Pre-identification ROM responses still have a documented potential for data contention; these controls do not establish general IIgs hardware compatibility. See README_IIGS_SAFETY.md for host identification, electrical limits and hardware checks. ONE//e uses a separate private-bus policy; its physical slot drivers remain isolated throughout standalone operation.

Boot, flash & multiboot

The card boots from a 16 MB QSPI flash. Golden Boot B2 and firmware F1.3.0 use the multiboot layout, with one slot each for Appletini and Multitini:

RegionOffsetSizeContents
Primary Golden0x0000001 MiBBOOT.BIN: FSBL and Golden updater
Trial Golden0x1000001 MiBTrial and backup BOOT.BIN
Appletini0x2000006 MiBAppletini FIRMWARE.BIN: FSBL, PL bitstream, CPU1 renderer and frontend
Multitini0x8000000x7E0000FIRMWARE-MULTITINI.BIN: supervisor bitstream and ARM firmware
Metadata A0xFE000064 KiBCRC-protected multiboot record
Metadata B0xFF000064 KiBSecond copy of the record

Firmware files carry a version 2 manifest with their product identity, exact length and CRC, and each executable links its own product marker, so renaming a file cannot move an image into the other slot. Golden B2 scans the SD root for FIRMWARE.BIN, FIRMWARE-APPLETINI.BIN and FIRMWARE-MULTITINI.BIN, in that order, and installs each valid file into the slot its manifest names. An update first revokes only its target slot, programs and reads back every byte, then commits the verified image and a new metadata record, and renames the file to .OK. A failed file stays .BIN and the scan continues; if several files target one product, the last success wins. The other slot is never touched.

Before every boot, Golden checks the full image in flash. It tries a one-shot request first, then the saved startup choice (default_product in the metadata record, outside the SD settings), then the other verified firmware. A missing, damaged, wrong-product or stale image never boots. A verified legacy single-slot image can still boot as a fallback; otherwise Golden stays in its serial monitor. Its one-second window at power-on listens on both UARTs at 921600 baud. ? opens it; boot appletini and boot multitini (B2.0.6) start one verified firmware without changing the saved choice, and a host can send an image over XMODEM-CRC:

python scripts/serial_firmware_update.py FIRMWARE.BIN --port COM3 --reboot-golden

A firmware switch (Switch to Multitini or Multitini’s Switch to Appletini) flushes writable media, drains video, closes SD owners, stops USB, holds CPU1 in reset and restarts through Golden with a one-shot request. Appletini also requires a safe Apple-bus hold: it holds RESET on a //e or II+ and pauses ONE//e, and refuses while SD sharing, a screenshot or a print job is active. A failed preparation cancels the switch.

BOOT.BIN never installs from the SD scan. Golden updates start from the running firmware: :selfupdate on the serial console (F1.0.1 and later), or from F1.3.0 the About tab’s Update Golden Boot.... The menu action is offered only while 0:/BOOT.BIN exists and SD sharing is off, and is enabled in ONE//e or in the initial boot menu of a II+/IIe, before storage handoff. Both paths check the image and the recovery firmware, write and verify the trial Golden, boot it, then repair and verify the primary. Golden B2.0.4 and later rename BOOT.BIN to BOOT.OK once it matches the verified primary. Both menus read the installed Golden version from its executable in flash. Follow the field procedure.

Earlier B1 Golden Boots copy FIRMWARE.BIN verbatim to 0x200000 and ignore the named files, so a tagged Appletini image installs on them with its multiboot controls disabled. Never give a B1 Golden a Multitini image as FIRMWARE.BIN. Golden B2.0.6 can also restore an identified, recovery-capable Appletini F1.1.0 or later image.

Direct QSPI programming over JTAG (scripts/program_boot.bat, scripts/program_firmware_slot.bat) covers bring-up and last-resort recovery, without the SD updater’s power-loss protection. Full notes: README_MULTIBOOT.md; README_BOOT_UPDATE.md describes the original single-slot layout and the serial procedure.

Multitini

Multitini is the second firmware: a stand-alone FPGA computer that loads machine cores (Commodore 64, Game Boy / Game Boy Color, Genesis / Mega Drive, NES, Vectrex, Apple IIgs) from /multitini/cores/ on the SD card, behind a shared supervisor that handles the menu, USB input, audio, storage and core loading. It does not use the Apple bus: its connector outputs stay in safe idle states, so the Apple keyboard and slot cards are never read. The source is at github.com/hasseily/multitini-one, GPL-3.0-only for original work, with imported cores and files keeping their own licenses. The Multitini guide covers use.

The two repositories share the multiboot image validator, metadata, updater and Golden self-update code; scripts/check_multiboot_sync.py in multitini-one checks that the shared files match.

Building the firmware

You need Vivado 2025.2 and Vitis 2025.2, plus a JTAG programmer for hardware bring-up and UART access for diagnostics. Generate the Vivado project, build the bitstream and XSA, then build the Vitis applications:

vivado -mode batch -source scripts/create_project.tcl
vivado -mode batch -source scripts/build_and_export_xsa.tcl
vitis -s scripts/create_vitis_workspace.py

Then create the two flash images:

scripts/make_boot_bin.bat
scripts/make_firmware_bin.bat

A C-only frontend change needs a Vitis rebuild and a new FIRMWARE.BIN. HDL, clock, AXI or constraint changes need the full Vivado and Vitis sequence, and the hardware and Vitis platform must match. Save work before running the Vitis script: it recreates vitis_workspace and terminates existing Vitis IDE/server and Java processes.

Normal hardware builds use current synthesis with no incremental reference. Recreate the project after source-list or block-design changes. For an incremental comparison, explicitly set APPLETINI_INCREMENTAL_REF_DCP to a tested checkpoint.

The build applies temporary setup margins, runs one post-route AggressiveExplore pass, then restores nominal constraints before checking export. The current gate requires at least +0.050 ns setup slack, nonnegative hold and pulse-width slack, clean routing and bus skew, and valid bound constraints with no failing or unconstrained internal endpoints. Never package a bitstream from a failed gate. Build records go under .timing_runs/; promoting a timing reference requires two passing clean full builds of the same commit. See README_VIVADO.md and scripts/SCRIPTS_README.md.

Multiboot release images rebuild the ARM code against a pinned, audited FSBL and FPGA image. Run vitis -s scripts/build_multiboot_arm.py with the exported platform in APP_MULTIBOOT_PLATFORM and the audited firmware directory in APP_MULTIBOOT_BASELINE. CPU1 is kept from the baseline unless APP_MULTIBOOT_REBUILD_CORE1=1 rebuilds it from source. The package audit checks the product manifest, CRC, partitions and linked memory limits; the Appletini image must fit its 6 MiB slot.

Tests

Source-level regressions are scripts/test_*.py, one per subsystem: the Appli-Card protocol and banking, the Uthernet II, config profiles, the SSC and ImageWriter interpreter, the AXI wrapper, PSRAM capture, and the SSI-263 formant pipeline. Most run with plain Python from the repository root. test_vtw.py is the simulator-backed accelerator gate: pin-level Disk II response timing, raw WOZ read and write at native and accelerated speed, and an end-to-end run from the soft core to the Disk II at every preset. Simulator checks need the Xilinx tools on PATH.

Keep appletini-software beside the firmware checkout, or set APPLETINI_SOFTWARE_ROOT: border/overlay and AD8088 source checks read migrated examples from there. Demo-disk and network-app tests run in the software repository. Focused firmware checks for the newer interfaces include:

python scripts/test_memory_api.py
python scripts/test_memory_api_hw.py
python scripts/test_ps_dma_command.py
python scripts/test_vtw_usb_joystick.py
python scripts/test_usb_gamepad_input.py
python scripts/test_usb_ps4.py
python scripts/test_usb_xbox_one.py
python scripts/test_slot2_card.py
python scripts/test_slot2_gamepad_service.py
python scripts/test_slot2_config_menu.py
python scripts/test_display_scaling.py
python scripts/test_display_modes_rtl.py
python scripts/test_iigs_bus_safety.py
python scripts/test_video_pixel_mask.py
python scripts/test_phasor_ssi_ui.py
python scripts/test_vtw_copy_engine.py

F1.3.0 adds checks for the glyph API, multiboot and the native boot path:

python scripts/test_glyph_api.py
python scripts/test_glyph_transport.py
python scripts/test_glyph_reset.py
python scripts/test_video_rom_service.py
python scripts/test_multiboot_frontend.py
python scripts/test_golden_about_menu.py
python scripts/test_golden_boot_policy.py
python scripts/test_native_slot3_boot.py
python scripts/test_vtw_boot_start.py

test_native_slot3_boot.py runs the actual Enhanced //e reset ROM, ProDOS 2.4.3 slot scan and BASIC.SYSTEM PR#3 against the old and fixed C8 helpers. test_glyph_transport.py drives the shipped SmartPort ROM against the production parser and needs py65 and a C compiler.

Some checks compile native C harnesses; RTL checks require the simulator. Each subsystem README lists its full suite and dependencies. Build and simulation results do not replace a board test.

Hardware behavior is checked over UART and JTAG and through the MCP server below. The firmware console exposes serve-path counters (write queue drops, deadline misses, lost cycles) that must all read zero on a healthy card.

MCP server

tools/mcp/appletini_mcp.py exposes a live Apple II, through the card’s UART, as MCP tools, so an agent or a script can watch and drive the machine.

ToolWhat it does
apple2_screen_textThe live text screen as plain text, 40 or 80 columns. Reads the DDR write-mirror shadow, so zero bus impact, and it works under graphics modes where error messages hide.
apple2_peekHex dump of main RAM from the shadow (writes since power-on).
apple2_soft_switchesDecoded //e soft-switch state and RamWorks bank.
apple2_machine_statusMachine id, aux memory, RamWorks, physical aux-card probe, firmware status.
apple2_healthServe-path counters; all must be zero.
apple2_bus_traceRecent bus cycles from the trace ring.
apple2_menu_keyDrive the Appletini config menu remotely.
apple2_consoleAny firmware console command; help lists them.
pip install mcp pyserial
claude mcp add appletini -e APPLETINI_PORT=COM5 -- python tools/mcp/appletini_mcp.py

The console is a single shared channel, so close any terminal holding the port first. reboot and reset restart the card’s firmware, not the Apple. The server drives the Appletini menu only; it does not inject Apple keyboard input. Shadows mirror writes since power-on, so ROM and never-written RAM read as zero.

Hardware

Rev. A3, designed by Karl ‘KKR75’ Asseily. The schematic is in the repository as schematics/AppleTini_ONE_vA3.pdf. On board: the Zynq-7020, DDR, QSPI flash, PSRAM for cycle capture, level translators to the Apple bus, mini-HDMI out, RJ45 Ethernet, USB-C host, a 3.5 mm audio jack with an optional optical output, a micro-SD slot, a DC power input, and two holes beside it at the top left which, bridged, supply the card’s 5 V from the DC input so it can run standalone or Multitini out of the Apple. In an Apple the slot’s +5 V powers the card. Pin and timing constraints are in hdl/constraints/.

Found a bug, or built something on the card? Open an issue or a pull request on GitHub, or write to us.

For development questions and to share your projects, join the Appletini FPGA Discord.