docs: rebuild system report and test workflow
This commit is contained in:
45
doc/reports/system/tools/collect_report_evidence.tcl
Normal file
45
doc/reports/system/tools/collect_report_evidence.tcl
Normal file
@@ -0,0 +1,45 @@
|
||||
# Usage from repository root:
|
||||
# vivado -mode batch -source doc/reports/system/tools/collect_report_evidence.tcl
|
||||
|
||||
set script_dir [file dirname [file normalize [info script]]]
|
||||
set repo_root [file normalize [file join $script_dir ../../../..]]
|
||||
set xpr [file join $repo_root fpga project Loongson_Soc.xpr]
|
||||
set out_dir [file join $repo_root doc reports system evidence vivado]
|
||||
file mkdir $out_dir
|
||||
|
||||
if {![file exists $xpr]} {
|
||||
puts stderr "ERROR: project not found: $xpr"
|
||||
puts stderr "Run: cd fpga && vivado -mode batch -source create_project.tcl"
|
||||
exit 2
|
||||
}
|
||||
|
||||
open_project $xpr
|
||||
set run [get_runs impl_1]
|
||||
if {[get_property PROGRESS $run] ne "100%"} {
|
||||
puts stderr "ERROR: impl_1 is not complete; run fpga/run_impl.tcl first"
|
||||
close_project
|
||||
exit 3
|
||||
}
|
||||
|
||||
open_run impl_1
|
||||
report_timing_summary -delay_type min_max -report_unconstrained -check_timing_verbose \
|
||||
-max_paths 20 -input_pins -file [file join $out_dir timing_summary_full.rpt]
|
||||
report_utilization -hierarchical -hierarchical_depth 4 \
|
||||
-file [file join $out_dir utilization_hierarchical.rpt]
|
||||
report_utilization -file [file join $out_dir utilization.rpt]
|
||||
report_power -file [file join $out_dir power.rpt]
|
||||
report_clock_interaction -delay_type min_max -file [file join $out_dir clock_interaction.rpt]
|
||||
report_cdc -details -file [file join $out_dir cdc.rpt]
|
||||
report_drc -file [file join $out_dir drc.rpt]
|
||||
report_methodology -file [file join $out_dir methodology.rpt]
|
||||
report_route_status -file [file join $out_dir route_status.rpt]
|
||||
|
||||
set fp [open [file join $out_dir revision.txt] w]
|
||||
puts $fp "Evidence generated from implemented design."
|
||||
puts $fp "Vivado version: [version -short]"
|
||||
puts $fp "Project: $xpr"
|
||||
close $fp
|
||||
|
||||
close_project
|
||||
puts "Report evidence written to: $out_dir"
|
||||
exit 0
|
||||
46
doc/reports/system/tools/run_fresh_impl.tcl
Normal file
46
doc/reports/system/tools/run_fresh_impl.tcl
Normal file
@@ -0,0 +1,46 @@
|
||||
# Force a clean implementation and generate the bitstream used by board tests.
|
||||
|
||||
set script_dir [file dirname [file normalize [info script]]]
|
||||
set repo_root [file normalize [file join $script_dir ../../../..]]
|
||||
set xpr [file join $repo_root fpga project Loongson_Soc.xpr]
|
||||
set dsp_report [file join $repo_root fpga project dsp_utilization.rpt]
|
||||
|
||||
if {![file exists $xpr]} {
|
||||
puts stderr "ERROR: project not found: $xpr"
|
||||
exit 2
|
||||
}
|
||||
|
||||
open_project $xpr
|
||||
reset_run synth_1
|
||||
launch_runs synth_1
|
||||
wait_on_run synth_1
|
||||
|
||||
set synth_run [get_runs synth_1]
|
||||
set synth_progress [get_property PROGRESS $synth_run]
|
||||
set synth_status [get_property STATUS $synth_run]
|
||||
puts "synth_1 progress: $synth_progress"
|
||||
puts "synth_1 status: $synth_status"
|
||||
if {$synth_progress ne "100%" || [string first "ERROR" [string toupper $synth_status]] >= 0} {
|
||||
puts stderr "ERROR: fresh synthesis failed"
|
||||
close_project
|
||||
exit 3
|
||||
}
|
||||
|
||||
launch_runs impl_1 -to_step write_bitstream
|
||||
wait_on_run impl_1
|
||||
|
||||
set run [get_runs impl_1]
|
||||
set progress [get_property PROGRESS $run]
|
||||
set status [get_property STATUS $run]
|
||||
puts "impl_1 progress: $progress"
|
||||
puts "impl_1 status: $status"
|
||||
if {$progress ne "100%" || [string first "ERROR" [string toupper $status]] >= 0} {
|
||||
puts stderr "ERROR: fresh implementation failed"
|
||||
close_project
|
||||
exit 4
|
||||
}
|
||||
|
||||
open_run impl_1
|
||||
report_utilization -file $dsp_report
|
||||
close_project
|
||||
exit 0
|
||||
137
doc/reports/system/tools/run_report_tests.sh
Executable file
137
doc/reports/system/tools/run_report_tests.sh
Executable file
@@ -0,0 +1,137 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Vivado 2019.2 invokes the host gcc while elaborating XSIM-generated C.
|
||||
# A read-only ccache directory otherwise surfaces only as XSIM 43-3409.
|
||||
export CCACHE_DISABLE=1
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ROOT="$(cd "$SCRIPT_DIR/../../../.." && pwd)"
|
||||
REPORT_DIR="$ROOT/doc/reports/system"
|
||||
EVIDENCE_DIR="$REPORT_DIR/evidence"
|
||||
SOFTWARE_EXAMPLES=(asm fft fft_dma mlkem_test mlkem_hello)
|
||||
|
||||
find_vivado() {
|
||||
if [[ -n "${VIVADO:-}" && -x "${VIVADO}" ]]; then
|
||||
printf '%s\n' "$VIVADO"
|
||||
return 0
|
||||
fi
|
||||
if command -v vivado >/dev/null 2>&1; then
|
||||
command -v vivado
|
||||
return 0
|
||||
fi
|
||||
|
||||
local candidate
|
||||
for candidate in \
|
||||
/opt/Xilinx/Vivado/2019.2/bin/vivado \
|
||||
/tools/Xilinx/Vivado/2019.2/bin/vivado; do
|
||||
if [[ -x "$candidate" ]]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
require_vivado() {
|
||||
if ! VIVADO_BIN="$(find_vivado)"; then
|
||||
echo "ERROR: Vivado not found." >&2
|
||||
echo "Set VIVADO=/path/to/Vivado/bin/vivado or source Vivado settings64.sh." >&2
|
||||
exit 127
|
||||
fi
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<'EOF'
|
||||
Usage: run_report_tests.sh <command>
|
||||
|
||||
Commands:
|
||||
prepare Create evidence directories and Vivado project if absent
|
||||
lint Run the project-aware Verilator lint check
|
||||
build Build asm, fft, fft_dma, mlkem_test and mlkem_hello
|
||||
soc Build all five; simulate all except board-only asm
|
||||
implement Run Vivado implementation and collect PPA/CDC/DRC reports
|
||||
refresh Parse collected evidence, regenerate figures and build the PDF
|
||||
quick Run lint and refresh only; does not run Vivado implementation
|
||||
|
||||
Board-only tests (matmul official 5000-group timing and reset behavior) are
|
||||
listed in REPORT_RERUN_CHECKLIST.md and are intentionally not automated here.
|
||||
EOF
|
||||
}
|
||||
|
||||
prepare() {
|
||||
mkdir -p "$EVIDENCE_DIR/build" "$EVIDENCE_DIR/soc" \
|
||||
"$EVIDENCE_DIR/board" "$EVIDENCE_DIR/vivado"
|
||||
if [[ ! -f "$ROOT/fpga/project/Loongson_Soc.xpr" ]]; then
|
||||
require_vivado
|
||||
(cd "$ROOT/fpga" && "$VIVADO_BIN" -mode batch -source create_project.tcl)
|
||||
fi
|
||||
}
|
||||
|
||||
lint() {
|
||||
prepare
|
||||
python3 "$ROOT/fpga/run-linter.py" "$ROOT/fpga/project/Loongson_Soc.xpr" \
|
||||
2>&1 | tee "$EVIDENCE_DIR/lint.log"
|
||||
}
|
||||
|
||||
build_example() {
|
||||
local example="$1"
|
||||
local example_dir="$ROOT/sdk/software/examples/$example"
|
||||
local build_log="$EVIDENCE_DIR/build/$example.log"
|
||||
make -C "$example_dir" LA32RSOC_WINDOWS_HOME="$ROOT" clean \
|
||||
2>&1 | tee "$build_log"
|
||||
make -C "$example_dir" LA32RSOC_WINDOWS_HOME="$ROOT" all \
|
||||
2>&1 | tee -a "$build_log"
|
||||
}
|
||||
|
||||
build() {
|
||||
prepare
|
||||
local example
|
||||
for example in "${SOFTWARE_EXAMPLES[@]}"; do
|
||||
build_example "$example"
|
||||
done
|
||||
}
|
||||
|
||||
run_soc_case() {
|
||||
local example="$1"
|
||||
require_vivado
|
||||
build_example "$example"
|
||||
(cd "$ROOT/fpga" && "$VIVADO_BIN" -mode batch \
|
||||
-source "$SCRIPT_DIR/run_soc_sim.tcl") 2>&1 | tee "$EVIDENCE_DIR/soc/$example.log"
|
||||
}
|
||||
|
||||
soc() {
|
||||
prepare
|
||||
build_example asm
|
||||
run_soc_case fft
|
||||
run_soc_case fft_dma
|
||||
run_soc_case mlkem_test
|
||||
run_soc_case mlkem_hello
|
||||
}
|
||||
|
||||
implement() {
|
||||
prepare
|
||||
require_vivado
|
||||
"$VIVADO_BIN" -mode batch -source "$SCRIPT_DIR/run_fresh_impl.tcl" \
|
||||
2>&1 | tee "$EVIDENCE_DIR/vivado/implementation.log"
|
||||
"$VIVADO_BIN" -mode batch -source "$SCRIPT_DIR/collect_report_evidence.tcl" \
|
||||
2>&1 | tee "$EVIDENCE_DIR/vivado/collection.log"
|
||||
python3 "$ROOT/fpga/check_dsp.py" "$ROOT/fpga/project/dsp_utilization.rpt" 0 \
|
||||
2>&1 | tee "$EVIDENCE_DIR/vivado/dsp_check.log"
|
||||
}
|
||||
|
||||
refresh() {
|
||||
make -C "$REPORT_DIR" metrics all
|
||||
}
|
||||
|
||||
case "${1:-}" in
|
||||
prepare) prepare ;;
|
||||
lint) lint ;;
|
||||
build) build ;;
|
||||
soc) soc ;;
|
||||
implement) implement ;;
|
||||
refresh) refresh ;;
|
||||
quick) lint; refresh ;;
|
||||
*) usage; exit 2 ;;
|
||||
esac
|
||||
41
doc/reports/system/tools/run_soc_sim.tcl
Normal file
41
doc/reports/system/tools/run_soc_sim.tcl
Normal file
@@ -0,0 +1,41 @@
|
||||
# Runs the currently built sdk/axi_ram.mif with the generic SoC testbench.
|
||||
# The caller is responsible for building the desired software example first.
|
||||
|
||||
set script_dir [file dirname [file normalize [info script]]]
|
||||
set repo_root [file normalize [file join $script_dir ../../../..]]
|
||||
set xpr [file join $repo_root fpga project Loongson_Soc.xpr]
|
||||
|
||||
# xelab inherits this when compiling generated C files with the host gcc.
|
||||
set ::env(CCACHE_DISABLE) 1
|
||||
|
||||
# Vivado 2019.2's bundled clang needs the matching 64-bit ncurses/tinfo pair.
|
||||
# On hosts that also carry /lib/libtinfo.so.5, the loader may otherwise mix it
|
||||
# with /lib64/libncurses.so.5 and fail with an unresolved _nc_tiparm symbol.
|
||||
if {[info exists ::env(LD_LIBRARY_PATH)] && $::env(LD_LIBRARY_PATH) ne ""} {
|
||||
append ::env(LD_LIBRARY_PATH) ":/lib64"
|
||||
} else {
|
||||
set ::env(LD_LIBRARY_PATH) "/lib64"
|
||||
}
|
||||
|
||||
if {![file exists $xpr]} {
|
||||
puts stderr "ERROR: project not found: $xpr"
|
||||
exit 2
|
||||
}
|
||||
if {![file exists [file join $repo_root sdk axi_ram.mif]]} {
|
||||
puts stderr "ERROR: sdk/axi_ram.mif not found; build a software example first"
|
||||
exit 3
|
||||
}
|
||||
|
||||
open_project $xpr
|
||||
set simset [get_filesets sim_1]
|
||||
set_property top tb_top $simset
|
||||
set_property incremental false $simset
|
||||
set_property xsim.elaborate.mt_level off $simset
|
||||
set_property xsim.elaborate.debug_level off $simset
|
||||
set_property xsim.simulate.log_all_signals false $simset
|
||||
update_compile_order -fileset sim_1
|
||||
launch_simulation
|
||||
run all
|
||||
close_sim
|
||||
close_project
|
||||
exit 0
|
||||
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