SearchA-ZP › px68k

px68k

2006 Open source · GPL-2.0 Online & downloadable

px68k is a portable Sharp X68000 emulator derived from Keropi (WinX68k), targeting PSP, Android and other platforms and available as a libretro core for RetroArch. It emulates the 68000 CPU, YM2151 sound and Human68k disk images.

Visit the official site ↗

Runs on: Windows, Linux, macOS, Android, PSP, RetroArch

px68k Online Emulator

Play px68k using JavaScript directly in your browser.

Configurations

ConfigurationEmulatorMachineOSLegal
X68000 (Human68k)px68kSharp X68000greyOpen ⛶

Machines emulated

Chips

Notes

Embedding

px68k is a Sharp X68000 emulator written in C. For the web it is rebuilt from source to WebAssembly with Emscripten (SDL2 backend). You self-host three files - px68k.js (the Emscripten loader/glue), px68k.wasm (the compiled machine), and px68k.data (a preload bundle carrying the X68000 IPL ROM, the CGROM font and a Human68k boot floppy), hand the module a <canvas>, and call main():

var Module = {
  canvas: document.getElementById("canvas"),   // SDL2 renders the X68000 screen here
  arguments: [],
  noInitialRun: true                            // we call main() ourselves after setup
};
PX68K(Module).then(function(m){
  m.callMain([]);                                // boots IPL -> Human68k -> A>
});

The main loop is ours. The native px68k runs an infinite while(1); under Emscripten that would hang the browser, so the one source patch to x11/winx68k.cpp hands the loop body to emscripten_set_main_loop. Each animation frame it runs one X68000 frame (unless a debug-pause flag is set) and pumps SDL keyboard events. The exported control surface:

MemberKindWhat it does
Module._emudbg_pause(1|0)exportSet / clear the paused flag the main loop checks once per frame. Our transport's pause / resume.
Module._emudbg_step_frame()exportRun one whole X68000 video frame (CPU + CRTC + MFP + video). Frame-step, and our boot fast-forward.
Module._emudbg_step_insn()exportRun exactly one 68000 instruction (C68k_Exec(&C68K, 1)). Single-step.
Module._emudbg_reg(i) / _emudbg_set_reg(i,v)exportRead / write one register: 0-7 = D0-D7, 8-15 = A0-A7, 16 = PC, 17 = SR.
Module._emudbg_read(addr)exportSide-effect-free byte of the 24-bit 68000 space (RAM + IPL ROM; 0 in I/O windows).
Module._emudbg_key(sym, down)exportFeed an SDL keycode to px68k's own key handler, driving the on-screen keyboard.
Module._emudbg_reset()exportSoft-reset the machine (re-runs the IPL).

Debugger integration

This is the point of the Tier-4 build: how a COMPILED WebAssembly core gets the same debugger as the pure-JS emulators. px68k keeps its 68000 state inside the wasm heap, unreachable from JS. Rather than serialise it out per frame, we added one small file, emudbg.c, whose functions - when CALLED by the debugger's ~10 Hz refresh loop or the Step button - copy the state out on demand. There is no per-instruction or per-cycle hook; with the debugger closed the core's hot path is untouched.

Where the state lives. px68k's 68000 is the C68K interpreter (m68000/c68k.c), whose whole register file is a single global C struct C68K:

typedef struct c68k_t {
  UINT32 D[8];   // data registers  D0-D7
  UINT32 A[8];   // address regs    A0-A7
  UINT32 flag_C, flag_V, flag_Z, flag_N, flag_X, flag_I, flag_S;
  UINT32 PC;      // program counter
  ...
} c68k_struc;
extern c68k_struc C68K;              // the live CPU - a plain global

What emudbg.c reads. emudbg_reg(i) returns C68K.D[i], C68K.A[i-8], C68K.PC, or the assembled SR via the core's own C68k_Get_Reg(&C68K, C68K_SR); emudbg_set_reg writes them back. emudbg_read(addr) reads the 24-bit bus side-effect-free: main RAM straight from the MEM buffer, the IPL ROM from IPL, and 0 for the I/O windows so auto-polling a memory view can never touch a device. One subtlety the notes must record: px68k stores RAM and ROM byte-swapped (MEM[addr ^ 1]) because the 68000 is big-endian on a little-endian host, so every debug read XORs the address with 1 to hand back bytes in true big-endian order, exactly what the shared m68000 disassembler expects.

