The ESR plugin provides an SC62015 (aka ESR-L) architecture for Binary Ninja.
Currently it only works as a crude disassembler, with the goal to lift all the instructions and create memory mapping for Sharp PC-E500 and Sharp Organizers.
For the older CPU family, see the ESR-H / SC61860 references and implementation plan. It links existing disassembler and CPU sources, records instruction-description discrepancies, and tracks the decoder, execution and organizer/card work still needed.
The shared Rust core, normal pce500 --model oz-9600 runner and web frontend
accept a verified OZROM01 bundle. The controller exports its full 336 × 240
working frame; fixed labels to the left are separate artwork. Physical LCD
crop, SRAM aliases, reset/SFR behavior and clocks remain unqualified.
The CLI defaults to strict. Select --oz9600-profile provisional-v1 for
empty-SRAM initialization and ordinary organizer use; the browser defaults to
that profile for OZ-9600. Before execution it selects initial CPU BP=D0 for
all-zero SRAM, including a saved empty image, and BP=00 for populated backing.
The ROM performs initialization and installs its own callbacks. This reset
assumption and the arithmetic, transfer, IRQ and nominal RTC policies remain
provisional. Historical experimental profiles remain available for diagnosis.
Logical RAM/RTC exports preserve records across reload and Reset; they are
backing for a fresh CPU, not complete running snapshots.
Schedule creation/recovery, Notebook editing, Calculator arithmetic, Scrapbook drawing/storage and a built-in application tour have native/browser proofs. Calendar entry into a saved Schedule can leave its text blank; ENTER to Index and ENTER back to View redraws the intact record. Exact physical timing, automatic alarm wake, complete peripherals and application edge cases remain follow-up work.
experimental-on-edge retains the MTI-writable basis and asserts ONKI once
per physical press, while SSR continues to report the held ON level. Native
and WASM ROM workflows now cancel idle PC Link and reopen a stored Telephone
record. This opt-in hypothesis does not qualify physical ON/RTC power wiring
or cancellation during an active transfer; historical profiles retain their
previous assertion behavior.
The native bitmap window uses the same
model factory and physical matrix/tablet input. Its optional OZ-707 ROM/SRAM
files launch BASIC and its guest-rendered touchscreen keypad. The normal CLI
accepts the same media with --oz9600-card-rom, --oz9600-card-sram and
--oz9600-card-sram-out. Card presence and memory aliases remain provisional;
the strict machine default is unchanged.
Representative controller frames from normal ROM execution under
provisional-v1 (acquired firmware is distributed separately):
Overall structure of instruction logic based on binja-avnera plugin by @whitequark.
Apache License 2.0.
Install dependencies using uv and run the checks:
# Install uv (if not already installed)
curl -LsSf https://astral.sh/uv/install.sh | sh
# or on macOS: brew install uv
# Install all dependencies and create virtual environment
uv sync --extra dev --extra pce500
# Run linting and formatting
uv run ruff check .
uv run ruff format .
# Run type checking
uv run pyright sc62015/pysc62015
# Run tests with coverage
FORCE_BINJA_MOCK=1 uv run pytest --cov=sc62015/pysc62015 --cov-report=term-missing --cov-report=xml
```
The CI workflow uploads coverage results to Codecov on each commit.
## CLI emulator (terminal LCD)
The Rust LLAMA CLI is the primary emulator core. See the
[typed-state and JSON boundary migration report](docs/rust_json_boundary_migration.md)
for internal representations, compatibility and validation evidence.
Run it with a terminal-rendered LCD view:
```bash
cargo run --release --manifest-path sc62015/core/Cargo.toml --bin sc62015-lcd -- --model pc-e500Notes:
- Use
--model iq-7000to switch ROM/profile. - The default
--mode interactiveuses nominal compatibility pacing, not hardware-calibrated speed. Use--mode turbofor unthrottled bounded execution.--mode deterministic --steps 1000000requires an explicit finite budget and a fixed IQ RTC seed (oroff); live guest keys are disabled in that mode, but Ctrl+C still exits. The headlesspce500runner remains unpaced. - IQ-7000 date/time screens seed the CLOCK workspace from the host clock by default; use
--iq7000-rtc YYYYMMDDHHMM(UTC) for deterministic captures or--iq7000-rtc offfor raw ROM behavior. The RTC peripheral converts UTC to the ROM's UTC+13 hardware basis before the World/Home city-offset code runs; the ROM applies its separate per-city summer-time flag. The RTC ticks across RUN/HALT/OFF, implements the ROM's F0/F1/F2/F4/F5/F6/F8 alarm protocol, and models due schedule/daily alarms as a held ON/power-wake level acknowledged by FA/FF. The physical wiring, exact timing, F7/FC/FD fields, and F9/FB effects remain provisional. - Use
--target-fps 30for normal LCD refresh cadence.--refresh-steps 20000is a maximum boundary budget between loop checks, independent of the host display clock. - Use
--input-steps 1000to poll for key presses more frequently. - Terminal execution also checks a 4 ms host-yield target between small Rust scheduler batches; long individual instructions can exceed that target.
- Use
--loop-diagnosticsto opt into instruction-history/loop detection. - Use
--no-alt-screenfor tmux capture panes. - Use
--force-ttywhen running detached. - Use
--pf-numbersto map digits 1–5 to PF1–PF5 (disables typing those digits). - Use
--bnida PATHto show function names in the status line (defaults torom-analysis/.../bnida.jsonif present). --force-key-irqis a diagnostic override that forces KEY interrupts; do not use it as evidence that ordinary keyboard input or HALT wake is correct.- Use
--card auto|present|absentto control memory card slot state.autoselects a blank writable PC-E500 card and an absent IQ-7000 card. - PC-E500 keys: Ctrl+1..5 or F1..F5 → PF1..PF5, Enter →
=, Backspace →BS. - Ctrl+P pauses/resumes after machine acknowledgement; Ctrl+C exits. Slow terminal output is isolated from machine execution and priority controls.
- Use
--debug-statefor call-stack/keyboard details; expensive diagnostics are off by default. Browser decoded-text and call-stack panels are also opt-in.
The Rust responsiveness v1 acceptance record and follow-ups cover bounded execution, controls/input, pacing, and current limitations, including partial native IQ-7000 key mappings. Faster-than-realtime throughput alone is not proof of responsive interaction.
The headless runner (--bin pce500) also supports reusable IQ-7000 probe captures:
cargo run --manifest-path sc62015/core/Cargo.toml --bin pce500 -- \
--model iq-7000 \
--runtime core \
--key-seq "memo,text:PASSPORT NO.\\nM6711888\\nEXPIRES 12/25/90,memo-enter,memo,search-down" \
--capture-png /tmp/iq7000-a0.png \
--capture-json /tmp/iq7000-a0.json \
--debug-probe-json /tmp/iq7000-a0.debug.json \
--debug-probe-range storage@0x1fd00:0x40Ordinary headless execution uses the shared CoreRuntime, matching the WASM,
terminal, and IQ-7000 PC-Link frontends. PC stop/trace, final LCD provenance,
structured memory probes, and exact bounded LCD-write logging now run on that
same scheduler. Historical trace-replay boot overlays, raw external-bus
transaction logging, and snapshots still require an explicit legacy runtime;
unsupported combinations fail instead of silently changing schedulers.
Runtime cycle_count advances in the SC62015 relative timing units documented
by the instruction table: a NOP is one unit, conditional/counting forms use
their selected path and initial I, and each fused PRE adds one unit. Forms
without a complete published total (the D8-DB transfer direction, HALT/OFF
entry, IR, and RESET) retain explicitly provisional compatibility values.
These units are not calibrated oscillator cycles: PC-E500 absolute timer
cadence and IQ-7000 timing still require machine-level qualification. CLI
timing diagnostics label this basis explicitly.
CoreRuntime::step_scheduler_boundaries(n) is the unambiguous execution API.
The shorter step(n) remains a compatibility alias. A running boundary usually
retires one instruction, while HALT/OFF can consume a boundary without retiring
one; neither argument is a timing-unit or wall-clock duration. Use
instruction_count() for retired work and cycle_count() for relative timing.
SCR.MTS selects the 4 ms or 16 ms main-timer compatibility period and
SCR.STS selects the approximately 0.5 s or 2 s sub-timer period. Changing a
selector currently starts a fresh period; exact divider phase at an SCR
write remains a hardware-timing question.
The Rust SIO bridge also advances from these retired-instruction timing units. RX-ready and TX-complete status therefore no longer depend on when a TCP/WASM host polls the bridge. Its delay constants remain a functional compatibility model, not a measured baud-rate model. Historical replacements for three PC-E500 serial ROM entries are disabled by default and available only through an explicit diagnostic opt-in; normal model runs execute the ROM and never manufacture a serial peer response.
Keyboard ingress is likewise explicit. Physical matrix contacts affect KIL
and can assert ISR.KEYI only while their column is selected. Translated host
events (for example digitizer samples) enter the host event FIFO without
changing KIL or manufacturing a silicon interrupt. The legacy combined
immediate-matrix/FIFO injection remains available only as a diagnostic helper.
The Python CPU(..., backend="llama") facade and pce500/run_pce500.py remain
CPU differential/parity tools. Their machine scheduler and peripheral callbacks
are Python-owned, so they are not another Rust machine runtime and should not be
used to benchmark or qualify the shared emulator. backend_stats() reports
execution_scope=cpu-only and scheduler_owner=python-caller to make this
boundary visible to tooling.
digitizer:0xNN and event:0xNN still inject exact translated input bytes. For
IQ-7000 app/editor work, the runner also accepts named event keys such as
calendar, memo, tel, home, world, shift, caps, caps-off,
search-up, search-down, newline, and memo-enter. caps-off injects the
CAPS key once; the IQ-7000 ROM starts with CAPS enabled, so include it before
lowercase/mixed-case text entry on a freshly booted image. text:... expands
printable characters through the generated per-model input map; use \\n inside
text to emit the MEMO newline key. IQ-7000 PNGs and the live web display share
the full right-hand fixed-segment renderer. It decodes the four LCD shadows,
not the potentially stale workspace copy. BATT/CARD/beep/alarm/arrows remain
provisional physical assignments, explicitly labelled in capture metadata.
See IQ-7000 LCD segments for the mapping,
confidence limits, and full-display capture API.
The live terminal LCD and browser share a model-specific physical key map.
On IQ-7000, F1–F8 select Calendar/Schedule/TEL/MEMO/Calc/Card/World/Home;
F9 is SHIFT, F10 is CAPS, F11 is newline, and F12 is ON. Enter stores;
Page Up/Down searches. A–Z, digits, arrows and edit keys use matrix contacts,
not translated FIFO events. Device CAPS controls case. Terminal input is a
serialized assisted keycap stream, not a faithful host-key hold/release stream.
The status line shows all thirteen candidate flags and preserves unknown bits.
See Rust input correctness for controls,
real-ROM acceptance, the PC-E500 BASIC blocker, and remaining limitations.
In the browser, click the device or choose ⋯ → Keyboard focus, then Resume for live
input. Letters & symbols follows your keyboard layout and accepts shifted
operators without a numeric keypad; Device keycaps preserves positional
keys and raw host Shift. F9 always operates device SHIFT; device CAPS owns case.
Hover a key for its host bindings. Host shortcuts and text fields are excluded.
⋯ → Shortcuts shows a compact reference. Depressed keys follow delivered
Rust contacts; cyan outlines indicate host-held keys. Save LCD PNG exports
actual display pixels; optional matching metadata is available separately.
Paste text… previews a qualified key sequence before submission. Unknown
characters block the whole paste; CAPS controls case and PC newlines can execute
commands. Long pastes are bounded and cancellable, with no RAM/FIFO injection.
Fast typing is buffered in order through real contacts. Device pace is the
non-accelerated default; Responsive briefly accelerates scan/release work
(including emulated RTC time). Pausing freezes the queue; Cancel queued keys cancels
it. No keys are inserted into the ROM keyboard FIFO.
The browser now uses reference-based physical case/key layouts for both devices.
See device appearance and scan handoff for the
visual audit, unmapped-button policy, and the path to measured/photo/3D assets.
The device-first UI checklist tracks the current
minimal-UI work and its remaining input, session and validation milestones.
CoreRuntime::set_external_interrupt_level is currently a neutral API/test
hook. Its level-sensitive EXI re-latch policy is an explicit emulator model
contract, not a measured device fact. Neither command-line frontend exposes a
host switch for that input yet.
See docs/sc62015_runtime_evidence.md for
the concise boundary between real-device-derived instruction behavior,
ROM-grounded interrupt dispatch, provisional machine timing/peripherals, and
implementation-only safeguards.
For repeatable captures, put the same settings in a scenario JSON file:
{
"model": "iq-7000",
"steps": 3000000,
"key_seq": [
"memo",
"text:PASSPORT NO.\\nM6711888\\nEXPIRES 12/25/90",
"memo-enter",
"memo",
"search-down"
],
"capture_png": "memo.png",
"capture_json": "memo.json",
"debug_probe_json": "memo.debug.json",
"debug_probe_range": ["storage@0x1fd00:0x40"]
}Run it with --scenario path/to/scenario.json. Relative capture/debug paths are
resolved relative to the scenario file.
The web/WASM Function Runner has the same deterministic IQ-7000 RTC seeding for screen probes:
cd web
npm run fnr:cli -- --model iq-7000 --iq7000-rtc 202604261330 --eval '
await e.step(100000);
await e.keys.app.tap("calendar");
await e.wait.screenChange();
await e.wait.lcdStable();
const calendar = await e.lcd.assertCalendarMonth({ year: 2026, month: 4, day: 26 });
const proof = await e.proof.metadata({ label: "calendar-apr-2026", assertions: { calendar } });
return { lines: await e.lcd.text(), proof };
' --proof-yaml calendar-proof.yamlFor new Function Runner scripts, prefer the explicit key namespaces:
e.keys.app.tap("calendar") for app selectors, e.keys.event.tap(0x18) for
ROM-visible translated events, and e.keys.phys.tap(0xNN) for raw physical
matrix/scanner codes. e.lcd.assertCalendarMonth(...) validates the compact
calendar day-number pixels directly, while e.lcd.text() now decodes those
same compact monthly-calendar day rows for readable CI output. --proof-yaml
writes concise YAML metadata with the RTC seed, ROM path, assertions, LCD text,
and pixel signature.
Commands are relative to the repository root.
- Rust CLI runner (primary): To boot the ROM and view decoded LCD text:
cargo run --manifest-path sc62015/core/Cargo.toml --bin pce500 -- --steps 20000- The shared
CoreRuntimeis the default. Use--runtime legacyonly for specialized historical replay/raw-bus diagnostics that report it is required. - Optional LCD logging:
RUST_LCD_TRACE=1 RUST_LCD_TRACE_MAX=2000 ... - Default ROM model:
pc-e500(usesdata/pc-e500-en.bin). Select IQ-7000 with--model iq-7000or pass--rom PATH.
- LCD terminal UI: A live terminal renderer that redraws decoded LCD lines on change:
cargo run --manifest-path sc62015/core/Cargo.toml --bin sc62015-lcd -- --model pc-e500- Keyboard: Ctrl+1..5 or F1..F5 map to PF1..PF5, Enter maps to
=, Backspace maps toBS, Ctrl+C exits. - Use
--no-alt-screenfor tmux capture panes,--force-ttywhen running detached,--input-stepsto reduce input latency,--pf-numbersto map digits 1–5 to PF1–PF5,--bnida PATHto show function names in the status line,--force-key-irqto force KEY interrupts if the ROM stays halted, and--card present|absentto control the memory card slot state.
The OZ-9600 external alarm wake experiment provides an opt-in wake policy, launch commands and its current qualification limits.
Measured OZ-9600 CPU and hardware contracts, with reuse limits for PV-F1 and related models, are indexed in the hardware research guide.


