Hardware API

The host talks to the Pico over USB-CDC at 115200 baud using a framed serial protocol. All payloads are JSON with "v":1 — including the chunked, base64-encoded ROM upload. An optional "id" is echoed back in the matching response. This is designed for scripted bring-up and CI.

Framing

Every transaction follows the same byte sequence. The receiver always sends ACK (or NACK on error) after EOT — whether the host or the Pico is sending:

Sender                         Receiver
  ENQ (0x05)          ────────►
  STX (0x02)          ────────►
                      ◄────────  ACK (0x06)     ← receiver ready for payload
  payload bytes       ────────►
  EOT (0x04)          ────────►
                      ◄────────  ACK (0x06) or NACK (0x15)   ← accepted/rejected

Byte

Value

Meaning

ENQ

0x05

Start frame

STX

0x02

Start payload

ACK

0x06

Ready / accepted

EOT

0x04

End payload

NACK

0x15

Rejected (bad frame, unknown command, or payload too large)

Important

Do not open a plain serial monitor on the port while using the Hardware API — unstructured output corrupts framing. Only one process may hold the port at a time. The monitor command also prints unstructured ASCII, and that state persists on the Pico until you disable it or start a read capture (which auto-disables it).

Commands

All commands are JSON sent in a framed payload (host → Pico), and every request includes "v":1. The Pico responds with a framed JSON payload (Pico → host).

Command

Request

Response

reset

{"v":1,"cmd":"reset","assert":true} or "assert":false

{"v":1,"ok":true,"cmd":"reset","asserted":true}

upload_rom

beginchunk (base64) × N → commit

per-phase acks; commit returns reset_vector

read

{"v":1,"cmd":"read","until":"stp","max_cycles":10000}

event stream then {"type":"event","event":"done",...}

request_addr

{"v":1,"cmd":"request_addr"}

{"v":1,"ok":true,"cmd":"request_addr","addr":"4000","phi2_hz":0.2}

monitor

{"v":1,"cmd":"monitor","enable":true}

enables/disables the ASCII bus table (off by default)

status

{"v":1,"cmd":"status"}

full hardware snapshot (clock, reset, ROM, monitor)

reset

Assert or release the 6502 RESET line (GP27). Use "assert":true to hold the CPU in reset, "assert":false to let it run.

upload_rom

A JSON-only chunked transfer (1476 raw bytes per chunk, base64-encoded):

  1. {"v":1,"cmd":"upload_rom","action":"begin","size":32768}

  2. {"v":1,"cmd":"upload_rom","action":"chunk","offset":0,"data":"<base64>"} — repeat until all 32 KB have been sent.

  3. {"v":1,"cmd":"upload_rom","action":"commit"}{"v":1,"ok":true,"reset_vector":"8000",...}

RESET is asserted for the duration of the upload so the CPU cannot fetch half-written ROM data, then released on commit.

read

Captures bus activity as JSON. Streams one event per PHI2 rising edge until the CPU fetches STP (0xDB on a read cycle) or max_cycles is reached. Each cycle event:

{"v":1,"type":"event","event":"cycle","seq":1,"addr":"8000","data":"18","rw":0}

Final event:

{"v":1,"type":"event","event":"done","ok":true,"reason":"stp","cycles":14,"addr":"800D"}

To use this in automated tests, end your ROM with a STP (0xDB) instruction (not BRK — that opcode is 0x00). Starting a read automatically disables the ASCII monitor on the Pico.

At the default 0.2 Hz clock, cycle events arrive about once every 5 seconds. The Romulan host client waits up to 12 s per frame. A full capture from reset through STP typically takes 10–120 s depending on program length.

request_addr

Returns the last address sampled on the bus (updated every PHI2 rising edge).

status

Returns a full hardware snapshot — clock frequency, reset state, ROM active flag, and whether the ASCII monitor is enabled — in a single JSON response.

monitor

Toggles the human-readable ASCII bus table (disabled by default). Mutually exclusive with scripted capture: table rows contain | (0x7C) and other bytes that collide with framed protocol traffic. Disable it before upload/read:

api.monitor(enable=False)

Example output:

| 01 |  18  |  8000   |  0 |  0.2  |

Use read (JSON cycle stream) for automated tests; reserve monitor for manual breadboard observation.