Timex.Emu is an emulator for the Timex 2048 — the author's first computer. The shared Z80 / tape / screen / keyboard modules also power a sibling ZX Spectrum 128 machine (see ZX Spectrum 128 support below).
Folder structure:
docs:
- Z80 CPU Manual
- Z80 CPU Peripherals
- Complete ROM
python:
timex.py— entry for the Timex 2048 (callsemulate.py --machine=timex2048).emulate.py— shared entry point; accepts--machine=<name>to pick a target (defaulttimex2048, alsospectrum128). All flags below work with either entry unless noted.machines/— per-machine wiring (timex2048.py,spectrum128.py).
Example command to load a user program:
python3 timex.py --program=helloworld.bin --startAt=8000 --breakAt=8000 --mapAt=8000 --hook-system
This loads user program helloworld.bin (--program) at 0x8000 (--mapAt), putting a break point at 0x8000 (--breakAt) and starting execution from 0x8000 too (--startAt). Additionally system function (i.e. print) are being hooked with python replacement.
To run with display (requires pygame-ce):
python3 timex.py
To run without display:
python3 timex.py --no-display
Keyboard mapping (PC → Timex 2048):
| PC Key | Timex 2048 |
|---|---|
| A-Z, 0-9 | Same |
| Enter | ENTER |
| Space | SPACE |
| Left/Right Shift | CAPS SHIFT |
| Left/Right Ctrl | SYMBOL SHIFT |
Common combinations:
Shift + 0— DELETE (backspace)Ctrl + P— " (double quote)Ctrl + Z— : (colon)Ctrl + N— , (comma)Ctrl + Symbol— ; (semicolon)
BASIC input modes:
- K mode (cursor shows
K) — default after ENTER. Single keypress gives keywords (e.g.P= PRINT,G= GOTO). Switches to L mode after first keyword. - L mode (cursor shows
L) — lowercase letter input. Entered automatically after typing a keyword in K mode. - C mode (cursor shows
C) — uppercase letters. Toggle with CAPS LOCK (Shift + 2). - E mode (cursor shows
E) — extended keywords. Enter by pressingShift + Ctrltogether. Then press a key (with or withoutCtrl) to get extended keywords like BEEP, INK, PAPER, etc. - G mode (cursor shows
G) — graphics characters. Enter withShift + 9.
Emulator keys:
F1— pause / resumeF2— open interactive debugger (in terminal)F5— soft reset (clears RAM, reloads ROM, rewinds tape)F6— tape play/stop (for multi-load games)F8— save state to .z80 file (load back with--z80=state_xxxx.z80)F11— toggle CRT scanline filterF12— save screenshot as PNGTab(hold) — turbo mode (fast-forward)Backspace(hold) — rewind time (~50 seconds buffer)Arrow keys— Kempston joystick directionsRight Alt— Kempston joystick fire
Example — typing BEEP 1,0:
- Make sure you're in K mode (press ENTER if needed)
Shift + Ctrl(enter E mode)Ctrl + Z(BEEP keyword)Space,1,Ctrl + N(comma),0Enter(execute)
Loading games from .tap files:
python3 timex.py --tape=game.tap
Then type LOAD "" (press J then Ctrl+P twice) and press Enter.
Loading .z80 snapshots (instant, no LOAD needed):
python3 timex.py --z80=game.z80
Additional options:
--scale=3— scale display (default 2x, use 3 or 4 for hi-res monitors)--debug— enable periodic PC/register logging to terminal--no-display— run headless (no pygame window)--attach-logger— attach the instruction logger at startup--tape-mode=trap|pulse— tape emulation strategy.trap(default) intercepts the ROMLD-BYTESroutine and injects blocks instantly.pulsestreams T-state-accurate EAR edges on port0xFEbit 6, loads in real time, and is the mode needed by turbo / custom loaders (e.g. multi-load games).
Sound: the ULA beeper (port 0xFE bit 4) is emulated and mixed to audio output via pygame — BEEP commands and in-game effects are audible.
Debugger commands (press F2 to enter):
| Command | Description |
|---|---|
ir |
8-bit registers |
ir16 |
16-bit registers |
if |
CPU flags |
d [0xADDR] |
disassemble at address (default: PC) |
m 0xADDR |
hex dump memory |
pram 0xADDR |
print single byte from RAM |
b 0xADDR |
set breakpoint |
bc 0xADDR |
clear breakpoint |
bd 0xADDR |
disable breakpoint (keep but skip) |
bl |
list breakpoints |
s |
single step |
n |
step over (skip into CALL/RST) |
c |
continue |
t |
print timing info (m-cycles, t-states) |
trace on/off |
enable/disable execution trace |
trace [n] |
show last n executed instructions |
stack [n] |
show stack entries (default 8) |
log |
attach/detach instruction logger |
? |
debugger help |
q / exit |
quit |
rom:
- Binary file containing ROM of actual machine
tests:
- a suite of unit tests
- running tests:
python3 -m unittest discover.
- running tests:
Note
To run zexall or zexdoc tests suite set ZEXALL or ZEXDOC environment variable to True respectivly
export ZEXALL=True
python3 -m unittest tests_cpu.tests_cpu.test_zexall
python3 -m unittest tests_cpu.tests_cpu.test_zexdoc
The emulator also runs as a ZX Spectrum 128 (sometimes called "Spectrum 128K" or, with Amstrad's +2, the grey-case machine). Most of the codebase is machine-agnostic — the CPU, screen rendering, keyboard, tape, and snapshot loader are shared. A dedicated Spectrum128Machine adds banked memory (8 × 16K RAM pages + 2 × 16K ROM banks), port 0x7FFD paging, and the 3.5469 MHz frame timing.
Launching:
python3 emulate.py --machine=spectrum128 --scale=3
ROM layout — drop the 128K ROM at rom/128.rom, either as:
- a single 32K file (bank 0 first = editor/menu ROM, bank 1 second = 48K BASIC), or
- a 16K file for bank 0 only; in that case a sibling
rom/128.1.romis loaded as bank 1 if present.
The standard Sinclair 128K ROM and the Amstrad +2 ROM both work. The +2A/+3 (black case, four ROMs + extra port 0x1FFD) is not supported.
Menu navigation (the "128 BASIC / Calculator / Tape Loader / Tape Tester / 48 BASIC" screen):
Shift + 7— cursor upShift + 6— cursor downEnter— select
Loading games:
.tapfiles work the same as on Timex (--tape=... --tape-mode=pulsefor custom loaders)..z80snapshots of 128K games work via--z80=<file>. The loader detects 128K hardware (v2 hw=3|4, v3 hw=4|5|6), fans pages into the right banks, and applies the port0x7FFDlatch from byte 35.
Current status:
- Boots to the 128 menu, 128 BASIC, and 48 BASIC.
- Runs 128K snapshots (e.g. Commando).
- AY-3-8912 sound on ports
0xFFFD/0xBFFD: three tone channels, mixer, logarithmic amplitude, 17-bit LFSR noise, and full envelope generator (all 16 shapes). F8state saving is a no-op on Spectrum 128 for now (no 128K .z80 writer yet).
Screenshots from games running in the emulator. Press F12 in-emulator to save a PNG (written to the current working directory as screenshot_<frame>.png); move captures into screenshots/ and reference them below.
| Game | Screenshot |
|---|---|
| Bruce Lee | ![]() |
| Knight Lore | ![]() |
| Commando | ![]() |
| Bomb Jack | ![]() |



