Python API Reference¶
Auto-generated from module docstrings.
Assembler¶
Minimal two-pass 6502/65C02 assembler.
Parses mnemonic assembly source into a mapping of CPU address -> byte —
the same shape produced by romulan.build_rom.parse_hex_file() — so
romulan.build_rom.build_rom() can consume either input format. The
input format is detected by romulan.build_rom.detect_format().
Supported syntax (all origins are CPU addresses, not file offsets):
; comment
label: ; alone on a line or before an instruction
.org $8000 ; set origin (must be in $8000-$FFFF)
.byte $EA, 5, label ; raw bytes, comma separated
.word $8000, label ; little-endian words (interrupt vectors)
CLC ; implied
ASL / ASL A ; accumulator
LDA #$05 ; immediate
LDA $10 ; zero page (numeric operand < $100)
STA $4000 ; absolute (numeric operand >= $100)
LDA $10,X ; zero page,X LDA $1234,Y absolute,Y
JMP ($1234) ; indirect (JMP only)
LDA ($10,X) / ($10),Y ; (zp,X) and (zp),Y
LDA ($10) ; 65C02 (zp) indirect
BNE loop ; relative branch to a label or address
Numbers may be written as $hex, 0xhex or decimal. When an operand
is a label it is always encoded in absolute (16-bit) form, because labels
can only refer to ROM addresses ($8000-$FFFF). A numeric operand narrower
than $100 uses the zero-page form when the mnemonic provides one, and the
absolute form otherwise (so JMP $0000 encodes as 4C 00 00).
Instruction set: all 56 official NMOS 6502 mnemonics plus the W65C02 additions used in the course (BRA, PHX, PHY, PLX, PLY, STZ, TRB, TSB, STP, WAI, accumulator INC/DEC, BIT immediate/indexed, (zp) indirect).
If no .org precedes the first byte, assembly starts at $8000. All
emitted bytes must land in the ROM region $8000-$FFFF; operand values
(e.g. the target of JMP $0000) are unrestricted.
Errors raise ValueError with a line number, mirroring
romulan.build_rom.parse_hex_file().
- romulan.assemble.parse_asm_file(path)[source]¶
Assemble a 6502 assembly source file into a CPU address -> byte map.
Two passes: the first collects labels and computes statement addresses; the second emits bytes, resolving label operands and checking ranges.
- Parameters:
path (Path) – Path to the assembly source file.
- Returns:
A mapping from CPU address ($8000-$FFFF) to byte value.
- Raises:
ValueError – On syntax errors, unknown mnemonics, unsupported addressing modes, undefined or duplicate labels, branches out of range, or bytes emitted outside the ROM region.
- Return type:
dict[int, int]
ROM builder¶
Build a 32 KB ROM image for the Pico-as-ROM 65C02 system.
The 32 KB image maps to CPU addresses $8000-$FFFF. File offset $0000 = CPU address $8000 File offset $7FFC = CPU address $FFFC (reset vector low byte) File offset $7FFF = CPU address $FFFF
- exception romulan.build_rom.InvalidInstructionError(message)[source]¶
Bases:
ExceptionRaised when a byte sequence contains an undefined 65C02 opcode.
- exception romulan.build_rom.SkippedInstructionError(message)[source]¶
Bases:
ExceptionRaised when a required instruction address is missing from the ROM dump.
- romulan.build_rom.cpu_to_offset(cpu_addr)[source]¶
Convert a CPU address ($8000-$FFFF) to a file offset (0-$7FFF).
- Parameters:
cpu_addr (int) – A 65C02 address in the ROM region.
- Returns:
The corresponding byte offset in a 32 KB ROM file.
- Raises:
ValueError – If
cpu_addris outside$8000–$FFFF.- Return type:
int
- romulan.build_rom.detect_format(path)[source]¶
Detect the input format of a ROM source file.
Sniffs the first meaningful line (skipping blank lines and full-line
;or@comments): if it matches the annotated hex dump shape (0xADDR 0xBYTE) the file is a hex dump, otherwise it is treated as 6502 assembly. File extensions are not consulted — either format may live in a.txtor.sfile.- Parameters:
path (Path) – Path to the input file.
- Returns:
"hex"for an annotated hex dump or"asm"for 6502 assembly.- Raises:
ValueError – If the file contains no data lines.
- Return type:
str
- romulan.build_rom.parse_hex_file(path)[source]¶
Parse an annotated hex dump file into a dict of CPU address -> byte.
Expected line format:
0x0000 0x18 @ CLC 0x0001 0xA9 @ LDA 0x5
File addresses are in the range
0x0000–0x7FFFand are mapped to CPU addresses by addingROM_BASE_ADDR(0x8000). Everything after@on a line is treated as a comment and ignored.- Parameters:
path (Path) – Path to the annotated hex dump file.
- Returns:
A mapping from CPU address to byte value.
- Raises:
ValueError – If a line cannot be parsed or an address is out of range.
- Return type:
dict[int, int]
- romulan.build_rom.verify_instructions(data, error_list)[source]¶
Validate a contiguous instruction stream and append errors.
Walks opcode lengths so operand bytes are never treated as opcodes. Errors are appended to
error_listandERROR_COUNTERis incremented.- Parameters:
data (List[int]) – Contiguous program bytes (not including reset/IRQ vector slots).
error_list (List) – List that receives human-readable error messages.
- Return type:
None
- romulan.build_rom.verify_instruction_order(data, error_list)[source]¶
Check that file addresses are contiguous (no gaps before the vectors).
- Parameters:
data (List[int]) – File-offset addresses from the parsed ROM dump.
error_list (List) – List that receives human-readable error messages.
- Return type:
None
- romulan.build_rom.error_processing(data_dict)[source]¶
Run opcode and address-order validation on a parsed ROM dump.
- Parameters:
data_dict (Dict[int, int]) – Mapping of CPU address to byte value from
parse_hex_file().- Returns:
A list of human-readable error messages (empty when validation passes).
- Return type:
List
- romulan.build_rom.build_rom(input_path, output_path, verbose=False)[source]¶
Parse a hex dump file and write a 32 KB ROM binary.
The input may be an annotated hex dump or 6502 assembly (auto-detected by
detect_format()). Hex dumps get opcode and address-order validation before writing; assembled input is valid by construction and may contain gaps from.orgdirectives, so that validation is skipped for it. Unused bytes are filled with$EA(NOP). The reset and IRQ/BRK vectors at$FFFC–$FFFFmust be present in the input or the build fails.- Side effects:
Resets
ERROR_COUNTER, may print errors and callsys.exit(1), creates parent directories foroutput_path, writes the binary file, and prints a summary to stdout. WhenverboseisTrue, structured NDJSON events are emitted to stderr.
- Parameters:
input_path (Path) – Path to the annotated hex dump or 6502 assembly file.
output_path (Path) – Destination path for the 32 KB
.binfile.verbose (bool) – If
True, emit structured build progress to stderr.
- Raises:
ValueError – If parsing fails or required vectors are missing.
- Return type:
None
Plain-text upload¶
Serial port discovery for the Pico-as-ROM 65C02 firmware.
ROM upload uses the framed v1 Hardware API
(romulan.hardware_api.HardwareAPI.upload_rom()). This module only
auto-detects the Pico USB-CDC port for the CLI and client helpers.
- romulan.upload_rom.find_pico_port()[source]¶
Auto-detect a Raspberry Pi Pico serial port.
Tries USB vendor ID
0x2E8A(Raspberry Pi) first, then falls back to common port name patterns on Linux, macOS, and Windows.- Returns:
The device path of the detected Pico (e.g.
/dev/ttyACM0).- Raises:
RuntimeError – If zero or more than one matching port is found.
- Return type:
str
Protocol v1 helpers¶
Wire-format helpers for the Pico-as-ROM hardware API (protocol v1).
This module defines the version-1 JSON envelope used to talk to the Pico
firmware: constants describing size limits, the dataclasses that model
firmware responses and events, a request builder, and parsers that validate
and unpack each kind of frame. It contains no I/O; romulan.hardware_api
uses these helpers to build commands and interpret replies.
- exception romulan.protocol_v1.ProtocolV1Error(message, *, error=None, detail=None)[source]¶
Bases:
ExceptionInvalid frame or firmware error response.
- Parameters:
message (str)
error (str | None)
detail (str | None)
- Return type:
None
- class romulan.protocol_v1.CycleEvent(seq, addr, data, rw)[source]¶
Bases:
objectA single captured 65C02 bus cycle.
- Parameters:
seq (int)
addr (str)
data (str)
rw (int)
- seq¶
Monotonic sequence number assigned by the firmware.
- Type:
int
- addr¶
Address on the bus, as a hex string.
- Type:
str
- data¶
Data byte on the bus, as a hex string.
- Type:
str
- rw¶
Read/write flag (0 = read, 1 = write).
- Type:
int
- class romulan.protocol_v1.DoneEvent(ok, reason, cycles, addr)[source]¶
Bases:
objectTerminating event that ends a bus-capture stream.
- Parameters:
ok (bool)
reason (str)
cycles (int)
addr (str)
- ok¶
Whether the capture completed successfully.
- Type:
bool
- reason¶
Why capture stopped (e.g.
"stp"or"max_cycles").- Type:
str
- cycles¶
Total number of cycles the firmware reports it captured.
- Type:
int
- addr¶
Address at which the capture stopped, as a hex string.
- Type:
str
- class romulan.protocol_v1.ReadResult(ok, reason, cycles=<factory>, stopped_addr='')[source]¶
Bases:
objectAggregated result of a capture: the done status plus all cycles.
- Parameters:
ok (bool)
reason (str)
cycles (list[CycleEvent])
stopped_addr (str)
- ok¶
Whether the capture completed successfully.
- Type:
bool
- reason¶
Why capture stopped.
- Type:
str
- cycles¶
The captured
CycleEventitems in order.- Type:
- stopped_addr¶
Address at which the capture stopped, as a hex string.
- Type:
str
- class romulan.protocol_v1.PeekResult(addr, data)[source]¶
Bases:
objectResult of a live bus/RAM peek (CPU read cycle, not ROM-image offset).
- Parameters:
addr (int)
data (int)
- addr¶
CPU address that was peeked (0–0xFFFF).
- Type:
int
- data¶
Data byte sampled on the matching bus cycle.
- Type:
int
- class romulan.protocol_v1.StatusResponse(phi2_hz, rom_active, reset_asserted, last_addr, read_active, monitor_enabled, upload_active=False, last_data='00', last_rw=0, resb=0, rwb=0, a15=0, phi2=0)[source]¶
Bases:
objectSnapshot of firmware/hardware state returned by the
statuscommand.- Parameters:
phi2_hz (float)
rom_active (bool)
reset_asserted (bool)
last_addr (str)
read_active (bool)
monitor_enabled (bool)
upload_active (bool)
last_data (str)
last_rw (int)
resb (int)
rwb (int)
a15 (int)
phi2 (int)
- phi2_hz¶
Current CPU clock (PHI2) frequency in hertz.
- Type:
float
- rom_active¶
Whether the ROM emulator is driving the bus.
- Type:
bool
- reset_asserted¶
Whether the CPU RESET line is asserted.
- Type:
bool
- last_addr¶
Last address seen on the bus, as a hex string.
- Type:
str
- last_data¶
Data byte from the last bus sample, as a hex string.
- Type:
str
- last_rw¶
Read/write flag from the last bus sample (0 = read, 1 = write).
- Type:
int
- read_active¶
Whether a bus-capture read is currently running.
- Type:
bool
- monitor_enabled¶
Whether the JSON monitor output is enabled.
- Type:
bool
- upload_active¶
Whether a ROM upload is in progress.
- Type:
bool
- resb¶
Raw RESB (reset) input level (0 = low/asserted, 1 = high/released).
- Type:
int
- rwb¶
Raw RWB input level (0 = write, 1 = read).
- Type:
int
- a15¶
Raw A15 input level (0 = RAM space, 1 = ROM space).
- Type:
int
- phi2¶
Raw PHI2 clock input level (0 = low, 1 = high).
- Type:
int
- class romulan.protocol_v1.PeekResponse(offset, count, data)[source]¶
Bases:
objectResponse from the
peekcommand returning ROM image bytes.- Parameters:
offset (int)
count (int)
data (bytes)
- offset¶
Byte offset within
rom_image[]that was read.- Type:
int
- count¶
Number of bytes returned (may be clipped to ROM bounds).
- Type:
int
- data¶
The returned bytes.
- Type:
bytes
- class romulan.protocol_v1.DriveResponse(enabled, value='00')[source]¶
Bases:
objectResponse from the
drivediagnostic command.- Parameters:
enabled (bool)
value (str)
- enabled¶
Whether the Pico is currently forcing D0-D7 as outputs.
- Type:
bool
- value¶
The forced byte as a 2-digit hex string, or
"00"when disabled.- Type:
str
- class romulan.protocol_v1.UploadProgress(action, received, expected=32768, offset=None, reset_vector=None)[source]¶
Bases:
objectProgress reported by the firmware during a ROM upload.
- Parameters:
action (str)
received (int)
expected (int)
offset (int | None)
reset_vector (str | None)
- action¶
Which upload phase this reply corresponds to (
"begin","chunk", or"commit").- Type:
str
- received¶
Total number of bytes received so far.
- Type:
int
- expected¶
Total number of bytes expected (defaults to
ROM_SIZE).- Type:
int
- offset¶
Byte offset of the acknowledged chunk, if reported.
- Type:
int | None
- reset_vector¶
Reset vector read back after commit, if reported.
- Type:
str | None
- romulan.protocol_v1.build_request(cmd, *, req_id=None, **fields)[source]¶
Build a v1 request envelope for a firmware command.
Adds the protocol version and command name, and (as a convenience) renames the reserved
assert_resetkeyword to the wire fieldassert.- Parameters:
cmd (str) – The command name (e.g.
"status"or"upload_rom").req_id (str | None) – Optional request id echoed back by the firmware.
**fields (Any) – Extra command-specific fields to include in the envelope.
- Returns:
A dict ready to be serialized to JSON and sent to the Pico.
- Return type:
dict[str, Any]
- romulan.protocol_v1.parse_frame(raw)[source]¶
Parse a JSON frame and validate the v1 envelope.
- Parameters:
raw (bytes | str) – The raw frame payload as
bytes(UTF-8) orstr.- Returns:
The decoded frame as a dict.
- Raises:
ProtocolV1Error – If the payload is not valid JSON, is not a JSON object, or declares an unsupported protocol version.
- Return type:
dict[str, Any]
- romulan.protocol_v1.parse_command_response(msg)[source]¶
Return a command acknowledgement, or raise on a firmware error.
- Parameters:
msg (dict[str, Any]) – A parsed frame expected to be a command response (not an event).
- Returns:
The same frame, unchanged, when it represents a successful ack.
- Raises:
ProtocolV1Error – If the frame is an event, reports
okfalse, or declares an unsupported protocol version.- Return type:
dict[str, Any]
- romulan.protocol_v1.parse_cycle_event(msg)[source]¶
Parse a
cycleevent frame into aCycleEvent.- Parameters:
msg (dict[str, Any]) – A parsed frame expected to be a
cycleevent.- Returns:
The captured bus cycle.
- Raises:
ProtocolV1Error – If the frame is not a
cycleevent or the version is unsupported.- Return type:
- romulan.protocol_v1.parse_done_event(msg)[source]¶
Parse a
doneevent frame into aDoneEvent.- Parameters:
msg (dict[str, Any]) – A parsed frame expected to be a
doneevent.- Returns:
The terminating capture event.
- Raises:
ProtocolV1Error – If the frame is not a
doneevent or the version is unsupported.- Return type:
- romulan.protocol_v1.parse_cycles_event(msg)[source]¶
Parse a batched
cyclesevent frame into a list ofCycleEvent.- Parameters:
msg (dict[str, Any]) – A parsed frame expected to be a
cyclesevent.- Returns:
The captured bus cycles in order.
- Raises:
ProtocolV1Error – If the frame is not a
cyclesevent, the version is unsupported, or a cycle entry is malformed.- Return type:
list[CycleEvent]
- romulan.protocol_v1.parse_status(msg)[source]¶
Parse a
statuscommand response into aStatusResponse.- Parameters:
msg (dict[str, Any]) – A parsed frame expected to be a successful
statusresponse.- Returns:
The decoded status snapshot.
- Raises:
ProtocolV1Error – If the frame reports an error or the version is unsupported.
- Return type:
- romulan.protocol_v1.parse_peek_response(msg)[source]¶
Parse a
peekcommand response into aPeekResponse.- Parameters:
msg (dict[str, Any]) – A parsed frame expected to be a successful
peekresponse.- Returns:
The decoded offset, count, and bytes.
- Raises:
ProtocolV1Error – If the frame reports an error, the version is unsupported, or the hex data is malformed.
- Return type:
- romulan.protocol_v1.parse_drive_response(msg)[source]¶
Parse a
drivecommand response into aDriveResponse.- Parameters:
msg (dict[str, Any]) – A parsed frame expected to be a successful
driveresponse.- Returns:
The decoded drive force state.
- Raises:
ProtocolV1Error – If the frame reports an error or the version is unsupported.
- Return type:
- romulan.protocol_v1.parse_live_peek_response(msg)[source]¶
Parse a live
peekcommand response into aPeekResult.Distinct from any ROM-image offset peek: fields are CPU
addrand sampled busdata(single byte).- Parameters:
msg (dict[str, Any]) – A parsed frame expected to be a successful
peekresponse.- Returns:
The decoded address and data byte.
- Raises:
ProtocolV1Error – If the frame reports an error, the version is unsupported, or required fields are missing/invalid.
- Return type:
- romulan.protocol_v1.parse_upload_response(msg)[source]¶
Parse an
upload_romcommand response into anUploadProgress.- Parameters:
msg (dict[str, Any]) – A parsed frame expected to be a successful
upload_romreply (begin, chunk, or commit).- Returns:
The decoded upload progress.
- Raises:
ProtocolV1Error – If the frame reports an error or the version is unsupported.
- Return type:
Hardware API client¶
High-level client for the Pico-as-ROM 65C02 hardware over the v1 serial protocol.
This module provides HardwareAPI, a client-side wrapper around the
framed JSON protocol implemented by the Pico firmware. It handles the
low-level ENQ/STX/ACK/EOT framing, encodes commands built by
romulan.protocol_v1, and exposes friendly methods for the common
operations: querying the current CPU address, asserting/releasing reset,
toggling the JSON monitor, reading status, uploading a ROM image,
live-peeking a CPU bus address, and capturing bus cycles.
Opening a HardwareAPI immediately opens the underlying serial port.
The class supports the context-manager protocol so the port is always closed:
with HardwareAPI("/dev/ttyACM0") as api:
api.upload_rom(rom_bytes)
- exception romulan.hardware_api.HardwareAPIError[source]¶
Bases:
ExceptionRaised when the Pico responds with NACK or a frame error occurs.
- class romulan.hardware_api.CaptureResult(reason, cycles=<factory>)[source]¶
Bases:
objectResult of a bus capture (read until STP).
- Parameters:
reason (str)
cycles (list[dict[str, Any]])
- reason¶
Why the capture stopped (e.g.
"stp"or"max_cycles").- Type:
str
- cycles¶
One dict per captured bus cycle, each with
seq,addr,data, andrwkeys.- Type:
list[dict[str, Any]]
- __repr__()[source]¶
Return a concise debug representation.
- Returns:
A string showing the stop reason and cycle count.
- Return type:
str
- classmethod from_read_result(result)[source]¶
Build a
CaptureResultfrom a protocolReadResult.- Parameters:
result (ReadResult) – The parsed read result returned by the capture loop.
- Returns:
A
CaptureResultwith each cycle flattened into a plain dict.- Return type:
- class romulan.hardware_api.HardwareAPI(port, baudrate=115200, timeout=30.0, verbose=False)[source]¶
Bases:
objectContext-manager compatible hardware API for Pico-as-ROM firmware v1.
Each instance owns a single serial connection to the Pico. The connection is opened as soon as the object is constructed, and closed by
close()or on exit from awithblock.- Parameters:
port (str)
baudrate (int)
timeout (float)
verbose (bool)
- port¶
The serial device path the client is connected to.
- baudrate¶
The serial baud rate in use.
- timeout¶
Idle / activity timeout in seconds (no useful framing progress).
- verbose¶
When
True, protocol traffic is logged to stderr.
- close()[source]¶
Close the serial port if it is open.
Safe to call multiple times; subsequent calls are no-ops.
- Return type:
None
- __exit__(exc_type, exc_val, exc_tb)[source]¶
Exit a
withblock, closing the serial port.- Parameters:
exc_type (Any)
exc_val (Any)
exc_tb (Any)
- Return type:
None
- property ser: Serial¶
The live
serial.Serialconnection.- Returns:
The open serial connection.
- Raises:
HardwareAPIError – If the port has already been closed.
- request_addr()[source]¶
Ask the firmware for the address currently on the CPU bus.
- Returns:
The current CPU address as an integer.
- Raises:
HardwareAPIError – If the response is missing the
addrfield or the firmware reports an error.TimeoutError – If the Pico does not respond in time.
- Return type:
int
- live_peek(addr)[source]¶
Live-peek one byte by running a short LDA absolute / STP stub on the CPU.
The firmware briefly resets the 65C02, patches
LDA $addr/STPat$8000, samples the data byte on the bus cycle whose address matchesaddr, then restores the previous ROM bytes. This reads live RAM (or ROM) contents — not a host-side ROM-image offset (seepeek()for that). Requires firmware with live-peek support.- Parameters:
addr (int) – CPU address to read (
0–0xFFFF).- Returns:
A
PeekResultwithaddranddata.- Raises:
ValueError – If
addris outside0–0xFFFF.HardwareAPIError – If the firmware reports an error (timeout, no matching cycle, busy, etc.).
TimeoutError – If the Pico does not respond in time.
- Return type:
- reset(assert_reset)[source]¶
Assert or release the 65C02 RESET line.
- Side effects:
Changes the CPU run state: asserting reset halts the CPU, while releasing it lets the CPU start executing from its reset vector.
- Parameters:
assert_reset (bool) –
Trueto hold the CPU in reset,Falseto release it.- Raises:
HardwareAPIError – If the firmware reports an error.
TimeoutError – If the Pico does not respond in time.
- Return type:
None
- monitor(enable)[source]¶
Enable or disable the firmware’s unframed JSON monitor output.
The monitor must be disabled before framed operations such as
upload_rom()andread_until_stp(), otherwise its free-form text would corrupt the framed protocol stream.- Parameters:
enable (bool) –
Trueto turn the monitor on,Falseto turn it off.- Raises:
HardwareAPIError – If the firmware reports an error.
TimeoutError – If the Pico does not respond in time.
- Return type:
None
- status()[source]¶
Query the firmware for its current status.
- Returns:
A
StatusResponsedescribing the clock frequency, ROM/reset/monitor state, and last bus sample.- Raises:
HardwareAPIError – If the firmware reports an error.
TimeoutError – If the Pico does not respond in time.
- Return type:
- peek(offset, count=16)[source]¶
Read back bytes from the loaded
rom_image[].This is useful for verifying that an upload landed at the expected offsets before releasing RESET.
- Parameters:
offset (int) – Byte offset within the 32 KB ROM image.
count (int) – Number of bytes to read (1-64; firmware caps at 64).
- Returns:
A
PeekResponsewith the offset, count, and returned bytes.- Raises:
ValueError – If
offsetorcountis out of range.HardwareAPIError – If the firmware reports an error or returns malformed data.
TimeoutError – If the Pico does not respond in time.
- Return type:
- set_clock(hz)[source]¶
Set the 65C02 PHI2 clock frequency.
- Parameters:
hz (float) – Target frequency in hertz. The firmware accepts 0.1..1000 Hz.
- Raises:
ValueError – If
hzis outside the supported range.HardwareAPIError – If the firmware reports an error.
TimeoutError – If the Pico does not respond in time.
- Return type:
None
- drive(value=None)[source]¶
Force the Pico to drive D0-D7 with a byte, or release the bus.
This is a diagnostic command. The CPU should be in reset or removed before forcing the data bus, otherwise the Pico and CPU contend.
- Parameters:
value (int | str | None) – Byte to drive on D0-D7. Pass
None(or omit) to release the bus and return to normal ROM emulation.- Returns:
A
DriveResponsewith the new state.- Raises:
ValueError – If
valueis outside 0..255.HardwareAPIError – If the firmware reports an error.
TimeoutError – If the Pico does not respond in time.
- Return type:
- upload_rom(data)[source]¶
Upload a full 32 KB ROM image to the Pico in a begin/chunk/commit sequence.
The image is sent as base64-encoded chunks and committed at the end. The reset vector is reported back by the firmware after commit.
- Side effects:
Disables the JSON monitor and flushes serial input before transferring, so the framed protocol is not corrupted.
- Parameters:
data (bytes) – The ROM image; must be exactly
ROM_SIZE(32 KB) bytes.- Returns:
A dict with keys
ok,bytes(bytes committed),reset_vector, andexpected(expected total size).- Raises:
ValueError – If
datais not exactlyROM_SIZEbytes.HardwareAPIError – If a chunk stalls or the firmware reports an error.
TimeoutError – If the Pico does not respond in time.
- Return type:
dict[str, Any]
- read_until_stp(max_cycles=10000, frame_timeout=None, on_cycle=None, batch_size=32, phi2_hz=None)[source]¶
Capture CPU bus cycles until the CPU executes STP or a limit is hit.
Holds reset, arms
read, then releases reset so capture starts from the reset vector (typically$8000). Pollsread_eventfor batched cycle and done frames.- Side effects:
Disables the JSON monitor, asserts then releases CPU reset. The current PHI2 clock is preserved unless
phi2_hzis provided.
- Parameters:
max_cycles (int) – Maximum number of bus cycles to capture before the firmware stops. Defaults to
10000.frame_timeout (float | None) – Idle timeout in seconds with no new cycle/done event.
nonepolls do not extend the deadline. Defaults totimeout.on_cycle (Callable[[CycleEvent], None] | None) – Optional callback invoked for each captured cycle (e.g. for live CLI output).
batch_size (int) – Number of cycles to request per
read_eventpoll. Defaults toREAD_EVENT_BATCH_SIZE.phi2_hz (float | None) – Optional clock frequency to set when arming capture. If omitted, the current clock speed is preserved.
- Returns:
A
CaptureResultwith the stop reason and captured cycles.- Raises:
HardwareAPIError – If the read is rejected or an unexpected frame arrives.
TimeoutError – If no cycle/done arrives within the idle timeout.
- Return type:
- romulan.hardware_api.open_hardware_api(port=None)[source]¶
Open a
HardwareAPI, auto-detecting the Pico port if needed.- Side effects:
Opens the serial port via
HardwareAPI.
- Parameters:
port (str | None) – Explicit serial device path. If
None, the port is auto-detected withfind_pico_port().- Returns:
A connected
HardwareAPIinstance.- Raises:
HardwareAPIError – If no Pico serial port can be found.
- Return type:
CLI entry point¶
Romulan CLI entry point.
- Usage:
romulan input.txt –build –upload [–port PORT] romulan hardware <subcommand> …
Examples
romulan program.txt –build # Build bin/rom.bin only romulan program.txt –build –upload # Build and upload romulan –upload # Upload existing bin/rom.bin romulan program.txt –upload –port /dev/ttyACM0 romulan hardware upload bin/rom.bin –port /dev/ttyACM0 romulan hardware capture –max-cycles 500 romulan hardware monitor –disable romulan hardware reset –assert romulan hardware request-addr romulan hardware peek –offset 0x7000 –count 16 romulan hardware clock –hz 100 romulan hardware status
- romulan.main.create_parser()[source]¶
Build the argument parser for the default build/upload workflow.
- Returns:
A configured
argparse.ArgumentParseraccepting the input file plus the--build,--upload,--output, and--portoptions.- Return type:
ArgumentParser
- romulan.main.main()[source]¶
Entry point for the
romulancommand-line tool.Routes to the
hardwaresub-command when it is the first argument; otherwise runs the default workflow, which builds a ROM image from an input file (--build) and/or uploads it to the Pico (--upload).- Side effects:
Parses
sys.argv, may read/write files, and may open the serial port to talk to the Pico. Exits viasys.exit()(orparser.error) on invalid arguments or failures.
- Return type:
None
Package¶
Romulan — host client for the Piclone 65C02 system.
Romulan assembles 32 KB ROM images from annotated hex dumps or 6502 assembly source and communicates with the Raspberry Pi Pico firmware over USB serial. Submodules:
romulan.assemble— two-pass 6502/65C02 assemblerromulan.build_rom— input format detection, parsers and ROM builderromulan.upload_rom— Pico serial port auto-detectionromulan.protocol_v1— v1 JSON envelope helpersromulan.hardware_api— framed serial protocol clientromulan.main— CLI entry point