docs: rebuild system report and test workflow

This commit is contained in:
2026-07-15 03:48:19 +08:00
parent 1b51f28263
commit 2359e7976c
21 changed files with 1176 additions and 516 deletions

View File

@@ -0,0 +1,175 @@
#!/usr/bin/env python3
"""Refresh report_metrics.tex from versioned Vivado and test evidence."""
from __future__ import annotations
import datetime as dt
import re
import subprocess
from pathlib import Path
REPORT_DIR = Path(__file__).resolve().parent.parent
ROOT = REPORT_DIR.parents[2]
METRICS = REPORT_DIR / "report_metrics.tex"
def read_first(*paths: Path) -> str:
for path in paths:
if path.is_file():
return path.read_text(encoding="utf-8", errors="replace")
return ""
def set_macro(text: str, name: str, value: str) -> str:
pattern = re.compile(r"(\\newcommand\{\\" + re.escape(name) + r"\}\{)[^\n}]*(\})")
if not pattern.search(text):
raise ValueError(f"macro not found: {name}")
return pattern.sub(lambda m: m.group(1) + value + m.group(2), text, count=1)
def number(value: str) -> str:
raw = value.replace(",", "").strip()
if re.fullmatch(r"-?\d+", raw):
return f"{int(raw):,}"
return raw
def capture(pattern: str, text: str, group: int = 1) -> str | None:
match = re.search(pattern, text, re.MULTILINE)
return match.group(group).strip() if match else None
def update_timing(metrics: str, timing: str) -> str:
summary = capture(r"^\s*(-?\d+\.\d+)\s+-?\d+\.\d+\s+\d+\s+\d+\s+", timing)
if summary:
metrics = set_macro(metrics, "OverallWNS", summary)
for clock, prefix in (("cpu_clk", "Cpu"), ("sys_clk", "Sys"), ("mlkem_clk", "Mlkem")):
freq = capture(r"^\s*" + clock + r"\s+\{[^\n]+?\}\s+\d+\.\d+\s+(\d+\.\d+)", timing)
wns = capture(r"^\s*" + clock + r"\s+(-?\d+\.\d+)\s+", timing)
if freq:
metrics = set_macro(metrics, prefix + "FreqMHz", freq)
if wns:
metrics = set_macro(metrics, prefix + "WNS", wns)
return metrics
def update_util(metrics: str, util: str) -> str:
rows = {
"SliceLUTs": r"^\| Slice LUTs\s+\|\s*([\d,]+)\s*\|",
"SliceFFs": r"^\| Slice Registers\s+\|\s*([\d,]+)\s*\|",
"BramTiles": r"^\| Block RAM Tile\s+\|\s*([\d.]+)\s*\|",
"DspCount": r"^\| DSPs?\s+\|\s*([\d,]+)\s*\|",
}
pct = {
"SliceLUTPct": r"^\| Slice LUTs\s+\|[^\n]*\|\s*([\d.]+)\s*\|\s*$",
"SliceFFPct": r"^\| Slice Registers\s+\|[^\n]*\|\s*([\d.]+)\s*\|\s*$",
"BramPct": r"^\| Block RAM Tile\s+\|[^\n]*\|\s*([\d.]+)\s*\|\s*$",
}
for name, pattern in rows.items():
value = capture(pattern, util)
if value:
metrics = set_macro(metrics, name, number(value))
for name, pattern in pct.items():
value = capture(pattern, util)
if value:
metrics = set_macro(metrics, name, value + r"\%")
return metrics
def update_power(metrics: str, power: str) -> str:
mappings = {
"TotalPower": r"\| Total On-Chip Power \(W\)\s*\|\s*([\d.]+)",
"DynamicPower": r"\| Dynamic \(W\)\s*\|\s*([\d.]+)",
"StaticPower": r"\| Device Static \(W\)\s*\|\s*([\d.]+)",
}
for name, pattern in mappings.items():
value = capture(pattern, power)
if value:
metrics = set_macro(metrics, name, value)
confidence = capture(r"\| Confidence Level\s*\|\s*([^|]+)\|", power)
if confidence:
metrics = set_macro(metrics, "PowerConfidence", confidence + "(未回标活动率)")
return metrics
def update_mlkem(metrics: str, log: str) -> str:
names = {
("512", "SW"): "MlkemFiveTwelveSW",
("512", "CPU"): "MlkemFiveTwelveCPU",
("512", "DMA"): "MlkemFiveTwelveDMA",
("768", "SW"): "MlkemSevenSixtyEightSW",
("768", "CPU"): "MlkemSevenSixtyEightCPU",
("768", "DMA"): "MlkemSevenSixtyEightDMA",
("1024", "SW"): "MlkemTenTwentyFourSW",
("1024", "CPU"): "MlkemTenTwentyFourCPU",
("1024", "DMA"): "MlkemTenTwentyFourDMA",
}
for (level, path), name in names.items():
value = capture(rf"^MLKEM{level}_{path}_ONLY_AVG_CYCLES=(\d+)$", log)
if value:
metrics = set_macro(metrics, name, number(value))
return metrics
def update_fft(metrics: str, cpu_log: str, dma_log: str) -> str:
cpu_ns = capture(r"Hardware FFT Time:\s*(\d+) ns", cpu_log)
dma_ns = capture(r"Hardware FFT Time:\s*(\d+) ns", dma_log)
sw_ns = capture(r"Software FFT Time:\s*(\d+) ns", dma_log or cpu_log)
cpu_speed = capture(r"Speedup Ratio\s*:\s*([\d.]+)x", cpu_log)
dma_speed = capture(r"Speedup Ratio\s*:\s*([\d.]+)x", dma_log)
for name, value in (("FftCpuNs", cpu_ns), ("FftDmaNs", dma_ns), ("FftSoftwareNs", sw_ns)):
if value:
metrics = set_macro(metrics, name, number(value))
for name, value in (("FftCpuSpeedup", cpu_speed), ("FftDmaSpeedup", dma_speed)):
if value:
metrics = set_macro(metrics, name, value)
err_match = re.search(r"Max Abs Error\s*:\s*Re=(\d+),\s*Im=(\d+)", dma_log)
if err_match:
metrics = set_macro(metrics, "FftMaxError", f"Re={err_match.group(1)},Im={err_match.group(2)}")
return metrics
def update_matmul(metrics: str, log: str) -> str:
elapsed = capture(r"Elapsed time:\s*([\d.]+) ms", log)
score = capture(r"Score percent:\s*([\d.]+)", log)
if elapsed:
metrics = set_macro(metrics, "MatmulBoardMs", elapsed)
if score:
metrics = set_macro(metrics, "MatmulScore", score + r"\%")
if "CRC32 matched." in log:
metrics = set_macro(metrics, "MatmulCrcStatus", "PASS")
return metrics
def main() -> None:
metrics = METRICS.read_text(encoding="utf-8")
vivado = REPORT_DIR / "evidence" / "vivado"
legacy = ROOT / "fpga" / "vivado_reports_nodsp"
metrics = update_timing(metrics, read_first(vivado / "timing_summary_full.rpt", legacy / "timing_summary_full.rpt"))
metrics = update_util(metrics, read_first(vivado / "utilization.rpt", legacy / "utilization.rpt"))
metrics = update_power(metrics, read_first(vivado / "power.rpt", legacy / "power.rpt"))
soc = REPORT_DIR / "evidence" / "soc"
metrics = update_mlkem(metrics, read_first(soc / "mlkem_test.log", ROOT / "sdk/software/examples/mlkem_test/result.txt"))
metrics = update_fft(
metrics,
read_first(soc / "fft.log", ROOT / "sdk/software/examples/fft/result.txt"),
read_first(soc / "fft_dma.log", ROOT / "sdk/software/examples/fft_dma/result.txt"),
)
metrics = update_matmul(metrics, read_first(REPORT_DIR / "evidence" / "board" / "matmul_official.log"))
try:
revision = subprocess.check_output(
["git", "-C", str(ROOT), "rev-parse", "--short", "HEAD"], text=True
).strip()
except (OSError, subprocess.CalledProcessError):
revision = "unknown"
source = "当前 evidence" if (vivado / "timing_summary_full.rpt").is_file() else "历史报告,待复测"
metrics = set_macro(metrics, "EvidenceRevision", f"{revision}({source})")
metrics = set_macro(metrics, "EvidenceDate", dt.date.today().isoformat())
METRICS.write_text(metrics, encoding="utf-8")
print(f"updated {METRICS}")
if __name__ == "__main__":
main()