feat: refactor crypto test paths and add new tests for firmware validation

- Updated test paths in  to point to the new directory structure for crypto files.
- Introduced  to validate firmware identity and reject stale firmware based on source changes and simulation logs.
- Enhanced  to include additional memory status and control registers, ensuring alignment with RTL definitions.
This commit is contained in:
maksyuki committed 2026-09-26 14:00:25 +08:00
1 parent b72e14ea40
commit 3c63fec8dc
60 files changed
+8976 -1390

No files matched your search

+29 -3
View File
@@ -163,7 +163,8 @@ without modifying them. Run `make rtl-migrate-connections`, then
ports and interface fields are intentionally unchanged. Run
`make rtl-migrate-names`, then format and lint the resulting diff.
`crypto_constants.py` packs the frozen CRYC1 image from the V1 tables;
`crypto_constants.py` packs the frozen CRYC1 image from
`data/crypto_constants.json` and emits an 848-byte compact firmware header;
`crypto_p0.py` independently checks it against the mathematical oracle in
`tests/crypto_reference.py`. The P0 runner also executes existing V1 tests,
full-width RSA-2048 public/private/recheck vectors, SHA padding boundaries and
@@ -174,8 +175,8 @@ that exits with code zero is still a failed test.
The `crypto-p0-constants`, `crypto-p0-rtl`, `crypto-p0-baseline` and
`crypto-p0-report` Make targets write below `build/<variant>/crypto/p0/`.
Select `CONFIG=configs/ci/ihp130.mk SYNTH=YOSYS` and one BUILD_TIMESTAMP for
all four. P0 preserves production RTL/HAL and records V1 lifecycle limitations;
it does not implement the six-bank V2 refreeze. Block synthesis calls the
all four on the frozen V1 checkout. The runner rejects V2 before writing any
P0 artifact. P0 records V1 lifecycle limitations. Block synthesis calls the
unchanged `synth.tcl` and `abc_balanced.script`, adding JSON checkpoints and
standalone interface elaboration only. Each new synthesis attempt uses a fresh
`synth-*` artifact directory recorded in the report, preventing an old netlist
@@ -184,6 +185,31 @@ the PCLK inventory; it is not a full-chip timing result. A timeout/interruption
retains partial checkpoints and cannot pass the aggregate report. See
[`../docs/ip/crypto.md`](../docs/ip/crypto.md).
`crypto_p1.py` owns the separate `crypto-p1-constants`, `crypto-p1-rtl`,
`crypto-p1-formal`, `crypto-p1-synth` and `crypto-p1-report` targets. P1 artifacts are below
`build/<variant>/crypto/p1/`; P0 artifacts are never reused as candidate
outputs. The RTL target exercises public APB initialization, AES/SHA and full
RSA-2048 on the IHP FUNCTIONAL model, actual DMA channels 4/5, storage masks
and Montgomery carries, then the explicit synchronous fallback. Formal
checks use running-clock/synchronous-response assumptions and verify control
invariants, private-result release, bounded scrub, APB access ownership and
AES output retention across abort/fatal faults. DMA fixtures also inject AXI
read and write errors. The exact proof/test mapping is in
[`../docs/ip/crypto-verification.md`](../docs/ip/crypto-verification.md).
They do not prove the cryptographic arithmetic or physical resistance.
The RTL target also runs `crypto_response_fault_tb.sv`, which models delayed,
duplicate, stale-epoch and missing SRAM responses; the product store defaults
to fixed one-cycle responses.
Candidate synthesis preserves the P0 SAT/ABC recipe and 10800-second limit.
The report requires `CRYPTO_P0_BASELINE_ROOT`, `CRYPTO_P1_LP_ROOT` and a
successful `CRYPTO_P1_CI_ROOT` for its focused gates. Capture full-SoC evidence
with `crypto_p1.py firmware --variant-root <variant> --firmware-kind lp|ci`
after creating the corresponding committed-profile manifest. This runs the
build/simulation and binds the actual configuration, input snapshot, ELF and
log/result hashes. `crypto_p1.py quality --variant-root <variant>` records
focused formatter and policy results. Report assembly rejects stale inputs
or artifacts; it cannot qualify an earlier binary using current source hashes.
Update or add tests in [`../tests`](../tests) for script behavior. Run
`ruff check .` and `python3 -m pytest -q`; build-flow changes also require the
relevant dry-run or regression profile from [`AGENTS.md`](../AGENTS.md).
+23 -36
View File
@@ -1,5 +1,5 @@
#!/usr/bin/env python3
"""Pack the frozen CRYC1 image from the revision-qualified V1 RTL tables."""
"""Pack CRYC1 and its compact firmware representation from public constants."""
from __future__ import annotations
@@ -7,7 +7,6 @@ import argparse
import hashlib
import json
from pathlib import Path
import re
import struct
import zlib
@@ -18,45 +17,32 @@ CRC32 = 0x99CA52FE
SHA256 = "b662a0729bf85789aa9c9bd4a9f8a13ebe44e296deebda486bef2792b1e9d47f"
def lookup(source: str, name: str, size: int, base: int) -> list[int]:
match = re.search(r"function automatic[^\n]*\b" + name + r"\(.*?endfunction", source, re.S)
if match is None:
raise ValueError(f"missing {name}")
body = match.group()
entries: dict[int, int] = {}
for key, value in re.findall(
r"(?:8'h|6'd)([0-9a-f]+):\s*" + name + r"\s*=\s*(?:8|32)'h([0-9a-f]+)", body
):
index = int(key, base)
if index in entries:
raise ValueError(f"duplicate {name}[{index}]")
entries[index] = int(value, 16)
default = re.search(r"default:\s*" + name + r"\s*=\s*(?:8|32)'h([0-9a-f]+)", body)
if default is None or set(entries) != set(range(size - 1)):
raise ValueError(f"incomplete {name}")
return [entries[i] for i in range(size - 1)] + [int(default[1], 16)]
def pack(root: Path) -> bytes:
package = (root / "rtl/ip/security/crypto_pkg.sv").read_text()
engine = (root / "rtl/ip/security/crypto_sha2_engine.sv").read_text()
source = json.loads((root / "scripts/data/crypto_constants.json").read_text())
aes = source["aes_bytes"]
sha = [int(word, 16) for word in source["sha_words"]]
if (int(source["layout_id"], 16) != LAYOUT_ID or len(aes) != 528 or
len(sha) != 80 or any(not 0 <= byte <= 255 for byte in aes)):
raise ValueError("invalid CRYC1 public constant source")
words = [0] * WORDS
words[:256] = lookup(package, "aes_sbox", 256, 16)
words[256:512] = lookup(package, "aes_inverse_sbox", 256, 16)
# Rcon is algorithmic in V1; preserve its defined zero at indices 0 and 15.
rcon = 1
for index in range(1, 15):
words[512 + index] = rcon
rcon = ((rcon << 1) ^ (0x11B if rcon & 0x80 else 0)) & 0xFF
words[1024:1088] = lookup(package, "sha2_k", 64, 10)
initial = re.findall(r"s_hash_state_q <= 256'h([0-9a-f]{64});", engine)
if len(initial) != 2:
raise ValueError("expected SHA-256 then SHA-224 initialization")
for start, value in ((1088, initial[1]), (1096, initial[0])):
words[start:start + 8] = [int(value[i:i + 8], 16) for i in range(0, 64, 8)]
words[:528] = aes
words[1024:1104] = sha
return struct.pack("<2048I", *words)
def compact_header(payload: bytes) -> str:
"""Only 848 initialized bytes; the HAL emits the reserved zero rows."""
validate(payload)
words = struct.unpack("<2048I", payload)
return ("/* Generated public CRYC1 constants; do not edit. */\n" +
"static const uint8_t rs_crypto_aes_constants[528] = {\n" +
"\n".join(" " + ", ".join(f"0x{x:02x}U" for x in words[i:i + 16]) + ","
for i in range(0, 528, 16)) + "\n};\n" +
"static const uint32_t rs_crypto_sha_constants[80] = {\n" +
"\n".join(" " + ", ".join(f"UINT32_C(0x{x:08x})" for x in words[i:i + 4]) + ","
for i in range(1024, 1104, 4)) + "\n};\n")
def validate(payload: bytes) -> dict[str, object]:
digest = hashlib.sha256(payload).hexdigest()
crc = zlib.crc32(payload)
@@ -75,6 +61,7 @@ def main() -> None:
manifest = validate(payload)
args.output.mkdir(parents=True, exist_ok=True)
(args.output / "crypto.cryc").write_bytes(payload)
(args.output / "crypto_constants.h").write_text(compact_header(payload))
(args.output / "crypto_constants.inc").write_text(
"/* Generated CRYC1 words; do not edit. */\n" +
"\n".join(f"UINT32_C(0x{word:08x})," for (word,) in struct.iter_unpack("<I", payload)) + "\n"
+3
View File
@@ -430,6 +430,9 @@ def main() -> None:
parser.add_argument("--case", choices=("crypto_vectors_p0_tb", "crypto_rsa2048_tb", "crypto_dma_p0_tb"))
args = parser.parse_args()
variant = args.variant_root.resolve()
if not identity(variant)["production_rtl_matches_baseline"]:
raise ValueError("CRYPTO-P0 requires the frozen V1 RTL checkout; use crypto-p1 targets for V2. "
"No P0 artifact has been written.")
output = variant / "crypto/p0"
output.mkdir(parents=True, exist_ok=True)
try:
+530
View File
@@ -0,0 +1,530 @@
#!/usr/bin/env python3
"""Focused CRYPTO-P1 evidence; all artifacts are separate from frozen P0."""
from __future__ import annotations
import argparse
import hashlib
import json
import os
from pathlib import Path
import re
import shutil
import struct
import subprocess
import sys
import tempfile
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from scripts.crypto_constants import compact_header, pack, validate # noqa: E402
from scripts.crypto_p0 import ( # noqa: E402
COMMON, INTERFACES, RTL, UTILITIES, digest, dump, execute, inventory, oracle,
synthesis_metrics,
)
def input_hashes(sources: list[Path]) -> dict[str, str]:
# Compilation lists exclude include-only SVH files; their contents still
# affect the compiled design and must participate in stale-evidence checks.
paths = set(sources) | set((ROOT / "rtl/ip/security").glob("*.svh"))
paths |= {ROOT / "dependencies/dependencies.lock.json", ROOT / "rtl/mini/filelist/ip.fl"}
return {str(p.relative_to(ROOT)): digest(p) for p in sorted(paths)}
def firmware_inputs() -> dict[str, str]:
names = subprocess.check_output(["git", "ls-files", "--cached", "--others", "--exclude-standard",
"--", "rtl", "crt", "app", "scripts", "configs", "dependencies", "Makefile",
"tests/c/crypto_firmware.c"], cwd=ROOT, text=True).splitlines()
return {name: digest(ROOT / name) for name in sorted(set(names))
if (ROOT / name).is_file() and not name.endswith((".md", ".png", ".jpg"))
and not name.startswith("rtl/mini/formal/")}
def firmware_runtime_inputs(variant: Path) -> dict[str, str]:
"""Include generated CPU outputs and selected PDK models used by the run."""
variant = variant.resolve()
inputs = firmware_inputs()
generated = variant / "generated/vexiiriscv"
if generated.is_dir():
for path in sorted(generated.rglob("*")):
if path.is_file():
inputs[str(path.relative_to(ROOT))] = digest(path)
pdk = ROOT / "physical/pdk/IHP-Open-PDK/ihp-sg13g2/libs.ref/sg13g2_sram/verilog"
for path in sorted(pdk.glob("RM_IHPSG13_1P_1024x32_c2_bm_bist.v")):
inputs[str(path.relative_to(ROOT))] = digest(path)
for path in sorted(pdk.glob("RM_IHPSG13_1P_core_behavioral_bm_bist.v")):
inputs[str(path.relative_to(ROOT))] = digest(path)
return inputs
def firmware_run(variant: Path, kind: str) -> None:
"""Capture the inputs before building/running, never at report assembly."""
output = variant / "crypto/p1/firmware-run.json"
manifest = json.loads((variant / "meta/manifest.json").read_text())
cfg = manifest["configuration"]
expected = {"PDK": "IHP130", "SIMU": "VERILATOR", "LINK_TYPE": "ld2_all_sram",
"HAVE_SVA": "YES", "APP": "ci_smoke" if kind == "ci" else "bringup"}
if kind == "ci":
expected["HAVE_CSR"] = "YES"
if any(cfg.get(k) != v for k, v in expected.items()):
raise ValueError(f"firmware variant configuration does not match {expected}")
timestamp = re.fullmatch(r"ihp130-(\d{4}-\d{2}-\d{2}-\d{2}-\d{2})-[0-9a-f]+", variant.name)
if timestamp is None:
raise ValueError("expected canonical IHP130 variant path")
command = ["make", "CONFIG=configs/ci/ihp130.mk", f"BUILD_TIMESTAMP={timestamp[1]}",
*[f"{k}={v}" for k, v in cfg.items()], "VERILATOR_SIM_ARGS=--fast-flash", "SOC_SIM_TIME=1800"]
if kind == "lp":
command += [f"APP_SRCS={ROOT / 'tests/c/crypto_firmware.c'}"]
command += ["firmware", "sim"]
tracked_inputs = firmware_inputs()
record = {"status": "running", "kind": kind, "manifest": manifest,
"source_sha256": tracked_inputs, "command": command}
dump(output, record)
try:
record["flow"] = execute(output.parent, "firmware-flow", command, timeout=3600)
current_tracked = firmware_inputs()
if current_tracked != tracked_inputs:
raise ValueError("tracked firmware inputs changed during the run")
runtime_inputs = firmware_runtime_inputs(variant)
result = variant / "sim/verilator/result-sim-check.json"
simulation = json.loads(result.read_text())
if simulation["status"] != "passed":
raise ValueError("firmware simulation did not pass")
paths = [result, Path(simulation["log"]), variant / "sw/firmware", variant / "meta/manifest.json"]
record.update(status="passed", source_sha256=runtime_inputs, simulation=simulation,
artifacts_sha256={str(p): digest(p) for p in paths})
except (OSError, ValueError, RuntimeError) as error:
record.update(status="failed", error=str(error))
raise
finally:
dump(output, record)
def checked_firmware(variant: Path, kind: str) -> dict:
path = variant / "crypto/p1/firmware-run.json"
record = json.loads(path.read_text())
if record.get("status") != "passed" or record.get("kind") != kind:
raise ValueError(f"missing successful {kind} run identity: {path}")
if record.get("source_sha256") != firmware_runtime_inputs(variant):
raise ValueError(f"stale firmware inputs: {path}")
for name, value in record["artifacts_sha256"].items():
if not Path(name).exists() or digest(Path(name)) != value:
raise ValueError(f"stale firmware artifact: {name}")
flow = json.loads(Path(record["flow"]["result"]).read_text())
if flow.get("status") != "passed" or flow.get("exit_code") != 0:
raise ValueError(f"firmware command did not succeed: {path}")
return {**record, "variant": str(variant), "run_record": str(path), "run_record_sha256": digest(path)}
def quality(output: Path) -> None:
paths = sorted({*RTL, *(ROOT / "rtl/mini/formal").glob("crypto_*.sv"),
*(ROOT / "tests/rtl").glob("crypto_*_tb.sv")} -
{ROOT / "tests/rtl/crypto_stream_tb.sv", ROOT / "tests/rtl/crypto_primitives_tb.sv",
ROOT / "tests/rtl/crypto_apb_tb.sv", ROOT / "tests/rtl/crypto_dma_tb.sv"})
results = []
for path in paths:
command = ["verible-verilog-format", "--flagfile=.verible-format", "--failsafe_success=false",
"--verify", str(path)]
result = subprocess.run(command, cwd=ROOT, capture_output=True, text=True, check=False)
results.append({"command": command, "exit_code": result.returncode,
"diagnostic": result.stdout + result.stderr})
passed = all(item["exit_code"] == 0 for item in results)
dump(output / "format.json", {"status": "passed" if passed else "failed", "checks": results,
"source_sha256": input_hashes(paths)})
if not passed:
raise ValueError(f"Crypto formatting failed: {output / 'format.json'}")
execute(output, "ruff", ["ruff", "check", "."])
execute(output, "quality-gates", ["make", "sw-format-check", "sw-policy-check", "sw-host-test",
"rtl-style-check", "rtl-readiness-check"])
dump(output / "quality-inputs.json", {"source_sha256": firmware_inputs()})
def inputs(output: Path) -> None:
output.mkdir(parents=True, exist_ok=True)
payload = pack(ROOT)
if payload != oracle().constant_image() or payload != pack(ROOT):
raise ValueError("CRYC1 independent oracle/reproducibility failure")
(output / "crypto.cryc").write_bytes(payload)
(output / "constants.hex").write_text(
"".join(f"{word:08x}\n" for (word,) in struct.iter_unpack("<I", payload)))
(output / "crypto_constants.h").write_text(compact_header(payload))
lengths = (0, 1, 3, 55, 56, 63, 64, 65, 127, 128, 129)
(output / "sha.hex").write_text("".join(
hashlib.new(name, bytes(range(length))).hexdigest().ljust(64, "0") + "\n"
for name in ("sha224", "sha256") for length in lengths))
fixture = oracle().rsa_fixture()
(output / "rsa.hex").write_text("".join(f"{fixture[key]:0512x}\n" for key in
("modulus", "private", "message", "ciphertext", "n0_prime", "montgomery")))
dump(output / "constants.json", {**validate(payload), "status": "passed",
"compact_bytes": 848, "oracle": "tests/crypto_reference.py"})
def rtl(output: Path, full_rsa: bool, macro: bool, jobs: int, top: str = "crypto_v2_tb",
simulator: str = "verilator", response_delay: int = 0,
duplicate_response: bool = False, drop_response_mask: int = 0) -> None:
inputs(output)
case = output / ("ihp-functional" if macro else "fallback")
if top != "crypto_v2_tb":
case = case / top
if simulator == "icarus":
case = case / "icarus"
case.mkdir(parents=True, exist_ok=True)
(case / "ccache-tmp").mkdir(exist_ok=True)
sources = [*INTERFACES, *UTILITIES, *[p for p in RTL if p.suffix == ".sv"]]
defines = ["-DSV_ASSRT_DISABLE"]
if response_delay:
defines += [f"-DCRYPTO_RESPONSE_DELAY={response_delay}"]
if duplicate_response:
defines += ["-DCRYPTO_RESPONSE_DUPLICATE"]
if drop_response_mask:
defines += [f"-DCRYPTO_RESPONSE_DROP_MASK={drop_response_mask}"]
includes = [ROOT / "rtl/ip/security", COMMON]
if top == "crypto_dma_v2_tb":
generated = case / "memory_map"
execute(case, "memory-map", [sys.executable, str(ROOT / "rtl/mini/address_map/generate_memory_map.py"),
"--map", str(ROOT / "rtl/mini/address_map/memory_map.json"),
"--output-dir", str(generated), "--have-sram-if", "NO"])
includes += [generated / "rtl", COMMON / "interface", COMMON / "stream", COMMON / "utils"]
sources = [COMMON / "interface/axi4_if.sv", *sources, COMMON / "stream/round_robin_arbiter.sv"]
sources += [ROOT / "rtl/ip/peripheral" / name for name in
("dma_pkg.sv", "dma_req_if.sv", "dma_axi4_master.sv", "dma_core.sv")]
if macro:
sources += [ROOT / "rtl/tech/tc_sram.sv"]
models = list((ROOT / "physical/pdk").rglob("RM_IHPSG13_1P_1024x32_c2_bm_bist.v"))
if len(models) != 1:
raise ValueError(f"expected exactly one IHP130 1024x32 model, found {models}")
sources += [*models, models[0].with_name("RM_IHPSG13_1P_core_behavioral_bm_bist.v")]
# SYNTHESIS omits the vendor's unsupported Verilator #0 A_DLY probe;
# the actual FUNCTIONAL array/bit-mask model remains fully enabled.
defines += ["-DPDK_IHP130", "-DHAVE_SRAM_MACRO", "-DFUNCTIONAL"]
if simulator == "verilator":
defines += ["-DSYNTHESIS"]
sources += [ROOT / "tests/rtl" / f"{top}.sv"]
command = ["verilator", "--binary", "--timing", "-Wno-fatal", "--timescale", "1ns/1ps", "--top-module", top,
"--Mdir", str(case / "obj"), "-j", str(jobs), *defines,
*[f"-I{path}" for path in includes], *map(str, sources)]
evidence = {"status": "running", "full_rsa": full_rsa, "macro": macro, "simulator": simulator,
"production_sha256": input_hashes(sources)}
dump(case / "functional.json", evidence)
try:
if simulator == "icarus":
fl = case / "sources.fl"
fl.write_text("\n".join([*[f"+define+{item[2:]}" for item in defines],
*[f"+incdir+{path}" for path in includes], *map(str, sources)]) + "\n")
converted = case / "test.v"
evidence["convert"] = execute(case, "convert", [sys.executable,
str(ROOT / "rtl/mini/script/convt_sv2v.py"), "-f", str(fl), "--output", str(converted)])
evidence["compile"] = execute(case, "compile", ["iverilog", "-g2012", "-s", top,
"-o", str(case / "test.vvp"), str(converted)])
simulation = ["vvp", str(case / "test.vvp")]
else:
evidence["compile"] = execute(case, "compile", command, env={
"CCACHE_DIR": str(case / "ccache"), "CCACHE_TEMPDIR": str(case / "ccache-tmp")})
simulation = [str(case / "obj" / f"V{top}")]
simulation += [f"+constants={output / 'constants.hex'}",
f"+sha={output / 'sha.hex'}", f"+rsa={output / 'rsa.hex'}"]
if full_rsa:
simulation += ["+full_rsa"]
markers = {"crypto_dma_v2_tb": "CRYPTO_V2_DMA_PASS", "crypto_storage_tb": "CRYPTO_V2_STORAGE_PASS",
"crypto_concurrent_tb": "CRYPTO_V2_CONCURRENT_PASS",
"crypto_response_fault_tb": "CRYPTO_V2_RESPONSE_FAULT_PASS",
"crypto_v2_tb": "CRYPTO_V2_PASS"}
evidence["simulation"] = execute(case, "simulation", simulation, timeout=3600, marker=markers[top])
if top == "crypto_dma_v2_tb":
evidence["error_simulations"] = {name: execute(case, name, [*simulation, f"+{name}"],
marker="CRYPTO_V2_DMA_ERROR_PASS") for name in ("dma_read_error", "dma_write_error")}
evidence["status"] = "passed"
except (OSError, ValueError, RuntimeError) as error:
evidence.update(status="failed", error=str(error))
raise
finally:
dump(case / "functional.json", evidence)
def main() -> None:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("command", choices=("constants", "rtl", "synth", "formal", "report", "firmware", "quality"))
parser.add_argument("--variant-root", type=Path, required=True)
parser.add_argument("--full-rsa", action="store_true")
parser.add_argument("--macro", action="store_true")
parser.add_argument("--jobs", type=int, default=4)
parser.add_argument("--case", choices=("crypto_v2_tb", "crypto_dma_v2_tb", "crypto_storage_tb", "crypto_concurrent_tb", "crypto_response_fault_tb"), default="crypto_v2_tb")
parser.add_argument("--simulator", choices=("verilator", "icarus"), default="verilator")
parser.add_argument("--response-delay", type=int, default=0)
parser.add_argument("--duplicate-response", action="store_true")
parser.add_argument("--drop-response-mask", type=lambda value: int(value, 0), default=0)
parser.add_argument("--baseline-root", type=Path)
parser.add_argument("--lp-variant-root", type=Path)
parser.add_argument("--ci-variant-root", type=Path)
parser.add_argument("--firmware-kind", choices=("lp", "ci"), default="lp")
args = parser.parse_args()
variant = args.variant_root.resolve()
manifest = json.loads((variant / "meta/manifest.json").read_text())
if manifest["profile"] != "ihp130":
raise ValueError("use configs/ci/ihp130.mk")
output = variant / "crypto/p1"
if args.command == "constants":
inputs(output)
elif args.command == "synth":
synth(variant, output)
elif args.command == "formal":
formal(output)
elif args.command == "firmware":
firmware_run(variant, args.firmware_kind)
elif args.command == "quality":
quality(output)
elif args.command == "report":
if args.baseline_root is None or args.lp_variant_root is None:
parser.error("report requires --baseline-root and --lp-variant-root")
report_all(variant, output, args.baseline_root.resolve(), args.lp_variant_root.resolve(),
args.ci_variant_root.resolve() if args.ci_variant_root else None)
else:
rtl(output, args.full_rsa, args.macro, args.jobs, args.case, args.simulator,
args.response_delay, args.duplicate_response, args.drop_response_mask)
def report_all(variant: Path, output: Path, baseline: Path, lp_variant: Path,
ci_variant: Path | None = None) -> None:
"""Summarize evidence without upgrading focused tests to release acceptance."""
manifest = json.loads((variant / "meta/manifest.json").read_text())
source_files = [*RTL, ROOT / "scripts/data/crypto_constants.json",
ROOT / "scripts/crypto_constants.py", ROOT / "scripts/crypto_p1.py",
ROOT / "crt/src/hal/crypto.c", ROOT / "crt/src/hal/crypto_lifecycle.c",
ROOT / "crt/include/retrosoc/hal/crypto.h", ROOT / "crt/include/retrosoc/hal/crypto_regs.h"]
identity = {"schema_version": 1, "feature": "crypto", "phase": "CRYPTO-P1",
"manifest": manifest, "report_command": sys.argv,
"source_snapshot_sha256": {str(p.relative_to(ROOT)): digest(p) for p in source_files}}
dump(output / "p1-report.json", {**identity, "status": "running"})
paths = ("ihp-functional", "ihp-functional/crypto_dma_v2_tb", "ihp-functional/crypto_storage_tb",
"ihp-functional/crypto_concurrent_tb", "ihp-functional/crypto_response_fault_tb",
"ihp-functional/icarus", "fallback", "fallback/crypto_storage_tb")
cases = {}
for name in paths:
path = output / name / "functional.json"
case = json.loads(path.read_text()) if path.exists() else {"status": "NOT_RUN"}
stale = [p for p, value in case.get("production_sha256", {}).items()
if not (ROOT / p).exists() or digest(ROOT / p) != value]
if stale:
case = {"status": "stale", "paths": stale}
cases[name] = {"path": str(path), "sha256": digest(path) if path.exists() else None, **case}
functional_pass = all(case.get("status") == "passed" for case in cases.values())
functional_pass &= cases["ihp-functional"].get("full_rsa") is True
dump(output / "functional.json", {**identity, "status": "passed" if functional_pass else "incomplete", "cases": cases})
cycle_values = {}
for name in ("ihp-functional", "ihp-functional/crypto_dma_v2_tb"):
log = output / name / "simulation.log"
if log.exists():
cycle_values.update({key: int(value) for key, value in re.findall(
r"CRYPTO_CYCLES operation=(\w+) cycles=(\d+)", log.read_text())})
dump(output / "cycles.json", {**identity, "status": "passed" if functional_pass else "incomplete",
"pclk_cycles": cycle_values, "counter_policy": "32-bit hardware counters; testbench bounds below wrap",
"evidence": [str(output / name / "simulation.log") for name in paths]})
proofs = json.loads((output / "formal.json").read_text())
formal_stale = [p for proof in proofs.get("proofs", {}).values()
for p, value in proof.get("source_sha256", {}).items() if digest(ROOT / p) != value]
proof_pass = proofs.get("status") == "passed" and len(proofs.get("proofs", {})) == 5 and not formal_stale
dump(output / "lifecycle.json", {**identity,
"status": "passed" if functional_pass and proof_pass else "incomplete",
"formal": str(output / "formal.json"), "stale_formal_sources": formal_stale,
"directed_coverage": ["boot scrub", "short/partial/CRC/padding rejection", "readback corruption/retry/lock",
"all-key-size AES directions and multi-block/tails", "22 SHA padding cases", "held DMA output abort",
"simultaneous AES/SHA/RSA", "FIFO and all mutable rows inspected after erase", "scrub fault/fatal latch",
"reset during load/verify", "private RSA verify success/failure with equal cycles"],
"coverage_limit": "Universal control interruption properties plus directed physical erasure/fault tests; see coverage.json for the exact decomposition."})
dma = cases["ihp-functional/crypto_dma_v2_tb"].get("error_simulations", {})
boundary_log = (output / "ihp-functional/simulation.log").read_text()
coverage = {
"command_completion": {"evidence": "crypto_v2_tb + crypto_apb_formal", "passed": functional_pass and proof_pass},
"padding_readback": {"evidence": "CRC-neutral bank-0 rows 528/529 corruption", "passed": functional_pass},
"timeout_lock_boundary": {"evidence": "six public-APB zeroize/COMMIT boundary cases",
"passed": "CRYPTO_V2_CANCEL_BOUNDARIES_PASS" in boundary_log},
"fatal_stream_drain": {"evidence": "crypto_stream_formal + SHA FIFO fault with held AES output",
"passed": proof_pass and "CRYPTO_V2_FATAL_DMA_DRAIN_PASS" in boundary_log},
"dma_axi_errors": {"evidence": dma, "passed": len(dma) == 2},
"response_fault_model": {"evidence": "crypto_response_fault_tb", "passed":
cases["ihp-functional/crypto_response_fault_tb"].get("status") == "passed"},
"state_interruption": {"evidence": "universal engine next-state abort/zeroize assertions, arbitrary lifecycle reset, bounded scrub proof, physical bank/FIFO inspections",
"passed": functional_pass and proof_pass},
}
coverage_pass = all(case["passed"] for case in coverage.values())
dump(output / "coverage.json", {**identity, "status": "passed" if coverage_pass else "incomplete",
"requirements": coverage, "method": "Proofs quantify over reachable states; physical data tests inspect all rows/slots. This does not claim arbitrary silicon fault or analog SRAM coverage."})
old = json.loads((baseline / "crypto/p0/baseline-synthesis.json").read_text())
new = json.loads((output / "candidate-synthesis.json").read_text())
old_inventory = json.loads((baseline / "crypto/p0/storage-inventory.json").read_text())["checkpoints"]["pre_memory_design"]
new_inventory = json.loads((output / "storage-inventory.json").read_text())["checkpoints"]["pre_memory_design"]
recipe_equal = old["recipe_sha256"] == new["recipe_sha256"]
for field in ("YOSYS_TARGET_PERIOD_PS", "YOSYS_FLATTEN_HIER", "SYNTH_RECIPE", "HAVE_SRAM_MACRO", "PDK"):
recipe_equal &= old["environment"][field] == new["environment"][field]
synthesis_stale = [p for p, value in new["production_sha256"].items() if digest(ROOT / p) != value]
synthesis_pass = new["status"] == "passed" and recipe_equal and not synthesis_stale
dump(output / "memory-synthesis-ab.json", {**identity,
"status": "passed" if synthesis_pass else "incomplete", "scope": "block observations",
"qualification": "P2 must repeat isolated clean-revision A/B; unrelated jobs overlapped these runs",
"same_recipe_and_target": recipe_equal, "stale_sources": synthesis_stale,
"baseline": {"report": str(baseline / "crypto/p0/baseline-synthesis.json"), "flow": old["flow"],
"inferred_bits": old_inventory["inferred_bits"], "register_bits": old_inventory["register_bits"]},
"candidate": {"report": str(output / "candidate-synthesis.json"), "flow": new["flow"],
"inferred_bits": new_inventory["inferred_bits"], "register_bits": new_inventory["register_bits"],
"macros": new_inventory["macros"]},
"duration_ratio": new["flow"]["duration_seconds"] / old["flow"]["duration_seconds"],
"rss_ratio": new["flow"]["peak_rss_kib"] / old["flow"]["peak_rss_kib"]})
try:
firmware = checked_firmware(lp_variant, "lp")
except (OSError, ValueError) as error:
firmware = {"status": "stale", "error": str(error), "variant": str(lp_variant)}
if ci_variant:
try:
ci = checked_firmware(ci_variant, "ci")
except (OSError, ValueError) as error:
ci = {"status": "stale", "error": str(error), "variant": str(ci_variant)}
else:
ci = {"status": "NOT_RUN"}
dump(output / "ci-smoke.json", {"schema_version": 1, "feature": "crypto", "phase": "CRYPTO-P1", **ci})
quality_path = output / "quality-gates.json"
quality = json.loads(quality_path.read_text()) if quality_path.exists() else {"status": "NOT_RUN"}
quality_identity = output / "quality-inputs.json"
if not quality_identity.exists() or json.loads(quality_identity.read_text())["source_sha256"] != firmware_inputs():
quality = {"status": "stale", "reason": "quality inputs missing or changed"}
warning_path = lp_variant / "meta/rtl-lint-warnings.json"
warnings = json.loads(warning_path.read_text()) if warning_path.exists() else {"status": "NOT_RUN"}
dump(output / "quality.json", {**identity, "gates": quality, "rtl_lint_warnings": warnings,
"format": json.loads((output / "format.json").read_text()) if (output / "format.json").exists() else {"status": "NOT_RUN"},
"pytest": "NOT_RUN (maintainer instruction)", "misra_deviations": []})
dump(output / "firmware.json", {"schema_version": 1, "feature": "crypto", "phase": "CRYPTO-P1", **firmware,
"scope": "Full SoC, focused LP public HAL initialization/AES/SHA/DMA/timeout/zeroize image"})
dump(output / "physical.json", {**identity, "status": "NOT_RUN", "phase": "CRYPTO-P2",
"gaps": ["whole-chip synthesis", "functional gate simulation", "STA", "macro placement and physical views",
"remaining PDK qualification", "isolated clean-revision synthesis A/B"]})
focused_pass = (functional_pass and proof_pass and coverage_pass and synthesis_pass and
firmware["status"] == "passed" and ci["status"] == "passed" and quality["status"] == "passed")
dump(output / "p1-report.json", {**identity, "status": "incomplete",
"focused_gates_status": "passed" if focused_pass else "incomplete",
"gates": {"functional": functional_pass, "formal": proof_pass, "block_synthesis": synthesis_pass,
"lp_firmware": firmware["status"] == "passed", "focused_quality": quality["status"] == "passed",
"ci_smoke": ci["status"] == "passed"},
"acceptance_gaps": ([] if ci["status"] == "passed" else [
"CI firmware provenance is stale or missing; rerun the locked profile with generated-input hashes"
if ci["status"] == "stale" else "Correctly configured ci_smoke has not passed"]) + [
"Independent review of the P1 fixes remains required",
"Whole-repository formatting and warning baseline results are separate from focused Crypto gates",
"Pytest not run by maintainer instruction; P2 qualification not started"],
"physical": "NOT_RUN (P2)",
"p0_report_matches_archive": (digest(output / "archive/p0-report.json") ==
digest(baseline / "crypto/p0/p0-report.json")) if (output / "archive/p0-report.json").exists() else None})
if not focused_pass:
raise ValueError("focused P1 evidence is missing/stale/failed; see p1-report.json")
def formal(output: Path) -> None:
solver = shutil.which("bitwuzla")
if solver is None:
raise FileNotFoundError("bitwuzla is required")
binary_dir = output / "formal/bin"
binary_dir.mkdir(parents=True, exist_ok=True)
wrapper = binary_dir / "bitwuzla"
shutil.copyfile(ROOT / "scripts/bitwuzla_smt2.py", wrapper)
wrapper.chmod(0o755)
report = {"status": "running", "proofs": {}}
dump(output / "formal.json", report)
try:
for top in ("crypto_control_formal", "crypto_rsa_release_formal", "crypto_scrub_progress_formal", "crypto_apb_formal", "crypto_stream_formal"):
case = output / "formal" / top
case.mkdir(parents=True, exist_ok=True)
names = ["crypto_mem_pkg.sv", "crypto_scrubber.sv"]
if top == "crypto_control_formal":
names += ["crypto_mem_ctrl.sv"]
elif top == "crypto_rsa_release_formal":
names += ["crypto_montgomery_sram.sv", "crypto_rsa_sram_core.sv"]
sources = [COMMON / "utils/register.sv", *[ROOT / "rtl/ip/security" / name for name in names],
ROOT / "rtl/mini/formal" / f"{top}.sv"]
if top == "crypto_apb_formal":
sources = [*INTERFACES, *UTILITIES, *[p for p in RTL if p.suffix == ".sv"],
ROOT / "rtl/mini/formal" / f"{top}.sv"]
elif top == "crypto_stream_formal":
sources = [*UTILITIES, *[ROOT / "rtl/ip/security" / name for name in
("crypto_pkg.sv", "crypto_mem_pkg.sv", "crypto_scrubber.sv", "crypto_clearable_fifo.sv",
"crypto_aes_sram_core.sv", "crypto_aes_sram_engine.sv")],
ROOT / "rtl/mini/formal" / f"{top}.sv"]
config = case / "prove.sby"
config.write_text("[options]\nmode prove\ndepth 24\n\n[engines]\n"
"smtbmc --presat --nounroll bitwuzla\n\n[script]\n"
f"read_slang -DSV_ASSRT_DISABLE -DFORMAL -USYNTHESIS -I{COMMON} --top {top} " + " ".join(map(str, sources)) +
("\nselect -assert-count 10 t:$check" if top == "crypto_rsa_release_formal" else "") +
f"\nprep -top {top}\nasync2sync\ndffunmap\nopt_clean\n")
hashes = input_hashes(sources)
result = execute(case, "proof", ["sby", "-f", "-d", str(case / "work"), str(config)],
timeout=600, env={"PATH": str(binary_dir) + os.pathsep + os.environ["PATH"],
"RETROSOC_BITWUZLA": solver})
if (case / "work/status").read_text().split()[0] != "PASS":
raise ValueError(f"{top} did not prove")
if hashes != input_hashes(sources):
raise ValueError(f"{top} sources changed during proof")
report["proofs"][top] = {**result, "status": "passed",
"source_sha256": hashes}
report["status"] = "passed"
except (OSError, ValueError, RuntimeError) as error:
report.update(status="failed", error=str(error))
raise
finally:
dump(output / "formal.json", report)
def synth(variant: Path, output: Path) -> None:
manifest = json.loads((variant / "meta/manifest.json").read_text())
cfg = manifest["configuration"]
if any(cfg[key] != value for key, value in {
"HAVE_SRAM_MACRO": "YES", "PDK_BEHAV": "NO", "SYNTH": "YOSYS",
"SYNTH_RECIPE": "balanced", "PDK": "IHP130",
}.items()):
raise ValueError("P1 block synthesis requires the unchanged IHP130 macro/balanced profile")
output.mkdir(parents=True, exist_ok=True)
work = Path(tempfile.mkdtemp(prefix="synth-", dir=output))
for name in ("out", "tmp", "rpt"):
(work / name).mkdir()
sources = [*INTERFACES, *UTILITIES, ROOT / "rtl/tech/tc_sram.sv",
*[p for p in RTL if p.suffix == ".sv"]]
fl = work / "crypto.fl"
fl.write_text("+define+PDK_IHP130\n+define+HAVE_SRAM_MACRO\n+define+SYNTHESIS\n+define+SV_ASSRT_DISABLE\n" +
f"+incdir+{ROOT / 'rtl/ip/security'}\n+incdir+{COMMON}\n" +
f"+incdir+{COMMON / 'interface'}\n" + "\n".join(map(str, sources)) + "\n")
directory = ROOT / "physical/smoke/syn/yosys/script"
period = subprocess.check_output([sys.executable, str(ROOT / "scripts/yosys_period.py"),
"--domains", str(ROOT / "rtl/mini/integration/clock_reset_domains.json"),
"--domain", "pclk"], text=True).strip()
env = {"PDK": "IHP130", "SOC": "MINI", "SYNTH_RECIPE": "balanced", "HAVE_SRAM_MACRO": "YES",
"SRAM_SIZE_KIB": "32", "SV_FLIST": str(fl), "TOP_DESIGN": "apb4_crypto",
"PROJ_NAME": "apb4_crypto", "WORK": str(work / "tmp"), "BUILD": str(work / "out"),
"REPORTS": str(work / "rpt"), "NETLIST": str(work / "out/crypto.v"),
"CONFIG": str(work / "out/crypto.config"), "YOSYS_TARGET_PERIOD_PS": period,
"YOSYS_FLATTEN_HIER": "1", "YOSYS_KEEP_HIER_INST": '"t:tc_sram*$*"',
"YOSYS_REPORT_INSTS": ""}
report = {"status": "running", "scope": "crypto_block", "manifest": manifest,
"environment": env, "production_sha256": input_hashes(sources),
"recipe_sha256": {p.name: digest(p) for p in
(directory / "synth.tcl", directory / "abc_balanced.script", directory / "crypto_block_synth.tcl")}}
dump(output / "candidate-synthesis.json", report)
try:
report["flow"] = execute(work, "yosys", ["yosys", "-t", "--perffile", str(work / "yosys-perf.json"),
"-c", str(directory / "crypto_block_synth.tcl")], timeout=10800, env=env)
report["metrics"] = synthesis_metrics(work / "rpt")
checkpoints = {p.stem: inventory(p) for p in (work / "rpt").glob("*_design.json")}
pre_memory = checkpoints["pre_memory_design"]
if (sum(pre_memory["macros"].values()) != 6 or pre_memory["rom_bits"] != 0 or
pre_memory["ram_bits"] > 1184):
raise ValueError("Crypto macro/inferred-storage contract failed")
report["status"] = "passed"
except (OSError, ValueError, RuntimeError, subprocess.CalledProcessError) as error:
report.update(status="failed", error=str(error))
raise
finally:
dump(output / "candidate-synthesis.json", report)
dump(output / "storage-inventory.json", {"status": report["status"],
"production_sha256": report["production_sha256"],
"checkpoints": {p.stem: inventory(p) for p in (work / "rpt").glob("*_design.json")},
"whole_soc": "NOT_RUN (P2)", "physical": "NOT_RUN (P2)"})
if __name__ == "__main__":
main()
+9
View File
@@ -0,0 +1,9 @@
# Public Crypto constants
`crypto_constants.json` is the canonical software source for CRYC1: 528 AES
bytes and 80 SHA words. `scripts/crypto_constants.py` pads these to the exact
8192-byte image and emits a compact 848-byte firmware header. Its frozen hash
and CRC are checked against the independent mathematical oracle in
`tests/crypto_reference.py`; production RTL contains no lookup tables.
The layout and identity are frozen in [the Crypto specification](../../docs/ip/crypto.md).
+615
View File
@@ -0,0 +1,615 @@
{
"layout_id": "0x43525901",
"aes_bytes": [
99,
124,
119,
123,
242,
107,
111,
197,
48,
1,
103,
43,
254,
215,
171,
118,
202,
130,
201,
125,
250,
89,
71,
240,
173,
212,
162,
175,
156,
164,
114,
192,
183,
253,
147,
38,
54,
63,
247,
204,
52,
165,
229,
241,
113,
216,
49,
21,
4,
199,
35,
195,
24,
150,
5,
154,
7,
18,
128,
226,
235,
39,
178,
117,
9,
131,
44,
26,
27,
110,
90,
160,
82,
59,
214,
179,
41,
227,
47,
132,
83,
209,
0,
237,
32,
252,
177,
91,
106,
203,
190,
57,
74,
76,
88,
207,
208,
239,
170,
251,
67,
77,
51,
133,
69,
249,
2,
127,
80,
60,
159,
168,
81,
163,
64,
143,
146,
157,
56,
245,
188,
182,
218,
33,
16,
255,
243,
210,
205,
12,
19,
236,
95,
151,
68,
23,
196,
167,
126,
61,
100,
93,
25,
115,
96,
129,
79,
220,
34,
42,
144,
136,
70,
238,
184,
20,
222,
94,
11,
219,
224,
50,
58,
10,
73,
6,
36,
92,
194,
211,
172,
98,
145,
149,
228,
121,
231,
200,
55,
109,
141,
213,
78,
169,
108,
86,
244,
234,
101,
122,
174,
8,
186,
120,
37,
46,
28,
166,
180,
198,
232,
221,
116,
31,
75,
189,
139,
138,
112,
62,
181,
102,
72,
3,
246,
14,
97,
53,
87,
185,
134,
193,
29,
158,
225,
248,
152,
17,
105,
217,
142,
148,
155,
30,
135,
233,
206,
85,
40,
223,
140,
161,
137,
13,
191,
230,
66,
104,
65,
153,
45,
15,
176,
84,
187,
22,
82,
9,
106,
213,
48,
54,
165,
56,
191,
64,
163,
158,
129,
243,
215,
251,
124,
227,
57,
130,
155,
47,
255,
135,
52,
142,
67,
68,
196,
222,
233,
203,
84,
123,
148,
50,
166,
194,
35,
61,
238,
76,
149,
11,
66,
250,
195,
78,
8,
46,
161,
102,
40,
217,
36,
178,
118,
91,
162,
73,
109,
139,
209,
37,
114,
248,
246,
100,
134,
104,
152,
22,
212,
164,
92,
204,
93,
101,
182,
146,
108,
112,
72,
80,
253,
237,
185,
218,
94,
21,
70,
87,
167,
141,
157,
132,
144,
216,
171,
0,
140,
188,
211,
10,
247,
228,
88,
5,
184,
179,
69,
6,
208,
44,
30,
143,
202,
63,
15,
2,
193,
175,
189,
3,
1,
19,
138,
107,
58,
145,
17,
65,
79,
103,
220,
234,
151,
242,
207,
206,
240,
180,
230,
115,
150,
172,
116,
34,
231,
173,
53,
133,
226,
249,
55,
232,
28,
117,
223,
110,
71,
241,
26,
113,
29,
41,
197,
137,
111,
183,
98,
14,
170,
24,
190,
27,
252,
86,
62,
75,
198,
210,
121,
32,
154,
219,
192,
254,
120,
205,
90,
244,
31,
221,
168,
51,
136,
7,
199,
49,
177,
18,
16,
89,
39,
128,
236,
95,
96,
81,
127,
169,
25,
181,
74,
13,
45,
229,
122,
159,
147,
201,
156,
239,
160,
224,
59,
77,
174,
42,
245,
176,
200,
235,
187,
60,
131,
83,
153,
97,
23,
43,
4,
126,
186,
119,
214,
38,
225,
105,
20,
99,
85,
33,
12,
125,
0,
1,
2,
4,
8,
16,
32,
64,
128,
27,
54,
108,
216,
171,
77,
0
],
"sha_words": [
"0x428a2f98",
"0x71374491",
"0xb5c0fbcf",
"0xe9b5dba5",
"0x3956c25b",
"0x59f111f1",
"0x923f82a4",
"0xab1c5ed5",
"0xd807aa98",
"0x12835b01",
"0x243185be",
"0x550c7dc3",
"0x72be5d74",
"0x80deb1fe",
"0x9bdc06a7",
"0xc19bf174",
"0xe49b69c1",
"0xefbe4786",
"0x0fc19dc6",
"0x240ca1cc",
"0x2de92c6f",
"0x4a7484aa",
"0x5cb0a9dc",
"0x76f988da",
"0x983e5152",
"0xa831c66d",
"0xb00327c8",
"0xbf597fc7",
"0xc6e00bf3",
"0xd5a79147",
"0x06ca6351",
"0x14292967",
"0x27b70a85",
"0x2e1b2138",
"0x4d2c6dfc",
"0x53380d13",
"0x650a7354",
"0x766a0abb",
"0x81c2c92e",
"0x92722c85",
"0xa2bfe8a1",
"0xa81a664b",
"0xc24b8b70",
"0xc76c51a3",
"0xd192e819",
"0xd6990624",
"0xf40e3585",
"0x106aa070",
"0x19a4c116",
"0x1e376c08",
"0x2748774c",
"0x34b0bcb5",
"0x391c0cb3",
"0x4ed8aa4a",
"0x5b9cca4f",
"0x682e6ff3",
"0x748f82ee",
"0x78a5636f",
"0x84c87814",
"0x8cc70208",
"0x90befffa",
"0xa4506ceb",
"0xbef9a3f7",
"0xc67178f2",
"0xc1059ed8",
"0x367cd507",
"0x3070dd17",
"0xf70e5939",
"0xffc00b31",
"0x68581511",
"0x64f98fa7",
"0xbefa4fa4",
"0x6a09e667",
"0xbb67ae85",
"0x3c6ef372",
"0xa54ff53a",
"0x510e527f",
"0x9b05688c",
"0x1f83d9ab",
"0x5be0cd19"
]
}
+9
View File
@@ -32,6 +32,7 @@ TEST_SOURCES = (
"crt/src/hal/spisd_math.c",
"crt/src/hal/i2s_math.c",
"crt/src/hal/dma_math.c",
"crt/src/hal/crypto_lifecycle.c",
"crt/src/hal/jpeg_math.c",
"crt/src/hal/apu.c",
"crt/src/hal/ga2d_math.c",
@@ -92,6 +93,11 @@ def main() -> int:
check=True,
)
executable = temporary_root / "runtime_tests"
crypto_constants_root = temporary_root / "crypto_constants"
subprocess.run([
sys.executable, str(root / "scripts/crypto_constants.py"),
"--root", str(root), "--output", str(crypto_constants_root),
], check=True)
command = [
compiler,
"-std=c11",
@@ -100,6 +106,9 @@ def main() -> int:
"-Werror",
"-fno-builtin",
"-DRS_APU_TEST_MMIO",
"-DRS_CRYPTO_TEST_MMIO",
"-I",
str(crypto_constants_root),
"-DRS_GA2D_TEST_MMIO",
"-DRS_NPU_TEST_MMIO",
"-DRS_FABRIC_MONITOR_TEST_MMIO",