docs: rebuild system report and test workflow
This commit is contained in:
175
doc/reports/system/tools/update_report_metrics.py
Executable file
175
doc/reports/system/tools/update_report_metrics.py
Executable 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()
|
||||
Reference in New Issue
Block a user