Skip to content
mblshaPublic

About

Binary Ninja plugin for the SC62015 architecture

Resources

Stars

4 stars

Watchers

1 watching

Forks

Repository files navigation

Binary Ninja ESR-* plugin

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.

OZ-9600 v1 profile

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):

Initialization and Welcome Schedule, Notebook, Calculator and Scrapbook Built-in application tour

Acknowledgements

Overall structure of instruction logic based on binja-avnera plugin by @whitequark.

License

Apache License 2.0.

Development

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-e500

Notes:

  • Use --model iq-7000 to switch ROM/profile.
  • The default --mode interactive uses nominal compatibility pacing, not hardware-calibrated speed. Use --mode turbo for unthrottled bounded execution. --mode deterministic --steps 1000000 requires an explicit finite budget and a fixed IQ RTC seed (or off); live guest keys are disabled in that mode, but Ctrl+C still exits. The headless pce500 runner 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 off for 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 30 for normal LCD refresh cadence. --refresh-steps 20000 is a maximum boundary budget between loop checks, independent of the host display clock.
  • Use --input-steps 1000 to 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-diagnostics to opt into instruction-history/loop detection.
  • Use --no-alt-screen for tmux capture panes.
  • Use --force-tty when running detached.
  • Use --pf-numbers to map digits 1–5 to PF1–PF5 (disables typing those digits).
  • Use --bnida PATH to show function names in the status line (defaults to rom-analysis/.../bnida.json if present).
  • --force-key-irq is 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|absent to control memory card slot state. auto selects 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-state for 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:0x40

Ordinary 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.yaml

For 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.

Native runner command reference

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 CoreRuntime is the default. Use --runtime legacy only 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 (uses data/pc-e500-en.bin). Select IQ-7000 with --model iq-7000 or 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 to BS, Ctrl+C exits.
    • Use --no-alt-screen for tmux capture panes, --force-tty when running detached, --input-steps to reduce input latency, --pf-numbers to map digits 1–5 to PF1–PF5, --bnida PATH to show function names in the status line, --force-key-irq to force KEY interrupts if the ROM stays halted, and --card present|absent to 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.

About

Binary Ninja plugin for the SC62015 architecture

Resources

Stars

4 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages