Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 14 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,14 @@ control, a bit-exact Python reference model, deterministic regression, and CI.
This is **not** a trained AI model or a complete neural-network accelerator.

<p align="center">
<img src="assets/rtl-regression.svg" width="100%" alt="Deterministic RTL regression matrix across vector lengths 1, 8, and 17">
<img src="assets/rtl-ci-transcript.svg" width="100%" alt="Successful public CI transcript: Icarus Verilog passed deterministic vectors at lengths 1, 8, and 17, and Yosys found no structural problems">
</p>

The figure is generated from the checked-in CI matrix. It counts verification
transactions; it is not a throughput, timing, area, power, FPGA, or silicon
benchmark.
This transcript comes from successful public
[GitHub Actions run 30224621114](https://github.com/Labeeb2339/edge-ai-rtl-lab/actions/runs/30224621114)
at commit `6fd552b`. The checked-in receipt can be regenerated from the run log
with `python tools/capture_ci_receipt.py`; the SVG renderer consumes that
receipt instead of inventing a result.

## What is implemented

Expand Down Expand Up @@ -96,6 +98,14 @@ lane. See [Architecture](docs/architecture.md) for timing and width details.

## Evidence and limits

<p align="center">
<img src="assets/rtl-regression.svg" width="100%" alt="Checked-in RTL regression workload across vector lengths 1, 8, and 17">
</p>

The matrix is generated from the checked-in CI workload. It counts verification
transactions; it is not a throughput, timing, area, power, FPGA, or silicon
benchmark.

The automated evidence in this repository is RTL simulation plus a Yosys
structural synthesis check. No FPGA or ASIC implementation, timing closure,
power measurement, silicon validation, or performance benchmark is claimed.
Expand Down
16 changes: 16 additions & 0 deletions assets/rtl-ci-transcript.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
32 changes: 32 additions & 0 deletions evidence/rtl-ci-run-30224621114.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"schema_version": "edge-ai-rtl-lab.ci-receipt.v1",
"repository": "Labeeb2339/edge-ai-rtl-lab",
"run_id": 30224621114,
"run_url": "https://github.com/Labeeb2339/edge-ai-rtl-lab/actions/runs/30224621114",
"commit": "6fd552b16f41dfa138c61f06aeb11ac26fb9acd8",
"created_at": "2026-07-26T23:08:54Z",
"conclusion": "success",
"regressions": [
{
"command": "python tools/run_regression.py --sim iverilog --vec-len 1 --random-cases 40",
"summary": "Simulator: iverilog; vectors: 47 (7 corner + 40 deterministic random); seed: 0x5eed2339",
"result": "PASS: 47 vectors checked (VEC_LEN=1, OUT_W=16)"
},
{
"command": "python tools/run_regression.py --sim iverilog --vec-len 8 --random-cases 200",
"summary": "Simulator: iverilog; vectors: 211 (11 corner + 200 deterministic random); seed: 0x5eed2339",
"result": "PASS: 211 vectors checked (VEC_LEN=8, OUT_W=16)"
},
{
"command": "python tools/run_regression.py --sim iverilog --vec-len 17 --random-cases 100",
"summary": "Simulator: iverilog; vectors: 111 (11 corner + 100 deterministic random); seed: 0x5eed2339",
"result": "PASS: 111 vectors checked (VEC_LEN=17, OUT_W=16)"
}
],
"synthesis": {
"command": "yosys -p 'read_verilog -sv -DSYNTHESIS rtl/int8_dot_product.sv; chparam -set VEC_LEN 8 int8_dot_product; hierarchy -check -top int8_dot_product; proc; opt; check -assert; stat'",
"tool": "Yosys 0.33 (git sha1 2584903a060)",
"result": "Found and reported 0 problems.",
"generic_cell_count": 36
}
}
98 changes: 98 additions & 0 deletions tools/capture_ci_receipt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
#!/usr/bin/env python3
"""Rebuild the checked-in RTL CI receipt from an authenticated GitHub run log."""

from __future__ import annotations

import argparse
import json
import re
import subprocess
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
REPOSITORY = "Labeeb2339/edge-ai-rtl-lab"
DEFAULT_RUN_ID = 30224621114


def _gh(*arguments: str) -> str:
completed = subprocess.run(
["gh", *arguments], cwd=ROOT, check=True, capture_output=True, text=True
)
return completed.stdout


def _payload(run_id: int) -> dict[str, object]:
metadata = json.loads(
_gh(
"run",
"view",
str(run_id),
"--repo",
REPOSITORY,
"--json",
"databaseId,headSha,status,conclusion,createdAt,url",
)
)
if metadata.get("status") != "completed" or metadata.get("conclusion") != "success":
raise SystemExit("the selected CI run is not a completed success")

log = _gh("run", "view", str(run_id), "--repo", REPOSITORY, "--log")
commands = re.findall(
r"Run (python tools/run_regression\.py --sim iverilog[^\r\n]+)", log
)
summaries = re.findall(r"(Simulator: iverilog; vectors: [^\r\n]+)", log)
results = re.findall(r"(PASS: \d+ vectors checked \(VEC_LEN=\d+, OUT_W=\d+\))", log)
if not (len(commands) == len(summaries) == len(results) == 3):
raise SystemExit("could not recover the three expected regression records")

yosys_command_match = re.search(r"Run (yosys -p '[^\r\n]+')", log)
yosys_tool_match = re.search(r"(Yosys \d+\.\d+ \(git sha1 [^)]+\))", log)
yosys_result_match = re.search(r"(Found and reported \d+ problems\.)", log)
cell_count_match = re.search(r"Number of cells:\s+(\d+)", log)
if not all(
(yosys_command_match, yosys_tool_match, yosys_result_match, cell_count_match)
):
raise SystemExit("could not recover the Yosys structural-check record")

