From 11243e8f12609983f195464a1e0ebc7a71888e39 Mon Sep 17 00:00:00 2001 From: daiyongyuan <1533208939@qq.com> Date: Sat, 12 Sep 2026 12:01:11 +0800 Subject: [PATCH 01/11] feat: support P2E bank difftest --- .../bebop/p2e/02_buildbitstream_event.step.py | 3 +- .../bebop/p2e/03_runworkload_api.step.py | 10 ++ .../bebop/p2e/03_runworkload_event.step.py | 96 +++++++++++++++++-- api/steps/config/01_install_event.step.py | 4 + api/steps/config/scripts/4_install.py | 53 ++++++++++ api/tests/test_bebop_p2e_diff.py | 64 +++++++++++++ bbdev | 4 +- 7 files changed, 223 insertions(+), 11 deletions(-) create mode 100644 api/tests/test_bebop_p2e_diff.py diff --git a/api/steps/bebop/p2e/02_buildbitstream_event.step.py b/api/steps/bebop/p2e/02_buildbitstream_event.step.py index fc953a9..4c99433 100644 --- a/api/steps/bebop/p2e/02_buildbitstream_event.step.py +++ b/api/steps/bebop/p2e/02_buildbitstream_event.step.py @@ -59,7 +59,8 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: os.makedirs(build_dir, exist_ok=True) build_cmd = ( - f"nix develop --ignore-environment --keep HOME --keep ALL_PROXY -c " + f"nix develop --ignore-env --keep-env-var HOME --keep-env-var ALL_PROXY " + f"--keep-env-var CARGO_TARGET_DIR -c " f"cargo run --release --features p2e -- build p2e " f"--rtl-dir=\"{vsrc_dir}\" " f"--out-dir=\"{build_dir}\"" diff --git a/api/steps/bebop/p2e/03_runworkload_api.step.py b/api/steps/bebop/p2e/03_runworkload_api.step.py index 88dace3..6fe1e14 100644 --- a/api/steps/bebop/p2e/03_runworkload_api.step.py +++ b/api/steps/bebop/p2e/03_runworkload_api.step.py @@ -23,6 +23,16 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse: "message": "image and bitstream parameters are required", }, ) + if body.get("diff", False) and not body.get("golden-elf"): + return ApiResponse( + status=400, + body={ + "success": False, + "failure": True, + "returncode": 400, + "message": "--diff requires --golden-elf ", + }, + ) await ctx.enqueue({ "topic": "bebop.p2e.runworkload", diff --git a/api/steps/bebop/p2e/03_runworkload_event.step.py b/api/steps/bebop/p2e/03_runworkload_event.step.py index f1ab863..49f310d 100644 --- a/api/steps/bebop/p2e/03_runworkload_event.step.py +++ b/api/steps/bebop/p2e/03_runworkload_event.step.py @@ -9,6 +9,7 @@ import glob import os import re +import shlex import sys from datetime import datetime @@ -51,6 +52,41 @@ def case_uses_multi_fpga(build_dir: str) -> bool: return len([path for path in part_dirs if os.path.isdir(path)]) > 1 +def runtime_build_command(bbdir: str, chip: str, diff: bool, vsrc_dir: str, build_dir: str) -> tuple[str, str]: + manifest = Path(bbdir) / "bebop" / "Cargo.toml" + features = ["p2e"] + if diff: + manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" + features.extend(["bemu", "difftest"]) + command = shlex.join( + [ + "env", + "BEBOP_P2E_RUNTIME_ONLY=1", + "BEBOP_P2E_REBUILD_RUNTIME=1", + f"VSRC_PATH={vsrc_dir}", + f"OUT_PATH={build_dir}", + "cargo", + "run", + "--release", + "--manifest-path", + str(manifest), + "--bin", + "bebop", + "--features", + ",".join(features), + "--", + "build", + "p2e", + "--rtl-dir", + vsrc_dir, + "--out-dir", + build_dir, + *(["--diff"] if diff else []), + ] + ) + return command, str(manifest.parent) + + async def handler(input_data: dict, ctx: FlowContext) -> None: origin_tid = get_origin_trace_id(input_data, ctx) try: @@ -79,6 +115,27 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ) return wave = bool(input_data.get("wave", False)) + diff = bool(input_data.get("diff", False)) + golden_elf = input_data.get("golden-elf", "") + golden_pk = bool(input_data.get("golden-pk", False)) + if diff and not golden_elf: + ctx.logger.error("--diff requires --golden-elf ") + await check_result( + ctx, 1, continue_run=False, + extra_fields={"error": "missing_golden_elf"}, + trace_id=origin_tid, + ) + return + if diff and not os.path.isfile(golden_elf): + ctx.logger.error(f"BEMU golden ELF not found: {golden_elf}") + await check_result( + ctx, 1, continue_run=False, + extra_fields={"error": "golden_elf_not_found", "golden_elf": golden_elf}, + trace_id=origin_tid, + ) + return + if diff: + golden_elf = os.path.abspath(golden_elf) if "wave_start" in input_data: ctx.logger.error("invalid parameter: --wave_start (use --wave-start)") await check_result( @@ -152,27 +209,42 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: # Rebuild the VVAC host runtime in the bitstream case. The bitstream is # deliberately left in place, so runtime/DPIC changes never trigger FPGA synthesis. - runtime_cmd = ( - f"env BEBOP_P2E_RUNTIME_ONLY=1 BEBOP_P2E_REBUILD_RUNTIME=1 " - f"cargo run --release --features p2e -- build p2e " - f"--rtl-dir=\"{vsrc_dir}\" " - f"--out-dir=\"{build_dir}\"" - ) + runtime_cmd, runtime_cwd = runtime_build_command(bbdir, chip, diff, vsrc_dir, build_dir) + runtime_env = {**os.environ.copy(), **bebop_cargo_env(bbdir, chip)} + if diff: + hpec_home = "/home/x-epic/hpe-24.12.01.s008" + runtime_env["BEBOP_BEMU_P2E_ABI"] = "1" + runtime_env["BEBOP_BEMU_CC"] = os.path.join( + hpec_home, "tools", "gcc-8.3.0", "gcc-8.3.0", "bin", "gcc" + ) + runtime_env["BEBOP_BEMU_CXX"] = os.path.join( + hpec_home, "tools", "gcc-8.3.0", "gcc-8.3.0", "bin", "g++" + ) + runtime_env["BEBOP_BEMU_DTC"] = os.path.join(bbdir, "result", "bin", "dtc") + runtime_env["CARGO_TARGET_DIR"] = os.path.join(bebop_dir, "target", f"{chip}-p2e-diff") + compiler_libs = [ + os.path.join(hpec_home, "tools", "gcc-8.3.0", "gmp-6.2.1", "lib"), + os.path.join(hpec_home, "tools", "gcc-8.3.0", "mpfr-4.1.0", "lib"), + os.path.join(hpec_home, "tools", "gcc-8.3.0", "mpc-1.2.1", "lib"), + ] + runtime_env["BEBOP_BEMU_COMPILER_LIBRARY_PATH"] = ":".join(compiler_libs) ctx.logger.info("Preparing bebop p2e runtime for the selected bitstream ...") runtime_result = await stream_run_logger_async( cmd=runtime_cmd, logger=ctx.logger, - cwd=bebop_dir, + cwd=runtime_cwd, stdout_prefix="bebop p2e runtime", stderr_prefix="bebop p2e runtime", - env={**os.environ.copy(), **bebop_cargo_env(bbdir, chip)}, + env=runtime_env, ) runtime_lib_dir = os.path.join(build_dir, "vvacDir", "runtimeDir", "lib", "lib_arm") rtcfg_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "rtcfg") libvctb_path = os.path.join(runtime_lib_dir, "libvCtb.so") libstdcxx_path = os.path.join(runtime_lib_dir, "libstdc++.so.6") bebop_p2e_path = os.path.join(build_dir, "bebop-p2e") - runtime_artifacts = (rtcfg_path, libvctb_path, libstdcxx_path, bebop_p2e_path) + runtime_artifacts = [rtcfg_path, libvctb_path, libstdcxx_path, bebop_p2e_path] + if diff: + runtime_artifacts.append(os.path.join(build_dir, "libriscv.so")) if runtime_result.returncode != 0 or not all(os.path.isfile(path) for path in runtime_artifacts): missing = [path for path in runtime_artifacts if not os.path.isfile(path)] if missing: @@ -208,6 +280,10 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: run_cmd += " --wave" if wave_start is not None: run_cmd += f" --wave-start=\"{wave_start}\"" + if diff: + run_cmd += f" --diff --golden-elf={shlex.quote(golden_elf)}" + if golden_pk: + run_cmd += " --golden-pk" for trace_name in ("itrace", "mtrace", "pmctrace", "ctrace", "banktrace"): if input_data.get(trace_name, False): run_cmd += f" --{trace_name}" @@ -241,6 +317,8 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: "fpga_location": fpga_location, "bdb_trace": os.path.join(run_log, "bdb.ndjson"), "uart_log": os.path.join(run_log, "uart.log"), + "bank_diff": os.path.join(run_log, "bank_diff.ndjson") if diff else None, + "diff": diff, "timestamp": timestamp, }, trace_id=origin_tid, diff --git a/api/steps/config/01_install_event.step.py b/api/steps/config/01_install_event.step.py index b752275..0012a32 100644 --- a/api/steps/config/01_install_event.step.py +++ b/api/steps/config/01_install_event.step.py @@ -112,6 +112,9 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: bemu = step4.install_bemu(chip, bbdir, gen) ctx.logger.info(f"Installed bemu crate: {bemu}") + bebop = step4.install_bebop(gen) + ctx.logger.info(f"Installed bebop crate: {bebop}") + # C. TO Workload BUILD SYSTEM workload = step4.install_workload(chip, bbdir, chip_name, gen) ctx.logger.info(f"Installed workload defs: {workload}") @@ -136,6 +139,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: "chip": chip_name, "arch": str(arch), "bemu": str(bemu), + "bebop": str(bebop), "workload": str(workload), } ) diff --git a/api/steps/config/scripts/4_install.py b/api/steps/config/scripts/4_install.py index 291fc77..2152182 100644 --- a/api/steps/config/scripts/4_install.py +++ b/api/steps/config/scripts/4_install.py @@ -125,6 +125,59 @@ def install_bemu(chip: pb.Chip, bbdir: Path, gen: Path) -> Path: return bemu / "Cargo.toml" +def install_bebop(gen: Path) -> Path: + bebop = gen.parent.parent / "generated" / "bebop" + bebop.mkdir(parents=True, exist_ok=True) + manifest = bebop / "Cargo.toml" + manifest.write_text( + """[workspace] +resolver = "2" + +[package] +name = "bebop" +version = "0.1.0" +edition = "2021" +build = "../../../../../bebop/build.rs" + +[[bin]] +name = "bebop" +path = "../../../../../bebop/src/main.rs" + +[features] +default = [] +verilator = ["dep:bebop-verilator"] +p2e = ["dep:bebop-p2e"] +bemu = ["dep:bebop-bemu"] +difftest = ["bebop-bemu/difftest"] + +[dependencies] +bebop-verilator = { path = "../../../../../bebop/src/nodes/verilator", optional = true } +bebop-p2e = { path = "../../../../../bebop/src/nodes/p2e", optional = true } +bebop-bemu = { path = "../../configs/generated/bemu", optional = true } +bebop-dasm = { path = "../../../../../bebop/src/nodes/lib/dasm" } +bebop-bank-hash = { path = "../../../../../bebop/src/nodes/lib/bank-hash" } +bebop-bemu-profile = { path = "../../../../../bebop/src/nodes/lib/bemu-profile" } +bebop-fd-redirect = { path = "../../../../../bebop/src/nodes/lib/fd-redirect" } +bebop-rtl-trace = { path = "../../../../../bebop/src/nodes/lib/rtl-trace" } +bebop-uart = { path = "../../../../../bebop/src/nodes/lib/uart" } +clap = { version = "4", features = ["derive"] } +libc = "0.2" +log = "0.4" +env_logger = "0.11" +nix = { version = "0.29", features = ["fs", "mman", "signal", "process"] } +toml = "0.8" +ctrlc = "3" +camino = "1.1" +snafu = "0.8" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +duct = "0.13" +""", + encoding="utf-8", + ) + return manifest + + def install_workload(chip: pb.Chip, bbdir: Path, name: str, gen: Path) -> Path: if not chip.profiles: raise ValueError(f"chip {name}: no compiler profiles") diff --git a/api/tests/test_bebop_p2e_diff.py b/api/tests/test_bebop_p2e_diff.py new file mode 100644 index 0000000..084f53a --- /dev/null +++ b/api/tests/test_bebop_p2e_diff.py @@ -0,0 +1,64 @@ +import importlib.util +import runpy +import sys +import types +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[3] +API = ROOT / "bbdev" / "api" +sys.path.insert(0, str(API)) + +motia = types.ModuleType("motia") +motia.FlowContext = object +motia.queue = lambda name: name +sys.modules["motia"] = motia + +event_path = API / "steps" / "bebop" / "p2e" / "03_runworkload_event.step.py" +spec = importlib.util.spec_from_file_location("bebop_p2e_runworkload_event", event_path) +event = importlib.util.module_from_spec(spec) +spec.loader.exec_module(event) + + +class BebopP2eDiffTest(unittest.TestCase): + def test_bbdev_parses_diff_options(self): + cli = runpy.run_path(str(ROOT / "bbdev" / "bbdev")) + args = cli["parse_args"]([ + "bebop-p2e", + "--runworkload", + "--chip pebble --image fw_payload-lenet --bitstream image.bit " + "--diff --golden-elf golden.elf --golden-pk", + ]) + parsed = cli["extract_command_info"](args)["args"] + self.assertEqual(parsed["golden-elf"], "golden.elf") + self.assertTrue(parsed["diff"]) + self.assertTrue(parsed["golden-pk"]) + + def test_diff_runtime_build_enables_bemu_and_difftest(self): + command, cwd = event.runtime_build_command("/repo", "pebble", True, "/rtl", "/case") + self.assertIn("--features p2e,bemu,difftest", command) + self.assertIn("/repo/examples/chips/pebble/generated/bebop/Cargo.toml", command) + self.assertTrue(command.endswith("--diff")) + self.assertEqual(cwd, "/repo/examples/chips/pebble/generated/bebop") + + def test_normal_runtime_build_only_enables_p2e(self): + cli = runpy.run_path(str(ROOT / "bbdev" / "bbdev")) + args = cli["parse_args"]([ + "bebop-p2e", + "--runworkload", + "--chip pebble --image fw_payload-lenet --bitstream image.bit --diff=false", + ]) + parsed = cli["extract_command_info"](args)["args"] + self.assertFalse(parsed["diff"]) + + command, cwd = event.runtime_build_command("/repo", "pebble", False, "/rtl", "/case") + self.assertIn("--features p2e", command) + self.assertNotIn("bemu", command) + self.assertNotIn("difftest", command) + self.assertFalse(command.endswith("--diff")) + self.assertEqual(cwd, "/repo/bebop") + + +if __name__ == "__main__": + unittest.main() diff --git a/bbdev b/bbdev index 485d6b3..3f951ce 100755 --- a/bbdev +++ b/bbdev @@ -85,7 +85,7 @@ WORKFLOW_COMMANDS = { "clean": 'Clean Bebop P2E build directory. Args: "--chip [--output-dir ]"', "verilog": 'Generate Bebop P2E SystemVerilog for DDR4 backdoor. Args: "--chip [--output-dir ]"', "buildbitstream": 'Build Bebop P2E runtime case. Args: "--chip [--vsrc-dir ] [--output-dir ]"', - "runworkload": 'Run Bebop P2E workload. Args: "--chip --image --bitstream [--fpga-location ] [--multi-fpga] [--wave] [--wave-start ] [--itrace] [--mtrace] [--pmctrace]"', + "runworkload": 'Run Bebop P2E workload. Args: "--chip --image --bitstream [--fpga-location ] [--multi-fpga] [--wave] [--wave-start ] [--diff --golden-elf [--golden-pk]] [--itrace] [--mtrace] [--pmctrace]"', "batch": 'Run Bebop p2e nextest batch regression (serial). Args: "--chip --bitstream --test "', }, "dc": { @@ -332,6 +332,8 @@ def _parse_sub_args(arg_string: str) -> dict: key, value = token[2:].split("=", 1) if not key or value == "": raise ValueError(f"Invalid argument: {token}") + if value in ("true", "false"): + value = value == "true" args_dict[key] = value i += 1 else: From dc761957d389a4bc37d55dcf3328c89da026e90f Mon Sep 17 00:00:00 2001 From: daiyongyuan <1533208939@qq.com> Date: Mon, 14 Sep 2026 15:25:47 +0800 Subject: [PATCH 02/11] fix: create bbdev runtime data directory --- bbdev | 1 + 1 file changed, 1 insertion(+) diff --git a/bbdev b/bbdev index 3f951ce..74ac7a8 100755 --- a/bbdev +++ b/bbdev @@ -614,6 +614,7 @@ def run_script_mode(args, cmd_info): # Serialize only engine startup. The reservation socket must be released before # iii can bind it, so without this lock another bbdev can select the same port # during that handoff. + os.makedirs(os.path.dirname(SERVER_START_LOCK), exist_ok=True) server_start_lock = open(SERVER_START_LOCK, "w") fcntl.flock(server_start_lock, fcntl.LOCK_EX) From 69524e161b07f731c571b4a1314bcadb8c2b004c Mon Sep 17 00:00:00 2001 From: daiyongyuan <1533208939@qq.com> Date: Mon, 14 Sep 2026 15:39:29 +0800 Subject: [PATCH 03/11] fix: defer generated chip protobuf import --- api/steps/uvm/scripts/uvm_common.py | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/api/steps/uvm/scripts/uvm_common.py b/api/steps/uvm/scripts/uvm_common.py index 314b14a..9671b0d 100644 --- a/api/steps/uvm/scripts/uvm_common.py +++ b/api/steps/uvm/scripts/uvm_common.py @@ -13,19 +13,14 @@ from utils.stream_run import stream_run_logger from .waive import apply_waivers +config_scripts = os.path.abspath( + os.path.join(os.path.dirname(__file__), "..", "..", "config", "scripts") +) +sys.path.insert(0, config_scripts) + def load_chip(bbdir: str, chip: str): - config_scripts = os.path.abspath( - os.path.join(os.path.dirname(__file__), "..", "..", "config", "scripts") - ) - if config_scripts not in sys.path: - sys.path.insert(0, config_scripts) - try: - import chip_pb2 - except ImportError as e: - raise FileNotFoundError( - f"missing {os.path.join(config_scripts, 'chip_pb2.py')}; run bbdev config --install" - ) from e + import chip_pb2 path = Path(bbdir) / "examples" / "chips" / chip / "configs" / "generated" / "chip.pb" if not path.is_file(): raise FileNotFoundError(f"missing {path}; run bbdev config --install") From 7ac32a1fffdf6c975ff3982bb07c5e7804e7944a Mon Sep 17 00:00:00 2001 From: daiyongyuan <1533208939@qq.com> Date: Tue, 15 Sep 2026 13:05:36 +0800 Subject: [PATCH 04/11] fix: serialize concurrent mill builds --- api/steps/mill/scripts/mill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/steps/mill/scripts/mill.py b/api/steps/mill/scripts/mill.py index 4fe2123..6f2f858 100644 --- a/api/steps/mill/scripts/mill.py +++ b/api/steps/mill/scripts/mill.py @@ -12,7 +12,7 @@ def elaborate_cmd(main: str, config: str, out: str, *, seq_mem: bool = False) -> f" --repl-seq-mem --repl-seq-mem-file={shlex.quote(os.path.join(out, 'mems.conf'))}" ) return ( - f"mill -i __.test.runMain {main} {config} " + f"flock /tmp/buckyball-mill.lock mill -i __.test.runMain {main} {config} " "--disable-annotation-unknown --strip-debug-info -O=debug " f"--split-verilog -o={shlex.quote(out)}{extra}" ) From ac31f7ece02993ddbcff47fe8f740ce2ed3f3807 Mon Sep 17 00:00:00 2001 From: daiyongyuan <1533208939@qq.com> Date: Tue, 15 Sep 2026 23:32:05 +0800 Subject: [PATCH 05/11] fix: handle UVM chip domains and generated accumulator names --- api/steps/uvm/scripts/uvm_common.py | 33 ++++++++++++++++++----------- api/tests/test_uvm_chip_pb.py | 8 +++++++ 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/api/steps/uvm/scripts/uvm_common.py b/api/steps/uvm/scripts/uvm_common.py index 9671b0d..ada4c6a 100644 --- a/api/steps/uvm/scripts/uvm_common.py +++ b/api/steps/uvm/scripts/uvm_common.py @@ -1,4 +1,5 @@ import os +import re import shlex import sys import tomllib @@ -32,15 +33,7 @@ def load_chip(bbdir: str, chip: str): def ball_domain(chip): - d0 = chip.cores[0].balldomain - key = [(m.ball_id, m.ball_dir, m.in_bw, m.out_bw) for m in d0.mappings] - isa = [(e.mnemonic, e.funct7, e.bid) for e in d0.isa] - for core in chip.cores[1:]: - k = [(m.ball_id, m.ball_dir, m.in_bw, m.out_bw) for m in core.balldomain.mappings] - i = [(e.mnemonic, e.funct7, e.bid) for e in core.balldomain.isa] - if k != key or i != isa: - raise ValueError("chip.pb cores have different balldomains") - return d0 + return chip.cores[0].balldomain def selected_mappings(domain, ball: str | None): @@ -66,13 +59,23 @@ def vcs_defines(domain, mapping, bank_entries: int): return defs +def smatmul_accumulator_filename(rtl_dir: Path) -> str: + text = (rtl_dir / "SMatMulUnit.sv").read_text() + modules = re.findall(r"\b(accumulator_\d+x\d+)\s+accumulator_ext\s*\(", text) + if len(modules) != 1: + raise ValueError(f"expected one SMatMul accumulator instance, found {modules!r}") + return f"{modules[0]}.sv" + + def _filelist( - verify_dir: Path, ball_dir: str, uvm_rel: str, rtl_rel: str, sim_dir: Path + verify_dir: Path, ball_dir: str, uvm_rel: str, rtl_rel: str, rtl_dir: Path, sim_dir: Path ) -> str: src = verify_dir / "filelists" / f"{ball_dir}_ball.f" dst = sim_dir / f"{ball_dir}_ball.f" sim_dir.mkdir(parents=True, exist_ok=True) text = src.read_text() + if "@SMATMUL_ACCUMULATOR@" in text: + text = text.replace("@SMATMUL_ACCUMULATOR@", smatmul_accumulator_filename(rtl_dir)) dst.write_text(text.replace("@UVM@", uvm_rel).replace("@RTL@", rtl_rel)) return str(dst.relative_to(verify_dir)) @@ -263,7 +266,7 @@ def build_ball(bbdir: str, chip_name: str, mill_cfg: str, domain, mapping, ctx) sim_dir = verify_dir / "build" / chip_name uvm_rel = os.path.relpath(Path(bbdir) / "verify" / "uvm", verify_dir) rtl_rel = os.path.relpath(rtl_dir, verify_dir) - flist = _filelist(verify_dir, ball, uvm_rel, rtl_rel, sim_dir) + flist = _filelist(verify_dir, ball, uvm_rel, rtl_rel, rtl_dir, sim_dir) cargo = ( f"nix develop {shlex.quote(str(Path(bbdir) / 'verify'))} --command " f"cargo build --manifest-path {shlex.quote(str(casegen))}" @@ -353,7 +356,13 @@ def run_chip(bbdir: str, chip: str, ball: str | None, ctx, do_run: bool) -> dict msg = load_chip(bbdir, chip) domain = ball_domain(msg) mill_cfg = msg.mill.verilator_config - maps = selected_mappings(domain, ball) + maps = [ + m + for m in selected_mappings(domain, ball) + if (Path(bbdir) / "examples" / "balls" / m.ball_dir / "verify" / "filelists" / f"{m.ball_dir}_ball.f").is_file() + ] + if ball is not None and not maps: + raise ValueError(f"UVM filelist missing for ball {ball!r}") ran = [] covs = [] run_root = None diff --git a/api/tests/test_uvm_chip_pb.py b/api/tests/test_uvm_chip_pb.py index bc0bac1..d671d7a 100644 --- a/api/tests/test_uvm_chip_pb.py +++ b/api/tests/test_uvm_chip_pb.py @@ -55,8 +55,16 @@ def test_unknown_ball(): raise AssertionError("expected ValueError") +def test_heterogeneous_chip_uses_first_domain(): + c = _chip() + other = c.cores.add() + other.balldomain.mappings.add().ball_dir = "gemmini" + assert [m.ball_dir for m in ball_domain(c).mappings] == ["lut"] + + if __name__ == "__main__": test_defines() test_registered_ip() test_unknown_ball() + test_heterogeneous_chip_uses_first_domain() print("ok") From 4f40306b6fa8a98b5369bcadfd218bd152fac7b7 Mon Sep 17 00:00:00 2001 From: daiyongyuan <1533208939@qq.com> Date: Fri, 18 Sep 2026 20:38:28 +0800 Subject: [PATCH 06/11] fix: reject unsupported UVM domains and missing filelists --- .../bebop/p2e/03_runworkload_event.step.py | 1 + api/steps/uvm/scripts/uvm_common.py | 124 ++++++++++++++---- api/tests/test_bebop_p2e_diff.py | 35 +++-- api/tests/test_uvm_chip_pb.py | 48 ++++++- 4 files changed, 162 insertions(+), 46 deletions(-) diff --git a/api/steps/bebop/p2e/03_runworkload_event.step.py b/api/steps/bebop/p2e/03_runworkload_event.step.py index 49f310d..53e7ea5 100644 --- a/api/steps/bebop/p2e/03_runworkload_event.step.py +++ b/api/steps/bebop/p2e/03_runworkload_event.step.py @@ -12,6 +12,7 @@ import shlex import sys from datetime import datetime +from pathlib import Path from motia import FlowContext, queue diff --git a/api/steps/uvm/scripts/uvm_common.py b/api/steps/uvm/scripts/uvm_common.py index ada4c6a..23ad740 100644 --- a/api/steps/uvm/scripts/uvm_common.py +++ b/api/steps/uvm/scripts/uvm_common.py @@ -22,7 +22,10 @@ def load_chip(bbdir: str, chip: str): import chip_pb2 - path = Path(bbdir) / "examples" / "chips" / chip / "configs" / "generated" / "chip.pb" + + path = ( + Path(bbdir) / "examples" / "chips" / chip / "configs" / "generated" / "chip.pb" + ) if not path.is_file(): raise FileNotFoundError(f"missing {path}; run bbdev config --install") msg = chip_pb2.Chip() @@ -33,7 +36,14 @@ def load_chip(bbdir: str, chip: str): def ball_domain(chip): - return chip.cores[0].balldomain + domain = chip.cores[0].balldomain + for core in chip.cores[1:]: + if ( + core.balldomain.mappings != domain.mappings + or core.balldomain.isa != domain.isa + ): + raise ValueError("chip.pb cores have different balldomains") + return domain def selected_mappings(domain, ball: str | None): @@ -63,19 +73,28 @@ def smatmul_accumulator_filename(rtl_dir: Path) -> str: text = (rtl_dir / "SMatMulUnit.sv").read_text() modules = re.findall(r"\b(accumulator_\d+x\d+)\s+accumulator_ext\s*\(", text) if len(modules) != 1: - raise ValueError(f"expected one SMatMul accumulator instance, found {modules!r}") + raise ValueError( + f"expected one SMatMul accumulator instance, found {modules!r}" + ) return f"{modules[0]}.sv" def _filelist( - verify_dir: Path, ball_dir: str, uvm_rel: str, rtl_rel: str, rtl_dir: Path, sim_dir: Path + verify_dir: Path, + ball_dir: str, + uvm_rel: str, + rtl_rel: str, + rtl_dir: Path, + sim_dir: Path, ) -> str: src = verify_dir / "filelists" / f"{ball_dir}_ball.f" dst = sim_dir / f"{ball_dir}_ball.f" sim_dir.mkdir(parents=True, exist_ok=True) text = src.read_text() if "@SMATMUL_ACCUMULATOR@" in text: - text = text.replace("@SMATMUL_ACCUMULATOR@", smatmul_accumulator_filename(rtl_dir)) + text = text.replace( + "@SMATMUL_ACCUMULATOR@", smatmul_accumulator_filename(rtl_dir) + ) dst.write_text(text.replace("@UVM@", uvm_rel).replace("@RTL@", rtl_rel)) return str(dst.relative_to(verify_dir)) @@ -84,7 +103,16 @@ def load_ip(bbdir: str, name: str) -> dict: registry = Path(bbdir) / "verify" / "uvm" / "ip.toml" if name not in tomllib.loads(registry.read_text()).get("ips", []): raise ValueError(f"IP {name!r} is not registered in {registry}") - root = Path(bbdir) / "arch" / "src" / "main" / "scala" / "framework" / "mem-core" / name + root = ( + Path(bbdir) + / "arch" + / "src" + / "main" + / "scala" + / "framework" + / "mem-core" + / name + ) resources = root / "src" / "main" / "resources" targets = [ { @@ -106,7 +134,9 @@ def load_ip(bbdir: str, name: str) -> dict: } -def _ip_filelist(resources: Path, source: str, sim_dir: Path, bbdir: str, rtl: Path) -> Path: +def _ip_filelist( + resources: Path, source: str, sim_dir: Path, bbdir: str, rtl: Path +) -> Path: dst = sim_dir / source sim_dir.mkdir(parents=True, exist_ok=True) text = (resources / source).read_text() @@ -201,7 +231,10 @@ def run_ip(bbdir: str, chip: str, name: str, ctx, cov_root: str) -> dict: model = manifest.parent / "target" / "debug" / f"lib{crate}" sim_root = root / "build" / "uvm" / chip - if any(not (sim_root / target["name"] / "simv").is_file() for target in config["targets"]): + if any( + not (sim_root / target["name"] / "simv").is_file() + for target in config["targets"] + ): config = build_ip(bbdir, chip, name, ctx) coverage = [] @@ -209,7 +242,7 @@ def run_ip(bbdir: str, chip: str, name: str, ctx, cov_root: str) -> dict: simv = sim_root / target["name"] / "simv" cov_dir = Path(cov_root) / target["name"] / "coverage" script = ( - f"env LD_LIBRARY_PATH=\"$VCS_RUNTIME_LIBRARY_PATH\" {shlex.quote(str(simv))} " + f'env LD_LIBRARY_PATH="$VCS_RUNTIME_LIBRARY_PATH" {shlex.quote(str(simv))} ' f"-sv_lib {shlex.quote(str(model))} +UVM_TESTNAME={shlex.quote(target['test'])} " f"-cm line+cond+tgl+assert -cm_name {shlex.quote(target['test'])}" ) @@ -254,7 +287,13 @@ def run_ip(bbdir: str, chip: str, name: str, ctx, cov_root: str) -> dict: ) index = index_dir / "index.txt" index.write_text("\n".join(rows) + "\n") - return {"chip": chip, "ip": name, "targets": [t["name"] for t in config["targets"]], "index": str(index), "log": cov_root} + return { + "chip": chip, + "ip": name, + "targets": [t["name"] for t in config["targets"]], + "index": str(index), + "log": cov_root, + } def build_ball(bbdir: str, chip_name: str, mill_cfg: str, domain, mapping, ctx) -> None: @@ -271,7 +310,13 @@ def build_ball(bbdir: str, chip_name: str, mill_cfg: str, domain, mapping, ctx) f"nix develop {shlex.quote(str(Path(bbdir) / 'verify'))} --command " f"cargo build --manifest-path {shlex.quote(str(casegen))}" ) - r = stream_run_logger(cmd=cargo, logger=ctx.logger, cwd=bbdir, stdout_prefix="uvm dpi", stderr_prefix="uvm dpi") + r = stream_run_logger( + cmd=cargo, + logger=ctx.logger, + cwd=bbdir, + stdout_prefix="uvm dpi", + stderr_prefix="uvm dpi", + ) if r.returncode != 0: raise RuntimeError(f"cargo build failed for {ball}") crate = tomllib.loads(casegen.read_text()).get("package", {}).get("name") @@ -294,32 +339,51 @@ def build_ball(bbdir: str, chip_name: str, mill_cfg: str, domain, mapping, ctx) f"-f {shlex.quote(flist)}" ) vcs = f"nix develop {shlex.quote(str(Path(bbdir) / 'verify'))} --command zsh -c {shlex.quote(script)}" - r = stream_run_logger(cmd=vcs, logger=ctx.logger, cwd=bbdir, stdout_prefix="uvm vcs", stderr_prefix="uvm vcs") + r = stream_run_logger( + cmd=vcs, + logger=ctx.logger, + cwd=bbdir, + stdout_prefix="uvm vcs", + stderr_prefix="uvm vcs", + ) if r.returncode != 0: raise RuntimeError(f"vcs failed for {ball}") -def run_ball(bbdir: str, chip_name: str, mill_cfg: str, domain, mapping, ctx, cov_dir: str) -> None: +def run_ball( + bbdir: str, chip_name: str, mill_cfg: str, domain, mapping, ctx, cov_dir: str +) -> None: ball = mapping.ball_dir verify_dir = Path(bbdir) / "examples" / "balls" / ball / "verify" simv = verify_dir / "build" / chip_name / "simv" if not simv.is_file(): build_ball(bbdir, chip_name, mill_cfg, domain, mapping, ctx) - crate = tomllib.loads((verify_dir / "casegen" / "Cargo.toml").read_text())["package"]["name"] + crate = tomllib.loads((verify_dir / "casegen" / "Cargo.toml").read_text())[ + "package" + ]["name"] dpi = verify_dir / "casegen" / "target" / "debug" / f"lib{crate.replace('-', '_')}" test = f"{ball}_ball_test" verify_config = verify_dir / "build" / chip_name / "verify_config.env" bank_entries = load_chip(bbdir, chip_name).cores[0].mem.bank.entries - verify_config.write_text(f"bank_entries={bank_entries}\nball_id={mapping.ball_id}\n") + verify_config.write_text( + f"bank_entries={bank_entries}\nball_id={mapping.ball_id}\n" + ) script = ( f"cd {shlex.quote(str(verify_dir))} && " - f"env LD_LIBRARY_PATH=\"$VCS_RUNTIME_LIBRARY_PATH\" BB_VERIFY_CONFIG={shlex.quote(str(verify_config))} {shlex.quote(str(simv))} " + f'env LD_LIBRARY_PATH="$VCS_RUNTIME_LIBRARY_PATH" BB_VERIFY_CONFIG={shlex.quote(str(verify_config))} ' + f"{shlex.quote(str(simv))} " f"-sv_lib {shlex.quote(str(dpi))} " f"+UVM_TESTNAME={shlex.quote(test)} +BID={mapping.ball_id} " f"-cm line+cond+tgl+assert -cm_name {shlex.quote(test)}" ) cmd = f"nix develop {shlex.quote(str(Path(bbdir) / 'verify'))} --command zsh -ic {shlex.quote(script)}" - r = stream_run_logger(cmd=cmd, logger=ctx.logger, cwd=bbdir, stdout_prefix="uvm run", stderr_prefix="uvm run") + r = stream_run_logger( + cmd=cmd, + logger=ctx.logger, + cwd=bbdir, + stdout_prefix="uvm run", + stderr_prefix="uvm run", + ) if r.returncode != 0: raise RuntimeError(f"uvm run failed for {ball} test={test}") Path(cov_dir).mkdir(parents=True, exist_ok=True) @@ -327,7 +391,13 @@ def run_ball(bbdir: str, chip_name: str, mill_cfg: str, domain, mapping, ctx, co f"nix develop {shlex.quote(str(Path(bbdir) / 'verify'))} --command " f"urg -dir {shlex.quote(str(simv))}.vdb -format text -report {shlex.quote(cov_dir)}" ) - r = stream_run_logger(cmd=urg, logger=ctx.logger, cwd=str(verify_dir), stdout_prefix="uvm urg", stderr_prefix="uvm urg") + r = stream_run_logger( + cmd=urg, + logger=ctx.logger, + cwd=str(verify_dir), + stdout_prefix="uvm urg", + stderr_prefix="uvm urg", + ) if r.returncode != 0: raise RuntimeError(f"urg failed for {ball}") @@ -356,13 +426,7 @@ def run_chip(bbdir: str, chip: str, ball: str | None, ctx, do_run: bool) -> dict msg = load_chip(bbdir, chip) domain = ball_domain(msg) mill_cfg = msg.mill.verilator_config - maps = [ - m - for m in selected_mappings(domain, ball) - if (Path(bbdir) / "examples" / "balls" / m.ball_dir / "verify" / "filelists" / f"{m.ball_dir}_ball.f").is_file() - ] - if ball is not None and not maps: - raise ValueError(f"UVM filelist missing for ball {ball!r}") + maps = selected_mappings(domain, ball) ran = [] covs = [] run_root = None @@ -404,7 +468,9 @@ def run_chip(bbdir: str, chip: str, ball: str | None, ctx, do_run: bool) -> dict return info -def run_uvm(bbdir: str, chip: str, ball: str | None, ip: str | None, ctx, do_run: bool) -> dict: +def run_uvm( + bbdir: str, chip: str, ball: str | None, ip: str | None, ctx, do_run: bool +) -> dict: if ip is None: return run_chip(bbdir, chip, ball, ctx, do_run) if do_run: @@ -412,4 +478,8 @@ def run_uvm(bbdir: str, chip: str, ball: str | None, ip: str | None, ctx, do_run run_root = log_dir(bbdir, chip, "verilog", stamp, "uvm", ip) return run_ip(bbdir, chip, ip, ctx, run_root) config = build_ip(bbdir, chip, ip, ctx) - return {"chip": chip, "ip": ip, "targets": [target["name"] for target in config["targets"]]} + return { + "chip": chip, + "ip": ip, + "targets": [target["name"] for target in config["targets"]], + } diff --git a/api/tests/test_bebop_p2e_diff.py b/api/tests/test_bebop_p2e_diff.py index 084f53a..3dd5896 100644 --- a/api/tests/test_bebop_p2e_diff.py +++ b/api/tests/test_bebop_p2e_diff.py @@ -5,7 +5,6 @@ import unittest from pathlib import Path - ROOT = Path(__file__).resolve().parents[3] API = ROOT / "bbdev" / "api" sys.path.insert(0, str(API)) @@ -24,19 +23,23 @@ class BebopP2eDiffTest(unittest.TestCase): def test_bbdev_parses_diff_options(self): cli = runpy.run_path(str(ROOT / "bbdev" / "bbdev")) - args = cli["parse_args"]([ - "bebop-p2e", - "--runworkload", - "--chip pebble --image fw_payload-lenet --bitstream image.bit " - "--diff --golden-elf golden.elf --golden-pk", - ]) + args = cli["parse_args"]( + [ + "bebop-p2e", + "--runworkload", + "--chip pebble --image fw_payload-lenet --bitstream image.bit " + "--diff --golden-elf golden.elf --golden-pk", + ] + ) parsed = cli["extract_command_info"](args)["args"] self.assertEqual(parsed["golden-elf"], "golden.elf") self.assertTrue(parsed["diff"]) self.assertTrue(parsed["golden-pk"]) def test_diff_runtime_build_enables_bemu_and_difftest(self): - command, cwd = event.runtime_build_command("/repo", "pebble", True, "/rtl", "/case") + command, cwd = event.runtime_build_command( + "/repo", "pebble", True, "/rtl", "/case" + ) self.assertIn("--features p2e,bemu,difftest", command) self.assertIn("/repo/examples/chips/pebble/generated/bebop/Cargo.toml", command) self.assertTrue(command.endswith("--diff")) @@ -44,15 +47,19 @@ def test_diff_runtime_build_enables_bemu_and_difftest(self): def test_normal_runtime_build_only_enables_p2e(self): cli = runpy.run_path(str(ROOT / "bbdev" / "bbdev")) - args = cli["parse_args"]([ - "bebop-p2e", - "--runworkload", - "--chip pebble --image fw_payload-lenet --bitstream image.bit --diff=false", - ]) + args = cli["parse_args"]( + [ + "bebop-p2e", + "--runworkload", + "--chip pebble --image fw_payload-lenet --bitstream image.bit --diff=false", + ] + ) parsed = cli["extract_command_info"](args)["args"] self.assertFalse(parsed["diff"]) - command, cwd = event.runtime_build_command("/repo", "pebble", False, "/rtl", "/case") + command, cwd = event.runtime_build_command( + "/repo", "pebble", False, "/rtl", "/case" + ) self.assertIn("--features p2e", command) self.assertNotIn("bemu", command) self.assertNotIn("difftest", command) diff --git a/api/tests/test_uvm_chip_pb.py b/api/tests/test_uvm_chip_pb.py index d671d7a..78d1fcf 100644 --- a/api/tests/test_uvm_chip_pb.py +++ b/api/tests/test_uvm_chip_pb.py @@ -1,12 +1,20 @@ import sys from pathlib import Path +from tempfile import TemporaryDirectory +from unittest.mock import patch API = Path(__file__).resolve().parents[1] sys.path.insert(0, str(API / "steps" / "config" / "scripts")) sys.path.insert(0, str(API / "steps" / "uvm")) import chip_pb2 -from scripts.uvm_common import ball_domain, load_ip, selected_mappings, vcs_defines +from scripts.uvm_common import ( + ball_domain, + load_ip, + run_chip, + selected_mappings, + vcs_defines, +) def _chip(): @@ -43,7 +51,11 @@ def test_registered_ip(): root = str(Path(__file__).resolve().parents[3]) axis = load_ip(root, "axis") assert axis["mill_module"] == "axis" - assert [target["name"] for target in axis["targets"]] == ["arbiter", "master", "slave"] + assert [target["name"] for target in axis["targets"]] == [ + "arbiter", + "master", + "slave", + ] def test_unknown_ball(): @@ -55,16 +67,42 @@ def test_unknown_ball(): raise AssertionError("expected ValueError") -def test_heterogeneous_chip_uses_first_domain(): +def test_homogeneous_chip(): + c = _chip() + c.cores.add().CopyFrom(c.cores[0]) + c.cores[1].balldomain.source_path = "another/config.toml" + assert ball_domain(c) == c.cores[0].balldomain + + +def test_heterogeneous_chip_rejected(): c = _chip() other = c.cores.add() other.balldomain.mappings.add().ball_dir = "gemmini" - assert [m.ball_dir for m in ball_domain(c).mappings] == ["lut"] + try: + ball_domain(c) + except ValueError as e: + assert "different balldomains" in str(e) + return + raise AssertionError("expected ValueError") + + +def test_missing_filelist_is_not_skipped(): + with TemporaryDirectory() as root, patch( + "scripts.uvm_common.load_chip", return_value=_chip() + ): + try: + run_chip(root, "test", None, None, False) + except FileNotFoundError as e: + assert Path(e.filename).name == "lut_ball.f" + return + raise AssertionError("expected FileNotFoundError") if __name__ == "__main__": test_defines() test_registered_ip() test_unknown_ball() - test_heterogeneous_chip_uses_first_domain() + test_homogeneous_chip() + test_heterogeneous_chip_rejected() + test_missing_filelist_is_not_skipped() print("ok") From 7dfc7dadf98c128fd854c11d4d402e7f489ad178 Mon Sep 17 00:00:00 2001 From: shirohasuki Date: Sat, 19 Sep 2026 19:46:19 +0800 Subject: [PATCH 07/11] refactor: update CPU configuration handling in JSON to proto conversion and enhance UVM script functionality --- api/steps/config/scripts/3_json2proto.py | 41 ++++---- api/steps/config/scripts/proto/chip.proto | 26 +++--- api/steps/uvm/scripts/uvm_common.py | 106 ++++++++++++++++----- api/tests/test_all_api_serial.py | 2 + api/tests/test_api_bebop_bemu_analysis.py | 11 +-- api/tests/test_api_bebop_bemu_sim.py | 2 +- api/tests/test_api_uvm_build.py | 3 + api/tests/test_api_uvm_run.py | 3 + api/tests/test_bebop_p2e_diff.py | 71 -------------- api/tests/test_uvm_chip_pb.py | 108 ---------------------- api/tests/test_uvm_waive.py | 24 ----- 11 files changed, 122 insertions(+), 275 deletions(-) create mode 100644 api/tests/test_api_uvm_build.py create mode 100644 api/tests/test_api_uvm_run.py delete mode 100644 api/tests/test_bebop_p2e_diff.py delete mode 100644 api/tests/test_uvm_chip_pb.py delete mode 100644 api/tests/test_uvm_waive.py diff --git a/api/steps/config/scripts/3_json2proto.py b/api/steps/config/scripts/3_json2proto.py index 068e066..21f2287 100644 --- a/api/steps/config/scripts/3_json2proto.py +++ b/api/steps/config/scripts/3_json2proto.py @@ -147,25 +147,12 @@ def _fill_boom(msg: pb.BoomCoreConfig, d: dict[str, Any]) -> None: msg.icache.n_ways = ic["nWays"] -def _check_core_kind(raw: dict[str, Any], pkg: str) -> str: - kind = raw.get("kind") +def _check_cpu_kind(cpu: dict[str, Any], pkg: str) -> str: + kind = cpu.get("kind") if kind not in ("rocket", "boom"): raise ValueError(f"{pkg}: kind must be 'rocket' or 'boom', got {kind!r}") - has_rocket = "rocketCore" in raw - has_boom = "boomCore" in raw - if kind == "rocket": - if not has_rocket: - raise ValueError(f"{pkg}: kind=rocket requires rocketCore") - if has_boom: - raise ValueError(f"{pkg}: kind=rocket forbids boomCore") - else: - if not has_boom: - raise ValueError(f"{pkg}: kind=boom requires boomCore") - if has_rocket: - raise ValueError(f"{pkg}: kind=boom forbids rocketCore") - bd = raw.get("balldomain") - if isinstance(bd, dict) and bd.get("ballNum", 0): - raise ValueError(f"{pkg}: kind=boom forbids balldomain.ballNum > 0") + if not isinstance(cpu.get("config"), dict): + raise ValueError(f"{pkg}: cpu.config must reference a TOML file") return kind @@ -191,8 +178,7 @@ def _fill_gp(msg: pb.GpDomainConfig, d: dict[str, Any], bbdir: Path) -> None: msg.lane_scale = d["laneScale"] -def _fill_core_param(msg: pb.CoreParamConfig, d: dict[str, Any], bbdir: Path) -> None: - msg.source_path = _rel(bbdir, d["_file"]) +def _fill_cpu_params(msg: pb.CpuConfig, d: dict[str, Any]) -> None: msg.core_data_bytes = d["coreDataBytes"] msg.x_len = d["xLen"] msg.vaddr_bits = d["vaddrBits"] @@ -207,22 +193,27 @@ def _fill_core(ci: pb.CoreInstance, raw: dict[str, Any], meta: dict[str, Any], b ci.pkg = meta["pkg"] ci.config_path = meta["config_path"] ci.balldomain_base_dir = meta["balldomain_base_dir"] - kind = _check_core_kind(raw, meta["pkg"]) - ci.kind = kind + cpu = raw["cpu"] + kind = _check_cpu_kind(cpu, meta["pkg"]) + ci.cpu.kind = kind + ci.cpu.source_path = _rel(bbdir, cpu["_file"]) if "balldomain" in raw: _fill_ball(ci.balldomain, raw["balldomain"], bbdir) if "memdomain" in raw: _fill_mem(ci.mem, raw["memdomain"], bbdir) if kind == "rocket": - _fill_rocket(ci.rocket_core, raw["rocketCore"]) + _fill_rocket(ci.cpu.rocket, cpu["config"]) else: - _fill_boom(ci.boom_core, raw["boomCore"]) + _fill_boom(ci.cpu.boom, cpu["config"]) + bd = raw.get("balldomain") + if isinstance(bd, dict) and bd.get("ballNum", 0): + raise ValueError(f"{meta['pkg']}: kind=boom forbids balldomain.ballNum > 0") if "frontend" in raw: _fill_frontend(ci.frontend, raw["frontend"], bbdir) if "gpdomain" in raw: _fill_gp(ci.gp_domain, raw["gpdomain"], bbdir) - if "core" in raw: - _fill_core_param(ci.core, raw["core"], bbdir) + if ci.balldomain.ball_num > 0: + _fill_cpu_params(ci.cpu, cpu) def _fill_tile(tp: pb.TilePlacement, meta: dict[str, Any], proto: dict[str, Any]) -> None: diff --git a/api/steps/config/scripts/proto/chip.proto b/api/steps/config/scripts/proto/chip.proto index 71c62ac..68cc01d 100644 --- a/api/steps/config/scripts/proto/chip.proto +++ b/api/steps/config/scripts/proto/chip.proto @@ -170,14 +170,19 @@ message GpDomainConfig { string source_path = 7; } -message CoreParamConfig { - uint32 core_data_bytes = 1; - uint32 x_len = 2; - uint32 vaddr_bits = 3; - uint32 paddr_bits = 4; - uint32 pg_idx_bits = 5; - uint32 n_pmps = 6; - string source_path = 7; +message CpuConfig { + string kind = 1; + oneof cpu { + RocketCoreConfig rocket = 2; + BoomCoreConfig boom = 3; + } + string source_path = 4; + uint32 core_data_bytes = 5; + uint32 x_len = 6; + uint32 vaddr_bits = 7; + uint32 paddr_bits = 8; + uint32 pg_idx_bits = 9; + uint32 n_pmps = 10; } message CoreInstance { @@ -188,12 +193,9 @@ message CoreInstance { BallDomain balldomain = 5; MemDomainConfig mem = 6; string balldomain_base_dir = 7; - RocketCoreConfig rocket_core = 8; + CpuConfig cpu = 8; FrontendConfig frontend = 9; GpDomainConfig gp_domain = 10; - CoreParamConfig core = 11; - string kind = 12; - BoomCoreConfig boom_core = 13; } message BemuBall { diff --git a/api/steps/uvm/scripts/uvm_common.py b/api/steps/uvm/scripts/uvm_common.py index 23ad740..f67d912 100644 --- a/api/steps/uvm/scripts/uvm_common.py +++ b/api/steps/uvm/scripts/uvm_common.py @@ -35,14 +35,39 @@ def load_chip(bbdir: str, chip: str): return msg -def ball_domain(chip): - domain = chip.cores[0].balldomain - for core in chip.cores[1:]: - if ( - core.balldomain.mappings != domain.mappings - or core.balldomain.isa != domain.isa - ): - raise ValueError("chip.pb cores have different balldomains") +def load_chip_uvm(bbdir: str, chip: str) -> dict[str, list[str]]: + path = Path(bbdir) / "examples" / "chips" / chip / "configs" / "chip.toml" + config = tomllib.loads(path.read_text())["uvm"] + if set(config) != {"balls", "ips"}: + raise ValueError(f"{path}: [uvm] must define exactly balls and ips") + if not config["balls"] and not config["ips"]: + raise ValueError(f"{path}: [uvm] has no targets") + return config + + +def ball_domain(chip, ball: str | None = None): + if ball is None: + domains = [core.balldomain for core in chip.cores] + domain = domains[0] + for other in domains[1:]: + if other.mappings != domain.mappings or other.isa != domain.isa: + raise ValueError("chip.pb cores have different balldomains") + return domain + + matches = [] + for core in chip.cores: + mappings = [m for m in core.balldomain.mappings if m.ball_dir == ball] + if mappings: + mapping = mappings[0] + isa = [e for e in core.balldomain.isa if e.bid == mapping.ball_id] + matches.append((core.balldomain, mapping, isa)) + if not matches: + raise ValueError(f"ball {ball!r} not in chip.pb") + + domain, mapping, isa = matches[0] + for _, other_mapping, other_isa in matches[1:]: + if other_mapping != mapping or other_isa != isa: + raise ValueError(f"ball {ball!r} has different definitions across cores") return domain @@ -148,6 +173,20 @@ def _ip_filelist( return dst +def check_uvm_result(result, target: str) -> None: + if result.returncode != 0: + raise RuntimeError(f"UVM failed for {target}") + counts = dict( + re.findall(r"UVM_(ERROR|FATAL)\s*:\s*(\d+)", result.stdout + result.stderr) + ) + if set(counts) != {"ERROR", "FATAL"}: + raise RuntimeError(f"UVM report summary missing for {target}") + if counts != {"ERROR": "0", "FATAL": "0"}: + raise RuntimeError( + f"UVM failed for {target}: errors={counts['ERROR']} fatals={counts['FATAL']}" + ) + + def build_ip(bbdir: str, chip: str, name: str, ctx) -> dict: config = load_ip(bbdir, name) root = Path(bbdir) / config["root"] @@ -257,8 +296,7 @@ def run_ip(bbdir: str, chip: str, name: str, ctx, cov_root: str) -> dict: stdout_prefix="uvm run", stderr_prefix="uvm run", ) - if result.returncode != 0: - raise RuntimeError(f"UVM failed for IP {name} target={target['name']}") + check_uvm_result(result, f"IP {name} target={target['name']}") cov_dir.mkdir(parents=True, exist_ok=True) urg = ( @@ -384,8 +422,7 @@ def run_ball( stdout_prefix="uvm run", stderr_prefix="uvm run", ) - if r.returncode != 0: - raise RuntimeError(f"uvm run failed for {ball} test={test}") + check_uvm_result(r, f"{ball} test={test}") Path(cov_dir).mkdir(parents=True, exist_ok=True) urg = ( f"nix develop {shlex.quote(str(Path(bbdir) / 'verify'))} --command " @@ -424,7 +461,7 @@ def dashboard_summary(cov_dir: str) -> dict[str, str]: def run_chip(bbdir: str, chip: str, ball: str | None, ctx, do_run: bool) -> dict: msg = load_chip(bbdir, chip) - domain = ball_domain(msg) + domain = ball_domain(msg, ball) mill_cfg = msg.mill.verilator_config maps = selected_mappings(domain, ball) ran = [] @@ -471,15 +508,36 @@ def run_chip(bbdir: str, chip: str, ball: str | None, ctx, do_run: bool) -> dict def run_uvm( bbdir: str, chip: str, ball: str | None, ip: str | None, ctx, do_run: bool ) -> dict: - if ip is None: + if ball is not None: return run_chip(bbdir, chip, ball, ctx, do_run) - if do_run: - stamp = datetime.now().strftime("%Y-%m-%d-%H-%M") - run_root = log_dir(bbdir, chip, "verilog", stamp, "uvm", ip) - return run_ip(bbdir, chip, ip, ctx, run_root) - config = build_ip(bbdir, chip, ip, ctx) - return { - "chip": chip, - "ip": ip, - "targets": [target["name"] for target in config["targets"]], - } + if ip is not None: + if do_run: + stamp = datetime.now().strftime("%Y-%m-%d-%H-%M") + run_root = log_dir(bbdir, chip, "verilog", stamp, "uvm", ip) + return run_ip(bbdir, chip, ip, ctx, run_root) + config = build_ip(bbdir, chip, ip, ctx) + return { + "chip": chip, + "ip": ip, + "targets": [target["name"] for target in config["targets"]], + } + + targets = load_chip_uvm(bbdir, chip) + results = [ + run_chip(bbdir, chip, target, ctx, do_run) for target in targets["balls"] + ] + for target in targets["ips"]: + if do_run: + stamp = datetime.now().strftime("%Y-%m-%d-%H-%M") + run_root = log_dir(bbdir, chip, "verilog", stamp, "uvm", target) + results.append(run_ip(bbdir, chip, target, ctx, run_root)) + else: + config = build_ip(bbdir, chip, target, ctx) + results.append( + { + "chip": chip, + "ip": target, + "targets": [item["name"] for item in config["targets"]], + } + ) + return {"chip": chip, "results": results} diff --git a/api/tests/test_all_api_serial.py b/api/tests/test_all_api_serial.py index 3cca9e0..f687934 100755 --- a/api/tests/test_all_api_serial.py +++ b/api/tests/test_all_api_serial.py @@ -19,6 +19,8 @@ def main(): "test_api_yosys_verilog.py", "test_api_yosys_synth.py", "test_api_dc_verilog.py", + "test_api_uvm_build.py", + "test_api_uvm_run.py", "test_api_kernel_build.py", "test_api_firesim_enumeratefpgas.py", "test_api_firesim_buildbitstream.py", diff --git a/api/tests/test_api_bebop_bemu_analysis.py b/api/tests/test_api_bebop_bemu_analysis.py index 4aba36d..3f86a66 100644 --- a/api/tests/test_api_bebop_bemu_analysis.py +++ b/api/tests/test_api_bebop_bemu_analysis.py @@ -1,12 +1,3 @@ -from pathlib import Path -import shutil -import tempfile - from _api_test_helper import run_bbdev_case -src = Path(__file__).resolve().parent / "fixtures" / "bemu-analysis" -dst = Path(tempfile.mkdtemp()) / "bemu-analysis" -shutil.copytree(src, dst) -run_bbdev_case( - f"bbdev bebop-bemu --analysis '--chip pebble --log-dir {dst} --itrace --mtrace'" -) +run_bbdev_case("bbdev bebop-bemu --analysis '--chip pebble --log-dir fixtures/bemu-analysis --itrace --mtrace'") # fmt: skip diff --git a/api/tests/test_api_bebop_bemu_sim.py b/api/tests/test_api_bebop_bemu_sim.py index 179a87d..8277407 100644 --- a/api/tests/test_api_bebop_bemu_sim.py +++ b/api/tests/test_api_bebop_bemu_sim.py @@ -1,4 +1,4 @@ from _api_test_helper import run_bbdev_case -run_bbdev_case("bbdev bebop-bemu --sim '--chip toy --binary toy_relu_test-singlecore-baremetal'") +run_bbdev_case("bbdev bebop-bemu --sim '--chip toy --binary toy_relu_test-singlecore-baremetal'") # fmt: skip run_bbdev_case("bbdev bebop-bemu --sim '--chip toy --binary toy_relu_test-linux --pk'") diff --git a/api/tests/test_api_uvm_build.py b/api/tests/test_api_uvm_build.py new file mode 100644 index 0000000..eba6741 --- /dev/null +++ b/api/tests/test_api_uvm_build.py @@ -0,0 +1,3 @@ +from _api_test_helper import run_bbdev_case + +run_bbdev_case("bbdev uvm --build '--chip toy --ip axis'") diff --git a/api/tests/test_api_uvm_run.py b/api/tests/test_api_uvm_run.py new file mode 100644 index 0000000..eaca26f --- /dev/null +++ b/api/tests/test_api_uvm_run.py @@ -0,0 +1,3 @@ +from _api_test_helper import run_bbdev_case + +run_bbdev_case("bbdev uvm --run '--chip toy --ip axis'") diff --git a/api/tests/test_bebop_p2e_diff.py b/api/tests/test_bebop_p2e_diff.py deleted file mode 100644 index 3dd5896..0000000 --- a/api/tests/test_bebop_p2e_diff.py +++ /dev/null @@ -1,71 +0,0 @@ -import importlib.util -import runpy -import sys -import types -import unittest -from pathlib import Path - -ROOT = Path(__file__).resolve().parents[3] -API = ROOT / "bbdev" / "api" -sys.path.insert(0, str(API)) - -motia = types.ModuleType("motia") -motia.FlowContext = object -motia.queue = lambda name: name -sys.modules["motia"] = motia - -event_path = API / "steps" / "bebop" / "p2e" / "03_runworkload_event.step.py" -spec = importlib.util.spec_from_file_location("bebop_p2e_runworkload_event", event_path) -event = importlib.util.module_from_spec(spec) -spec.loader.exec_module(event) - - -class BebopP2eDiffTest(unittest.TestCase): - def test_bbdev_parses_diff_options(self): - cli = runpy.run_path(str(ROOT / "bbdev" / "bbdev")) - args = cli["parse_args"]( - [ - "bebop-p2e", - "--runworkload", - "--chip pebble --image fw_payload-lenet --bitstream image.bit " - "--diff --golden-elf golden.elf --golden-pk", - ] - ) - parsed = cli["extract_command_info"](args)["args"] - self.assertEqual(parsed["golden-elf"], "golden.elf") - self.assertTrue(parsed["diff"]) - self.assertTrue(parsed["golden-pk"]) - - def test_diff_runtime_build_enables_bemu_and_difftest(self): - command, cwd = event.runtime_build_command( - "/repo", "pebble", True, "/rtl", "/case" - ) - self.assertIn("--features p2e,bemu,difftest", command) - self.assertIn("/repo/examples/chips/pebble/generated/bebop/Cargo.toml", command) - self.assertTrue(command.endswith("--diff")) - self.assertEqual(cwd, "/repo/examples/chips/pebble/generated/bebop") - - def test_normal_runtime_build_only_enables_p2e(self): - cli = runpy.run_path(str(ROOT / "bbdev" / "bbdev")) - args = cli["parse_args"]( - [ - "bebop-p2e", - "--runworkload", - "--chip pebble --image fw_payload-lenet --bitstream image.bit --diff=false", - ] - ) - parsed = cli["extract_command_info"](args)["args"] - self.assertFalse(parsed["diff"]) - - command, cwd = event.runtime_build_command( - "/repo", "pebble", False, "/rtl", "/case" - ) - self.assertIn("--features p2e", command) - self.assertNotIn("bemu", command) - self.assertNotIn("difftest", command) - self.assertFalse(command.endswith("--diff")) - self.assertEqual(cwd, "/repo/bebop") - - -if __name__ == "__main__": - unittest.main() diff --git a/api/tests/test_uvm_chip_pb.py b/api/tests/test_uvm_chip_pb.py deleted file mode 100644 index 78d1fcf..0000000 --- a/api/tests/test_uvm_chip_pb.py +++ /dev/null @@ -1,108 +0,0 @@ -import sys -from pathlib import Path -from tempfile import TemporaryDirectory -from unittest.mock import patch - -API = Path(__file__).resolve().parents[1] -sys.path.insert(0, str(API / "steps" / "config" / "scripts")) -sys.path.insert(0, str(API / "steps" / "uvm")) - -import chip_pb2 -from scripts.uvm_common import ( - ball_domain, - load_ip, - run_chip, - selected_mappings, - vcs_defines, -) - - -def _chip(): - c = chip_pb2.Chip() - core = c.cores.add() - m = core.balldomain.mappings.add() - m.ball_id = 5 - m.ball_dir = "lut" - m.in_bw = 2 - m.out_bw = 1 - isa = core.balldomain.isa.add() - isa.mnemonic = "LUT" - isa.funct7 = 66 - isa.bid = 5 - return c - - -def test_defines(): - c = _chip() - d = ball_domain(c) - m = selected_mappings(d, "lut")[0] - defs = vcs_defines(d, m, 4096) - assert defs == [ - "+define+BB_IN_BW=2", - "+define+BB_OUT_BW=1", - "+define+BB_MMIO_READ_BW=0", - "+define+BB_MMIO_WRITE_BW=0", - "+define+BB_BANK_ADDR_W=12", - "+define+LUT_FUNCT7=66", - ] - - -def test_registered_ip(): - root = str(Path(__file__).resolve().parents[3]) - axis = load_ip(root, "axis") - assert axis["mill_module"] == "axis" - assert [target["name"] for target in axis["targets"]] == [ - "arbiter", - "master", - "slave", - ] - - -def test_unknown_ball(): - try: - selected_mappings(ball_domain(_chip()), "transpose") - except ValueError as e: - assert "transpose" in str(e) - return - raise AssertionError("expected ValueError") - - -def test_homogeneous_chip(): - c = _chip() - c.cores.add().CopyFrom(c.cores[0]) - c.cores[1].balldomain.source_path = "another/config.toml" - assert ball_domain(c) == c.cores[0].balldomain - - -def test_heterogeneous_chip_rejected(): - c = _chip() - other = c.cores.add() - other.balldomain.mappings.add().ball_dir = "gemmini" - try: - ball_domain(c) - except ValueError as e: - assert "different balldomains" in str(e) - return - raise AssertionError("expected ValueError") - - -def test_missing_filelist_is_not_skipped(): - with TemporaryDirectory() as root, patch( - "scripts.uvm_common.load_chip", return_value=_chip() - ): - try: - run_chip(root, "test", None, None, False) - except FileNotFoundError as e: - assert Path(e.filename).name == "lut_ball.f" - return - raise AssertionError("expected FileNotFoundError") - - -if __name__ == "__main__": - test_defines() - test_registered_ip() - test_unknown_ball() - test_homogeneous_chip() - test_heterogeneous_chip_rejected() - test_missing_filelist_is_not_skipped() - print("ok") diff --git a/api/tests/test_uvm_waive.py b/api/tests/test_uvm_waive.py deleted file mode 100644 index 2ca2af0..0000000 --- a/api/tests/test_uvm_waive.py +++ /dev/null @@ -1,24 +0,0 @@ -import sys -from pathlib import Path - -API = Path(__file__).resolve().parents[1] -sys.path.insert(0, str(API / "steps" / "uvm")) - -from scripts.waive import apply_waivers - - -def test_waivers_are_target_agnostic_and_idempotent(tmp_path): - rtl = tmp_path / "rtl" - rtl.mkdir() - source = rtl / "Dut.sv" - source.write_text("module Dut;\n wire _GEN = 1'b0;\nendmodule\n") - - assert apply_waivers(rtl) == 1 - assert source.read_text() == ( - "module Dut;\n" - "//VCS coverage off\n" - " wire _GEN = 1'b0;\n" - "//VCS coverage on\n" - "endmodule\n" - ) - assert apply_waivers(rtl) == 0 From 2ffd614db61ecbc66aeddbf8738e8afe2bdecbd5 Mon Sep 17 00:00:00 2001 From: shirohasuki Date: Mon, 21 Sep 2026 15:12:43 +0800 Subject: [PATCH 08/11] feat: add support for diff option in Bebop P2E and Verilator workflows --- api/steps/bebop/bemu/02_sim_event.step.py | 7 +- api/steps/bebop/p2e/01_verilog_api.step.py | 2 +- .../bebop/p2e/03_runworkload_api.step.py | 11 -- .../bebop/p2e/03_runworkload_event.step.py | 37 +++---- api/steps/bebop/p2e/04_batch_api.step.py | 2 + api/steps/bebop/p2e/04_batch_event.step.py | 103 +++++++++++++++--- .../bebop/verilator/02_verilog_api.step.py | 2 +- .../bebop/verilator/03_build_event.step.py | 4 +- .../bebop/verilator/04_sim_event.step.py | 2 +- .../bebop/verilator/06_batch_event.step.py | 10 +- api/steps/config/scripts/4_install.py | 19 +++- api/steps/kernel/01_build_event.step.py | 16 +++ .../06_bebop_verilator_verilog_event.step.py | 9 +- .../mill/07_bebop_p2e_verilog_event.step.py | 9 +- api/steps/mill/scripts/mill.py | 2 +- bbdev | 8 +- mcp/tools/bebop_p2e_batch.py | 2 + mcp/tools/bebop_p2e_runworkload.py | 3 + mcp/tools/bebop_p2e_verilog.py | 4 +- mcp/tools/bebop_verilator_verilog.py | 4 +- 20 files changed, 174 insertions(+), 82 deletions(-) diff --git a/api/steps/bebop/bemu/02_sim_event.step.py b/api/steps/bebop/bemu/02_sim_event.step.py index d5a1f5a..8771c45 100644 --- a/api/steps/bebop/bemu/02_sim_event.step.py +++ b/api/steps/bebop/bemu/02_sim_event.step.py @@ -69,7 +69,12 @@ def resolve_bemu_binary(bbdir: str, chip: str, binary_name: str) -> str | None: if Path(binary_name).name != binary_name: return None - return search_workload(workloads_output_root(bbdir), binary_name) + workload = search_workload(workloads_output_root(bbdir), binary_name) + if workload is not None: + return workload + + kernel = Path(bbdir) / "bb-tests" / "output" / "kernel" / chip / binary_name + return str(kernel) if kernel.is_file() else None def is_native_host_elf(path: str) -> bool: diff --git a/api/steps/bebop/p2e/01_verilog_api.step.py b/api/steps/bebop/p2e/01_verilog_api.step.py index e0fd8b9..0c2c171 100644 --- a/api/steps/bebop/p2e/01_verilog_api.step.py +++ b/api/steps/bebop/p2e/01_verilog_api.step.py @@ -18,7 +18,7 @@ async def handler(req: ApiRequest, ctx: FlowContext) -> ApiResponse: chip = require_chip(body) except ValueError as e: return ApiResponse(status=400, body={"error": str(e)}) - data = {"chip": chip} + data = {"chip": chip, "diff": bool(body.get("diff", False))} if body.get("output_dir"): data["output_dir"] = body["output_dir"] await ctx.enqueue({"topic": "bebop.p2e.verilog", "data": {**data, "_trace_id": ctx.trace_id}}) diff --git a/api/steps/bebop/p2e/03_runworkload_api.step.py b/api/steps/bebop/p2e/03_runworkload_api.step.py index 6fe1e14..472cf70 100644 --- a/api/steps/bebop/p2e/03_runworkload_api.step.py +++ b/api/steps/bebop/p2e/03_runworkload_api.step.py @@ -23,17 +23,6 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse: "message": "image and bitstream parameters are required", }, ) - if body.get("diff", False) and not body.get("golden-elf"): - return ApiResponse( - status=400, - body={ - "success": False, - "failure": True, - "returncode": 400, - "message": "--diff requires --golden-elf ", - }, - ) - await ctx.enqueue({ "topic": "bebop.p2e.runworkload", "data": {**body, "_trace_id": ctx.trace_id}, diff --git a/api/steps/bebop/p2e/03_runworkload_event.step.py b/api/steps/bebop/p2e/03_runworkload_event.step.py index 53e7ea5..3c705dc 100644 --- a/api/steps/bebop/p2e/03_runworkload_event.step.py +++ b/api/steps/bebop/p2e/03_runworkload_event.step.py @@ -117,26 +117,6 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: return wave = bool(input_data.get("wave", False)) diff = bool(input_data.get("diff", False)) - golden_elf = input_data.get("golden-elf", "") - golden_pk = bool(input_data.get("golden-pk", False)) - if diff and not golden_elf: - ctx.logger.error("--diff requires --golden-elf ") - await check_result( - ctx, 1, continue_run=False, - extra_fields={"error": "missing_golden_elf"}, - trace_id=origin_tid, - ) - return - if diff and not os.path.isfile(golden_elf): - ctx.logger.error(f"BEMU golden ELF not found: {golden_elf}") - await check_result( - ctx, 1, continue_run=False, - extra_fields={"error": "golden_elf_not_found", "golden_elf": golden_elf}, - trace_id=origin_tid, - ) - return - if diff: - golden_elf = os.path.abspath(golden_elf) if "wave_start" in input_data: ctx.logger.error("invalid parameter: --wave_start (use --wave-start)") await check_result( @@ -179,6 +159,17 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ) return + image_base = os.path.splitext(image_path)[0] + elf_path = f"{image_base}.elf" if os.path.isfile(f"{image_base}.elf") else image_base + if diff and not os.path.isfile(elf_path): + ctx.logger.error(f"workload ELF for P2E DiffTest not found: {elf_path}") + await check_result( + ctx, 1, continue_run=False, + extra_fields={"error": "workload_elf_not_found", "elf": elf_path}, + trace_id=origin_tid, + ) + return + if not bitstream or not os.path.isfile(bitstream): ctx.logger.error(f"bitstream .bit file not found: {bitstream}") await check_result( @@ -282,9 +273,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: if wave_start is not None: run_cmd += f" --wave-start=\"{wave_start}\"" if diff: - run_cmd += f" --diff --golden-elf={shlex.quote(golden_elf)}" - if golden_pk: - run_cmd += " --golden-pk" + run_cmd += f" --diff --image-elf={shlex.quote(elf_path)}" for trace_name in ("itrace", "mtrace", "pmctrace", "ctrace", "banktrace"): if input_data.get(trace_name, False): run_cmd += f" --{trace_name}" @@ -318,7 +307,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: "fpga_location": fpga_location, "bdb_trace": os.path.join(run_log, "bdb.ndjson"), "uart_log": os.path.join(run_log, "uart.log"), - "bank_diff": os.path.join(run_log, "bank_diff.ndjson") if diff else None, + "bank_diff": os.path.join(run_log, "diff.ndjson") if diff else None, "diff": diff, "timestamp": timestamp, }, diff --git a/api/steps/bebop/p2e/04_batch_api.step.py b/api/steps/bebop/p2e/04_batch_api.step.py index 843a445..95fe93d 100644 --- a/api/steps/bebop/p2e/04_batch_api.step.py +++ b/api/steps/bebop/p2e/04_batch_api.step.py @@ -44,10 +44,12 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse: body={"error": f"Invalid test type: {test_type}. Must be 'elf-tests' or 'pk-tests'"} ) + diff = bool(body.get("diff", False)) data = { "chip": chip, "bitstream": bitstream, "test": test_type, + "diff": diff, } await ctx.enqueue({"topic": "bebop.p2e.batch", "data": {**data, "_trace_id": ctx.trace_id}}) return ApiResponse(status=202, body={"trace_id": ctx.trace_id}) diff --git a/api/steps/bebop/p2e/04_batch_event.step.py b/api/steps/bebop/p2e/04_batch_event.step.py index aee02f0..9f5a176 100644 --- a/api/steps/bebop/p2e/04_batch_event.step.py +++ b/api/steps/bebop/p2e/04_batch_event.step.py @@ -8,7 +8,10 @@ """ import os import re +import shlex import sys +import tomllib +from pathlib import Path from motia import FlowContext, queue @@ -20,7 +23,8 @@ sys.path.insert(0, bebop_path) from utils.event_common import require_chip -from utils.path import bebop_cargo_env, get_buckyball_path, rtl_dir, workloads_output_root +from utils.path import bebop_cargo_env, get_buckyball_path, rtl_dir, workload_tests_root, workloads_output_root +from utils.search_workload import search_workload from utils.stream_run import stream_run_logger_async from utils.event_common import check_result, get_origin_trace_id from regression import regression_workload_toml @@ -85,8 +89,9 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: cargo_env = bebop_cargo_env(bbdir, chip) test_type = input_data.get("test", "elf-tests") + diff = bool(input_data.get("diff", False)) try: - workload_toml = regression_workload_toml(chip, "p2e", test_type, bbdir) + workload_toml = regression_workload_toml(chip, "p2e", test_type, bbdir, diff=diff) except ValueError as e: ctx.logger.error(str(e)) await check_result( @@ -96,30 +101,90 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ) return - ctx.logger.info(f"Running {test_type} with workload config: {workload_toml}") + ctx.logger.info(f"Running {test_type} with workload config: {workload_toml} diff={diff}") ctx.logger.info(f"P2E case dir (from bitstream): {build_dir}") + if test_type == "elf-tests": + workload_names = tomllib.loads(Path(workload_toml).read_text())["workloads"]["tests"] + converter = os.path.join(bbdir, "bbdev", "api", "steps", "workload", "scripts", "elf2hex.py") + search_root = workload_tests_root(bbdir, chip) + for image_name in workload_names: + if not image_name.endswith(".hex"): + raise ValueError(f"P2E ELF workload must end in .hex: {image_name}") + elf_name = image_name.removesuffix(".hex") + elf_path = search_workload(search_root, elf_name) + if elf_path is None: + raise FileNotFoundError(f"P2E workload ELF not found: {elf_name}") + convert_result = await stream_run_logger_async( + cmd=shlex.join(["python3", converter, elf_path]), + logger=ctx.logger, + cwd=os.path.dirname(elf_path), + stdout_prefix=f"p2e image {elf_name}", + stderr_prefix=f"p2e image {elf_name}", + ) + if convert_result.returncode != 0: + await check_result( + ctx, + convert_result.returncode, + continue_run=False, + extra_fields={"task": "tohex", "elf": elf_path}, + trace_id=origin_tid, + ) + return + vsrc_dir = rtl_dir(bbdir, chip, "p2e", input_data.get("vsrc_dir")) # Rebuild VVAC host runtime in the bitstream case (same as runworkload). - runtime_cmd = ( - f"env BEBOP_P2E_RUNTIME_ONLY=1 BEBOP_P2E_REBUILD_RUNTIME=1 " - f"cargo run --release --features p2e -- build p2e " - f"--rtl-dir=\"{vsrc_dir}\" " - f"--out-dir=\"{build_dir}\"" - ) + manifest = Path(bebop_dir) / "Cargo.toml" + features = ["p2e"] + runtime_env = {**os.environ.copy(), **cargo_env} + if diff: + manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" + features.extend(["bemu", "difftest"]) + hpec_home = "/home/x-epic/hpe-24.12.01.s008" + runtime_env.update({ + "BEBOP_BEMU_P2E_ABI": "1", + "BEBOP_BEMU_CC": os.path.join(hpec_home, "tools", "gcc-8.3.0", "gcc-8.3.0", "bin", "gcc"), + "BEBOP_BEMU_CXX": os.path.join(hpec_home, "tools", "gcc-8.3.0", "gcc-8.3.0", "bin", "g++"), + "BEBOP_BEMU_DTC": os.path.join(bbdir, "result", "bin", "dtc"), + "CARGO_TARGET_DIR": os.path.join(bebop_dir, "target", f"{chip}-p2e-diff"), + "BEBOP_BEMU_COMPILER_LIBRARY_PATH": ":".join([ + os.path.join(hpec_home, "tools", "gcc-8.3.0", "gmp-6.2.1", "lib"), + os.path.join(hpec_home, "tools", "gcc-8.3.0", "mpfr-4.1.0", "lib"), + os.path.join(hpec_home, "tools", "gcc-8.3.0", "mpc-1.2.1", "lib"), + ]), + }) + feature_arg = ",".join(features) + runtime_cmd = shlex.join([ + "env", + "BEBOP_P2E_RUNTIME_ONLY=1", + "BEBOP_P2E_REBUILD_RUNTIME=1", + f"VSRC_PATH={vsrc_dir}", + f"OUT_PATH={build_dir}", + "cargo", "run", "--release", + "--manifest-path", str(manifest), + "--bin", "bebop", + "--features", feature_arg, + "--", "build", "p2e", + "--rtl-dir", vsrc_dir, + "--out-dir", build_dir, + *( ["--diff"] if diff else [] ), + ]) ctx.logger.info("Preparing bebop p2e runtime for the selected bitstream ...") runtime_result = await stream_run_logger_async( cmd=runtime_cmd, logger=ctx.logger, - cwd=bebop_dir, + cwd=str(manifest.parent), stdout_prefix="bebop p2e runtime", stderr_prefix="bebop p2e runtime", - env={**os.environ.copy(), **cargo_env}, + env=runtime_env, ) rtcfg_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "rtcfg") libvctb_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "lib", "lib_arm", "libvCtb.so") - if runtime_result.returncode != 0 or not all(os.path.isfile(path) for path in (rtcfg_path, libvctb_path)): - missing = [path for path in (rtcfg_path, libvctb_path) if not os.path.isfile(path)] + runtime_artifacts = [rtcfg_path, libvctb_path] + if diff: + runtime_artifacts.append(os.path.join(build_dir, "libriscv.so")) + if runtime_result.returncode != 0 or not all(os.path.isfile(path) for path in runtime_artifacts): + missing = [path for path in runtime_artifacts if not os.path.isfile(path)] if missing: ctx.logger.error(f"P2E runtime artifacts missing: {missing}") await check_result( @@ -141,7 +206,8 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: # ── Build bebop p2e (tests), linked against case libvCtb ─────────────── build_cmd = ( - f"nix develop -c cargo {cargo_out} build --release --features p2e --tests" + f"nix develop -c cargo {cargo_out} build --release " + f"--manifest-path {shlex.quote(str(manifest))} --features {shlex.quote(feature_arg)} --tests" ) ctx.logger.info("Building bebop p2e (tests)...") build_result = await stream_run_logger_async( @@ -150,7 +216,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: cwd=bebop_dir, stdout_prefix="bebop p2e build", stderr_prefix="bebop p2e build", - env={**os.environ.copy(), **cargo_env}, + env=runtime_env, ) if build_result.returncode != 0: @@ -162,12 +228,12 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: return # ── Run nextest ─────────────────────────────────────────────────────── - env = os.environ.copy() - env.update(cargo_env) + env = runtime_env.copy() env["OUT_PATH"] = build_dir harness = nextest_harness_args(workload_toml, elf_root, env, p2e_bitstream=bitstream) nextest_cmd = ( - f"nix develop -c cargo {cargo_out} nextest run --release --features p2e " + f"nix develop -c cargo {cargo_out} nextest run --release " + f"--manifest-path {shlex.quote(str(manifest))} --features {shlex.quote(feature_arg)} " f"--test test_p2e --config-file \"{nextest_config}\" {harness}" ) @@ -188,6 +254,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: "bitstream": bitstream, "build_dir": build_dir, "test_type": test_type, + "diff": diff, "nextest_config": nextest_config, "workload_toml": workload_toml, } diff --git a/api/steps/bebop/verilator/02_verilog_api.step.py b/api/steps/bebop/verilator/02_verilog_api.step.py index fc470fe..dc20614 100644 --- a/api/steps/bebop/verilator/02_verilog_api.step.py +++ b/api/steps/bebop/verilator/02_verilog_api.step.py @@ -34,7 +34,7 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse: }, ) - data = {"chip": chip, "rushB": bool(body.get("rushB", False))} + data = {"chip": chip, "rushB": bool(body.get("rushB", False)), "diff": bool(body.get("diff", False))} if body.get("output_dir"): data["output_dir"] = body["output_dir"] await ctx.enqueue({"topic": "bebop.verilator.verilog", "data": {**data, "_trace_id": ctx.trace_id}}) diff --git a/api/steps/bebop/verilator/03_build_event.step.py b/api/steps/bebop/verilator/03_build_event.step.py index 7de3a6b..ff44628 100644 --- a/api/steps/bebop/verilator/03_build_event.step.py +++ b/api/steps/bebop/verilator/03_build_event.step.py @@ -54,7 +54,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: features = ["verilator"] if diff: manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" - build_dir = manifest.parent + build_dir = str(manifest.parent) features.extend(["bemu", "difftest"]) dramsim_header = os.path.join(bbdir, "result", "include", "dramsim3.h") if not os.path.isfile(dramsim_header): @@ -85,7 +85,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: str(jobs), ] ) - build_inner = f"cd {shlex.quote(build_dir)} && exec {cargo_build_cmd}" + build_inner = f"cd {shlex.quote(str(build_dir))} && exec {cargo_build_cmd}" build_cmd = f"nix develop -c sh -c {shlex.quote(build_inner)}" env = {**os.environ, "VSRC_PATH": vsrc_dir, **bebop_cargo_env(bbdir, chip)} ctx.logger.info(f"Building bebop verilator (diff={diff}) ...") diff --git a/api/steps/bebop/verilator/04_sim_event.step.py b/api/steps/bebop/verilator/04_sim_event.step.py index 4990b42..7ea6c2a 100644 --- a/api/steps/bebop/verilator/04_sim_event.step.py +++ b/api/steps/bebop/verilator/04_sim_event.step.py @@ -245,7 +245,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: "chip": chip, "binary": binary_path, "log_dir": run_log, - "bank_diff": os.path.join(run_log, "bank_diff.ndjson") if diff else None, + "bank_diff": os.path.join(run_log, "diff.ndjson") if diff else None, "timestamp": timestamp, }, trace_id=origin_tid, diff --git a/api/steps/bebop/verilator/06_batch_event.step.py b/api/steps/bebop/verilator/06_batch_event.step.py index 77244dc..2610a98 100644 --- a/api/steps/bebop/verilator/06_batch_event.step.py +++ b/api/steps/bebop/verilator/06_batch_event.step.py @@ -120,16 +120,8 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: manifest = f"{bebop_dir}/Cargo.toml" features = "verilator" if diff: + manifest = f"{bbdir}/examples/chips/{chip}/generated/bebop/Cargo.toml" features = "verilator,bemu,difftest" - preload = os.pathsep.join( - path - for path in ( - f"{bbdir}/result/lib/libdramsim3.so", - os.environ.get("LD_PRELOAD", ""), - ) - if path - ) - env["LD_PRELOAD"] = preload if input_data.get("clean-before", input_data.get("clean_before", False)): artifact_dir = os.path.join(env["CARGO_TARGET_DIR"], "test-artifacts") diff --git a/api/steps/config/scripts/4_install.py b/api/steps/config/scripts/4_install.py index 2152182..c24dde9 100644 --- a/api/steps/config/scripts/4_install.py +++ b/api/steps/config/scripts/4_install.py @@ -143,6 +143,18 @@ def install_bebop(gen: Path) -> Path: name = "bebop" path = "../../../../../bebop/src/main.rs" +[[test]] +name = "test_verilator" +path = "../../../../../bebop/tests/test_verilator.rs" +harness = false +required-features = ["verilator"] + +[[test]] +name = "test_p2e" +path = "../../../../../bebop/tests/test_p2e.rs" +harness = false +required-features = ["p2e"] + [features] default = [] verilator = ["dep:bebop-verilator"] @@ -166,12 +178,17 @@ def install_bebop(gen: Path) -> Path: env_logger = "0.11" nix = { version = "0.29", features = ["fs", "mman", "signal", "process"] } toml = "0.8" -ctrlc = "3" camino = "1.1" snafu = "0.8" serde = { version = "1", features = ["derive"] } serde_json = "1" duct = "0.13" + +[dev-dependencies] +libtest-mimic = "0.8" +assert_cmd = "2" +walkdir = "2" +chrono = { version = "0.4", default-features = false, features = ["clock"] } """, encoding="utf-8", ) diff --git a/api/steps/kernel/01_build_event.step.py b/api/steps/kernel/01_build_event.step.py index 9bc9f1e..25a2911 100644 --- a/api/steps/kernel/01_build_event.step.py +++ b/api/steps/kernel/01_build_event.step.py @@ -1,4 +1,5 @@ import os +import shutil import sys from motia import FlowContext, queue @@ -231,11 +232,26 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: payload_name = fw_payload_name(hart_params, cmake_model_name if model else "", chip) fw_payload_bin = os.path.join(output_dir, f"{payload_name}.bin") fw_payload_hex = os.path.join(output_dir, f"{payload_name}.hex") + fw_payload_elf = os.path.join(output_dir, f"{payload_name}.elf") + built_payload_elf = os.path.join( + kernel_build, + "opensbi", + "platform", + "buckyball", + "firmware", + "fw_payload.elf", + ) if not os.path.exists(fw_payload_bin): ctx.logger.error(f"{payload_name}.bin not found") await check_result(ctx, 1, continue_run=False, trace_id=origin_tid) return + if not os.path.isfile(built_payload_elf): + ctx.logger.error(f"fw_payload.elf not found: {built_payload_elf}") + await check_result(ctx, 1, continue_run=False, trace_id=origin_tid) + return + + shutil.copy2(built_payload_elf, fw_payload_elf) ctx.logger.info(f"Converting {fw_payload_bin} to Verilog hex format for P2E...") success = bin_to_hex(fw_payload_bin, fw_payload_hex, base_address=0x80000000) diff --git a/api/steps/mill/06_bebop_verilator_verilog_event.step.py b/api/steps/mill/06_bebop_verilator_verilog_event.step.py index 09ee4bb..9696d01 100644 --- a/api/steps/mill/06_bebop_verilator_verilog_event.step.py +++ b/api/steps/mill/06_bebop_verilator_verilog_event.step.py @@ -4,6 +4,7 @@ Generates Verilog via mill for bebop verilator """ import os +import shutil import sys from motia import FlowContext, queue @@ -61,12 +62,16 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: input_data.get("output_dir"), rushb=bool(input_data.get("rushB", False)), ) - os.makedirs(build_dir, exist_ok=True) + shutil.rmtree(build_dir, ignore_errors=True) + os.makedirs(build_dir) ctx.logger.info(f"Using mill config: {mill_config}") ctx.logger.info(f"Using build directory: {build_dir}") + command = mill_run.elaborate_cmd("sims.verilator.Elaborate", mill_config, build_dir) + if input_data.get("diff"): + command += " --difftest" returncode = ( await stream_run_logger_async( - cmd=mill_run.elaborate_cmd("sims.verilator.Elaborate", mill_config, build_dir), + cmd=command, logger=ctx.logger, cwd=arch, stdout_prefix="bebop verilator verilog", diff --git a/api/steps/mill/07_bebop_p2e_verilog_event.step.py b/api/steps/mill/07_bebop_p2e_verilog_event.step.py index 2b8784b..d89e891 100644 --- a/api/steps/mill/07_bebop_p2e_verilog_event.step.py +++ b/api/steps/mill/07_bebop_p2e_verilog_event.step.py @@ -1,4 +1,5 @@ import os +import shutil import sys from motia import FlowContext, queue @@ -39,13 +40,17 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: mill_config, build_dir = rtl_out( bbdir, chip, "p2e", input_data.get("output_dir"), ) - os.makedirs(build_dir, exist_ok=True) + shutil.rmtree(build_dir, ignore_errors=True) + os.makedirs(build_dir) ctx.logger.info(f"Using mill config: {mill_config}") ctx.logger.info(f"Using build directory: {build_dir}") prefix = "bebop p2e verilog" + command = mill_run.elaborate_cmd("sims.p2e.Elaborate", mill_config, build_dir) + if input_data.get("diff"): + command += " --difftest" returncode = ( await stream_run_logger_async( - cmd=mill_run.elaborate_cmd("sims.p2e.Elaborate", mill_config, build_dir), + cmd=command, logger=ctx.logger, cwd=arch, stdout_prefix=prefix, diff --git a/api/steps/mill/scripts/mill.py b/api/steps/mill/scripts/mill.py index 6f2f858..bd6b3db 100644 --- a/api/steps/mill/scripts/mill.py +++ b/api/steps/mill/scripts/mill.py @@ -7,7 +7,7 @@ def elaborate_cmd(main: str, config: str, out: str, *, seq_mem: bool = False) -> str: extra = "" if seq_mem: - extra = ( + extra += ( " -lowering-options=disallowLocalVariables" f" --repl-seq-mem --repl-seq-mem-file={shlex.quote(os.path.join(out, 'mems.conf'))}" ) diff --git a/bbdev b/bbdev index 74ac7a8..d73ea5d 100755 --- a/bbdev +++ b/bbdev @@ -70,7 +70,7 @@ WORKFLOW_COMMANDS = { }, "bebop-verilator": { "clean": 'Clean Bebop Verilator build directory. Args: "--chip [--output-dir ]"', - "verilog": 'Generate Bebop Verilog files. Args: "--chip [--output-dir ]"', + "verilog": 'Generate Bebop Verilog files. Args: "--chip [--diff] [--output-dir ]"', "build": 'Build Bebop Verilator simulation executable. Args: "--chip [--diff] [--jobs ] [--output-dir ]"', "sim": 'Run Bebop Verilator simulation. Args: "--chip --binary [--rushB] [--log-dir ] [--fst-dir ] [--no-wave]"', "run": 'Integrated Bebop Verilator build+sim+run. Args: "--binary [--diff] [--rushB] [--jobs ] [--chip ] [--log-dir ] [--fst-dir ] [--no-wave]"', @@ -83,10 +83,10 @@ WORKFLOW_COMMANDS = { }, "bebop-p2e": { "clean": 'Clean Bebop P2E build directory. Args: "--chip [--output-dir ]"', - "verilog": 'Generate Bebop P2E SystemVerilog for DDR4 backdoor. Args: "--chip [--output-dir ]"', + "verilog": 'Generate Bebop P2E SystemVerilog for DDR4 backdoor. Args: "--chip [--diff] [--output-dir ]"', "buildbitstream": 'Build Bebop P2E runtime case. Args: "--chip [--vsrc-dir ] [--output-dir ]"', - "runworkload": 'Run Bebop P2E workload. Args: "--chip --image --bitstream [--fpga-location ] [--multi-fpga] [--wave] [--wave-start ] [--diff --golden-elf [--golden-pk]] [--itrace] [--mtrace] [--pmctrace]"', - "batch": 'Run Bebop p2e nextest batch regression (serial). Args: "--chip --bitstream --test "', + "runworkload": 'Run Bebop P2E workload. Args: "--chip --image --bitstream [--fpga-location ] [--multi-fpga] [--wave] [--wave-start ] [--diff] [--itrace] [--mtrace] [--pmctrace]"', + "batch": 'Run Bebop p2e nextest batch regression (serial). Args: "--chip --bitstream --test [--diff]"', }, "dc": { "verilog": 'Generate top-scoped DC RTL under arch/build//. Args: "--chip [--top ]"', diff --git a/mcp/tools/bebop_p2e_batch.py b/mcp/tools/bebop_p2e_batch.py index d306c61..ccbb71e 100644 --- a/mcp/tools/bebop_p2e_batch.py +++ b/mcp/tools/bebop_p2e_batch.py @@ -12,6 +12,7 @@ def bbdev_bebop_p2e_batch( chip: str, bitstream: str, test: str, + diff: bool = False, ) -> str: """Batch bebop-p2e regression. test: elf-tests|pk-tests. POST /bebop/p2e/batch.""" for n, v in (("chip", chip), ("bitstream", bitstream)): @@ -26,6 +27,7 @@ def bbdev_bebop_p2e_batch( "chip": chip, "bitstream": bitstream, "test": test, + "diff": diff, }, ) ) diff --git a/mcp/tools/bebop_p2e_runworkload.py b/mcp/tools/bebop_p2e_runworkload.py index 4ccd2a9..41cd248 100644 --- a/mcp/tools/bebop_p2e_runworkload.py +++ b/mcp/tools/bebop_p2e_runworkload.py @@ -16,6 +16,7 @@ def bbdev_bebop_p2e_runworkload( itrace: bool = False, mtrace: bool = False, pmctrace: bool = False, + diff: bool = False, ) -> str: """Run one image on bebop-p2e FPGA. POST /bebop/p2e/runworkload.""" for n, v in (("chip", chip), ("image", image), ("bitstream", bitstream)): @@ -28,4 +29,6 @@ def bbdev_bebop_p2e_runworkload( params["mtrace"] = True if pmctrace: params["pmctrace"] = True + if diff: + params["diff"] = True return fmt(submit("/bebop/p2e/runworkload", params)) diff --git a/mcp/tools/bebop_p2e_verilog.py b/mcp/tools/bebop_p2e_verilog.py index b3e35b6..4037f54 100644 --- a/mcp/tools/bebop_p2e_verilog.py +++ b/mcp/tools/bebop_p2e_verilog.py @@ -10,7 +10,7 @@ def register(mcp): @mcp.tool() def bbdev_bebop_p2e_verilog( - chip: str, output_dir: Optional[str] = None + chip: str, output_dir: Optional[str] = None, diff: bool = False ) -> str: """Generate Verilog for bebop-p2e. POST /bebop/p2e/verilog.""" if e := need("chip", chip): @@ -18,6 +18,6 @@ def bbdev_bebop_p2e_verilog( return fmt( submit( "/bebop/p2e/verilog", - opt({"chip": chip}, output_dir=output_dir), + opt({"chip": chip, "diff": diff}, output_dir=output_dir), ) ) diff --git a/mcp/tools/bebop_verilator_verilog.py b/mcp/tools/bebop_verilator_verilog.py index 959a80d..c94457d 100644 --- a/mcp/tools/bebop_verilator_verilog.py +++ b/mcp/tools/bebop_verilator_verilog.py @@ -8,8 +8,8 @@ def register(mcp): @mcp.tool() - def bbdev_bebop_verilator_verilog(chip: str) -> str: + def bbdev_bebop_verilator_verilog(chip: str, diff: bool = False) -> str: """Generate Verilog for bebop-verilator. POST /bebop/verilator/verilog.""" if e := need("chip", chip): return err(e) - return fmt(submit("/bebop/verilator/verilog", {"chip": chip})) + return fmt(submit("/bebop/verilator/verilog", {"chip": chip, "diff": diff})) From 3fc00c06fc6729d7ae983dddf79da5b58768821c Mon Sep 17 00:00:00 2001 From: shirohasuki Date: Tue, 22 Sep 2026 02:19:09 +0800 Subject: [PATCH 09/11] feat: integrate diff option into Bebop P2E build and runtime processes --- .../bebop/p2e/02_buildbitstream_api.step.py | 1 + .../bebop/p2e/02_buildbitstream_event.step.py | 42 ++++++--- .../bebop/p2e/03_runworkload_event.step.py | 87 +------------------ api/steps/bebop/p2e/04_batch_event.step.py | 58 ++----------- api/steps/config/scripts/4_install.py | 2 +- .../02_eval_performance_event.step.py | 67 +++++++------- bbdev | 2 +- mcp/tools/bebop_p2e_buildbitstream.py | 3 +- 8 files changed, 80 insertions(+), 182 deletions(-) diff --git a/api/steps/bebop/p2e/02_buildbitstream_api.step.py b/api/steps/bebop/p2e/02_buildbitstream_api.step.py index 61dc957..ba2a845 100644 --- a/api/steps/bebop/p2e/02_buildbitstream_api.step.py +++ b/api/steps/bebop/p2e/02_buildbitstream_api.step.py @@ -25,6 +25,7 @@ async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse: data = { "chip": chip, + "diff": bool(body.get("diff", False)), "vsrc_dir": vsrc_dir, "output_dir": body.get("output_dir") or body.get("output-dir"), } diff --git a/api/steps/bebop/p2e/02_buildbitstream_event.step.py b/api/steps/bebop/p2e/02_buildbitstream_event.step.py index 4c99433..e2a0385 100644 --- a/api/steps/bebop/p2e/02_buildbitstream_event.step.py +++ b/api/steps/bebop/p2e/02_buildbitstream_event.step.py @@ -7,8 +7,10 @@ 3. Validate generated bitstream and runtime artifacts """ import os +import shlex import sys from datetime import datetime +from pathlib import Path from motia import FlowContext, queue @@ -58,32 +60,50 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ) os.makedirs(build_dir, exist_ok=True) - build_cmd = ( - f"nix develop --ignore-env --keep-env-var HOME --keep-env-var ALL_PROXY " - f"--keep-env-var CARGO_TARGET_DIR -c " - f"cargo run --release --features p2e -- build p2e " - f"--rtl-dir=\"{vsrc_dir}\" " - f"--out-dir=\"{build_dir}\"" - ) + diff = bool(input_data.get("diff", False)) + manifest = Path(bebop_dir) / "Cargo.toml" + features = ["p2e"] + build_env = {**os.environ, **bebop_cargo_env(bbdir, chip)} + if diff: + manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" + features.extend(["bemu", "difftest"]) + build_env["CARGO_TARGET_DIR"] = os.path.join(bebop_dir, "target", f"{chip}-p2e-diff") + build_cmd = shlex.join([ + "nix", "develop", "--ignore-env", + "--keep-env-var", "HOME", + "--keep-env-var", "ALL_PROXY", + "--keep-env-var", "CARGO_TARGET_DIR", + "-c", "cargo", "run", "--release", + "--manifest-path", str(manifest), + "--bin", "bebop", + "--features", ",".join(features), + "--", "build", "p2e", + "--rtl-dir", vsrc_dir, + "--out-dir", build_dir, + *(["--diff"] if diff else []), + ]) ctx.logger.info("Building bebop p2e runtime case ...") build_result = await stream_run_logger_async( cmd=build_cmd, logger=ctx.logger, - cwd=bebop_dir, + cwd=str(manifest.parent), stdout_prefix="bebop p2e build", stderr_prefix="bebop p2e build", - env={**os.environ, **bebop_cargo_env(bbdir, chip)}, + env=build_env, ) rtcfg_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "rtcfg") libvctb_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "lib", "lib_arm", "libvCtb.so") bitstream_path = os.path.join(build_dir, "fpgaCompDir", "bitstream.bit") + runtime_path = os.path.join(build_dir, "bebop-p2e") if build_result.returncode == 0: missing = [ path - for path in (rtcfg_path, libvctb_path, bitstream_path) + for path in (rtcfg_path, libvctb_path, bitstream_path, runtime_path) if not os.path.exists(path) ] + if diff and not os.path.isfile(os.path.join(build_dir, "libriscv.so")): + missing.append(os.path.join(build_dir, "libriscv.so")) if missing: ctx.logger.error(f"P2E build artifacts missing: {missing}") await check_result( @@ -109,6 +129,8 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: "rtcfg": rtcfg_path, "libvCtb": libvctb_path, "bitstream": bitstream_path, + "runtime": runtime_path, + "diff": diff, "timestamp": timestamp, } if input_data.get("from_regression_buildbitstream") and build_result.returncode == 0: diff --git a/api/steps/bebop/p2e/03_runworkload_event.step.py b/api/steps/bebop/p2e/03_runworkload_event.step.py index 3c705dc..c96bdb4 100644 --- a/api/steps/bebop/p2e/03_runworkload_event.step.py +++ b/api/steps/bebop/p2e/03_runworkload_event.step.py @@ -12,7 +12,6 @@ import shlex import sys from datetime import datetime -from pathlib import Path from motia import FlowContext, queue @@ -24,7 +23,7 @@ sys.path.insert(0, scripts_path) from utils.event_common import require_chip -from utils.path import bebop_cargo_env, get_buckyball_path, log_dir, rtl_dir +from utils.path import bebop_cargo_env, get_buckyball_path, log_dir from utils.stream_run import stream_run_logger_async from utils.event_common import check_result, get_origin_trace_id from resolve_image import resolve_image @@ -53,41 +52,6 @@ def case_uses_multi_fpga(build_dir: str) -> bool: return len([path for path in part_dirs if os.path.isdir(path)]) > 1 -def runtime_build_command(bbdir: str, chip: str, diff: bool, vsrc_dir: str, build_dir: str) -> tuple[str, str]: - manifest = Path(bbdir) / "bebop" / "Cargo.toml" - features = ["p2e"] - if diff: - manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" - features.extend(["bemu", "difftest"]) - command = shlex.join( - [ - "env", - "BEBOP_P2E_RUNTIME_ONLY=1", - "BEBOP_P2E_REBUILD_RUNTIME=1", - f"VSRC_PATH={vsrc_dir}", - f"OUT_PATH={build_dir}", - "cargo", - "run", - "--release", - "--manifest-path", - str(manifest), - "--bin", - "bebop", - "--features", - ",".join(features), - "--", - "build", - "p2e", - "--rtl-dir", - vsrc_dir, - "--out-dir", - build_dir, - *(["--diff"] if diff else []), - ] - ) - return command, str(manifest.parent) - - async def handler(input_data: dict, ctx: FlowContext) -> None: origin_tid = get_origin_trace_id(input_data, ctx) try: @@ -194,60 +158,27 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: multi_fpga = True ctx.logger.info(f"Detected multi-FPGA P2E case: {build_dir}") - vsrc_dir = rtl_dir(bbdir, chip, "p2e", input_data.get("vsrc_dir")) timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M") run_log = log_dir(bbdir, chip, "p2e", timestamp, "p2e", image_name, input_data.get("vsrc_dir")) os.makedirs(run_log, exist_ok=True) - # Rebuild the VVAC host runtime in the bitstream case. The bitstream is - # deliberately left in place, so runtime/DPIC changes never trigger FPGA synthesis. - runtime_cmd, runtime_cwd = runtime_build_command(bbdir, chip, diff, vsrc_dir, build_dir) - runtime_env = {**os.environ.copy(), **bebop_cargo_env(bbdir, chip)} - if diff: - hpec_home = "/home/x-epic/hpe-24.12.01.s008" - runtime_env["BEBOP_BEMU_P2E_ABI"] = "1" - runtime_env["BEBOP_BEMU_CC"] = os.path.join( - hpec_home, "tools", "gcc-8.3.0", "gcc-8.3.0", "bin", "gcc" - ) - runtime_env["BEBOP_BEMU_CXX"] = os.path.join( - hpec_home, "tools", "gcc-8.3.0", "gcc-8.3.0", "bin", "g++" - ) - runtime_env["BEBOP_BEMU_DTC"] = os.path.join(bbdir, "result", "bin", "dtc") - runtime_env["CARGO_TARGET_DIR"] = os.path.join(bebop_dir, "target", f"{chip}-p2e-diff") - compiler_libs = [ - os.path.join(hpec_home, "tools", "gcc-8.3.0", "gmp-6.2.1", "lib"), - os.path.join(hpec_home, "tools", "gcc-8.3.0", "mpfr-4.1.0", "lib"), - os.path.join(hpec_home, "tools", "gcc-8.3.0", "mpc-1.2.1", "lib"), - ] - runtime_env["BEBOP_BEMU_COMPILER_LIBRARY_PATH"] = ":".join(compiler_libs) - ctx.logger.info("Preparing bebop p2e runtime for the selected bitstream ...") - runtime_result = await stream_run_logger_async( - cmd=runtime_cmd, - logger=ctx.logger, - cwd=runtime_cwd, - stdout_prefix="bebop p2e runtime", - stderr_prefix="bebop p2e runtime", - env=runtime_env, - ) runtime_lib_dir = os.path.join(build_dir, "vvacDir", "runtimeDir", "lib", "lib_arm") rtcfg_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "rtcfg") libvctb_path = os.path.join(runtime_lib_dir, "libvCtb.so") - libstdcxx_path = os.path.join(runtime_lib_dir, "libstdc++.so.6") bebop_p2e_path = os.path.join(build_dir, "bebop-p2e") - runtime_artifacts = [rtcfg_path, libvctb_path, libstdcxx_path, bebop_p2e_path] + runtime_artifacts = [rtcfg_path, libvctb_path, bebop_p2e_path] if diff: runtime_artifacts.append(os.path.join(build_dir, "libriscv.so")) - if runtime_result.returncode != 0 or not all(os.path.isfile(path) for path in runtime_artifacts): + if not all(os.path.isfile(path) for path in runtime_artifacts): missing = [path for path in runtime_artifacts if not os.path.isfile(path)] if missing: ctx.logger.error(f"P2E runtime artifacts missing: {missing}") await check_result( ctx, - runtime_result.returncode or 1, + 1, continue_run=False, extra_fields={ "task": "runtime", - "vsrc_dir": vsrc_dir, "build_dir": build_dir, "missing": missing, }, @@ -255,10 +186,6 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ) return - # Run the case-local executable produced above. It must load the VVAC - # runtime's libstdc++ (6.0.25), not Cargo/Nix's newer libstdc++; mixing the - # two ABIs crashes inside ICtbMgr::init(). Apply LD_LIBRARY_PATH only to - # the runtime process so Cargo itself keeps its normal library environment. run_cmd = ( f"\"{bebop_p2e_path}\" run p2e " f"--image=\"{image_path}\" " @@ -279,12 +206,6 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: run_cmd += f" --{trace_name}" ctx.logger.info(f"Running bebop p2e runworkload: {run_cmd}") run_env = {**os.environ.copy(), **bebop_cargo_env(bbdir, chip)} - inherited_library_path = run_env.get("LD_LIBRARY_PATH") - run_env["LD_LIBRARY_PATH"] = ( - f"{runtime_lib_dir}:{inherited_library_path}" - if inherited_library_path - else runtime_lib_dir - ) run_result = await stream_run_logger_async( cmd=run_cmd, logger=ctx.logger, diff --git a/api/steps/bebop/p2e/04_batch_event.step.py b/api/steps/bebop/p2e/04_batch_event.step.py index 9f5a176..adff768 100644 --- a/api/steps/bebop/p2e/04_batch_event.step.py +++ b/api/steps/bebop/p2e/04_batch_event.step.py @@ -2,9 +2,9 @@ bebop p2e batch event handler Runs bebop p2e nextest batch regression (aligned with runworkload): - 1. Resolve VSRC from bitstream case and rebuild VVAC runtime into case dir - 2. Build bebop with p2e feature and OUT_PATH= - 3. Run cargo nextest with the same OUT_PATH (serial, single FPGA) + 1. Resolve the runtime from the bitstream case + 2. Build the test harness against that runtime + 3. Run cargo nextest serially on one FPGA """ import os import re @@ -23,7 +23,7 @@ sys.path.insert(0, bebop_path) from utils.event_common import require_chip -from utils.path import bebop_cargo_env, get_buckyball_path, rtl_dir, workload_tests_root, workloads_output_root +from utils.path import bebop_cargo_env, get_buckyball_path, workload_tests_root, workloads_output_root from utils.search_workload import search_workload from utils.stream_run import stream_run_logger_async from utils.event_common import check_result, get_origin_trace_id @@ -65,7 +65,6 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: return bitstream = os.path.abspath(bitstream) - # Same as runworkload: case home is parent of fpgaCompDir/ build_dir = os.path.dirname(os.path.dirname(bitstream)) if not os.path.isdir(build_dir): ctx.logger.error(f"P2E build case not found for bitstream: {build_dir}") @@ -132,68 +131,29 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ) return - vsrc_dir = rtl_dir(bbdir, chip, "p2e", input_data.get("vsrc_dir")) - # Rebuild VVAC host runtime in the bitstream case (same as runworkload). manifest = Path(bebop_dir) / "Cargo.toml" features = ["p2e"] runtime_env = {**os.environ.copy(), **cargo_env} if diff: manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" features.extend(["bemu", "difftest"]) - hpec_home = "/home/x-epic/hpe-24.12.01.s008" - runtime_env.update({ - "BEBOP_BEMU_P2E_ABI": "1", - "BEBOP_BEMU_CC": os.path.join(hpec_home, "tools", "gcc-8.3.0", "gcc-8.3.0", "bin", "gcc"), - "BEBOP_BEMU_CXX": os.path.join(hpec_home, "tools", "gcc-8.3.0", "gcc-8.3.0", "bin", "g++"), - "BEBOP_BEMU_DTC": os.path.join(bbdir, "result", "bin", "dtc"), - "CARGO_TARGET_DIR": os.path.join(bebop_dir, "target", f"{chip}-p2e-diff"), - "BEBOP_BEMU_COMPILER_LIBRARY_PATH": ":".join([ - os.path.join(hpec_home, "tools", "gcc-8.3.0", "gmp-6.2.1", "lib"), - os.path.join(hpec_home, "tools", "gcc-8.3.0", "mpfr-4.1.0", "lib"), - os.path.join(hpec_home, "tools", "gcc-8.3.0", "mpc-1.2.1", "lib"), - ]), - }) + runtime_env["CARGO_TARGET_DIR"] = os.path.join(bebop_dir, "target", f"{chip}-p2e-diff") feature_arg = ",".join(features) - runtime_cmd = shlex.join([ - "env", - "BEBOP_P2E_RUNTIME_ONLY=1", - "BEBOP_P2E_REBUILD_RUNTIME=1", - f"VSRC_PATH={vsrc_dir}", - f"OUT_PATH={build_dir}", - "cargo", "run", "--release", - "--manifest-path", str(manifest), - "--bin", "bebop", - "--features", feature_arg, - "--", "build", "p2e", - "--rtl-dir", vsrc_dir, - "--out-dir", build_dir, - *( ["--diff"] if diff else [] ), - ]) - ctx.logger.info("Preparing bebop p2e runtime for the selected bitstream ...") - runtime_result = await stream_run_logger_async( - cmd=runtime_cmd, - logger=ctx.logger, - cwd=str(manifest.parent), - stdout_prefix="bebop p2e runtime", - stderr_prefix="bebop p2e runtime", - env=runtime_env, - ) rtcfg_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "rtcfg") libvctb_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "lib", "lib_arm", "libvCtb.so") - runtime_artifacts = [rtcfg_path, libvctb_path] + runtime_artifacts = [rtcfg_path, libvctb_path, os.path.join(build_dir, "bebop-p2e")] if diff: runtime_artifacts.append(os.path.join(build_dir, "libriscv.so")) - if runtime_result.returncode != 0 or not all(os.path.isfile(path) for path in runtime_artifacts): + if not all(os.path.isfile(path) for path in runtime_artifacts): missing = [path for path in runtime_artifacts if not os.path.isfile(path)] if missing: ctx.logger.error(f"P2E runtime artifacts missing: {missing}") await check_result( ctx, - runtime_result.returncode or 1, + 1, continue_run=False, extra_fields={ "task": "runtime", - "vsrc_dir": vsrc_dir, "build_dir": build_dir, "missing": missing, }, @@ -201,7 +161,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ) return - # cargo global --config so OUT_PATH reaches bebop-p2e build.rs (same as runworkload). + # OUT_PATH makes the test harness link against this bitstream case's runtime. cargo_out = f"--config=\"env.OUT_PATH='{build_dir}'\"" # ── Build bebop p2e (tests), linked against case libvCtb ─────────────── diff --git a/api/steps/config/scripts/4_install.py b/api/steps/config/scripts/4_install.py index c24dde9..e19cd28 100644 --- a/api/steps/config/scripts/4_install.py +++ b/api/steps/config/scripts/4_install.py @@ -158,7 +158,7 @@ def install_bebop(gen: Path) -> Path: [features] default = [] verilator = ["dep:bebop-verilator"] -p2e = ["dep:bebop-p2e"] +p2e = ["dep:bebop-p2e", "bebop-bemu?/p2e"] bemu = ["dep:bebop-bemu"] difftest = ["bebop-bemu/difftest"] diff --git a/api/steps/regression/02_eval_performance_event.step.py b/api/steps/regression/02_eval_performance_event.step.py index d2db347..fd35f26 100644 --- a/api/steps/regression/02_eval_performance_event.step.py +++ b/api/steps/regression/02_eval_performance_event.step.py @@ -26,7 +26,7 @@ if utils_path not in sys.path: sys.path.insert(0, utils_path) -from utils.path import get_buckyball_path, rtl_dir, log_dir +from utils.path import bebop_cargo_env, get_buckyball_path, log_dir from utils.stream_run import stream_run_logger_async from utils.event_common import check_result, get_origin_trace_id @@ -131,27 +131,33 @@ def _p2e_run_cmds(bbdir, bitstream, image_name, chip, input_data): multi_fpga = bool(input_data.get("multi-fpga", False)) if not multi_fpga and _p2e.case_uses_multi_fpga(build_dir): multi_fpga = True - vsrc_dir = rtl_dir(bbdir, chip, "p2e", input_data.get("vsrc_dir")) timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M") run_log = log_dir(bbdir, chip, "p2e", timestamp, "p2e", image_name, input_data.get("vsrc_dir")) os.makedirs(run_log, exist_ok=True) - runtime_cmd = ( - f"env BEBOP_P2E_RUNTIME_ONLY=1 BEBOP_P2E_REBUILD_RUNTIME=1 " - f'cargo run --release --features p2e -- build p2e ' - f'--rtl-dir="{vsrc_dir}" ' - f'--out-dir="{build_dir}"' - ) - run_cmd = ( - f"cargo run --release --features p2e " - f"--config=\"env.OUT_PATH='{build_dir}'\" " - f"-- run p2e " - f'--image="{image_path}" ' - f'--bitstream="{bitstream}" ' - f'--log-dir="{log_dir}"' + runtime_lib_dir = os.path.join( + build_dir, "vvacDir", "runtimeDir", "lib", "lib_arm" ) + runtime = os.path.join(build_dir, "bebop-p2e") + artifacts = [ + os.path.join(build_dir, "vvacDir", "runtimeDir", "rtcfg"), + os.path.join(runtime_lib_dir, "libvCtb.so"), + runtime, + ] + missing = [path for path in artifacts if not os.path.isfile(path)] + if missing: + raise FileNotFoundError(f"P2E runtime artifacts missing: {missing}") + + run_args = [ + runtime, + "run", "p2e", + "--image", image_path, + "--bitstream", bitstream, + "--log-dir", run_log, + ] if multi_fpga: - run_cmd += " --multi-fpga" - return runtime_cmd, run_cmd, f"{bbdir}/bebop", run_log, build_dir + run_args.append("--multi-fpga") + run_env = {**os.environ, **bebop_cargo_env(bbdir, chip)} + return shlex.join(run_args), run_env, f"{bbdir}/bebop", run_log def _perfetto_cmd(trace_dir, trace_toml, mlir_files): @@ -265,7 +271,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ctx.logger.info(f"[eval-performance] model {model}: p2e runworkload") image_name = os.path.splitext(os.path.basename(fw_hex))[0] try: - runtime_cmd, run_cmd, bebop_cwd, run_log, build_dir = _p2e_run_cmds( + run_cmd, run_env, bebop_cwd, run_log = _p2e_run_cmds( bbdir, bitstream, image_name, chip, input_data ) except FileNotFoundError as e: @@ -277,40 +283,27 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: extra["build_dir"] = os.path.dirname( os.path.dirname(os.path.abspath(bitstream)) ) + elif str(e).startswith("P2E runtime artifacts missing"): + err = "p2e_runtime_missing" await _fail(ctx, origin_tid, err, **extra) return - rt_result = await stream_run_logger_async( - cmd=runtime_cmd, logger=ctx.logger, cwd=bebop_cwd, - stdout_prefix=f"p2e runtime {model}", - stderr_prefix=f"p2e runtime {model}", - ) - rtcfg = os.path.join(build_dir, "vvacDir", "runtimeDir", "rtcfg") - libvctb = os.path.join( - build_dir, "vvacDir", "runtimeDir", "lib", "lib_arm", "libvCtb.so" - ) - if rt_result.returncode != 0 or not all( - os.path.isfile(p) for p in (rtcfg, libvctb) - ): - await _fail(ctx, origin_tid, "p2e_runtime_failed", - model=model, returncode=rt_result.returncode) - return run_result = await stream_run_logger_async( - cmd=run_cmd, logger=ctx.logger, cwd=bebop_cwd, + cmd=run_cmd, logger=ctx.logger, cwd=bebop_cwd, env=run_env, stdout_prefix=f"p2e run {model}", stderr_prefix=f"p2e run {model}", ) if run_result.returncode != 0: await _fail(ctx, origin_tid, "p2e_run_failed", model=model, returncode=run_result.returncode, - log_dir=log_dir) + log_dir=run_log) return uart_path = Path(run_log) / "uart_hart_0.log" if not uart_path.is_file(): uart_path = Path(run_log) / "uart.log" if not uart_path.is_file(): - ctx.logger.error(f"uart log missing under {log_dir}") - await _fail(ctx, origin_tid, "uart_missing", model=model, log_dir=log_dir) + ctx.logger.error(f"uart log missing under {run_log}") + await _fail(ctx, origin_tid, "uart_missing", model=model, log_dir=run_log) return try: acc_i = accuracy_from_uart(spec["metric"], uart_path.read_text()) diff --git a/bbdev b/bbdev index d73ea5d..5c19b0b 100755 --- a/bbdev +++ b/bbdev @@ -84,7 +84,7 @@ WORKFLOW_COMMANDS = { "bebop-p2e": { "clean": 'Clean Bebop P2E build directory. Args: "--chip [--output-dir ]"', "verilog": 'Generate Bebop P2E SystemVerilog for DDR4 backdoor. Args: "--chip [--diff] [--output-dir ]"', - "buildbitstream": 'Build Bebop P2E runtime case. Args: "--chip [--vsrc-dir ] [--output-dir ]"', + "buildbitstream": 'Build Bebop P2E runtime case. Args: "--chip [--diff] [--vsrc-dir ] [--output-dir ]"', "runworkload": 'Run Bebop P2E workload. Args: "--chip --image --bitstream [--fpga-location ] [--multi-fpga] [--wave] [--wave-start ] [--diff] [--itrace] [--mtrace] [--pmctrace]"', "batch": 'Run Bebop p2e nextest batch regression (serial). Args: "--chip --bitstream --test [--diff]"', }, diff --git a/mcp/tools/bebop_p2e_buildbitstream.py b/mcp/tools/bebop_p2e_buildbitstream.py index ec00944..52423bd 100644 --- a/mcp/tools/bebop_p2e_buildbitstream.py +++ b/mcp/tools/bebop_p2e_buildbitstream.py @@ -11,6 +11,7 @@ def register(mcp): @mcp.tool() def bbdev_bebop_p2e_buildbitstream( chip: str, + diff: bool = False, vsrc_dir: Optional[str] = None, output_dir: Optional[str] = None, ) -> str: @@ -21,7 +22,7 @@ def bbdev_bebop_p2e_buildbitstream( submit( "/bebop/p2e/buildbitstream", opt( - {"chip": chip}, + {"chip": chip, "diff": diff}, vsrc_dir=vsrc_dir, output_dir=output_dir, ), From b06f40617fb2fdebe86dba38901d830e0e01201e Mon Sep 17 00:00:00 2001 From: shirohasuki Date: Tue, 22 Sep 2026 03:10:50 +0800 Subject: [PATCH 10/11] refactor: improve build directory validation and update CPU configuration messages in proto files --- .../bebop/p2e/02_buildbitstream_event.step.py | 18 ++++++++++- api/steps/bebop/p2e/04_batch_event.step.py | 32 +------------------ api/steps/config/scripts/3_json2proto.py | 12 +++---- api/steps/config/scripts/proto/chip.proto | 27 +++++++++------- 4 files changed, 38 insertions(+), 51 deletions(-) diff --git a/api/steps/bebop/p2e/02_buildbitstream_event.step.py b/api/steps/bebop/p2e/02_buildbitstream_event.step.py index e2a0385..91341c0 100644 --- a/api/steps/bebop/p2e/02_buildbitstream_event.step.py +++ b/api/steps/bebop/p2e/02_buildbitstream_event.step.py @@ -58,7 +58,23 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: or input_data.get("output-dir") or f"{bebop_dir}/build/{chip}-{timestamp}" ) - os.makedirs(build_dir, exist_ok=True) + build_path = Path(build_dir) + if build_path.exists() and ( + not build_path.is_dir() or any(build_path.iterdir()) + ): + ctx.logger.error(f"P2E build directory is not empty: {build_dir}") + await check_result( + ctx, + 1, + continue_run=False, + extra_fields={ + "error": "build_dir_not_empty", + "build_dir": build_dir, + }, + trace_id=origin_tid, + ) + return + build_path.mkdir(parents=True, exist_ok=True) diff = bool(input_data.get("diff", False)) manifest = Path(bebop_dir) / "Cargo.toml" diff --git a/api/steps/bebop/p2e/04_batch_event.step.py b/api/steps/bebop/p2e/04_batch_event.step.py index adff768..49d7655 100644 --- a/api/steps/bebop/p2e/04_batch_event.step.py +++ b/api/steps/bebop/p2e/04_batch_event.step.py @@ -10,7 +10,6 @@ import re import shlex import sys -import tomllib from pathlib import Path from motia import FlowContext, queue @@ -23,8 +22,7 @@ sys.path.insert(0, bebop_path) from utils.event_common import require_chip -from utils.path import bebop_cargo_env, get_buckyball_path, workload_tests_root, workloads_output_root -from utils.search_workload import search_workload +from utils.path import bebop_cargo_env, get_buckyball_path, workloads_output_root from utils.stream_run import stream_run_logger_async from utils.event_common import check_result, get_origin_trace_id from regression import regression_workload_toml @@ -103,34 +101,6 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ctx.logger.info(f"Running {test_type} with workload config: {workload_toml} diff={diff}") ctx.logger.info(f"P2E case dir (from bitstream): {build_dir}") - if test_type == "elf-tests": - workload_names = tomllib.loads(Path(workload_toml).read_text())["workloads"]["tests"] - converter = os.path.join(bbdir, "bbdev", "api", "steps", "workload", "scripts", "elf2hex.py") - search_root = workload_tests_root(bbdir, chip) - for image_name in workload_names: - if not image_name.endswith(".hex"): - raise ValueError(f"P2E ELF workload must end in .hex: {image_name}") - elf_name = image_name.removesuffix(".hex") - elf_path = search_workload(search_root, elf_name) - if elf_path is None: - raise FileNotFoundError(f"P2E workload ELF not found: {elf_name}") - convert_result = await stream_run_logger_async( - cmd=shlex.join(["python3", converter, elf_path]), - logger=ctx.logger, - cwd=os.path.dirname(elf_path), - stdout_prefix=f"p2e image {elf_name}", - stderr_prefix=f"p2e image {elf_name}", - ) - if convert_result.returncode != 0: - await check_result( - ctx, - convert_result.returncode, - continue_run=False, - extra_fields={"task": "tohex", "elf": elf_path}, - trace_id=origin_tid, - ) - return - manifest = Path(bebop_dir) / "Cargo.toml" features = ["p2e"] runtime_env = {**os.environ.copy(), **cargo_env} diff --git a/api/steps/config/scripts/3_json2proto.py b/api/steps/config/scripts/3_json2proto.py index 21f2287..dc394c0 100644 --- a/api/steps/config/scripts/3_json2proto.py +++ b/api/steps/config/scripts/3_json2proto.py @@ -104,9 +104,7 @@ def _fill_ball(msg: pb.BallDomain, d: dict[str, Any], bbdir: Path) -> None: e.bid = item["bid"] -def _fill_rocket(msg: pb.RocketCoreConfig, d: dict[str, Any]) -> None: - msg.x_len = d["xLen"] - msg.pg_levels = d["pgLevels"] +def _fill_rocket(msg: pb.RocketCpuConfig, d: dict[str, Any]) -> None: msg.use_vm = d["useVM"] msg.use_zba = d["useZba"] msg.use_zbb = d["useZbb"] @@ -134,7 +132,7 @@ def _fill_rocket(msg: pb.RocketCoreConfig, d: dict[str, Any]) -> None: msg.btb.n_ras = btb["nRAS"] -def _fill_boom(msg: pb.BoomCoreConfig, d: dict[str, Any]) -> None: +def _fill_boom(msg: pb.BoomCpuConfig, d: dict[str, Any]) -> None: msg.fetch_width = d["fetchWidth"] msg.decode_width = d["decodeWidth"] msg.num_rob_entries = d["numRobEntries"] @@ -178,12 +176,13 @@ def _fill_gp(msg: pb.GpDomainConfig, d: dict[str, Any], bbdir: Path) -> None: msg.lane_scale = d["laneScale"] -def _fill_cpu_params(msg: pb.CpuConfig, d: dict[str, Any]) -> None: +def _fill_tile_params(msg: pb.TileParamConfig, d: dict[str, Any]) -> None: msg.core_data_bytes = d["coreDataBytes"] msg.x_len = d["xLen"] msg.vaddr_bits = d["vaddrBits"] msg.paddr_bits = d["paddrBits"] msg.pg_idx_bits = d["pgIdxBits"] + msg.pg_levels = d["pgLevels"] msg.n_pmps = d["nPMPs"] @@ -212,8 +211,6 @@ def _fill_core(ci: pb.CoreInstance, raw: dict[str, Any], meta: dict[str, Any], b _fill_frontend(ci.frontend, raw["frontend"], bbdir) if "gpdomain" in raw: _fill_gp(ci.gp_domain, raw["gpdomain"], bbdir) - if ci.balldomain.ball_num > 0: - _fill_cpu_params(ci.cpu, cpu) def _fill_tile(tp: pb.TilePlacement, meta: dict[str, Any], proto: dict[str, Any]) -> None: @@ -221,6 +218,7 @@ def _fill_tile(tp: pb.TilePlacement, meta: dict[str, Any], proto: dict[str, Any] tp.virtual_bank_count = meta["virtual_bank_count"] tp.core_indices.extend(meta["core_indices"]) tp.mem_ball_channel_num = meta["mem_ball_channel_num"] + _fill_tile_params(tp.param, proto) dc = proto["privateDCache"] tp.private_dcache.enable = dc["enable"] tp.private_dcache.ways = dc["ways"] diff --git a/api/steps/config/scripts/proto/chip.proto b/api/steps/config/scripts/proto/chip.proto index 68cc01d..90e3ad0 100644 --- a/api/steps/config/scripts/proto/chip.proto +++ b/api/steps/config/scripts/proto/chip.proto @@ -114,9 +114,7 @@ message RocketBtbConfig { uint32 n_ras = 3; } -message RocketCoreConfig { - uint32 x_len = 1; - uint32 pg_levels = 2; +message RocketCpuConfig { bool use_vm = 3; bool use_zba = 4; bool use_zbb = 5; @@ -140,7 +138,7 @@ message BoomICacheConfig { uint32 n_ways = 2; } -message BoomCoreConfig { +message BoomCpuConfig { uint32 fetch_width = 1; uint32 decode_width = 2; uint32 num_rob_entries = 3; @@ -170,19 +168,23 @@ message GpDomainConfig { string source_path = 7; } +message TileParamConfig { + uint32 core_data_bytes = 1; + uint32 x_len = 2; + uint32 vaddr_bits = 3; + uint32 paddr_bits = 4; + uint32 pg_idx_bits = 5; + uint32 pg_levels = 6; + uint32 n_pmps = 7; +} + message CpuConfig { string kind = 1; oneof cpu { - RocketCoreConfig rocket = 2; - BoomCoreConfig boom = 3; + RocketCpuConfig rocket = 2; + BoomCpuConfig boom = 3; } string source_path = 4; - uint32 core_data_bytes = 5; - uint32 x_len = 6; - uint32 vaddr_bits = 7; - uint32 paddr_bits = 8; - uint32 pg_idx_bits = 9; - uint32 n_pmps = 10; } message CoreInstance { @@ -235,6 +237,7 @@ message TilePlacement { uint32 mem_ball_channel_num = 4; PrivateDCacheConfig private_dcache = 5; SharedMemConfig shared_mem = 6; + TileParamConfig param = 7; } message WorkloadConfig { From 9ef03d7f58e924ecca315146b2b0960ba92a931f Mon Sep 17 00:00:00 2001 From: shirohasuki Date: Tue, 22 Sep 2026 10:50:23 +0800 Subject: [PATCH 11/11] refactor: streamline preset configuration management and enhance error handling in Bebop workflows --- api/services/presets.py | 30 ++--- api/steps/bebop/bemu/02_sim_event.step.py | 12 +- api/steps/bebop/bemu/03_batch_event.step.py | 12 +- api/steps/bebop/bemu/04_analysis_api.step.py | 6 +- api/steps/bebop/bemu/scripts/bemu_analysis.py | 34 +++-- .../bebop/bemu/scripts/test_bemu_analysis.py | 120 ------------------ .../bebop/p2e/02_buildbitstream_event.step.py | 61 ++------- api/steps/bebop/p2e/04_batch_event.step.py | 17 ++- api/steps/bebop/regression_harness.py | 12 -- api/steps/bebop/vcs/03_batch_event.step.py | 4 +- .../bebop/verilator/03_build_event.step.py | 2 +- .../bebop/verilator/06_batch_event.step.py | 20 ++- .../config/scripts/2_parameter_derivation.py | 4 +- api/steps/config/scripts/3_json2proto.py | 7 +- api/steps/config/scripts/4_install.py | 14 +- api/steps/dc/scripts/emit_seq_mem_models.py | 12 +- api/steps/ip/02_replace_event.step.py | 7 +- api/steps/ip/scripts/generate.py | 16 +-- api/steps/ip/scripts/replace.py | 28 ++-- api/steps/ip/scripts/sram_compiler.py | 12 +- api/steps/kernel/01_build_event.step.py | 29 ++--- api/steps/pt/scripts/power.py | 9 +- .../00_buildbitstream_event.step.py | 2 +- .../02_eval_performance_event.step.py | 18 +-- api/steps/regression/scripts/accuracy.py | 8 +- api/steps/regression/scripts/dc_area.py | 5 +- api/steps/regression/scripts/model_layout.py | 70 +++------- api/steps/regression/scripts/result.py | 10 +- api/steps/uvm/scripts/waive.py | 7 +- api/steps/vcs/03_build_event.step.py | 10 +- api/steps/verilator/03_build_event.step.py | 6 +- api/steps/verilator/04_sim_event.step.py | 10 +- api/steps/workload/01_build_event.step.py | 28 +--- api/steps/yosys/03_synth_event.step.py | 14 +- api/utils/event_common.py | 5 +- api/utils/model.py | 19 +++ api/utils/path.py | 18 +-- api/utils/port.py | 7 +- api/utils/workers.py | 9 +- .../workload_manifest.py} | 4 +- mcp/__main__.py | 5 +- mcp/common.py | 28 +++- mcp/tools/__init__.py | 7 +- 43 files changed, 247 insertions(+), 511 deletions(-) delete mode 100644 api/steps/bebop/bemu/scripts/test_bemu_analysis.py delete mode 100644 api/steps/bebop/regression_harness.py create mode 100644 api/utils/model.py rename api/{steps/bebop/regression.py => utils/workload_manifest.py} (94%) diff --git a/api/services/presets.py b/api/services/presets.py index 8db2aff..6ef8be8 100644 --- a/api/services/presets.py +++ b/api/services/presets.py @@ -65,19 +65,11 @@ def create_code_agent_manager() -> ToolManager: return manager -# Predefined tool set configurations -PRESET_CONFIGS = { - "file_tools": { - "name": "File Operations", - "description": "Basic file system operations", - "tools": create_file_tools, - }, - "code_agent": { - "name": "Code Agent", - "description": "Tools for code generation and manipulation", - "tools": create_code_agent_tools, - }, -} +def preset_configs(): + return { + "file_tools": create_file_tools, + "code_agent": create_code_agent_tools, + } def get_preset(name: str) -> List[Tool]: @@ -93,14 +85,14 @@ def get_preset(name: str) -> List[Tool]: Raises: ValueError: If tool set does not exist """ - config = PRESET_CONFIGS.get(name) - if not config: - available = ", ".join(PRESET_CONFIGS.keys()) + presets = preset_configs() + factory = presets.get(name) + if factory is None: + available = ", ".join(presets) raise ValueError(f"Unknown preset: {name}. Available: {available}") - - return config["tools"]() + return factory() def list_presets() -> List[str]: """List all available predefined tool sets""" - return list(PRESET_CONFIGS.keys()) + return list(preset_configs()) diff --git a/api/steps/bebop/bemu/02_sim_event.step.py b/api/steps/bebop/bemu/02_sim_event.step.py index 8771c45..12d0103 100644 --- a/api/steps/bebop/bemu/02_sim_event.step.py +++ b/api/steps/bebop/bemu/02_sim_event.step.py @@ -27,10 +27,6 @@ from bemu_common import bemu_core_manifest, bemu_manifest, bemu_tile_index, chip_emu_manifest -PERFETTO_TARGETS = { - "buddy-buckyball-lenet-run": "buddy-buckyball-lenet-perfetto", -} - config = { "name": "bebop-bemu-sim", "description": "Run bebop bemu emulator", @@ -138,9 +134,11 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: return ctx.logger.info(f"binary_path: {binary_path}") binary_dir = os.path.dirname(binary_path) - perfetto_target = ( - PERFETTO_TARGETS.get(binary_name) if input_data.get("tool-profile") else None - ) + perfetto_target = None + if input_data.get("tool-profile"): + if not binary_name.endswith("-run"): + raise ValueError(f"tool-profile binary must end with '-run': {binary_name}") + perfetto_target = f"{binary_name.removesuffix('-run')}-perfetto" if perfetto_target: clean_model_trace(binary_dir) diff --git a/api/steps/bebop/bemu/03_batch_event.step.py b/api/steps/bebop/bemu/03_batch_event.step.py index 0422530..f5bb215 100644 --- a/api/steps/bebop/bemu/03_batch_event.step.py +++ b/api/steps/bebop/bemu/03_batch_event.step.py @@ -27,8 +27,7 @@ from utils.stream_run import stream_run_logger_async from utils.event_common import check_result, get_origin_trace_id from bemu_common import bemu_manifest, chip_emu_manifest -from regression import regression_workload_toml -from regression_harness import nextest_harness_args +from utils.workload_manifest import resolve_workload_toml config = { "name": "bebop-bemu-batch", @@ -82,7 +81,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: env.update(bebop_cargo_env(bbdir, chip)) test_type = input_data.get("test", "elf-tests") try: - workload_toml = regression_workload_toml( + workload_toml = resolve_workload_toml( chip, "bemu", test_type, bbdir, rushB=rushB ) except ValueError as e: @@ -137,7 +136,12 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ) ctx.logger.info("Cleaned previous bebop test artifacts") - harness = nextest_harness_args(workload_toml, elf_root, env) + harness = shlex.join([ + "--", + "--workload-toml", workload_toml, + "--bb-tests-root", elf_root, + *(["--rushb-backend", "bemu"] if rushB else []), + ]) nextest_cmd = ( f"nix develop -c cargo nextest run --manifest-path {shlex.quote(str(bemu_cargo_manifest))} " "--test test_bemu " diff --git a/api/steps/bebop/bemu/04_analysis_api.step.py b/api/steps/bebop/bemu/04_analysis_api.step.py index 20ce605..eaef193 100644 --- a/api/steps/bebop/bemu/04_analysis_api.step.py +++ b/api/steps/bebop/bemu/04_analysis_api.step.py @@ -21,9 +21,6 @@ "enqueues": ["bebop.bemu.analysis"], } -ALLOWED = {"chip", "log-dir", "log_dir", "itrace", "mtrace"} - - def _fail(message: str, status: int = 400) -> ApiResponse: return ApiResponse( status=status, @@ -38,7 +35,8 @@ def _fail(message: str, status: int = 400) -> ApiResponse: async def handler(request: ApiRequest, ctx: FlowContext) -> ApiResponse: body = request.body or {} - unknown = sorted(k for k in body if k not in ALLOWED) + allowed = {"chip", "log-dir", "log_dir", "itrace", "mtrace"} + unknown = sorted(k for k in body if k not in allowed) if unknown: return _fail(f"unknown parameter(s): {', '.join(unknown)}") diff --git a/api/steps/bebop/bemu/scripts/bemu_analysis.py b/api/steps/bebop/bemu/scripts/bemu_analysis.py index a7d91e5..e12b4e6 100644 --- a/api/steps/bebop/bemu/scripts/bemu_analysis.py +++ b/api/steps/bebop/bemu/scripts/bemu_analysis.py @@ -9,21 +9,8 @@ from collections import Counter from pathlib import Path -ISA = { - 0: "fence", - 1: "barrier", - 16: "mvout", - 32: "mset", - 33: "mvin", - 34: "mmio_set", - 35: "mvin_mmio", -} - -MATRIX_MNEMONICS = frozenset({"MATRIX", "MATRIX_F32", "SMATMUL_OS"}) - -_API = Path(__file__).resolve().parents[4] -if str(_API) not in sys.path: - sys.path.insert(0, str(_API)) +if str(Path(__file__).resolve().parents[4]) not in sys.path: + sys.path.insert(0, str(Path(__file__).resolve().parents[4])) def abs_log_dir(value: object) -> Path: @@ -42,6 +29,15 @@ def _hex_u(value: object, field: str, line_no: int) -> int: def chip_maps(bbdir: str, chip: str) -> tuple[dict[int, str], set[int], int]: + system_isa = { + 0: "fence", + 1: "barrier", + 16: "mvout", + 32: "mset", + 33: "mvin", + 34: "mmio_set", + 35: "mvin_mmio", + } path = ( Path(bbdir) / "examples" @@ -75,7 +71,7 @@ def chip_maps(bbdir: str, chip: str) -> tuple[dict[int, str], set[int], int]: isa = ball["ballISA"] ball_path = ball["_file"] - names: dict[int, str] = dict(ISA) + names: dict[int, str] = dict(system_isa) matrix: set[int] = set() seen: set[int] = set() for entry in isa: @@ -89,13 +85,13 @@ def chip_maps(bbdir: str, chip: str) -> tuple[dict[int, str], set[int], int]: raise ValueError(f"ballISA mnemonic must be a non-empty string: {ball_path}") if funct in seen: raise ValueError(f"duplicate ballISA funct7 {funct}: {ball_path}") - if funct in ISA: + if funct in system_isa: raise ValueError( - f"ballISA funct7 {funct} ({mnemonic}) collides with ISA {ISA[funct]}: {ball_path}" + f"ballISA funct7 {funct} ({mnemonic}) collides with ISA {system_isa[funct]}: {ball_path}" ) seen.add(funct) names[funct] = mnemonic.lower() - if mnemonic in MATRIX_MNEMONICS: + if mnemonic in {"MATRIX", "MATRIX_F32", "SMATMUL_OS"}: matrix.add(funct) mem = core["memdomain"] diff --git a/api/steps/bebop/bemu/scripts/test_bemu_analysis.py b/api/steps/bebop/bemu/scripts/test_bemu_analysis.py deleted file mode 100644 index eaf6c2c..0000000 --- a/api/steps/bebop/bemu/scripts/test_bemu_analysis.py +++ /dev/null @@ -1,120 +0,0 @@ -#!/usr/bin/env python3 -import tempfile -import unittest -from pathlib import Path - -from bemu_analysis import abs_log_dir, analysis_dir, chip_maps - -BBDIR = Path(__file__).resolve().parents[6] - - -def _write_bdb(text: str) -> Path: - tmp = Path(tempfile.mkdtemp()) - (tmp / "bdb.ndjson").write_text(text, encoding="utf-8") - return tmp - - -class AbsLogDirTest(unittest.TestCase): - def test_relative_fails(self): - with self.assertRaisesRegex(ValueError, "absolute path"): - abs_log_dir("log/2026-08-19-13-00-bemu-buddy-buckyball-yolo26-run") - - def test_absolute_ok(self): - path = abs_log_dir("/tmp/bemu-log") - self.assertTrue(path.is_absolute()) - - -class ChipMapsTest(unittest.TestCase): - def test_pebble_loads_ballisa(self): - names, matrix, depth = chip_maps(str(BBDIR), "pebble") - self.assertEqual(names[0], "fence") - self.assertEqual(names[32], "mset") - self.assertEqual(names[65], "smatmul") - self.assertEqual(names[48], "im2col") - self.assertEqual(names[49], "transpose") - self.assertEqual(names[51], "fp2int") - self.assertEqual(names[52], "int2fp") - self.assertEqual(matrix, {65}) - self.assertEqual(depth, 1024) - - -class AnalysisDirTest(unittest.TestCase): - def setUp(self): - self.names, self.matrix, self.depth = chip_maps(str(BBDIR), "pebble") - - def test_itrace_mtrace(self): - log_dir = _write_bdb( - "\n".join( - [ - '{"type":"itrace","clk":10,"event":"complete","funct":"0x00","pc":"0x0","rs1":"0x0","rs2":"0x0"}', - '{"type":"itrace","clk":20,"event":"complete","funct":"0x41","pc":"0x0","rs1":"0x0","rs2":"0x0000000100010400"}', - '{"type":"mtrace","clk":15,"event":"read","addr":"0x0","rows":1024,"line_bytes":16,"row_stride":16,"vbank_id":0,"pbank_id":0,"group_id":0}', - '{"type":"mtrace","clk":16,"event":"write","addr":"0x0","rows":1,"line_bytes":16,"row_stride":16,"vbank_id":1,"pbank_id":1,"group_id":0}', - "", - ] - ) - ) - text = analysis_dir( - log_dir, - names=self.names, - matrix=self.matrix, - bank_depth=self.depth, - itrace=True, - mtrace=True, - ) - self.assertIn("span_cycles: 20", text) - self.assertIn("smatmul", text) - self.assertIn("(1024,16,256) n=1", text) - self.assertIn(f"bank_depth: {self.depth}", text) - self.assertIn("mean_rows/bank_depth:", text) - self.assertNotIn("mean_rows/1024:", text) - self.assertIn("rows=1: 1 (50.0%)", text) - - def test_bank_depth_from_arg(self): - log_dir = _write_bdb( - '{"type":"mtrace","clk":1,"event":"read","addr":"0x0","rows":32,"line_bytes":16,"row_stride":16,"vbank_id":0,"pbank_id":0,"group_id":0}\n' - ) - text = analysis_dir( - log_dir, - names=self.names, - matrix=self.matrix, - bank_depth=64, - itrace=False, - mtrace=True, - ) - self.assertIn("bank_depth: 64", text) - self.assertIn("mean_rows: 32.00", text) - self.assertIn("mean_rows/bank_depth: 0.5000", text) - self.assertNotIn("mean_rows/1024", text) - - def test_unknown_funct_fails(self): - log_dir = _write_bdb( - '{"type":"itrace","clk":1,"event":"complete","funct":"0x99","pc":"0x0","rs1":"0x0","rs2":"0x0"}\n' - ) - with self.assertRaisesRegex(ValueError, "unknown funct 0x99"): - analysis_dir( - log_dir, - names=self.names, - matrix=self.matrix, - bank_depth=self.depth, - itrace=True, - mtrace=False, - ) - - def test_missing_itrace_fails(self): - log_dir = _write_bdb( - '{"type":"mtrace","clk":1,"event":"read","addr":"0x0","rows":1,"line_bytes":16,"row_stride":16,"vbank_id":0,"pbank_id":0,"group_id":0}\n' - ) - with self.assertRaisesRegex(ValueError, "no itrace events"): - analysis_dir( - log_dir, - names=self.names, - matrix=self.matrix, - bank_depth=self.depth, - itrace=True, - mtrace=False, - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/api/steps/bebop/p2e/02_buildbitstream_event.step.py b/api/steps/bebop/p2e/02_buildbitstream_event.step.py index 91341c0..b9f8bc3 100644 --- a/api/steps/bebop/p2e/02_buildbitstream_event.step.py +++ b/api/steps/bebop/p2e/02_buildbitstream_event.step.py @@ -8,6 +8,7 @@ """ import os import shlex +import shutil import sys from datetime import datetime from pathlib import Path @@ -52,37 +53,23 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: normalize_p2e_timescale(vsrc_dir, ctx.logger) timestamp = datetime.now().strftime("%Y-%m-%d-%H-%M") build_dir = ( - input_data.get("build_dir") - or input_data.get("build-dir") - or input_data.get("output_dir") - or input_data.get("output-dir") + input_data.get("output_dir") or f"{bebop_dir}/build/{chip}-{timestamp}" ) - build_path = Path(build_dir) - if build_path.exists() and ( - not build_path.is_dir() or any(build_path.iterdir()) - ): - ctx.logger.error(f"P2E build directory is not empty: {build_dir}") - await check_result( - ctx, - 1, - continue_run=False, - extra_fields={ - "error": "build_dir_not_empty", - "build_dir": build_dir, - }, - trace_id=origin_tid, - ) - return - build_path.mkdir(parents=True, exist_ok=True) + build_path = Path(build_dir).resolve() + build_root = (Path(bebop_dir) / "build").resolve() + if build_root not in build_path.parents: + raise ValueError(f"P2E output_dir must be under {build_root}: {build_path}") + if build_path.exists(): + shutil.rmtree(build_path) + build_path.mkdir(parents=True) diff = bool(input_data.get("diff", False)) - manifest = Path(bebop_dir) / "Cargo.toml" + manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" features = ["p2e"] build_env = {**os.environ, **bebop_cargo_env(bbdir, chip)} if diff: - manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" - features.extend(["bemu", "difftest"]) + features.append("bemu") build_env["CARGO_TARGET_DIR"] = os.path.join(bebop_dir, "target", f"{chip}-p2e-diff") build_cmd = shlex.join([ "nix", "develop", "--ignore-env", @@ -112,32 +99,6 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: libvctb_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "lib", "lib_arm", "libvCtb.so") bitstream_path = os.path.join(build_dir, "fpgaCompDir", "bitstream.bit") runtime_path = os.path.join(build_dir, "bebop-p2e") - if build_result.returncode == 0: - missing = [ - path - for path in (rtcfg_path, libvctb_path, bitstream_path, runtime_path) - if not os.path.exists(path) - ] - if diff and not os.path.isfile(os.path.join(build_dir, "libriscv.so")): - missing.append(os.path.join(build_dir, "libriscv.so")) - if missing: - ctx.logger.error(f"P2E build artifacts missing: {missing}") - await check_result( - ctx, - 1, - continue_run=False, - extra_fields={ - "task": "build", - "vsrc_dir": vsrc_dir, - "build_dir": build_dir, - "missing": missing, - "error": "p2e_artifact_not_found", - "timestamp": timestamp, - }, - trace_id=origin_tid, - ) - return - extra_fields = { "task": "build", "vsrc_dir": vsrc_dir, diff --git a/api/steps/bebop/p2e/04_batch_event.step.py b/api/steps/bebop/p2e/04_batch_event.step.py index 49d7655..52080a0 100644 --- a/api/steps/bebop/p2e/04_batch_event.step.py +++ b/api/steps/bebop/p2e/04_batch_event.step.py @@ -25,8 +25,7 @@ from utils.path import bebop_cargo_env, get_buckyball_path, workloads_output_root from utils.stream_run import stream_run_logger_async from utils.event_common import check_result, get_origin_trace_id -from regression import regression_workload_toml -from regression_harness import nextest_harness_args +from utils.workload_manifest import resolve_workload_toml config = { "name": "bebop-p2e-batch", @@ -88,7 +87,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: test_type = input_data.get("test", "elf-tests") diff = bool(input_data.get("diff", False)) try: - workload_toml = regression_workload_toml(chip, "p2e", test_type, bbdir, diff=diff) + workload_toml = resolve_workload_toml(chip, "p2e", test_type, bbdir, diff=diff) except ValueError as e: ctx.logger.error(str(e)) await check_result( @@ -106,7 +105,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: runtime_env = {**os.environ.copy(), **cargo_env} if diff: manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" - features.extend(["bemu", "difftest"]) + features.append("bemu") runtime_env["CARGO_TARGET_DIR"] = os.path.join(bebop_dir, "target", f"{chip}-p2e-diff") feature_arg = ",".join(features) rtcfg_path = os.path.join(build_dir, "vvacDir", "runtimeDir", "rtcfg") @@ -160,7 +159,15 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: # ── Run nextest ─────────────────────────────────────────────────────── env = runtime_env.copy() env["OUT_PATH"] = build_dir - harness = nextest_harness_args(workload_toml, elf_root, env, p2e_bitstream=bitstream) + harness_args = [ + "--", + "--workload-toml", workload_toml, + "--bb-tests-root", elf_root, + "--p2e-bitstream", bitstream, + ] + if diff: + harness_args.append("--diff") + harness = shlex.join(harness_args) nextest_cmd = ( f"nix develop -c cargo {cargo_out} nextest run --release " f"--manifest-path {shlex.quote(str(manifest))} --features {shlex.quote(feature_arg)} " diff --git a/api/steps/bebop/regression_harness.py b/api/steps/bebop/regression_harness.py deleted file mode 100644 index 280bad9..0000000 --- a/api/steps/bebop/regression_harness.py +++ /dev/null @@ -1,12 +0,0 @@ -def nextest_harness_args( - workload_toml: str, - bb_tests_root: str, - env: dict, - *, - p2e_bitstream: str | None = None, -) -> str: - env["BEBOP_WORKLOAD_TOML"] = workload_toml - env["BEBOP_BB_TESTS_ROOT"] = bb_tests_root - if p2e_bitstream: - env["BEBOP_P2E_BITSTREAM"] = p2e_bitstream - return "" diff --git a/api/steps/bebop/vcs/03_batch_event.step.py b/api/steps/bebop/vcs/03_batch_event.step.py index 4d0cb1d..d0e786b 100644 --- a/api/steps/bebop/vcs/03_batch_event.step.py +++ b/api/steps/bebop/vcs/03_batch_event.step.py @@ -15,7 +15,7 @@ if bebop_path not in sys.path: sys.path.insert(0, bebop_path) -from regression import regression_workload_toml +from utils.workload_manifest import resolve_workload_toml from utils.event_common import check_result, get_origin_trace_id, require_chip from utils.path import get_buckyball_path, log_dir, rtl_dir, workloads_output_root from utils.search_workload import search_workload @@ -30,7 +30,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: chip = require_chip(input_data) bbdir = get_buckyball_path() test_type = input_data.get("test", "elf-tests") - manifest = regression_workload_toml(chip, "verilator", test_type, bbdir) + manifest = resolve_workload_toml(chip, "verilator", test_type, bbdir) except ValueError as error: await check_result(ctx, 1, extra_fields={"error": str(error)}, trace_id=origin_tid) return diff --git a/api/steps/bebop/verilator/03_build_event.step.py b/api/steps/bebop/verilator/03_build_event.step.py index ff44628..47ea936 100644 --- a/api/steps/bebop/verilator/03_build_event.step.py +++ b/api/steps/bebop/verilator/03_build_event.step.py @@ -55,7 +55,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: if diff: manifest = Path(bbdir) / "examples" / "chips" / chip / "generated" / "bebop" / "Cargo.toml" build_dir = str(manifest.parent) - features.extend(["bemu", "difftest"]) + features.append("bemu") dramsim_header = os.path.join(bbdir, "result", "include", "dramsim3.h") if not os.path.isfile(dramsim_header): ctx.logger.info(f"Nix environment is missing {dramsim_header}; rebuilding result") diff --git a/api/steps/bebop/verilator/06_batch_event.step.py b/api/steps/bebop/verilator/06_batch_event.step.py index 2610a98..fad0b33 100644 --- a/api/steps/bebop/verilator/06_batch_event.step.py +++ b/api/steps/bebop/verilator/06_batch_event.step.py @@ -28,8 +28,7 @@ ) from utils.stream_run import stream_run_logger_async from utils.event_common import check_result, get_origin_trace_id -from regression import regression_workload_toml -from regression_harness import nextest_harness_args +from utils.workload_manifest import resolve_workload_toml config = { "name": "bebop-verilator-batch", @@ -77,7 +76,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: ) return try: - workload_toml = regression_workload_toml( + workload_toml = resolve_workload_toml( chip, "verilator", test_type, bbdir, rushB=rushB, diff=diff ) except ValueError as e: @@ -112,7 +111,6 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: env.update(bebop_cargo_env(bbdir, chip)) env.update( { - "BEBOP_ARCH_CONFIG": chip, "VSRC_PATH": vsrc_dir, } ) @@ -121,14 +119,24 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: features = "verilator" if diff: manifest = f"{bbdir}/examples/chips/{chip}/generated/bebop/Cargo.toml" - features = "verilator,bemu,difftest" + features = "verilator,bemu" if input_data.get("clean-before", input_data.get("clean_before", False)): artifact_dir = os.path.join(env["CARGO_TARGET_DIR"], "test-artifacts") shutil.rmtree(artifact_dir, ignore_errors=True) ctx.logger.info(f"Cleaned previous bebop test artifacts: {artifact_dir}") - harness = nextest_harness_args(workload_toml, elf_root, env) + harness_args = [ + "--", + "--workload-toml", workload_toml, + "--bb-tests-root", elf_root, + "--arch-config", chip, + ] + if diff: + harness_args.append("--diff") + if rushB: + harness_args.extend(["--rushb-backend", "verilator"]) + harness = shlex.join(harness_args) nextest_cmd = ( f"nix develop -c cargo nextest run --release --manifest-path {shlex.quote(manifest)} " f"--features {shlex.quote(features)} --test test_verilator " diff --git a/api/steps/config/scripts/2_parameter_derivation.py b/api/steps/config/scripts/2_parameter_derivation.py index e275646..285ba09 100644 --- a/api/steps/config/scripts/2_parameter_derivation.py +++ b/api/steps/config/scripts/2_parameter_derivation.py @@ -9,8 +9,6 @@ from pathlib import Path from typing import Any -_SYSTEM_FUNCTS = frozenset({0, 1, 16, 32, 33, 34, 35}) - def _die(msg: str) -> None: raise ValueError(msg) @@ -421,7 +419,7 @@ def _bemu_balls(repo: Path, topo: dict[str, Any]) -> list[dict[str, str]]: bid = entry.get("bid") if not isinstance(funct7, int) or not isinstance(bid, int): _die(f"ballISA entry must have funct7 and bid: {entry!r}") - if funct7 in _SYSTEM_FUNCTS: + if funct7 in {0, 1, 16, 32, 33, 34, 35}: continue ball_class = bid_to_class.get(bid) if not ball_class: diff --git a/api/steps/config/scripts/3_json2proto.py b/api/steps/config/scripts/3_json2proto.py index dc394c0..bf8cafc 100644 --- a/api/steps/config/scripts/3_json2proto.py +++ b/api/steps/config/scripts/3_json2proto.py @@ -9,15 +9,14 @@ from pathlib import Path from typing import Any -_SCRIPTS = Path(__file__).resolve().parent -if str(_SCRIPTS) not in sys.path: - sys.path.insert(0, str(_SCRIPTS)) +if str(Path(__file__).resolve().parent) not in sys.path: + sys.path.insert(0, str(Path(__file__).resolve().parent)) import chip_pb2 as pb # noqa: E402 def _load_derive(): - path = _SCRIPTS / "2_parameter_derivation.py" + path = Path(__file__).resolve().with_name("2_parameter_derivation.py") spec = importlib.util.spec_from_file_location("parameter_derivation", path) if spec is None or spec.loader is None: raise RuntimeError(f"cannot load {path}") diff --git a/api/steps/config/scripts/4_install.py b/api/steps/config/scripts/4_install.py index e19cd28..ed08879 100644 --- a/api/steps/config/scripts/4_install.py +++ b/api/steps/config/scripts/4_install.py @@ -8,16 +8,11 @@ import sys from pathlib import Path -_SCRIPTS = Path(__file__).resolve().parent -if str(_SCRIPTS) not in sys.path: - sys.path.insert(0, str(_SCRIPTS)) +if str(Path(__file__).resolve().parent) not in sys.path: + sys.path.insert(0, str(Path(__file__).resolve().parent)) import chip_pb2 as pb # noqa: E402 -_ENGINE = Path("bebop/src/nodes/bemu") -_BEMU_CRATE = _ENGINE / "chip" - - def load_chip(pb_path: Path) -> pb.Chip: chip = pb.Chip() chip.ParseFromString(pb_path.read_bytes()) @@ -110,7 +105,7 @@ def install_arch(src_pb: Path, gen: Path) -> Path: def install_bemu(chip: pb.Chip, bbdir: Path, gen: Path) -> Path: - src = bbdir / _BEMU_CRATE + src = bbdir / "bebop" / "src" / "nodes" / "bemu" / "chip" cargo = src / "Cargo.toml" build_rs = src / "build.rs" if not cargo.is_file(): @@ -160,12 +155,11 @@ def install_bebop(gen: Path) -> Path: verilator = ["dep:bebop-verilator"] p2e = ["dep:bebop-p2e", "bebop-bemu?/p2e"] bemu = ["dep:bebop-bemu"] -difftest = ["bebop-bemu/difftest"] [dependencies] bebop-verilator = { path = "../../../../../bebop/src/nodes/verilator", optional = true } bebop-p2e = { path = "../../../../../bebop/src/nodes/p2e", optional = true } -bebop-bemu = { path = "../../configs/generated/bemu", optional = true } +bebop-bemu = { path = "../../configs/generated/bemu", optional = true, default-features = false } bebop-dasm = { path = "../../../../../bebop/src/nodes/lib/dasm" } bebop-bank-hash = { path = "../../../../../bebop/src/nodes/lib/bank-hash" } bebop-bemu-profile = { path = "../../../../../bebop/src/nodes/lib/bemu-profile" } diff --git a/api/steps/dc/scripts/emit_seq_mem_models.py b/api/steps/dc/scripts/emit_seq_mem_models.py index 4dc110c..2a18a4e 100644 --- a/api/steps/dc/scripts/emit_seq_mem_models.py +++ b/api/steps/dc/scripts/emit_seq_mem_models.py @@ -14,12 +14,6 @@ from pathlib import Path -LINE = re.compile( - r"name\s+(?P\S+)\s+depth\s+(?P\d+)\s+width\s+(?P\d+)" - r"\s+ports\s+(?P\S+)(?:\s+mask_gran\s+(?P\d+))?$" -) - - def write_masked(lines: list[str], *, addr: str, data: str, mask: str, width: int, gran: int) -> None: lines.extend( [ @@ -96,7 +90,11 @@ def main() -> int: line = raw.strip() if not line: continue - match = LINE.fullmatch(line) + match = re.fullmatch( + r"name\s+(?P\S+)\s+depth\s+(?P\d+)\s+width\s+(?P\d+)" + r"\s+ports\s+(?P\S+)(?:\s+mask_gran\s+(?P\d+))?", + line, + ) if match is None: raise ValueError(f"invalid mems.conf entry: {line}") values = match.groupdict() diff --git a/api/steps/ip/02_replace_event.step.py b/api/steps/ip/02_replace_event.step.py index 873ee2c..6312ec8 100644 --- a/api/steps/ip/02_replace_event.step.py +++ b/api/steps/ip/02_replace_event.step.py @@ -16,9 +16,6 @@ from utils.path import get_buckyball_path, rtl_dir from replace import replace_sources -_SOURCE_LIST = {"dc": "dc_sources.list", "yosys": "yosys_sources.list"} - - config = { "name": "ip-replace", "description": "assemble top-scoped synthesis RTL plus generated SRAM macros", @@ -33,7 +30,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: try: chip = require_chip(input_data) consumer = input_data.get("consumer") or "dc" - if consumer not in _SOURCE_LIST: + if consumer not in {"dc", "yosys"}: raise ValueError("consumer must be dc or yosys") top_module = input_data.get("top") or "DigitalTop" if not isinstance(top_module, str) or not top_module: @@ -41,7 +38,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: bbdir = Path(get_buckyball_path()).resolve() build_dir = Path(rtl_dir(bbdir, chip, "tapeout" if consumer == "dc" else "verilog", input_data.get("output_dir"))).resolve() - source_list_path = build_dir / _SOURCE_LIST[consumer] + source_list_path = build_dir / f"{consumer}_sources.list" if not source_list_path.is_file(): raise FileNotFoundError(f"missing source list: {source_list_path}") sources = [line.strip() for line in source_list_path.read_text().splitlines() if line.strip()] diff --git a/api/steps/ip/scripts/generate.py b/api/steps/ip/scripts/generate.py index 6ed0ca1..20fd4a4 100644 --- a/api/steps/ip/scripts/generate.py +++ b/api/steps/ip/scripts/generate.py @@ -6,18 +6,13 @@ from collections import Counter from pathlib import Path -_DC = Path(__file__).resolve().parents[2] / "dc" / "scripts" -if str(_DC) not in sys.path: - sys.path.insert(0, str(_DC)) +if str(Path(__file__).resolve().parents[2] / "dc" / "scripts") not in sys.path: + sys.path.insert(0, str(Path(__file__).resolve().parents[2] / "dc" / "scripts")) from tapeout import SramGeom, get_tapeout_contract from macro_compiler import run_macro_compiler from sram_compiler import generate_sram_dbs, leaf_names_from_macros -_MODULE = re.compile(r"(?m)^module\s+(\w+)\s*\(") -_INST = re.compile(r"(?m)^\s*([A-Za-z_][A-Za-z0-9_$]*)\s+[A-Za-z_][A-Za-z0-9_$]*\s*\(") - - def pad_mems_conf(text: str) -> str: lines = [] for raw in text.splitlines(): @@ -76,7 +71,7 @@ def parse_mems(text: str) -> list[tuple[str, int, int]]: def module_insts(text: str) -> dict[str, Counter[str]]: - hits = list(_MODULE.finditer(text)) + hits = list(re.finditer(r"(?m)^module\s+(\w+)\s*\(", text)) out: dict[str, Counter[str]] = {} for i, m in enumerate(hits): name = m.group(1) @@ -85,7 +80,10 @@ def module_insts(text: str) -> dict[str, Counter[str]]: start = m.end() end = hits[i + 1].start() if i + 1 < len(hits) else len(text) counts: Counter[str] = Counter() - for inst in _INST.finditer(text[start:end]): + for inst in re.finditer( + r"(?m)^\s*([A-Za-z_][A-Za-z0-9_$]*)\s+[A-Za-z_][A-Za-z0-9_$]*\s*\(", + text[start:end], + ): leaf = inst.group(1) if leaf in ("module", "assign", "wire", "input", "output"): raise ValueError(f"module {name}: refused instance parse {leaf!r}") diff --git a/api/steps/ip/scripts/replace.py b/api/steps/ip/scripts/replace.py index 4440051..264a6a2 100644 --- a/api/steps/ip/scripts/replace.py +++ b/api/steps/ip/scripts/replace.py @@ -4,18 +4,6 @@ import re from pathlib import Path -MODULE_NAME_RE = re.compile( - r"^\s*(?:\(\*.*?\*\)\s*)*module\s+([A-Za-z_][A-Za-z0-9_$]*)\b", - re.M, -) -INSTANCE_RE = re.compile( - r"^\s*([A-Za-z_][A-Za-z0-9_$]*)\s*(?:#\s*\(.*?\)\s*)?([A-Za-z_][A-Za-z0-9_$]*)\s*\(", - re.M | re.S, -) -COMMENT_RE = re.compile(r"/\*.*?\*/|//.*?$", re.M | re.S) -MODULE_TOKEN_RE = re.compile(r"\bmodule\b") - - def replace_sources( *, source_paths: list[str], @@ -32,10 +20,14 @@ def replace_sources( texts: dict[str, str] = {} for path in paths: text = path.read_text() - match = MODULE_NAME_RE.search(text) + match = re.search( + r"^\s*(?:\(\*.*?\*\)\s*)*module\s+([A-Za-z_][A-Za-z0-9_$]*)\b", + text, + re.M, + ) if match is None: - stripped = COMMENT_RE.sub("", text) - if MODULE_TOKEN_RE.search(stripped): + stripped = re.sub(r"/\*.*?\*/|//.*?$", "", text, flags=re.M | re.S) + if re.search(r"\bmodule\b", stripped): raise RuntimeError(f"no module declaration: {path}") continue name = match.group(1) @@ -53,7 +45,11 @@ def replace_sources( pending = [top_module] while pending: cur = pending.pop() - for child, _inst in INSTANCE_RE.findall(texts[cur]): + for child, _inst in re.findall( + r"^\s*([A-Za-z_][A-Za-z0-9_$]*)\s*(?:#\s*\(.*?\)\s*)?([A-Za-z_][A-Za-z0-9_$]*)\s*\(", + texts[cur], + re.M | re.S, + ): if child in modules and child not in reachable: reachable.add(child) pending.append(child) diff --git a/api/steps/ip/scripts/sram_compiler.py b/api/steps/ip/scripts/sram_compiler.py index b5adda9..44795be 100644 --- a/api/steps/ip/scripts/sram_compiler.py +++ b/api/steps/ip/scripts/sram_compiler.py @@ -4,12 +4,12 @@ import sys from pathlib import Path -_HERE = Path(__file__).resolve().parent -_API = _HERE.parents[2] -_IP = _HERE.parents[4] / "thirdparty" / "soc-framework" / "ip" -for _p in (str(_API), str(_IP)): - if _p not in sys.path: - sys.path.insert(0, _p) +for path in ( + Path(__file__).resolve().parents[2], + Path(__file__).resolve().parents[4] / "thirdparty" / "soc-framework" / "ip", +): + if str(path) not in sys.path: + sys.path.insert(0, str(path)) from smic180.compiler import generate_smic180_sram_dbs diff --git a/api/steps/kernel/01_build_event.step.py b/api/steps/kernel/01_build_event.step.py index 25a2911..e31caf3 100644 --- a/api/steps/kernel/01_build_event.step.py +++ b/api/steps/kernel/01_build_event.step.py @@ -18,19 +18,6 @@ sys.path.insert(0, scripts_path) from bin_to_hex import bin_to_hex -KERNEL_MODELS = { - "bert", - "deepseekr1", - "gemma4", - "lenet", - "llama2", - "mobilenet", - "qwen3", - "resnet", - "stable-diffusion", - "yolo", -} - config = { "name": "kernel-build", "description": "build RISC-V kernel + rootfs for image via bb-tests/workloads/lib/kernel", @@ -87,8 +74,20 @@ def kernel_model(input_data: dict) -> str: raise ValueError("model must be a string") model = model.lower() - if model not in KERNEL_MODELS: - valid = ", ".join(sorted(KERNEL_MODELS)) + supported = { + "bert", + "deepseekr1", + "gemma4", + "lenet", + "llama2", + "mobilenet", + "qwen3", + "resnet", + "stable-diffusion", + "yolo", + } + if model not in supported: + valid = ", ".join(sorted(supported)) raise ValueError(f"unknown kernel model: {model}; valid models: {valid}") return model diff --git a/api/steps/pt/scripts/power.py b/api/steps/pt/scripts/power.py index 4884923..fcc3251 100644 --- a/api/steps/pt/scripts/power.py +++ b/api/steps/pt/scripts/power.py @@ -6,15 +6,12 @@ from pathlib import Path -_POWER_VALUE = re.compile(r"([-+]?\d+(?:\.\d*)?(?:[eE][-+]?\d+)?)\s*([munp]?W)\b") -_UNIT_TO_WATTS = {"W": 1.0, "mW": 1e-3, "uW": 1e-6, "nW": 1e-9, "pW": 1e-12} - - def _watts(value: str) -> float: - match = _POWER_VALUE.fullmatch(value) + match = re.fullmatch(r"([-+]?\d+(?:\.\d*)?(?:[eE][-+]?\d+)?)\s*([munp]?W)\b", value) if match is None: raise ValueError(f"unsupported power value: {value}") - return float(match.group(1)) * _UNIT_TO_WATTS[match.group(2)] + scale = {"W": 1.0, "mW": 1e-3, "uW": 1e-6, "nW": 1e-9, "pW": 1e-12} + return float(match.group(1)) * scale[match.group(2)] def read_dynamic_power(report: str | Path) -> dict[str, str] | None: diff --git a/api/steps/regression/00_buildbitstream_event.step.py b/api/steps/regression/00_buildbitstream_event.step.py index a93f0e1..b314f03 100644 --- a/api/steps/regression/00_buildbitstream_event.step.py +++ b/api/steps/regression/00_buildbitstream_event.step.py @@ -51,6 +51,6 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: } output_dir = input_data.get("output_dir") or input_data.get("output-dir") if output_dir: - data["build_dir"] = output_dir + data["output_dir"] = output_dir await ctx.enqueue({"topic": "bebop.p2e.verilog", "data": data}) diff --git a/api/steps/regression/02_eval_performance_event.step.py b/api/steps/regression/02_eval_performance_event.step.py index fd35f26..02f16ad 100644 --- a/api/steps/regression/02_eval_performance_event.step.py +++ b/api/steps/regression/02_eval_performance_event.step.py @@ -29,6 +29,7 @@ from utils.path import bebop_cargo_env, get_buckyball_path, log_dir from utils.stream_run import stream_run_logger_async from utils.event_common import check_result, get_origin_trace_id +from utils.model import model_layout_name sys.path.insert(0, os.path.join(get_buckyball_path(), "bb-tests", "workloads", "scripts")) import build as workload_build # noqa: E402 @@ -62,17 +63,9 @@ def _load_sibling(rel): return mod -_workload = _load_sibling("../workload/01_build_event.step.py") _kernel = _load_sibling("../kernel/01_build_event.step.py") _p2e = _load_sibling("../bebop/p2e/03_runworkload_event.step.py") -MODEL_LAYOUT = _workload.MODEL_LAYOUT -PERFETTO_SCRIPT = ( - Path(get_buckyball_path()) - / "bb-tests" / "workloads" / "src" / "ModelTest" - / "e2e" / "framework" / "trace" / "perfetto.py" -) - def _fail(ctx, origin_tid, error, **extra): fields = {"task": "regression.eval-performance", "error": error} @@ -82,8 +75,7 @@ def _fail(ctx, origin_tid, error, **extra): def _workload_build(bbdir, chip, model, logger, task_scope): model_key = model.lower() - if MODEL_LAYOUT.get(model_key) is None: - raise ValueError(f"Unknown model: {model}") + model_layout_name(model_key) workload_build.build_workload( bbdir, chip, model=model_key, logger=logger, task_scope=task_scope ) @@ -165,7 +157,7 @@ def _perfetto_cmd(trace_dir, trace_toml, mlir_files): for mlir in mlir_files: args += ["--mlir", str(mlir)] return ( - f"{sys.executable} {shlex.quote(str(PERFETTO_SCRIPT))} " + f"{sys.executable} {shlex.quote(str(Path(get_buckyball_path()) / 'bb-tests/workloads/src/ModelTest/e2e/framework/trace/perfetto.py'))} " + " ".join(shlex.quote(a) for a in args) ) @@ -209,7 +201,9 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: model_results = [] for model in models: model_key = model.lower() - if MODEL_LAYOUT.get(model_key) is None: + try: + model_layout_name(model_key) + except ValueError: ctx.logger.error(f"Unknown model: {model}") await _fail(ctx, origin_tid, "unknown_model", model=model) return diff --git a/api/steps/regression/scripts/accuracy.py b/api/steps/regression/scripts/accuracy.py index f19cdee..601ad77 100644 --- a/api/steps/regression/scripts/accuracy.py +++ b/api/steps/regression/scripts/accuracy.py @@ -9,10 +9,6 @@ import re from typing import Any -_TOP1 = re.compile(r"^top1=(\d+)/(\d+)\s*$", re.M) -_MAP = re.compile(r"^map=([0-9]*\.?[0-9]+)\s*$", re.M) - - def accuracy_from_uart(metric: str, uart: str) -> float: if not isinstance(metric, str) or not metric: raise ValueError("accuracy metric must be a non-empty string") @@ -27,7 +23,7 @@ def accuracy_from_uart(metric: str, uart: str) -> float: def _top1(uart: str) -> float: - hits = _TOP1.findall(uart) + hits = re.findall(r"^top1=(\d+)/(\d+)\s*$", uart, re.M) if not hits: raise ValueError("uart missing top1=/ line") correct_s, total_s = hits[-1] @@ -41,7 +37,7 @@ def _top1(uart: str) -> float: def _map(uart: str) -> float: - hits = _MAP.findall(uart) + hits = re.findall(r"^map=([0-9]*\.?[0-9]+)\s*$", uart, re.M) if not hits: raise ValueError("uart missing map= line") val = float(hits[-1]) diff --git a/api/steps/regression/scripts/dc_area.py b/api/steps/regression/scripts/dc_area.py index b49b7f9..64c07d1 100644 --- a/api/steps/regression/scripts/dc_area.py +++ b/api/steps/regression/scripts/dc_area.py @@ -1,10 +1,7 @@ import re -_AREA = re.compile(r"Total cell area:\s+([\d.]+)") - - def area_mm2_from_rpt(text: str) -> float: - m = _AREA.search(text) + m = re.search(r"Total cell area:\s+([\d.]+)", text) if not m: raise ValueError("Total cell area not found") return float(m.group(1)) / 1e6 diff --git a/api/steps/regression/scripts/model_layout.py b/api/steps/regression/scripts/model_layout.py index e8fe084..b4d2c49 100644 --- a/api/steps/regression/scripts/model_layout.py +++ b/api/steps/regression/scripts/model_layout.py @@ -7,52 +7,7 @@ """ import os from pathlib import Path - -MODEL_LAYOUT = { - "lenet": "LeNet", - "mobilenet": "MobileNetV3", - "resnet": "ResNet18", - "yolo": "YOLO26", - "bert": "Bert", - "qwen3": "Qwen3", - "gemma4": "Gemma4", - "deepseekr1": "DeepSeekR1", - "llama2": "llama2", - "stable-diffusion": "StableDiffusion", - "whisper": "Whisper", - "buddynext": "BuddyNext", -} - -MODEL_PERFETTO = { - "lenet": { - "trace_toml": "bb-tests/workloads/src/ModelTest/e2e/models/models/LeNet/trace/trace-nodes.toml", - "mlir_files": [ - "bb-tests/output/{chip}/workloads/src/ModelTest/e2e/models/archs/buckyball/{chip}/LeNet/subgraph0_linalg.mlir", - "bb-tests/output/{chip}/workloads/src/ModelTest/e2e/models/archs/buckyball/{chip}/LeNet/subgraph0_buckyball.mlir", - ], - }, - "mobilenet": { - "trace_toml": "bb-tests/workloads/src/ModelTest/e2e/models/models/MobileNetV3/trace/trace.toml", - "mlir_files": [ - "bb-tests/output/{chip}/workloads/src/ModelTest/e2e/models/archs/buckyball/{chip}/MobileNetV3/subgraph0_linalg.mlir", - "bb-tests/output/{chip}/workloads/src/ModelTest/e2e/models/archs/buckyball/{chip}/MobileNetV3/subgraph0_buckyball.mlir", - ], - }, - "resnet": { - "trace_toml": "bb-tests/workloads/src/ModelTest/e2e/models/models/ResNet18/trace/trace.toml", - "mlir_files": [ - "bb-tests/output/{chip}/workloads/src/ModelTest/e2e/models/archs/buckyball/{chip}/ResNet18/subgraph0_linalg.mlir", - "bb-tests/output/{chip}/workloads/src/ModelTest/e2e/models/archs/buckyball/{chip}/ResNet18/subgraph0_buckyball.mlir", - ], - }, - "yolo": { - "trace_toml": "bb-tests/workloads/src/ModelTest/e2e/models/models/YOLO26/trace/trace.toml", - "mlir_files": [ - "bb-tests/output/{chip}/workloads/src/ModelTest/e2e/models/archs/buckyball/{chip}/YOLO26/subgraph0_linalg.mlir", - "bb-tests/output/{chip}/workloads/src/ModelTest/e2e/models/archs/buckyball/{chip}/YOLO26/subgraph0_buckyball.mlir", - ], - }, -} +from utils.model import model_layout_name def _archs_root(bbdir: str, chip: str) -> Path: @@ -72,10 +27,7 @@ def _archs_root(bbdir: str, chip: str) -> Path: def layout_name(model: str) -> str: - layout = MODEL_LAYOUT.get(model.lower()) - if layout is None: - raise ValueError(f"missing layout mapping for model: {model}") - return layout + return model_layout_name(model) def chip_output_dir(bbdir: str, chip: str, model: str) -> Path: @@ -108,15 +60,23 @@ def bridge_model_layout(bbdir: str, chip: str, model: str) -> Path: def perfetto_inputs(bbdir: str, chip: str, model: str) -> dict: - spec = MODEL_PERFETTO.get(model.lower()) - if spec is None: + if model.lower() not in {"lenet", "mobilenet", "resnet", "yolo"}: raise KeyError(f"no perfetto spec for model: {model}") - trace_toml = Path(bbdir) / spec["trace_toml"] + layout = layout_name(model) + trace_name = "trace-nodes.toml" if model.lower() == "lenet" else "trace.toml" + trace_toml = ( + Path(bbdir) + / "bb-tests/workloads/src/ModelTest/e2e/models/models" + / layout + / "trace" + / trace_name + ) if not trace_toml.is_file(): raise FileNotFoundError(f"perfetto trace toml missing: {trace_toml}") + output = _archs_root(bbdir, chip) / chip / layout mlir_files = [] - for rel in spec["mlir_files"]: - path = Path(bbdir) / rel.replace("{chip}", chip) + for name in ("subgraph0_linalg.mlir", "subgraph0_buckyball.mlir"): + path = output / name if not path.is_file(): raise FileNotFoundError(f"perfetto mlir file missing: {path}") mlir_files.append(path) diff --git a/api/steps/regression/scripts/result.py b/api/steps/regression/scripts/result.py index b09f0c6..954e44c 100644 --- a/api/steps/regression/scripts/result.py +++ b/api/steps/regression/scripts/result.py @@ -1,17 +1,9 @@ import json -import os from pathlib import Path def result_path(bbdir: str) -> Path: - rel = os.environ.get("EVAL_RESULT_PATH", "chipcrowd-eval-result.json") - root = Path(bbdir).resolve() - target = (root / rel).resolve() - try: - target.relative_to(root) - except ValueError as e: - raise ValueError(f"EVAL_RESULT_PATH escapes repo: {rel}") from e - return target + return Path(bbdir).resolve() / "chipcrowd-eval-result.json" def merge_metrics(bbdir: str, **fields) -> dict: diff --git a/api/steps/uvm/scripts/waive.py b/api/steps/uvm/scripts/waive.py index d64ebe1..3450ec5 100644 --- a/api/steps/uvm/scripts/waive.py +++ b/api/steps/uvm/scripts/waive.py @@ -2,11 +2,6 @@ from pathlib import Path -WAIVE_LINE_PATTERNS = ( - re.compile(r"^\s*(?:wire|reg|logic)\b.*\b_(?:GEN|T)(?:_\d+)?\b"), -) - - def apply_waivers(rtl_dir: Path) -> int: if not rtl_dir.is_dir(): raise ValueError(f"missing generated RTL directory: {rtl_dir}") @@ -15,7 +10,7 @@ def apply_waivers(rtl_dir: Path) -> int: lines = path.read_text().splitlines(keepends=True) output = [] for line in lines: - if any(pattern.search(line) for pattern in WAIVE_LINE_PATTERNS) and ( + if re.search(r"^\s*(?:wire|reg|logic)\b.*\b_(?:GEN|T)(?:_\d+)?\b", line) and ( not output or output[-1] != "//VCS coverage off\n" ): output.extend(("//VCS coverage off\n", line, "//VCS coverage on\n")) diff --git a/api/steps/vcs/03_build_event.step.py b/api/steps/vcs/03_build_event.step.py index 4a04953..f1265cf 100644 --- a/api/steps/vcs/03_build_event.step.py +++ b/api/steps/vcs/03_build_event.step.py @@ -27,7 +27,8 @@ } -TESTBENCH = r'''`timescale 1ns/1ps +def testbench_source() -> str: + return r'''`timescale 1ns/1ps module BBSimVcsHarness; reg clock = 1'b0; reg reset = 1'b1; @@ -63,7 +64,8 @@ # a SystemVerilog testbench, so it needs only the memory model plus these DPI # callbacks. Trace callbacks are intentionally no-ops; functional execution, # UART, and SCU exit remain available to every chip using BBSimHarness. -VCS_DPI = r'''#include +def dpi_source() -> str: + return r'''#include #include #include @@ -154,8 +156,8 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: artifact_dir.mkdir(parents=True) testbench = artifact_dir / "BBSimVcsHarness.sv" dpi_shim = artifact_dir / "vcs_dpi.cc" - testbench.write_text(TESTBENCH, encoding="utf-8") - dpi_shim.write_text(VCS_DPI, encoding="utf-8") + testbench.write_text(testbench_source(), encoding="utf-8") + dpi_shim.write_text(dpi_source(), encoding="utf-8") arch_dir = Path(bbdir) / "arch" csrcs = [ diff --git a/api/steps/verilator/03_build_event.step.py b/api/steps/verilator/03_build_event.step.py index 0d634cc..8b6a5bb 100644 --- a/api/steps/verilator/03_build_event.step.py +++ b/api/steps/verilator/03_build_event.step.py @@ -104,9 +104,9 @@ def _gcc_lib_dir(soname: str) -> str: sources = " ".join(vsrcs + csrcs) jobs = input_data.get("jobs", "") - # Enable ccache if available + build_env = os.environ.copy() if subprocess.run("command -v ccache", shell=True, capture_output=True).returncode == 0: - os.environ["OBJCACHE"] = "ccache" + build_env["OBJCACHE"] = "ccache" # Use lld for faster linking if available use_lld = subprocess.run("command -v ld.lld", shell=True, capture_output=True).returncode == 0 @@ -139,6 +139,7 @@ def _gcc_lib_dir(soname: str) -> str: cwd=bbdir, stdout_prefix="verilator verilation", stderr_prefix="verilator verilation", + env=build_env, ) if result.returncode != 0: await check_result( @@ -154,6 +155,7 @@ def _gcc_lib_dir(soname: str) -> str: cwd=bbdir, stdout_prefix="verilator build", stderr_prefix="verilator build", + env=build_env, ) # ================================================================================== diff --git a/api/steps/verilator/04_sim_event.step.py b/api/steps/verilator/04_sim_event.step.py index ba88a00..f47bf3e 100644 --- a/api/steps/verilator/04_sim_event.step.py +++ b/api/steps/verilator/04_sim_event.step.py @@ -27,10 +27,6 @@ } -DRAM_BASE = 0x80000000 -DRAM_SIZE = 0x10000000 - - def validate_bbsim_elf(path: str) -> tuple[bool, str]: """Validate the ELF layout accepted by BBSimDRAM before starting Verilator.""" try: @@ -53,6 +49,8 @@ def validate_bbsim_elf(path: str) -> tuple[bool, str]: if phoff + phnum * phentsize > file_size: return False, "program headers extend past the end of the file" + dram_base = 0x80000000 + dram_size = 0x10000000 load_segments = 0 for index in range(phnum): binary.seek(phoff + index * phentsize) @@ -67,11 +65,11 @@ def validate_bbsim_elf(path: str) -> tuple[bool, str]: load_segments += 1 if p_filesz > p_memsz or p_offset + p_filesz > file_size: return False, "has an invalid loadable segment" - if p_paddr < DRAM_BASE or p_paddr + p_memsz > DRAM_BASE + DRAM_SIZE: + if p_paddr < dram_base or p_paddr + p_memsz > dram_base + dram_size: return ( False, f"load segment {index} at 0x{p_paddr:x} is outside " - f"BBSimDRAM [0x{DRAM_BASE:x}, 0x{DRAM_BASE + DRAM_SIZE:x})", + f"BBSimDRAM [0x{dram_base:x}, 0x{dram_base + dram_size:x})", ) except (OSError, struct.error): return False, "cannot read a valid ELF64 program-header table" diff --git a/api/steps/workload/01_build_event.step.py b/api/steps/workload/01_build_event.step.py index d9d8d47..bda8d8f 100644 --- a/api/steps/workload/01_build_event.step.py +++ b/api/steps/workload/01_build_event.step.py @@ -13,6 +13,7 @@ from utils.path import get_buckyball_path from utils.event_common import check_result, get_origin_trace_id +from utils.model import model_layout_name _workload_build_path = os.path.join( get_buckyball_path(), "bb-tests", "workloads", "scripts", "build.py" @@ -31,27 +32,11 @@ "enqueues": [], } -# CLI model → layout dir under archs/buckyball// -MODEL_LAYOUT = { - "lenet": "LeNet", - "mobilenet": "MobileNetV3", - "resnet": "ResNet18", - "yolo": "YOLO26", - "bert": "Bert", - "qwen3": "Qwen3", - "gemma4": "Gemma4", - "deepseekr1": "DeepSeekR1", - "llama2": "llama2", - "stable-diffusion": "StableDiffusion", - "whisper": "Whisper", - "buddynext": "BuddyNext", -} - - def chips_for_model(bbdir: str, model_key: str) -> set[str]: """Chips that currently ship a layout for this model (many-to-many).""" - layout = MODEL_LAYOUT.get(model_key) - if layout is None: + try: + layout = model_layout_name(model_key) + except ValueError: return set() root = ( Path(bbdir) @@ -179,8 +164,9 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: if model: model_key = model.lower() - layout = MODEL_LAYOUT.get(model_key) - if layout is None: + try: + layout = model_layout_name(model_key) + except ValueError: ctx.logger.error(f"Unknown model: {model}") await check_result( ctx, 1, continue_run=False, diff --git a/api/steps/yosys/03_synth_event.step.py b/api/steps/yosys/03_synth_event.step.py index 3c297bb..0a0a493 100644 --- a/api/steps/yosys/03_synth_event.step.py +++ b/api/steps/yosys/03_synth_event.step.py @@ -24,15 +24,13 @@ } -POWER_TOTAL_RE = re.compile( - r"^Total\s+([0-9.eE+-]+)\s+([0-9.eE+-]+)\s+([0-9.eE+-]+)\s+([0-9.eE+-]+)", - re.M, -) - - def dynamic_power_report(power_report: str, activity_source: str) -> tuple[str, float] | None: """Return OpenSTA's internal-plus-switching power, excluding leakage.""" - match = POWER_TOTAL_RE.search(power_report) + match = re.search( + r"^Total\s+([0-9.eE+-]+)\s+([0-9.eE+-]+)\s+([0-9.eE+-]+)\s+([0-9.eE+-]+)", + power_report, + re.M, + ) if match is None: return None internal, switching, _leakage, _total = (float(value) for value in match.groups()) @@ -77,7 +75,7 @@ async def handler(input_data: dict, ctx: FlowContext) -> None: yosys_cfg = load_yosys_config() top_module = input_data.get("top") or yosys_cfg.get("top") or "DigitalTop" - liberty = yosys_cfg.get("liberty") or os.environ.get("YOSYS_LIBERTY") + liberty = yosys_cfg.get("liberty") if isinstance(liberty, str): liberty = os.path.expandvars(os.path.expanduser(liberty)) diff --git a/api/utils/event_common.py b/api/utils/event_common.py index d2c6eca..b757439 100644 --- a/api/utils/event_common.py +++ b/api/utils/event_common.py @@ -6,9 +6,6 @@ from utils.process_registry import set_current_task_scope -_CHIP_RE = re.compile(r"^[A-Za-z0-9_-]+$") - - def require_chip(data: dict) -> str: chip = data.get("chip") if not isinstance(chip, str) or not chip: @@ -18,7 +15,7 @@ def require_chip(data: dict) -> str: raise ValueError( "do not pass --config; mill class comes from chip config sims" ) - if not _CHIP_RE.fullmatch(chip): + if not re.fullmatch(r"[A-Za-z0-9_-]+", chip): raise ValueError(f"invalid chip: {chip}") return chip diff --git a/api/utils/model.py b/api/utils/model.py new file mode 100644 index 0000000..ab2bf26 --- /dev/null +++ b/api/utils/model.py @@ -0,0 +1,19 @@ +def model_layout_name(model: str) -> str: + aliases = { + "lenet": "LeNet", + "mobilenet": "MobileNetV3", + "resnet": "ResNet18", + "yolo": "YOLO26", + "bert": "Bert", + "qwen3": "Qwen3", + "gemma4": "Gemma4", + "deepseekr1": "DeepSeekR1", + "llama2": "llama2", + "stable-diffusion": "StableDiffusion", + "whisper": "Whisper", + "buddynext": "BuddyNext", + } + try: + return aliases[model.lower()] + except KeyError as error: + raise ValueError(f"unknown model: {model}") from error diff --git a/api/utils/path.py b/api/utils/path.py index e55646a..6d32ed3 100644 --- a/api/utils/path.py +++ b/api/utils/path.py @@ -7,25 +7,9 @@ def get_buckyball_path(): current_dir = os.path.dirname(__file__) - # bbdev/api/utils -> bbdev/api -> bbdev -> buckyball - inferred = os.path.realpath( + root = os.path.realpath( os.path.dirname(os.path.dirname(os.path.dirname(current_dir))) ) - - root = os.environ.get("BUCKYBALL_ROOT") - if root: - if not os.path.isabs(root): - raise ValueError("BUCKYBALL_ROOT must be an absolute path") - if not os.path.isdir(root): - raise ValueError(f"BUCKYBALL_ROOT does not exist: {root}") - root = os.path.realpath(root) - if root != inferred: - raise ValueError( - f"BUCKYBALL_ROOT={root} does not match bbdev tree root {inferred}" - ) - else: - root = inferred - home = os.path.realpath(str(Path.home())) if not Path(root).is_relative_to(home): raise ValueError(f"buckyball root must be under $HOME ({home}): {root}") diff --git a/api/utils/port.py b/api/utils/port.py index 431e9b9..e450ba4 100644 --- a/api/utils/port.py +++ b/api/utils/port.py @@ -1,14 +1,11 @@ import socket -HOST = "127.0.0.1" - - def find_available_port(start_port: int = 5000, end_port: int = 5500) -> int: """Find an available port in the specified range""" for port in range(start_port, end_port + 1): try: with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: - sock.bind((HOST, port)) + sock.bind(("127.0.0.1", port)) return port except OSError: continue @@ -43,7 +40,7 @@ def reserve_port( for port in _port_order(start_port, end_port, preferred_port): sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) try: - sock.bind((HOST, port)) + sock.bind(("127.0.0.1", port)) return port, sock except OSError: sock.close() diff --git a/api/utils/workers.py b/api/utils/workers.py index e7542df..2262430 100644 --- a/api/utils/workers.py +++ b/api/utils/workers.py @@ -3,13 +3,8 @@ import subprocess from pathlib import Path -from utils.port import HOST - -SERVER_PORTS_FILE = "bbdev-server.json" - - def server_ports_path(workflow_dir: str) -> str: - return os.path.join(workflow_dir, "data", SERVER_PORTS_FILE) + return os.path.join(workflow_dir, "data", "bbdev-server.json") def write_server_ports(workflow_dir: str, http_port: int, worker_port: int, bb_root: str) -> None: @@ -49,7 +44,7 @@ def list_engine_workers(worker_port: int, timeout_s: float = 10.0) -> list: "--function-id", "engine::workers::list", "--address", - HOST, + "127.0.0.1", "--port", str(worker_port), "--timeout-ms", diff --git a/api/steps/bebop/regression.py b/api/utils/workload_manifest.py similarity index 94% rename from api/steps/bebop/regression.py rename to api/utils/workload_manifest.py index 89108cf..89d68d7 100644 --- a/api/steps/bebop/regression.py +++ b/api/utils/workload_manifest.py @@ -1,7 +1,7 @@ import os from pathlib import Path -from utils.path import get_buckyball_path +from .path import get_buckyball_path def validate_chip(chip: str | None) -> str: @@ -20,7 +20,7 @@ def chip_regression_dir(chip: str, backend: str, bbdir: str | None = None) -> Pa return root / "examples" / "chips" / chip / "regression" / "batch" / backend -def regression_workload_toml( +def resolve_workload_toml( chip: str, backend: str, test_type: str, diff --git a/mcp/__main__.py b/mcp/__main__.py index 9b9d78d..5b50374 100644 --- a/mcp/__main__.py +++ b/mcp/__main__.py @@ -6,9 +6,8 @@ import sys from pathlib import Path -ROOT = Path(__file__).resolve().parent -if str(ROOT) not in sys.path: - sys.path.insert(0, str(ROOT)) +if str(Path(__file__).resolve().parent) not in sys.path: + sys.path.insert(0, str(Path(__file__).resolve().parent)) from server import main diff --git a/mcp/common.py b/mcp/common.py index d9938bd..15d12e4 100644 --- a/mcp/common.py +++ b/mcp/common.py @@ -17,12 +17,28 @@ from typing import Any, Dict, Optional # bbdev/mcp -> repo root -REPO = Path(__file__).resolve().parents[2] -BBDEV = REPO / "bbdev" / "bbdev" -API = REPO / "bbdev" / "api" -MOTIA = API / ".venv" / "bin" / "motia" -LOG = REPO / "bbdev" / "server.log" -STATE_DIR = API / "data" / "state_store.db" +def repo_path() -> Path: + return Path(__file__).resolve().parents[2] + + +def bbdev_path() -> Path: + return repo_path() / "bbdev" / "bbdev" + + +def api_path() -> Path: + return repo_path() / "bbdev" / "api" + + +def motia_path() -> Path: + return api_path() / ".venv" / "bin" / "motia" + + +def log_path() -> Path: + return repo_path() / "bbdev" / "server.log" + + +def state_dir() -> Path: + return api_path() / "data" / "state_store.db" _proc: Optional[subprocess.Popen] = None _port: Optional[int] = None _log_fh = None diff --git a/mcp/tools/__init__.py b/mcp/tools/__init__.py index bb31b3f..70ff219 100644 --- a/mcp/tools/__init__.py +++ b/mcp/tools/__init__.py @@ -48,7 +48,8 @@ from . import regression_eval_performance from . import regression_eval_area_power -MODULES = [ +def modules(): + return [ validate, task_status, task_cancel, @@ -94,9 +95,9 @@ regression_check, regression_eval_performance, regression_eval_area_power, -] + ] def register_all(mcp) -> None: - for mod in MODULES: + for mod in modules(): mod.register(mcp)