Files

229 lines
9.3 KiB
Python

"""Frozen APUC 1.0 coefficient-container definitions and validator."""
from __future__ import annotations
import hashlib
import struct
import zlib
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Final
APUC_MAGIC: Final = 0x43555041
APUC_ABI: Final = 0x00010000
APUC_HEADER_BYTES: Final = 64
APUC_PAYLOAD_BYTES: Final = 15 * 1024 * 4
APUC_BYTES: Final = APUC_HEADER_BYTES + APUC_PAYLOAD_BYTES
APUC_PROFILE: Final = 1
APUC_LAYOUT_ID: Final = 1
APUC_BANK_COUNT: Final = 15
APUC_TABLE_COUNT: Final = 10
APUC_PAYLOAD_CRC: Final = 0x25E7C27D
APUC_COEFFICIENT_ID: Final = (0x8A0B038D, 0x806C780D)
APUC_APUM_PAYLOAD_CRC: Final = 0xB9034B22
APUC_LN2_Q24: Final = 11629080
APUC_LOGICAL_SHA256: Final = "f416585119e488c82e5cea1dabb17424e33a16dd8041393135903a613edced46"
APUC_PAYLOAD_SHA256: Final = "8d030b8a0d786c80a7fa072439700a4485711971d79a6a62e78fcafb1657d83a"
APUC_SHA256: Final = "d670688442ea6efe1e638108157f55fc55c75218c8edbee931aaca3497567818"
APUC_TABLES: Final = {
"hann": (480, "5fb8748b60ff53c721d3f97c2516b9f77aebd8bb15103a072219505769d4af61"),
"twiddle_real": (256, "49faa57826aa487032eef985d7d25f29b5346692f22ab9780209cb118c9c5b8a"),
"twiddle_imag": (256, "3552f5ed86694388ee674dcb988509e1f8fec5c61c7435e289a27591a2c817ec"),
"mel": (10280, "a13dc9eaf00e6e2be9710c4a1bd432239c0181b5d2ee9903250c667d906f16be"),
"dct": (400, "93a71d890110064feb515cb7527740ce3ff0116451931e27925ac1c36339153e"),
"log": (1025, "20b0b265de2c9e319dc70b2fa0ee136add8693937108c5f0c65ccc0ca146050a"),
"fir3": (63, "77ccbd59d8346f709d299ce762c1976dd8abbba3263d47ed65ef394db570870a"),
"fir6": (63, "a00ceab9105dddbed5e6d2426753dd5dbc99c9b0c166b1f19f43c530ceb9faac"),
"softmax": (125, "410f11dd7f10d5516b41efdec861afe6a7f2a34dd116163f7758ac5110b65ae7"),
"apum_profile": (
1496,
"4c30d9a5e054436bab5acfb4651ea361d0211956c9211e9d36d182b9fdc88991",
),
}
def _expected_locations() -> dict[str, list[tuple[int, int]]]:
locations: dict[str, list[tuple[int, int]]] = {
name: [] for name in APUC_TABLES
}
locations["hann"] = [(0, index) for index in range(480)]
locations["dct"] = [(0, 480 + index) for index in range(400)]
locations["fir3"] = [(0, 880 + index) for index in range(63)]
locations["fir6"] = [(0, 943 + index) for index in range(63)]
locations["twiddle_real"] = [(1, index) for index in range(256)]
locations["twiddle_imag"] = [(2, index) for index in range(256)]
locations["log"] = [(1 + (index & 1), 256 + (index >> 1)) for index in range(1025)]
locations["mel"] = [
(3 + (index >> 10), index & 1023) if index < 10240 else (13, index - 10240)
for index in range(10280)
]
locations["apum_profile"] = [
(13, 40 + index) if index < 984 else (14, index - 984)
for index in range(1496)
]
locations["softmax"] = [(14, 512 + index) for index in range(125)]
return locations
def _expected_profile_offsets() -> list[int]:
offsets = list(range(0, 0x500, 4))
for start in (0x1000, 0x1540, 0x2840, 0x2D80, 0x4080, 0x45C0, 0x58C0, 0x5E00, 0x7100):
offsets.extend(range(start, start + 0x200, 4))
offsets.extend(range(0x7630, 0x7690, 4))
return offsets
def validate_layout_manifest(image: bytes, manifest: dict[str, Any]) -> None:
"""Validate the generated layout with a frozen oracle independent of its packer."""
validate_apuc(image)
if manifest.get("schema_version") != 1:
raise ValueError("APUC layout manifest schema version is not 1")
generator = manifest.get("generator")
if not isinstance(generator, dict):
raise ValueError("APUC layout manifest lacks generator identity")
generator_path = generator.get("path")
generator_sha = generator.get("sha256")
generator_revision = generator.get("revision")
repository_commit = generator.get("repository_commit")
if generator_path != "scripts/generate_apu_kws_rtl_constants.py" or not isinstance(
generator_sha, str
):
raise ValueError("APUC generator path/SHA identity is incomplete")
if generator_revision != f"sha256:{generator_sha}":
raise ValueError("APUC generator revision does not match its source SHA")
if (
not isinstance(repository_commit, str)
or len(repository_commit) != 40
or any(character not in "0123456789abcdef" for character in repository_commit.lower())
):
raise ValueError("APUC generator repository revision is not a full Git commit")
source = Path(__file__).resolve().parents[1] / generator_path
if not source.is_file() or hashlib.sha256(source.read_bytes()).hexdigest() != generator_sha:
raise ValueError("APUC generator source does not match the recorded revision")
expected_container = {
"magic": f"0x{APUC_MAGIC:08x}",
"abi": f"0x{APUC_ABI:08x}",
"bytes": APUC_BYTES,
"header_bytes": APUC_HEADER_BYTES,
"payload_bytes": APUC_PAYLOAD_BYTES,
"payload_crc32": f"0x{APUC_PAYLOAD_CRC:08x}",
"coefficient_id": [f"0x{word:08x}" for word in APUC_COEFFICIENT_ID],
"associated_apum_crc32": f"0x{APUC_APUM_PAYLOAD_CRC:08x}",
"logical_sha256": APUC_LOGICAL_SHA256,
"payload_sha256": APUC_PAYLOAD_SHA256,
"sha256": APUC_SHA256,
}
if manifest.get("container") != expected_container:
raise ValueError("APUC container manifest does not match the frozen identity")
if manifest.get("layout") != {
"geometry": "tc_sram_1024x32",
"banks": APUC_BANK_COUNT,
"words_per_bank": 1024,
"populated_words": 14444,
"zero_padding_words": 916,
}:
raise ValueError("APUC physical layout summary does not match the frozen geometry")
if manifest.get("apum_profile_offsets") != _expected_profile_offsets():
raise ValueError("APUC APUM-profile offsets do not match the independent oracle")
table_records = manifest.get("tables")
if not isinstance(table_records, dict) or set(table_records) != set(APUC_TABLES):
raise ValueError("APUC layout manifest does not contain the exact ten tables")
payload_words = struct.unpack(f"<{APUC_BANK_COUNT * 1024}I", image[APUC_HEADER_BYTES:])
expected_locations = _expected_locations()
occupied: set[tuple[int, int]] = set()
for name, (word_count, frozen_sha) in APUC_TABLES.items():
record = table_records.get(name)
locations = expected_locations[name]
expected_record = {
"words": word_count,
"sha256_le32": frozen_sha,
"locations": [[bank, row] for bank, row in locations],
}
if record != expected_record:
raise ValueError(f"APUC table {name} does not match the independent map/hash oracle")
if any(location in occupied for location in locations):
raise ValueError(f"APUC table {name} overlaps another table")
occupied.update(locations)
table_bytes = b"".join(
struct.pack("<I", payload_words[(bank * 1024) + row]) for bank, row in locations
)
if hashlib.sha256(table_bytes).hexdigest() != frozen_sha:
raise ValueError(f"APUC payload words for {name} do not match the frozen hash")
if len(occupied) != 14444:
raise ValueError("APUC independent populated-position count is not 14444")
for bank in range(APUC_BANK_COUNT):
for row in range(1024):
if (bank, row) not in occupied and payload_words[(bank * 1024) + row] != 0:
raise ValueError(f"APUC padding is nonzero at bank {bank} row {row}")
@dataclass(frozen=True)
class ApucHeader:
magic: int
abi: int
total_bytes: int
payload_offset: int
payload_bytes: int
profile: int
layout_id: int
bank_count: int
table_count: int
payload_crc: int
coefficient_id: tuple[int, int]
associated_apum_crc: int
ln2_q24: int
def validate_apuc(image: bytes) -> ApucHeader:
"""Validate exact APUC 1.0 bytes and return the frozen header."""
if len(image) != APUC_BYTES:
raise ValueError(f"APUC image must be exactly {APUC_BYTES} bytes")
words = struct.unpack_from("<16I", image)
expected = (
APUC_MAGIC,
APUC_ABI,
APUC_BYTES,
APUC_HEADER_BYTES,
APUC_PAYLOAD_BYTES,
APUC_PROFILE,
APUC_LAYOUT_ID,
APUC_BANK_COUNT,
APUC_TABLE_COUNT,
APUC_PAYLOAD_CRC,
*APUC_COEFFICIENT_ID,
APUC_APUM_PAYLOAD_CRC,
APUC_LN2_Q24,
0,
0,
)
if words != expected:
raise ValueError("APUC header does not match the frozen profile")
payload = image[APUC_HEADER_BYTES:]
if zlib.crc32(payload) & 0xFFFFFFFF != APUC_PAYLOAD_CRC:
raise ValueError("APUC payload CRC mismatch")
if hashlib.sha256(payload).hexdigest() != APUC_PAYLOAD_SHA256:
raise ValueError("APUC payload SHA-256 mismatch")
if hashlib.sha256(image).hexdigest() != APUC_SHA256:
raise ValueError("APUC image SHA-256 mismatch")
return ApucHeader(
magic=words[0],
abi=words[1],
total_bytes=words[2],
payload_offset=words[3],
payload_bytes=words[4],
profile=words[5],
layout_id=words[6],
bank_count=words[7],
table_count=words[8],
payload_crc=words[9],
coefficient_id=(words[10], words[11]),
associated_apum_crc=words[12],
ln2_q24=words[13],
)