Files
retroSoC/scripts/apu_abi_digest.py
T

590 lines
23 KiB
Python

"""APU V1 ABI digest tool: canonical table inventory, serialization, and CRC-32.
Per docs/ip/apu.md (ABI_DIGEST at offset 0x054 and the Phase8 completion
contract) the digest is the nonzero CRC-32 over the complete frozen V1 tables:
- the APB V1 register/field table and the 128-byte job-descriptor layout
(canonical source rtl/ip/multimedia/apu_define.svh, mirrored by
crt/include/retrosoc/hal/apu_regs.h; both sides are re-verified equal here),
- the hardware discovery values (IP_ID, IP_VERSION, CAPABILITY0/1, IRQ mask)
reported by the current release (P7-qualified configuration),
- the format/output-mode/PCM-format/operation identifier tables
(crt/include/retrosoc/hal/apu.h enums; operation IDs 0/1 are the frozen V1
contract values of docs/ip/apu.md "Descriptor ABI" and have no named code
constant),
- the supported APUMC V1/V2 container constants (scripts/apu_isa.py),
- the APUM model container constants (scripts/apu_kws.py),
- the microcode class/opcode/predicate/wait-source/primitive/trap tables, the
instruction field positions, the entry word layout, and the P3/P4/P5
primitive masks (scripts/apu_isa.py abi_manifest()),
- the reserved MP3 identifiers: format ID 1, capability bit 1, the three-entry
APUMC layout whose entry 1 is the unsupported-MP3 trap stub
(rtl/ip/multimedia/apu_p5_codecs.apus), the trap instruction/detail, and the
P5 rejection tuple (code 3/stage 2/detail 0x01000001).
The deferred P6 design archive is documentation-only and contributes nothing;
no P6 constant exists in any parsed source.
Serialization (the release identity; keep stable): canonical ASCII JSON with
recursively sorted keys, separators `,` and `:`, no insignificant whitespace,
no terminal newline, and every integer encoded as a JSON decimal number. The
digest is the reflected CRC-32/ISO-HDLC (binascii.crc32, the same variant as
apu_isa.crc32_iso_hdlc) over that complete byte stream.
CLI: default prints the digest and section summary; --check compares the
computed digest against the handwritten rtl/ip/multimedia/apu_reg.sv AbiDigest
localparam and the crt/include/retrosoc/hal/apu_regs.h RS_APU_DIGEST_IMPLEMENTED
define, requiring all three to match and be nonzero; --json PATH dumps the
canonical inventory for review.
"""
from __future__ import annotations
import argparse
import binascii
import json
import re
import sys
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "scripts"))
import apu_kws # noqa: E402
import apu_kws_coeff # noqa: E402
from apu_isa import ( # noqa: E402
APUMC_ABI_V2,
ControlOpcode,
Entry,
Instruction,
InstructionClass,
abi_manifest,
)
RTL_DEFINE = ROOT / "rtl/ip/multimedia/apu_define.svh"
RTL_REGISTER = ROOT / "rtl/ip/multimedia/apu_reg.sv"
RTL_MICROCODE = ROOT / "rtl/ip/multimedia/apu_p5_codecs.apus"
C_HEADER = ROOT / "crt/include/retrosoc/hal/apu_regs.h"
C_HAL = ROOT / "crt/include/retrosoc/hal/apu.h"
SECTION_ORDER = (
"discovery",
"apb",
"kws_model",
"formats",
"apumc",
"apum",
"apuc",
"isa",
"mp3_stub",
)
def _rtl_defines() -> dict[str, int]:
"""Parse every `define in the canonical hardware/software ABI header."""
values: dict[str, int] = {}
pattern = re.compile(
r"^`define\s+((?:APB4_APU__|RETROSOC_APU_KWS__)\w+)\s+"
r"((?:\d+)'[hHdDbB][0-9a-fA-F_]+|\d+)\s*$"
)
for line in RTL_DEFINE.read_text(encoding="utf-8").splitlines():
match = pattern.match(line)
if match is None:
continue
name, literal = match.groups()
if "'" in literal:
_, value = literal.split("'", maxsplit=1)
radix = {"h": 16, "d": 10, "b": 2}[value[0].lower()]
values[name] = int(value[1:].replace("_", ""), radix)
else:
values[name] = int(literal, 10)
return values
def _c_abi_defines() -> dict[str, int]:
"""Parse the handwritten RS_APU_ABI_* mirror of the SVH define file."""
values: dict[str, int] = {}
pattern = re.compile(
r"^#define\s+RS_APU_ABI_(\w+)\s+"
r"(?:UINT32_C\((0[xX][0-9a-fA-F]+)\)|(\d+)[uU]?)\s*$"
)
for line in C_HEADER.read_text(encoding="utf-8").splitlines():
match = pattern.match(line)
if match is None:
continue
name, hexadecimal, decimal = match.groups()
values[name] = int(hexadecimal, 16) if hexadecimal is not None else int(decimal, 10)
return values
def _c_discovery_defines() -> dict[str, int]:
"""Parse the implemented hardware discovery values from the C header."""
values: dict[str, int] = {}
pattern = re.compile(
r"^#define\s+RS_APU_(IP_ID_VALUE|IP_VERSION_VALUE|IRQ_ALL"
r"|CAPABILITY0_IMPLEMENTED|CAPABILITY0_P7_IMPLEMENTED"
r"|CAPABILITY1_IMPLEMENTED|CAPABILITY1_P7_IMPLEMENTED)"
r"\s+UINT32_C\((0[xX][0-9a-fA-F]+)\)\s*$"
)
for line in C_HEADER.read_text(encoding="utf-8").splitlines():
match = pattern.match(line)
if match is not None:
values[match.group(1)] = int(match.group(2), 16)
values["CAPABILITY0_IMPLEMENTED"] = values["CAPABILITY0_P7_IMPLEMENTED"]
values["CAPABILITY1_IMPLEMENTED"] = values["CAPABILITY1_P7_IMPLEMENTED"]
return values
def _hex_literal(pattern: str, text: str) -> int:
match = re.search(pattern, text)
if match is None:
raise ValueError(f"pattern not found in {RTL_REGISTER.name}: {pattern}")
return int(match.group(1).replace("_", ""), 16)
def _rtl_discovery_values() -> dict[str, int]:
"""Extract the release-selected discovery localparams from apu_reg.sv."""
text = RTL_REGISTER.read_text(encoding="utf-8")
return {
"IP_ID_VALUE": _hex_literal(
r"localparam logic \[31:0\] IpId = 32'h([0-9a-fA-F_]+);", text
),
"IP_VERSION_VALUE": _hex_literal(
r"localparam logic \[31:0\] IpVersion = EnableP7 \? 32'h([0-9a-fA-F_]+) :",
text,
),
"CAPABILITY0_IMPLEMENTED": _hex_literal(
r"localparam logic \[31:0\] Capability0 = EnableP7 \? 32'h([0-9a-fA-F_]+) :",
text,
),
"CAPABILITY1_IMPLEMENTED": _hex_literal(
r"localparam logic \[31:0\] Capability1 = \(EnableP5 \|\| EnableP7\) \? "
r"32'h([0-9a-fA-F_]+) :",
text,
),
"IRQ_ALL": _hex_literal(
r"localparam logic \[11:0\] IrqMask = EnableP7 \? 12'h([0-9a-fA-F_]+) :",
text,
),
}
def _c_enum_values(enum_name: str) -> dict[str, int]:
"""Parse a simple `typedef enum { NAME = n, ... } name;` block from apu.h."""
text = C_HAL.read_text(encoding="utf-8")
match = re.search(r"typedef enum \{([^}]*)\}\s+" + enum_name + r"\s*;", text)
if match is None:
raise ValueError(f"enum {enum_name} not found in {C_HAL.name}")
return {
name: int(value)
for name, value in re.findall(r"(\w+)\s*=\s*(\d+)\s*,?", match.group(1))
}
def _manifest_rtl_exact() -> dict[str, int]:
"""Map every apu_isa manifest value to its handwritten SVH define name."""
manifest = abi_manifest()
exact = {
"APUMC_MAGIC": manifest["apumc"]["magic"],
"APUMC_ABI": manifest["apumc"]["abi"],
"APUMC_ABI_V1": manifest["apumc"]["abi_v1"],
"APUMC_ABI_V2": manifest["apumc"]["abi_v2"],
"APUMC_V1": manifest["apumc"]["abi_v1"],
"APUMC_V2": manifest["apumc"]["abi_v2"],
"APUMC_HEADER_BYTES": manifest["apumc"]["header_bytes"],
"APUMC_ENTRY_BYTES": manifest["apumc"]["entry_bytes"],
"APUMC_ENTRY_COUNT": manifest["apumc"]["entry_count"],
"APUMC_MAX_INSTRUCTIONS": manifest["apumc"]["max_instructions"],
"APUMC_MAX_INSTRUCTIONS_V1": manifest["apumc"]["max_instructions_v1"],
"APUMC_MAX_INSTRUCTIONS_V2": manifest["apumc"]["max_instructions_v2"],
"APUMC_P3_PRIMITIVE_MASK": manifest["p3_implemented_primitive_mask"],
"APUMC_P4_PRIMITIVE_MASK": manifest["p4_implemented_primitive_mask"],
"APUMC_P5_PRIMITIVE_MASK": manifest["p5_implemented_primitive_mask"],
"LOCAL_DATA_BYTES": manifest["p4_local_data_bytes"],
"LOCAL_TABLE_SCRATCH_BYTES": manifest["p4_local_table_scratch_bytes"],
}
for name, value in manifest["classes"].items():
exact[f"MC_CLASS_{name.upper()}"] = value
for name, value in manifest["control_opcodes"].items():
exact[f"MC_CONTROL_{name.upper()}"] = value
for name, value in manifest["scalar_opcodes"].items():
exact[f"MC_SCALAR_{name.upper()}"] = value
for class_name, opcodes in manifest["deferred_opcodes"].items():
for name, value in opcodes.items():
exact[f"MC_{class_name.upper()}_{name.upper()}"] = value
predicate_names = {
"always": "ALWAYS",
"eq": "EQ",
"ne": "NE",
"slt": "SIGNED_LT",
"sge": "SIGNED_GE",
"ult": "UNSIGNED_LT",
"uge": "UNSIGNED_GE",
"input_exhausted": "INPUT_EXHAUSTED",
"input_ready": "INPUT_READY",
"output_ready": "OUTPUT_READY",
"kernel_done": "KERNEL_DONE",
"transport_done": "TRANSPORT_DONE",
}
for name, value in manifest["predicates"].items():
exact[f"MC_PRED_{predicate_names[name]}"] = value
for name, value in manifest["wait_sources"].items():
exact[f"MC_WAIT_{name.upper()}"] = value
for name, value in manifest["primitives"].items():
exact[f"MC_PRIMITIVE_{name.upper()}"] = value
for name, value in manifest["instruction_fields"].items():
exact[f"MC_INSTRUCTION_{name.upper()}"] = value
for name, value in manifest["entry_words"].items():
exact[f"APUMC_ENTRY_{name.upper()}"] = value
trap_names = {
"illegal": "ILLEGAL",
"pc_range": "PC_RANGE",
"call_stack": "CALL_STACK",
"loop": "LOOP",
"local_range": "LOCAL_RANGE",
"unavailable": "UNAVAILABLE",
"watchdog": "WATCHDOG",
"retired_budget": "RETIRED_BUDGET",
"engine": "ENGINE",
"explicit": "EXPLICIT",
}
for name, value in manifest["trap_reasons"].items():
if name != "reserved":
exact[f"MC_TRAP_{trap_names[name]}"] = value
return exact
def _require_equal(what: str, left: dict[str, int], right: dict[str, int]) -> None:
if left != right:
only_left = sorted(set(left) - set(right))
only_right = sorted(set(right) - set(left))
differ = sorted(key for key in set(left) & set(right) if left[key] != right[key])
raise ValueError(f"{what} drift: only-left {only_left}, only-right {only_right}, "
f"differing {[(key, left[key], right[key]) for key in differ]}")
def _mp3_stub_section(apb: dict[str, int]) -> dict[str, int]:
"""Reconstruct the reserved-MP3 APUMC stub contract from the released image."""
lines = RTL_MICROCODE.read_text(encoding="utf-8").splitlines()
entry_lines = [line for line in lines if line.startswith(".entry ")]
if len(entry_lines) != 3:
raise ValueError(f"{RTL_MICROCODE.name} must declare exactly three .entry lines")
mp3_tokens = entry_lines[1].split()
if mp3_tokens[:5] != [".entry", "1", "mp3", "mp3", "mp3"]:
raise ValueError(f"unexpected MP3 entry directive: {entry_lines[1]}")
numbers = [int(token, 0) for token in mp3_tokens[5:]]
if numbers != [1, 1, 0, 0, 0, 0, 0]:
raise ValueError(f"unexpected MP3 stub entry fields: {entry_lines[1]}")
try:
label_index = lines.index("mp3:")
except ValueError as error:
raise ValueError("mp3 label missing from released microcode image") from error
body = [line.strip() for line in lines[label_index + 1:] if line.strip()]
trap_match = re.fullmatch(r"trap 0x([0-9a-fA-F]+)", body[0])
if trap_match is None:
raise ValueError(f"mp3 label must be followed by its trap stub, found: {body[0]}")
trap_detail = int(trap_match.group(1), 16)
if trap_detail != 0x01000001:
raise ValueError(f"unexpected MP3 stub trap detail 0x{trap_detail:08x}")
stub = Entry(
format_id=1,
entry_pc=0,
first_pc=0,
last_pc=0,
max_loop_count=numbers[0],
max_retired=numbers[1],
scratch_base=numbers[2],
scratch_bytes=numbers[3],
primitive_mask=numbers[4],
table_offset=numbers[5],
table_bytes=numbers[6],
)
words = stub.words(APUMC_ABI_V2)
if words != (0x00000001, 0, 0, 0, 1, 1, 0, 0):
raise ValueError(f"MP3 stub entry words drifted: {[hex(word) for word in words]}")
trap_instruction = Instruction(
instruction_class=int(InstructionClass.CONTROL),
opcode=int(ControlOpcode.TRAP),
immediate=trap_detail,
).encode()
if trap_instruction != 0x0200000001000001:
raise ValueError(f"MP3 stub trap instruction drifted: 0x{trap_instruction:016x}")
section = {
"format_id": 1,
"capability_bit": apb["CAPABILITY0_MP3"],
"entry_count": apb["APUMC_ENTRY_COUNT"],
"entry_index": 1,
"trap_instruction": trap_instruction,
"trap_detail": trap_detail,
"rejection_error_code": apb["ERROR_CODE_UNSUPPORTED"],
"rejection_error_stage": apb["ERROR_STAGE_RING"],
"rejection_detail": trap_detail,
}
for index, word in enumerate(words):
section[f"entry_words[{index}]"] = word
return section
def canonical_inventory() -> dict[str, dict[str, int]]:
"""Build the complete frozen V1 table inventory, failing on any drift."""
rtl = _rtl_defines()
apb = {
name.removeprefix("APB4_APU__"): value
for name, value in rtl.items()
if name.startswith("APB4_APU__")
}
kws_model = {
name.removeprefix("RETROSOC_APU_KWS__"): value
for name, value in rtl.items()
if name.startswith("RETROSOC_APU_KWS__")
}
_require_equal("APB define/C mirror", apb, _c_abi_defines())
_require_equal("ISA manifest/SVH", _manifest_rtl_exact(),
{name: apb[name] for name in _manifest_rtl_exact()})
_require_equal(
"APUM container/SVH",
{
"APUM_MAGIC": apu_kws.APUM_MAGIC,
"APUM_ABI": apu_kws.APUM_ABI,
"APUM_IMAGE_BYTES": apu_kws.APUM_BYTES,
"APUM_PAYLOAD_CRC": apu_kws.APUM_PAYLOAD_CRC,
},
{name: apb[name] for name in ("APUM_MAGIC", "APUM_ABI", "APUM_IMAGE_BYTES",
"APUM_PAYLOAD_CRC")},
)
_require_equal(
"APUC container/SVH",
{
"APUC_MAGIC": apu_kws_coeff.APUC_MAGIC,
"APUC_ABI": apu_kws_coeff.APUC_ABI,
"APUC_IMAGE_BYTES": apu_kws_coeff.APUC_BYTES,
"APUC_HEADER_BYTES": apu_kws_coeff.APUC_HEADER_BYTES,
"APUC_PAYLOAD_BYTES": apu_kws_coeff.APUC_PAYLOAD_BYTES,
"APUC_PAYLOAD_CRC": apu_kws_coeff.APUC_PAYLOAD_CRC,
"APUC_COEFFICIENT_ID_LO": apu_kws_coeff.APUC_COEFFICIENT_ID[0],
"APUC_COEFFICIENT_ID_HI": apu_kws_coeff.APUC_COEFFICIENT_ID[1],
},
{
name: apb[name]
for name in (
"APUC_MAGIC",
"APUC_ABI",
"APUC_IMAGE_BYTES",
"APUC_HEADER_BYTES",
"APUC_PAYLOAD_BYTES",
"APUC_PAYLOAD_CRC",
"APUC_COEFFICIENT_ID_LO",
"APUC_COEFFICIENT_ID_HI",
)
},
)
discovery_c = _c_discovery_defines()
discovery_rtl = _rtl_discovery_values()
_require_equal("discovery C/RTL", discovery_rtl,
{name: discovery_c[name] for name in discovery_rtl})
formats = {name.lower().removeprefix("rs_apu_"): value
for name, value in _c_enum_values("rs_apu_format_t").items()}
output_modes = {name.lower().removeprefix("rs_apu_"): value
for name, value in _c_enum_values("rs_apu_output_t").items()}
pcm_formats = {name.lower().removeprefix("rs_apu_"): value
for name, value in _c_enum_values("rs_apu_pcm_t").items()}
_require_equal(
"format ID/capability bit",
{name: formats[name] for name in ("wav", "mp3", "flac")},
{name: apb[f"CAPABILITY0_{name.upper()}"] for name in ("wav", "mp3", "flac")},
)
manifest = abi_manifest()
isa: dict[str, int] = {}
for group in ("classes", "control_opcodes", "scalar_opcodes", "predicates",
"wait_sources", "primitives", "trap_reasons", "instruction_fields",
"entry_words"):
for name, value in manifest[group].items():
isa[f"{group}.{name}"] = value
for class_name, opcodes in manifest["deferred_opcodes"].items():
for name, value in opcodes.items():
isa[f"deferred_opcodes.{class_name}.{name}"] = value
for phase in ("p3", "p4", "p5"):
isa[f"implemented_primitive_mask.{phase}"] = manifest[
f"{phase}_implemented_primitive_mask"]
isa["local_memory.p4_data_bytes"] = manifest["p4_local_data_bytes"]
isa["local_memory.p4_table_scratch_bytes"] = manifest["p4_local_table_scratch_bytes"]
apumc = {name: value for name, value in manifest["apumc"].items()}
apum = {
"magic": apu_kws.APUM_MAGIC,
"abi": apu_kws.APUM_ABI,
"image_bytes": apu_kws.APUM_BYTES,
"header_bytes": apu_kws.APUM_HEADER_BYTES,
"operator_offset": apu_kws.APUM_OPERATOR_OFFSET,
"operator_count": apu_kws.APUM_OPERATOR_COUNT,
"operator_bytes": apu_kws.APUM_OPERATOR_BYTES,
"tensor_offset": apu_kws.APUM_TENSOR_OFFSET,
"tensor_count": apu_kws.APUM_TENSOR_COUNT,
"tensor_bytes": apu_kws.APUM_TENSOR_BYTES,
"parameter_offset": apu_kws.APUM_PARAMETER_OFFSET,
"parameter_bytes": apu_kws.APUM_PARAMETER_BYTES,
"parameter_end": apu_kws.APUM_PARAMETER_END,
"payload_crc": apu_kws.APUM_PAYLOAD_CRC,
}
apuc = {
"magic": apu_kws_coeff.APUC_MAGIC,
"abi": apu_kws_coeff.APUC_ABI,
"image_bytes": apu_kws_coeff.APUC_BYTES,
"header_bytes": apu_kws_coeff.APUC_HEADER_BYTES,
"payload_bytes": apu_kws_coeff.APUC_PAYLOAD_BYTES,
"profile": apu_kws_coeff.APUC_PROFILE,
"layout_id": apu_kws_coeff.APUC_LAYOUT_ID,
"bank_count": apu_kws_coeff.APUC_BANK_COUNT,
"table_count": apu_kws_coeff.APUC_TABLE_COUNT,
"payload_crc": apu_kws_coeff.APUC_PAYLOAD_CRC,
"coefficient_id_low": apu_kws_coeff.APUC_COEFFICIENT_ID[0],
"coefficient_id_high": apu_kws_coeff.APUC_COEFFICIENT_ID[1],
"associated_apum_crc": apu_kws_coeff.APUC_APUM_PAYLOAD_CRC,
"ln2_q24": apu_kws_coeff.APUC_LN2_Q24,
}
discovery = {
"ip_id": discovery_c["IP_ID_VALUE"],
"ip_version": discovery_c["IP_VERSION_VALUE"],
"capability0": discovery_c["CAPABILITY0_IMPLEMENTED"],
"capability0_p7": discovery_c["CAPABILITY0_P7_IMPLEMENTED"],
"capability1": discovery_c["CAPABILITY1_IMPLEMENTED"],
"capability1_p7": discovery_c["CAPABILITY1_P7_IMPLEMENTED"],
"irq_all": discovery_c["IRQ_ALL"],
}
format_section = {f"format.{name}": value for name, value in formats.items()}
format_section.update({f"output_mode.{name}": value
for name, value in output_modes.items()})
format_section.update({f"pcm_format.{name}": value
for name, value in pcm_formats.items()})
format_section["operation.file_decode"] = 0
format_section["operation.kws_memory_window"] = 1
inventory = {
"discovery": discovery,
"apb": apb,
"kws_model": kws_model,
"formats": format_section,
"apumc": apumc,
"apum": apum,
"apuc": apuc,
"isa": isa,
"mp3_stub": _mp3_stub_section(apb),
}
if tuple(inventory) != SECTION_ORDER:
raise ValueError("section order drift")
return inventory
def serialize_inventory(inventory: dict[str, dict[str, int]]) -> bytes:
"""Serialize the inventory to the documented release-identity byte stream."""
return json.dumps(
inventory,
ensure_ascii=True,
separators=(",", ":"),
sort_keys=True,
).encode("ascii")
def compute_abi_digest() -> int:
"""Return the CRC-32/ISO-HDLC digest over the serialized V1 tables."""
return binascii.crc32(serialize_inventory(canonical_inventory())) & 0xFFFFFFFF
def rtl_implemented_digest() -> int:
"""Extract the handwritten AbiDigest localparam from apu_reg.sv."""
text = RTL_REGISTER.read_text(encoding="utf-8")
match = re.search(
r"localparam logic \[31:0\] AbiDigest = EnableP7 \? "
r"32'h([0-9a-fA-F_]+) : 32'd0;",
text,
)
if match is None:
raise ValueError(f"AbiDigest localparam not found in {RTL_REGISTER.name}")
return int(match.group(1).replace("_", ""), 16)
def c_implemented_digest() -> int:
"""Extract the handwritten P9 release digest from apu_regs.h."""
text = C_HEADER.read_text(encoding="utf-8")
match = re.search(
r"^#define\s+RS_APU_DIGEST_P9_IMPLEMENTED\s+UINT32_C\((0[xX][0-9a-fA-F]+)\)\s*$",
text,
re.MULTILINE,
)
if match is None:
raise ValueError(f"RS_APU_DIGEST_P9_IMPLEMENTED not found in {C_HEADER.name}")
return int(match.group(1), 16)
def main(argv: list[str] | None = None) -> int:
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("--check", action="store_true",
help="require RTL/C handwritten constants to match the computed digest")
parser.add_argument("--json", metavar="PATH",
help="dump the canonical inventory as JSON for review")
args = parser.parse_args(argv)
try:
inventory = canonical_inventory()
digest = binascii.crc32(serialize_inventory(inventory)) & 0xFFFFFFFF
except ValueError as error:
print(f"apu_abi_digest: error: {error}", file=sys.stderr)
return 1
if args.json is not None:
payload = {
"digest": f"0x{digest:08x}",
"serialization": "canonical ASCII JSON; recursively sorted keys; compact "
"separators; decimal integers; no terminal newline; "
"CRC-32/ISO-HDLC",
"sections": {
name: {key: f"0x{value:016x}" for key, value in sorted(entries.items())}
for name, entries in inventory.items()
},
}
Path(args.json).write_text(json.dumps(payload, indent=2) + "\n", encoding="utf-8")
print(f"wrote canonical inventory to {args.json}")
print(f"0x{digest:08x}")
for name in SECTION_ORDER:
print(f" {name}: {len(inventory[name])} entries")
print(f" serialized: {len(serialize_inventory(inventory))} bytes")
if args.check:
try:
rtl_digest = rtl_implemented_digest()
c_digest = c_implemented_digest()
except ValueError as error:
print(f"apu_abi_digest: error: {error}", file=sys.stderr)
return 1
ok = digest != 0 and rtl_digest == digest and c_digest == digest
print(f" computed: 0x{digest:08x}")
print(f" apu_reg.sv: 0x{rtl_digest:08x}")
print(f" apu_regs.h: 0x{c_digest:08x}")
if not ok:
print("apu_abi_digest: FAIL: digest mismatch or zero digest", file=sys.stderr)
return 1
print("apu_abi_digest: OK: computed digest matches both handwritten constants")
return 0
if __name__ == "__main__":
raise SystemExit(main())