% Copyright (c) 2026 Rana Umar Nadeem, Samrah Mumtaz, Muhammad Imran # Transcript Parsers `chia_openpiton/parse.py` holds pure functions that take text and return data. They do no I/O, start no subprocesses, and need no Ray. Because they decide whether a simulation passed, the tier-0 tests run them against captured logs in `chia_openpiton/test/fixtures/`, with no toolchain present. The patterns come from OpenPiton's source. `piton/verif/env/manycore/pc_cmp.v.pyv` prints the PASS line once every hart in the finish mask hits the good trap. `piton/verif/env/manycore/monitor.v.pyv` owns the `fail` task and the max-cycles abort. OpenPiton's `regreport` tool renders `status.log` and the regression summary. ## Verdicts An RTL simulation exits 0 whether or not the program passed, so the adapter reads the verdict from the transcript. `sim_verdict()` classifies these lines: | Transcript line | Verdict | |---|---| | `179911750: Simulation -> PASS (HIT GOOD TRAP)` | `"pass"` | | `1234500 : Simulation -> FAIL(HIT BAD TRAP)` | `"fail"` | | `1234500 : Simulation -> FAIL(TIMEOUT)` | `"timeout"` | | `1000250 : Simulation -> (terminated by reaching max cycles = 2000)` | `"maxcycles"` | | `Info: spc(0) thread(3) -> timeout happen`, with no verdict line | `"timeout"` | | None of these | `None` | The PASS line has no space before the colon and the FAIL line has one; the patterns accept both. The checks run in a fixed order: FAIL, then max cycles, then PASS, then `-> timeout happen`. A FAIL whose message contains `TIMEOUT`, matched case-insensitively, counts as `"timeout"`. The order makes the parser fail closed: a FAIL line wins over a PASS line in the same transcript, and `timeout happen` never overrides a verdict line. ## Function reference ```python # sim.log sim_verdict(text: str) -> Verdict | None sim_time(text: str) -> int | None fail_reason(text: str) -> str max_cycles(text: str) -> int | None first_requests(text: str) -> dict[int, int] # generic tile: {tile: first request address} # fake_uart.log hart_cycles(text: str) -> dict[int, tuple[int, int]] # status.log status_diag(text: str) -> tuple[str, str] | None cycles(text: str) -> int | None exec_cycles(text: str) -> int | None num_tiles(text: str) -> int | None # regreport summary and verilator_coverage regress_summary(text: str) -> dict[str, object] coverage_summary(text: str) -> dict[str, object] # sims output sims_die(text: str) -> str model_dir(text: str) -> str build_failure_reason(stdout: str, stderr: str = "") -> str build_errors(stdout: str, stderr: str = "", limit: int = 20) -> tuple[str, ...] undefined_name(error: str) -> tuple[str, str] | None close_names(name: str, source: str, limit: int = 3) -> tuple[str, ...] with_close_names(error: str, source: str) -> str # Verilator verilator_version(text: str) -> tuple[int, int] | None needs_no_timing(version_text: str) -> bool # diaglists and transcripts diaglist_group(text: str, group: str) -> tuple[DiagEntry, ...] first_divergence(reference: str, actual: str) -> tuple[int, str, str] | None ``` | Function | Returns | |---|---| | `sim_verdict` | `"pass"`, `"fail"`, `"timeout"`, `"maxcycles"`, or `None` for an empty transcript or one without a verdict | | `sim_time` | The simulation time that prefixes the verdict line, such as `179911750` | | `fail_reason` | The message inside `Simulation -> FAIL(...)`, such as `HIT BAD TRAP`, or `""` | | `max_cycles` | The count from a max-cycles abort, or `None` | | `hart_cycles` | `{hart: (start, end)}` from the lines `h ` that `matmul_scaled.c` prints, in core clock cycles. A log without such lines gives `{}`. A hart that appears twice, or ends before it starts, raises `ValueError`, since keeping one reading would hide a garbled log. A line another hart's output cut into is left out, so fewer harts than tiles shows that the log is incomplete | | `status_diag` | `(diag_name, status_text)` from the `Diag:` line. The status is regreport's text, such as `PASS`, `FAIL`, `Timeout`, `MaxCycles Hit`, `FAIL (Monitor)`, or `Unknown (No Status)` | | `cycles`, `exec_cycles`, `num_tiles` | The `Cyc=`, `ExecCyc=`, and `NumTiles=` values, or `None` | | `regress_summary` | A dict with `passed`, `counts`, and `diag_count`, parsed from a `regreport ... -summary` table. `passed` is `True` or `False` from a `REGRESSION PASSED` or `REGRESSION FAILED` line, else `None`. `counts` maps each status row present (`PASS`, `FAIL`, `Diag Problem`, `License Problem`, `MaxCycles Hit`, `Socket Problem`, `Timeout`, `LessThreads`, `Simics Problem`, `Performance`, `Killed By Job Q`, `Unknown`, `UnFinished`, `flexlm error`) to its count. `diag_count` comes from the `Diag Count:` row | | `coverage_summary` | `{"hit": 8749, "total": 24311, "percent": 35.99}` from the `verilator_coverage --annotate` line `Total coverage (8749/24311) 35.00%`. The percent comes from the counts, because `verilator_coverage` prints it rounded down to a whole number. All three values are `None` when the line is missing, so a failed `verilator_coverage` run never reads as 0% | | `sims_die` | The message from `sims: Caught a SIGDIE. at line N.`, without the Perl file and line suffix, or `""` | | `model_dir` | The path from `sims: creating model directory `, or `""` | | `build_failure_reason` | A failure tag from the table below, or `""` | | `build_errors` | The first `limit` Verilator `%Error` lines and C++ compiler `error:` lines, in order, without Verilator's closing `Exiting due to N error(s)` line | | `undefined_name` | `(file, name)` from a Verilator error for a name nothing declares, `Can't find definition of variable: 'X'` or `Member 'X' not found in structure`, or `None`. Verilator 5.020 suggests no alternative for these | | `close_names` | Up to `limit` names in `source` most like `name`, closest first, at `difflib`'s default cutoff, without `name` itself | | `with_close_names` | The error with `(names in closest to 'X': ...)` appended, or the error unchanged when it names no undefined name or no close name exists | | `verilator_version` | `(major, minor)` from `verilator --version`, for release strings (`Verilator 4.038 2020-07-11 rev ...`) and development builds (`Verilator 5.049 devel rev ...`), or `None` | | `needs_no_timing` | `True` for Verilator 5 and later, which refuse OpenPiton's bare `#1` delays without `--timing` or `--no-timing`. `False` for Verilator 4, which has no such flag, and for text it cannot parse | | `diaglist_group` | The tests of one diaglist group, as `DiagEntry` values | | `first_divergence` | The 1-based line number and both lines at the first line where the texts differ. `None` when one text is a line-for-line prefix of the other, which includes identical texts | [run()](workspace_node.md) calls `sim_verdict` and `sim_time` on the full `sim.log`, and `cycles` and `exec_cycles` on `status.log`. `PitonRunResult.fake_uart` keeps the last 8000 characters of `fake_uart.log`. A `hart_cycles` line takes about 16 characters, so the kept text holds the line of every hart on a mesh of up to about 500 harts. `build()` calls `needs_no_timing`, `build_failure_reason`, and, on the full output of a failed build, `build_errors`. For an error that names an undefined name in a file inside the checkout, `build()` reads that file and appends `with_close_names`: a misspelled name was the usual failure of RTL repair on build errors (2026-10-23), where the agents declared the misspelled name instead of correcting it. [PitonToolServer](tool_server.md) calls `first_divergence`, and the MACE shell calls `coverage_summary` (see [Code Coverage](../01_mace_user/Code_Coverage.md)). ## Build failure tags `build_failure_reason` searches stdout and stderr together and returns the first tag that matches: | Order | Tag | Matches | |---|---|---| | 1 | `verilator_needs_timing_flag` | `%Error-NEEDTIMINGOPT` | | 2 | `verilator_bad_option` | `%Error: Invalid option: