% Copyright (c) 2026 Rana Umar Nadeem, Samrah Mumtaz, Muhammad Imran # OpenPitonWorkspaceNode `OpenPitonWorkspaceNode`, in `chia_openpiton/openpiton_workspace.py`, drives one OpenPiton checkout on one worker. `sims`, `build`, `run`, and `regress` call OpenPiton's `sims` tool. The other members query or edit the checkout directly. ```python from chia.base.ChiaFunction import get from chia_openpiton.openpiton_workspace import OpenPitonWorkspaceNode with OpenPitonWorkspaceNode("/work/openpiton") as node: cfg = get(node.configure.chia_remote(x_tiles=1, y_tiles=1)) art = get(node.build.chia_remote(cfg)) res = get(node.run.chia_remote(cfg, "hello_world.c")) assert res.success ``` ## Construction ```python OpenPitonWorkspaceNode(piton_root: str, *, root_on_remote_worker: bool = False, **kwargs) ``` `piton_root` must name an existing local directory. With `root_on_remote_worker=True`, the node accepts an absolute path that exists only on a remote worker. That mode requires `require_colocated=False`, and the caller schedules each call. The other keyword arguments go to CHIA's `ColocatedNode`: | Argument | Default | Effect | |---|---|---| | `placement_group` | `None` | Pin members to an existing placement group | | `require_colocated` | `True` | Reserve a one-bundle placement group of `{"CPU": 1, "openpiton": 1}`; `False` leaves placement to the caller | | `bundle_index` | `0` | Bundle to pin to | | `reserve_bundle` | `None` | Bundle shape for a self-reserved group | | `pg_strategy` | `"STRICT_PACK"` | Strategy for a self-reserved group | | `wait_for_pg` | `True` | Block until the reservation is ready | | `pg_ready_timeout_s` | `None` | Timeout for that wait | As a context manager, the node releases a placement group it reserved. `node.task_options` returns the scheduling options that place an actor, such as a [PitonToolServer](tool_server.md), on the node's bundle. ## Calling members Each member declares `resources={"openpiton": 1}` and has five call forms: | Call | Behaviour | |---|---| | `node.build.chia_remote(cfg)` | Runs remotely on the node's bundle; returns a Ray reference | | `node.build.chia_remote_blocking(cfg)` | Same, and returns the value | | `node.build(cfg)` | Runs in the calling process | | `node.build.options(**opts)` | Returns a handle with extra Ray options and the root still bound | | `OpenPitonWorkspaceNode.build.chia_remote(root, cfg)` | Runs remotely, unpinned | ## Members The signatures below are the class attributes. Instance calls omit `piton_root`. ```python sims(piton_root: str, args: str, core: str = "ariane", cwd: str | None = None, timeout_seconds: int = 3600) -> tuple[int, str, str] configure(piton_root: str, x_tiles: int = 1, y_tiles: int = 1, core: str = "ariane", network_config: str = "2dmesh_config", config_rtl: tuple[str, ...] = ("MINIMAL_MONITORING",), caches: dict[str, tuple[int, int]] | None = None, address_map: str | None = None, extra_flags: tuple[str, ...] = (), sys: str = "manycore", timeout_seconds: int = 300) -> PitonConfig build(piton_root: str, config: PitonConfig, sim_type: str = "vlt", clean: bool = False, extra_build_args: tuple[str, ...] = (), timeout_seconds: int = 7200) -> PitonBuildArtifact run(piton_root: str, config: PitonConfig, test: str, sim_type: str = "vlt", precompiled: bool = False, asm_diag_root: str | None = None, finish_mask: str | None = None, rtl_timeout: int | None = None, max_cycle: int | None = None, extra_run_args: tuple[str, ...] = (), timeout_seconds: int = 3600) -> PitonRunResult regress(piton_root: str, config: PitonConfig, tests: tuple[str, ...], group: str = "", sim_type: str = "vlt", precompiled: bool = False, asm_diag_root: str | None = None, timeout_seconds: int = 3600) -> PitonRegressResult put_file(piton_root: str, relpath: str, content: bytes | str) -> str apply_edits(piton_root: str, edits: dict[str, str], staged: str = "") -> dict revert_edits(piton_root: str) -> list[str] collect(piton_root: str, base_dir: str, patterns: tuple[str, ...], max_bytes_per_file: int | None = None) -> PitonCollectResult clean(piton_root: str, config: PitonConfig) -> bool ``` | Member | Behaviour | |---|---| | `sims` | Runs `sims ` verbatim in `cwd`, default `$PITON_ROOT/build`, and returns `(returncode, stdout, stderr)`. `core` selects the environment prologue | | `configure` | Returns a validated [PitonConfig](piton_config.md) with the checkout's revisions recorded | | `build` | Builds the model, or returns the cached one | | `run` | Runs one diag and judges it from the transcript | | `regress` | Calls `run` serially for each entry of `tests`. `success` holds when at least one test ran and none failed. It is a single-worker fallback; fan `run` out across workers for parallel regressions | | `put_file` | Writes `content` to `/`, creates parent directories, and returns the path. A `relpath` that escapes the checkout raises `ValueError` | | `apply_edits` | Replaces the content of existing files: `edits` maps a path relative to the root to the file's full new content, and `staged` names a JSON file of more edits under `build/.mace_edit_staging/`, which wins for the same file and is deleted once read. It first reverts edits a previous call left applied, keeps each original under `build/.mace_edit_backup/`, and restores every file on any error. A path outside the checkout, one that names no existing file, or two paths that name the same file raise `ValueError`, as does a `staged` name outside the staging directory; a `staged` file that does not exist adds nothing. Returns the merged edits and their unified diff | | `revert_edits` | Restores every file `apply_edits` changed and returns their paths; with nothing applied it does nothing | | `collect` | Globs `patterns` under `base_dir`, which is absolute or relative to the root; `**` is recursive. Skips matches outside `base_dir`, including symlinks whose target lies outside it. Files over `max_bytes_per_file` go to `skipped`; `0` skips every non-empty file, and `None` sets no cap | | `clean` | Removes the configuration's model directory and returns whether it existed. `sims -clean` removes only VCS output (`csrc`, `simv`, `simv.daidir`, `AxisWork`) and leaves `obj_dir` in place | `verilator_version_text(root, core="ariane", timeout_seconds=120) -> str` is a plain static method. It returns `verilator --version` as seen inside OpenPiton's environment and caches successful results per `(root, core)`. `source_fingerprint(root, version_text, timeout_seconds=60) -> str` is another plain static method. It returns the SHA-256 hex digest that the build cache checks, described under Build cache below. ### configure A given `address_map` replaces `piton/verif/env/manycore/devices_ariane.xml`, the checkout's single copy of the simulation device map. `configure` then runs four probes concurrently, and a failed git probe records `""`: | Probe | Field | |---|---| | `git rev-parse HEAD` | `source_rev` | | `git rev-parse HEAD:piton/design/chip/tile/ariane` | `ariane_rev` | | `git diff -- piton/verif/env/manycore` | `diff` | | `verilator --version`, inside OpenPiton's environment | `verilator_version` | It returns the resulting `PitonConfig`, whose construction raises `ValueError` on an invalid mesh, core, network, or cache. A `caches` value of `None` or an empty mapping selects `DEFAULT_CACHES`, and a partial mapping keeps the defaults for the caches it leaves out. ### build 1. Rejects a `sim_type` outside `vlt`, `vcs`, `ncv`, `icv`, `msm`, and `riv` with `ValueError`. Only `vlt` (Verilator) is license-free. 2. Refuses an Ariane configuration whose L1D or L1I has more ways than its L1.5 (see [Ariane's way rule](piton_config.md)). It returns a failed artifact without running `sims`: `returncode=-1`, `failure_reason="way_rule"`, and the lines of `config.way_violations()` as `errors`. A cached model of such a configuration is not served either. 3. Raises `ValueError` when `git diff -- piton/verif/env/manycore` differs from `config.diff`, for example after another `configure(address_map=...)` call on the checkout. The check runs only for a configuration that recorded the checkout's state, a non-empty `source_rev` or `diff`, as `configure()` sets. A configuration with both fields empty, such as each one MACE's loop constructs, builds the checkout as it stands. 4. Returns the cached model, if one exists and the checkout has not changed since it was built. 5. With `clean=True`, or when the cached model is stale, deletes the model's `obj_dir` and marker. 6. For `sim_type="vlt"`, adds `--no-timing` when Verilator is version 5 or later, unless an `extra_build_args` entry already contains `timing`. The version comes from `config.verilator_version`, or from `verilator_version_text()`. 7. Runs, in `$PITON_ROOT/build`: ```text sims -build_id= -_build [-_build_args= ...] ``` `success` requires exit status 0 and the model binary. A failed build carries the tag `build_timeout` when its timeout stopped it, else a [build_failure_reason](parsers.md) tag, or `no_model_binary` when no tag matches, as when `sims` exits 0 without a binary. ### Build cache A successful build writes the marker file `.mace_build_ok` into the model directory. The marker holds `config.key` and the checkout's source fingerprint, taken before `sims` ran. With `clean=False`, `build` returns at once, without running `sims`, when the binary exists and the marker holds the current key and fingerprint. That artifact has `reused=True` and `wall_time_s=0.0`. `build` trusts the marker over the binary, because a worker killed mid-link can leave a truncated binary. The fingerprint hashes the Verilator version and, for the checkout and its Ariane submodule, three things: - the commit, from `git rev-parse HEAD`; - the edits to tracked files, from `git diff HEAD`; - the untracked files that `.gitignore` does not list, by content, or by size and modification time above 1 MiB. OpenPiton's `.gitignore` lists the build outputs, such as pyHP's `.tmp.v` files and everything under `build/`, so a build leaves the fingerprint of its checkout unchanged. The `aws` submodule is left out: simulation does not read it, and its diff on a Linux checkout runs to hundreds of megabytes. On a patched checkout the fingerprint takes about 0.1 s. A build ID covers the configuration, so a patch, an RTL edit, or a new commit leaves it unchanged. When the marker's fingerprint no longer matches, `build` logs `the checkout or Verilator changed since it was built`, deletes the model's `obj_dir` and marker, and rebuilds. A marker written before the fingerprint existed holds only the key, so its model rebuilds once. Edits inside the Ariane submodule's own submodules, such as `corev_apu/rv_plic`, change the fingerprint only when they move a submodule commit or make a clean submodule dirty. After such an edit, rebuild with `clean=True`. ### run For the default `sys="manycore"`, `run` executes this command in a fresh run directory: ```text sims -build_id= [-precompiled] [-asm_diag_root=] -finish_mask= [-rtl_timeout=] [-max_cycle=] -_run ``` `finish_mask` defaults to `config.finish_mask`, one `1` per tile, so a multi-tile run passes only when every hart hits the good trap. `precompiled=True` makes `sims` look in `$ARIANE_ROOT/tmp/riscv-tests/build` for a prebuilt riscv-tests ELF instead of compiling the source. `asm_diag_root` adds a directory to search for the diag source. For another `sys`, `run` drops the manycore options and the test name; pass test selection in `extra_run_args`. The verdict comes from `parse.sim_verdict()` over the full `sim.log`, or over stdout when there is no `sim.log`. A call that timed out without a verdict gets `"timeout"`. ## Directory layout ```text $PITON_ROOT/build/ working directory for build() and the sims() member manycore/ config.sys mace_<12 hex digits>/ config.build_id (model_dir) .mace_build_ok success marker obj_dir/Vcmp_top Verilator model (binary_path) runs/ --<8 hex>/ run_dir, one per run() call sim.log status.log fake_uart.log mem.image symbol.tbl diag.exe ``` The run directory name replaces `/` in the test name with `_`. For another `sys`, the model lives under `build//`, and the binary is the single executable `V*` file in `obj_dir`. ## Timeouts and processes | Member | Default `timeout_seconds` | |---|---| | `sims` | 3600 | | `configure` | 300, per probe | | `build` | 7200 | | `run` | 3600 | | `regress` | 3600, per test | Every `sims` and `verilator --version` command runs under `bash -lc` in its own session. On timeout, the adapter kills the whole process group, keeps the partial output, returns `returncode=-1`, and appends `sims timed out after s` to stderr. A launch failure also returns `-1`, without that marker. On `KeyboardInterrupt`, the adapter kills the process group and re-raises. The `git` probes run without a shell and return `""` on timeout. A SIGTERM or SIGKILL that ends the Python process reaches neither path, so each command also starts a watchdog, which checks every 2 s that the process that started it is alive and kills the command's process group once it has exited. Stopping a batch therefore stops its builds and simulations too. ## Environment Each `bash -lc` command first unsets `LD_LIBRARY_PATH`, then exports `PITON_ROOT` and sources `piton/piton_settings.bash`. The `sparc` and `pico` cores need nothing more. For `core="ariane"`, the prologue sets these variables before sourcing: | Variable | Value | |---|---| | `ARIANE_ROOT` | `$PITON_ROOT/piton/design/chip/tile/ariane/`, with the trailing slash | | `RISCV` | The existing value, else `$HOME/scratch/riscv_install` | | `VERILATOR_ROOT` | `$ARIANE_ROOT/tmp/verilator-4.014/`, only when unset and that Verilator is executable | | `LIBRARY_PATH` | `$RISCV/lib` | | `LD_LIBRARY_PATH` | `$RISCV/lib` | OpenPiton's prebuilt `configsrch`, which `sims` runs to look up each tool's version, is a host binary. The Nix dev shell exports an `LD_LIBRARY_PATH` that holds Nix's `libstdc++`, which needs a newer glibc than an Ubuntu or Debian host has. With that path, `configsrch` fails to load, and `sims` stops with `configsrch returned error code 1`. The prologue clears the caller's value for this reason. `piton_settings.bash` puts `/usr/bin` first on `PATH`, so after sourcing, the Ariane prologue prepends `$RISCV/bin`, and every prologue prepends `$VERILATOR_ROOT/bin` when `VERILATOR_ROOT` is set. Setting `VERILATOR_ROOT` therefore selects the Verilator for every core. ## Result types ### PitonBuildArtifact | Field | Type | Meaning | |---|---|---| | `success` | `bool` | Exit status 0 and model binary present | | `returncode` | `int` | `sims` exit status; `-1` on timeout, launch failure, or a refused (`way_rule`) configuration | | `config` | `PitonConfig` | The configuration built | | `sim_type` | `str` | Simulator selector | | `model_dir` | `str` | `$PITON_ROOT/build//` | | `binary_path` | `str` | Model binary; `""` when none exists after the build | | `wall_time_s` | `float` | Build wall time; `0.0` when reused | | `verilator_version` | `str` | Version text behind the `--no-timing` decision | | `cache_key` | `str` | `config.key` | | `failure_reason` | `str` | Failure tag, `way_rule` for a refused configuration; `""` on success | | `reused` | `bool` | `True` when served from the build cache | | `stdout`, `stderr` | `str` | Tail of each stream. `sims` prints Verilator's output to stdout | | `errors` | `tuple[str, ...]` | Error lines from the full output of a failed build, from [build_errors](parsers.md), each one that names an undefined name followed by the closest names in its file ([with_close_names](parsers.md)), or for a `way_rule` refusal the caches that break the rule; `()` on success | ### PitonRunResult | Field | Type | Meaning | |---|---|---| | `success` | `bool` | `returncode != -1` and `verdict == "pass"` | | `returncode` | `int` | `sims` exit status; `-1` on timeout or launch failure | | `test` | `str` | Diag name | | `sim_type` | `str` | Simulator selector | | `run_dir` | `str` | This run's directory | | `verdict` | `Verdict` or `None` | `"pass"`, `"fail"`, `"timeout"`, or `"maxcycles"`; `None` when the transcript has no verdict and the call did not time out | | `sim_time` | `int` or `None` | Simulation time stamped on the verdict line | | `cycles` | `int` or `None` | `Cyc=` from `status.log` | | `exec_cycles` | `int` or `None` | `ExecCyc=` from `status.log` | | `wall_time_s` | `float` | Run wall time | | `sim_log_tail` | `str` | Tail of `sim.log`, or of stdout when there is no `sim.log` | | `status_log` | `str` | Tail of `status.log` | | `fake_uart` | `str` | Tail of `fake_uart.log`, the program's UART output | | `stdout`, `stderr` | `str` | As in `PitonBuildArtifact` | | `first_requests` | `dict[int, int]` | For `core="generic"`, each tile's first request address, read from the whole `sim.log` with [first_requests](parsers.md); `{}` for the other cores | Each tail keeps the last 8000 characters. For the configurations this adapter builds, `status.log` has no `Cyc=` field, so `cycles` stays `None` and `sim_time` measures run length. The static method `PitonRunResult.decide(returncode, verdict)` holds the pass rule. ### Other results | Type | Fields | |---|---| | `PitonRegressResult` | `success`, `group`, `sim_type`, `num_tests`, `num_failures`, `results` (a list of `PitonRunResult`), `results_dir` (`/runs`), and `report`, which `regress` leaves empty | | `PitonCollectResult` | `base_dir`, `files` (relative path to text), `skipped` (relative path to size, for files over the cap), and `listing` (relative path to size, for every match) |