What the debugger needsWhere it comes from in the wasm core
D0-D7 A0-A7 PC (writable)C68K.D[], C68K.A[], C68K.PC via emudbg_reg/_set_reg.
SR + T S X N Z V C flagsC68k_Get_Reg(&C68K, C68K_SR); flags are decoded from the SR word in the plug-in.
16 MB 68000 bus / RAM / IPL ROMemudbg_readMEM[a^1] (RAM), IPL[(a-0xFC0000)^1] (ROM), 0 elsewhere.
Disassemblythe shared m68000 decoder pointed at emudbg_read.

Pause / step, and their honesty. Pause and resume flip a single emudbg_paused flag that the patched main loop tests once per frame (never per instruction). Because C68K is an interpreter with a real C68k_Exec(&C68K, cycles) entry point, single-instruction step is exact: emudbg_step_insn() calls C68k_Exec(&C68K, 1), which advances one instruction. The register and memory views change by one 68000 op, unlike the JIT-in-wasm cores whose step is a whole time-slice. emudbg_step_frame() runs one whole video frame for coarse stepping. Breakpoints and watchpoints are surfaced in the UI but not enforced. A native PC check would have to sit in the interpreter's hot dispatch loop, which the golden rule forbids, so they are honestly labelled best-effort rather than pretended exact.

Reaching another compiled core. The recipe is general: find the struct/globals holding the CPU registers and the guest-RAM pointer, add a handful of EMSCRIPTEN_KEEPALIVE functions that copy them out (and a paused flag + a step that calls the core's smallest advance), export them, and read them from JS each refresh. A core that keeps its registers in a plain struct, as C68K does, is fully debuggable from JS with a source change measured in dozens of lines.

Architecture

px68k is a full-system Sharp X68000: the 68000 CPU, the custom CRTC/video (text + graphics planes, sprites/BG), the MFP, DMAC, FDC floppy controller, SASI/SCSI, the RTC, and Yamaha YM2151 (OPM) + ADPCM sound. It boots real X68000 software from floppy images entirely client-side.

  • m68000/c68k.c - the C68K Motorola 68000 interpreter; C68K is the register file, C68k_Exec runs it.
  • x68k/*.c - the machine: crtc/gvram/tvram video, mfp, dmac, fdc/fdd + disk_xdf/disk_dim/disk_d88 floppy, mem_wrap the 24-bit bus.
  • x11/*.c - the SDL front end: windraw (blits the composited 16-bpp screen to the SDL2 window surface), keyboard, winx68k.cpp the main loop (patched for Emscripten).
  • fmgen/*.cpp - the OPM/OPNA/PSG FM sound cores.
  • px68k.data - preloaded into the wasm filesystem: iplrom.dat (X68000 IPL, 128 KB), cgrom.dat (CG/font ROM, 768 KB), MasterDisk.xdf (a Human68k v3.02 master floppy) and its sram.dat.

The default machine is a Human68k command prompt, so the shared 68000 disassembler drives the disasm view correctly across RAM and the IPL ROM.

What was patched to build this (the reproducible bits). Source: github.com/hissorii/px68k. Two source changes only: (1) a new emudbg.c with the sampling hooks above; (2) a small patch to x11/winx68k.cpp - hand the loop to emscripten_set_main_loop, point the ROM directory at the preloaded /home/web_user/keropi, mount MasterDisk.xdf in drive 0, force Config.FrameRate=1 (the browser rAF can dip below realtime and px68k's auto-frameskip would otherwise never composite the screen), and two extern "C" wrappers so the C hooks can call the C++ frame/reset. Build (Emscripten 6.0.3):

# compile every C/C++ unit with the SDL2 port + BSD-type shims
emcc -O2 -fno-strict-aliasing -sUSE_SDL=2 \
     -D_GNU_SOURCE -include sys/types.h -DNO_MERCURY \
     -I./x11 -I./x68k -I./fmgen -I./win32api -c <each .c/.cpp>
# link, exporting the emudbg_* hooks + the malloc/runtime helpers JS needs
em++ *.o -o px68k.js -sUSE_SDL=2 -sALLOW_MEMORY_GROWTH=1 \
     -sMODULARIZE=1 -sEXPORT_NAME=PX68K -sINVOKE_RUN=0 -sFORCE_FILESYSTEM=1 \
     -sEXPORTED_FUNCTIONS=_main,_emudbg_reg,_emudbg_set_reg,_emudbg_pc,\
_emudbg_read,_emudbg_read_block,_emudbg_pause,_emudbg_step_frame,\
_emudbg_step_insn,_emudbg_key,_emudbg_reset,_malloc,_free \
     -sEXPORTED_RUNTIME_METHODS=ccall,cwrap,getValue,setValue,HEAPU8,HEAPU32,callMain \
     --preload-file rom@/home/web_user/keropi