return {
"schema_version": "edge-ai-rtl-lab.ci-receipt.v1",
"repository": REPOSITORY,
"run_id": metadata["databaseId"],
"run_url": metadata["url"],
"commit": metadata["headSha"],
"created_at": metadata["createdAt"],
"conclusion": metadata["conclusion"],
"regressions": [
{"command": command, "summary": summary, "result": result}
for command, summary, result in zip(
commands, summaries, results, strict=True
)
],
"synthesis": {
"command": yosys_command_match.group(1),
"tool": yosys_tool_match.group(1),
"result": yosys_result_match.group(1),
"generic_cell_count": int(cell_count_match.group(1)),
},
}


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--run-id", type=int, default=DEFAULT_RUN_ID)
parser.add_argument("--output", type=Path)
args = parser.parse_args()
output = args.output or ROOT / "evidence" / f"rtl-ci-run-{args.run_id}.json"
if not output.is_absolute():
output = ROOT / output
output.parent.mkdir(parents=True, exist_ok=True)
output.write_text(
json.dumps(_payload(args.run_id), indent=2, sort_keys=False) + "\n",
encoding="utf-8",
newline="\n",
)
print(f"wrote {output.relative_to(ROOT)}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow absolute receipt output paths

When --output is an absolute path outside the repository (for example, a temporary file used to compare a freshly captured receipt), the receipt is written successfully but this status message raises ValueError because Path.relative_to(ROOT) only accepts descendants of ROOT. This makes a valid-looking --output invocation exit nonzero after modifying the requested file; print the absolute path or fall back when it is outside the repository.

Useful? React with 👍 / 👎.



if __name__ == "__main__":
main()
75 changes: 74 additions & 1 deletion tools/render_readme_assets.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,17 @@
from __future__ import annotations

import argparse
import json
from html import escape
from pathlib import Path

from run_regression import corner_cases, dot_product_int8


ROOT = Path(__file__).resolve().parents[1]
OUTPUT = ROOT / "assets" / "rtl-regression.svg"
SATURATION_OUTPUT = ROOT / "assets" / "rtl-saturation-evidence.svg"
CI_RECEIPT = ROOT / "evidence" / "rtl-ci-run-30224621114.json"
CI_OUTPUT = ROOT / "assets" / "rtl-ci-transcript.svg"
SCENARIOS = ((1, 40), (8, 200), (17, 100))


Expand Down Expand Up @@ -220,13 +223,83 @@ def map_y(value: int) -> float:
'''


def render_ci_transcript() -> str:
receipt = json.loads(CI_RECEIPT.read_text(encoding="utf-8"))
if receipt.get("schema_version") != "edge-ai-rtl-lab.ci-receipt.v1":
raise ValueError("unsupported RTL CI receipt schema")
if receipt.get("conclusion") != "success":
raise ValueError("RTL CI receipt is not a successful run")
regressions = receipt.get("regressions")
synthesis = receipt.get("synthesis")
if not isinstance(regressions, list) or len(regressions) != 3 or not isinstance(synthesis, dict):
raise ValueError("RTL CI receipt is missing expected simulator or synthesis records")

expected_commands = [
f"python tools/run_regression.py --sim iverilog --vec-len {vec_len} --random-cases {random_cases}"
for vec_len, random_cases in SCENARIOS
]
transcript: list[tuple[str, str]] = []
for row, expected_command in zip(regressions, expected_commands, strict=True):
if not isinstance(row, dict) or row.get("command") != expected_command:
raise ValueError("RTL CI regression command does not match the checked-in matrix")
summary = str(row.get("summary", ""))
result = str(row.get("result", ""))
if not summary.startswith("Simulator: iverilog; vectors:") or not result.startswith("PASS:"):
raise ValueError("RTL CI regression record is malformed")
transcript.extend((("command", f"$ {expected_command}"), ("output", summary), ("pass", result)))

if synthesis.get("result") != "Found and reported 0 problems.":
raise ValueError("RTL CI synthesis receipt did not pass its structural check")
transcript.extend(
(
("command", "$ yosys ... hierarchy -check; proc; opt; check -assert; stat"),
("output", str(synthesis.get("tool"))),
("pass", str(synthesis.get("result"))),
(
"muted",
f"generic RTL cells: {int(synthesis.get('generic_cell_count', -1))} (structural count, not area)",
),
)
)

text_lines = []
y = 115
colours = {"command": "#d8dee9", "output": "#9fb1c3", "pass": "#7ee2a8", "muted": "#77889a"}
for kind, value in transcript:
text_lines.append(
f'<text x="54" y="{y}" fill="{colours[kind]}" class="terminal">{escape(value)}</text>'
)
y += 36 if kind == "pass" else 29

run_id = int(receipt["run_id"])
commit = escape(str(receipt["commit"])[:7])
return f'''<svg xmlns="http://www.w3.org/2000/svg" width="1280" height="650" viewBox="0 0 1280 650" role="img" aria-labelledby="ci-title ci-desc">
<title id="ci-title">Successful Edge AI RTL Lab simulator and synthesis transcript</title>
<desc id="ci-desc">A terminal-style rendering of GitHub Actions run {run_id}: Icarus Verilog passed all deterministic vectors at vector lengths 1, 8, and 17, and Yosys reported no structural problems.</desc>
<style>
.chrome {{ font: 600 15px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }}
.terminal {{ font: 15px ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; }}
</style>
<rect width="1280" height="650" rx="12" fill="#10151c"/>
<rect width="1280" height="58" rx="12" fill="#202833"/>
<path d="M0 46H1280V58H0Z" fill="#202833"/>
<circle cx="25" cy="29" r="7" fill="#ff6b63"/><circle cx="49" cy="29" r="7" fill="#f4bd4f"/><circle cx="73" cy="29" r="7" fill="#62c86f"/>
<text x="104" y="35" fill="#aebdcc" class="chrome">github/actions · rtl-ci · run {run_id} · {commit}</text>
{''.join(text_lines)}
<path d="M32 602H1248" stroke="#2a3440"/>
<text x="54" y="630" fill="#77889a" class="chrome">captured from the successful public CI log · functional simulation + structural synthesis only</text>
</svg>
'''


def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--check", action="store_true", help="fail if the committed SVG is stale")
args = parser.parse_args()
expected_outputs = {
OUTPUT: render(),
SATURATION_OUTPUT: render_saturation(),
CI_OUTPUT: render_ci_transcript(),
}

if args.check:
Expand Down
Loading