This repository has been archived on 2026-08-21. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
ciciec2026/doc/reports/system/tools/update_report_metrics.py

176 lines
6.9 KiB
Python
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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()