Updates
This commit is contained in:
@@ -22,6 +22,13 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
from typing import Optional
|
||||
|
||||
# 1.8 V supply rail spec (i.MX 8M Mini internal regulator, ±5 %)
|
||||
V18_NOMINAL_V = 1.800
|
||||
V18_SPEC_MIN_V = 1.710 # −5 %
|
||||
V18_SPEC_MAX_V = 1.890 # +5 %
|
||||
V18_DROOP_WARN_MV = 50.0 # mV droop depth worth flagging
|
||||
V18_RIPPLE_WARN_MV = 20.0 # mV RMS ripple worth flagging
|
||||
|
||||
# MIPI D-PHY HS-TX spec limits
|
||||
HS_VDIFF_MIN_MV = 140.0 # |Vdiff| minimum (mV)
|
||||
HS_VDIFF_MAX_MV = 270.0 # |Vdiff| maximum (mV)
|
||||
@@ -318,6 +325,97 @@ def analyze_file(path: Path) -> ChannelMetrics:
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class V1V8Metrics:
|
||||
timestamp: str
|
||||
capture_num: int
|
||||
|
||||
sample_rate_mhz: float
|
||||
duration_us: float
|
||||
n_samples: int
|
||||
|
||||
mean_v: float # mean supply voltage
|
||||
min_v: float # minimum (worst-case droop)
|
||||
max_v: float # maximum
|
||||
droop_mv: float # mean − min (droop depth)
|
||||
ripple_mv_rms: float # AC ripple (std dev of voltage)
|
||||
|
||||
spec_pass: bool # mean within ±5 % of 1.8 V
|
||||
droop_pass: bool # minimum above V18_SPEC_MIN_V
|
||||
|
||||
warnings: list = field(default_factory=list)
|
||||
|
||||
def summary(self) -> str:
|
||||
ok = lambda c: "✓" if c else "✗"
|
||||
lines = [
|
||||
f"Capture {self.capture_num:04d} {self.timestamp} [pwr/1v8]",
|
||||
f" Mean voltage : {self.mean_v:.4f} V "
|
||||
f"(spec {V18_SPEC_MIN_V:.2f}–{V18_SPEC_MAX_V:.2f} V) {ok(self.spec_pass)}",
|
||||
f" Min voltage : {self.min_v:.4f} V {ok(self.droop_pass)}",
|
||||
f" Droop depth : {self.droop_mv:.1f} mV",
|
||||
f" Ripple RMS : {self.ripple_mv_rms:.2f} mV",
|
||||
]
|
||||
for w in self.warnings:
|
||||
lines.append(f" WARNING: {w}")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
def analyze_1v8_file(path: Path) -> "V1V8Metrics":
|
||||
"""Analyse a 1.8 V supply rail CSV captured by the Rigol DS1202Z-E."""
|
||||
m = re.match(r"(\d{8}_\d{6})_pwr_(\d+)_1v8\.csv", path.name, re.IGNORECASE)
|
||||
if not m:
|
||||
raise ValueError(f"Filename does not match 1v8 pattern: {path.name}")
|
||||
timestamp, cap_str = m.groups()
|
||||
capture_num = int(cap_str)
|
||||
|
||||
times, volts = _read_csv(path)
|
||||
dt = float(np.diff(times).mean())
|
||||
sample_rate = 1.0 / dt
|
||||
duration_us = (float(times[-1]) - float(times[0])) * 1e6
|
||||
|
||||
mean_v = float(volts.mean())
|
||||
min_v = float(volts.min())
|
||||
max_v = float(volts.max())
|
||||
droop_mv = (mean_v - min_v) * 1000.0
|
||||
ripple_mv_rms = float(volts.std()) * 1000.0
|
||||
|
||||
spec_pass = V18_SPEC_MIN_V <= mean_v <= V18_SPEC_MAX_V
|
||||
droop_pass = min_v >= V18_SPEC_MIN_V
|
||||
|
||||
warnings = []
|
||||
if not spec_pass:
|
||||
warnings.append(
|
||||
f"Mean supply {mean_v:.4f} V outside spec "
|
||||
f"({V18_SPEC_MIN_V:.2f}–{V18_SPEC_MAX_V:.2f} V)"
|
||||
)
|
||||
if not droop_pass:
|
||||
warnings.append(
|
||||
f"Supply droops to {min_v:.4f} V — below {V18_SPEC_MIN_V:.2f} V spec min"
|
||||
)
|
||||
if droop_mv > V18_DROOP_WARN_MV:
|
||||
warnings.append(
|
||||
f"Droop depth {droop_mv:.1f} mV — possible insufficient decoupling near MIPI PHY"
|
||||
)
|
||||
if ripple_mv_rms > V18_RIPPLE_WARN_MV:
|
||||
warnings.append(f"Ripple {ripple_mv_rms:.1f} mV RMS is elevated")
|
||||
|
||||
return V1V8Metrics(
|
||||
timestamp = timestamp,
|
||||
capture_num = capture_num,
|
||||
sample_rate_mhz = round(sample_rate / 1e6, 1),
|
||||
duration_us = round(duration_us, 2),
|
||||
n_samples = len(times),
|
||||
mean_v = round(mean_v, 4),
|
||||
min_v = round(min_v, 4),
|
||||
max_v = round(max_v, 4),
|
||||
droop_mv = round(droop_mv, 1),
|
||||
ripple_mv_rms = round(ripple_mv_rms, 2),
|
||||
spec_pass = spec_pass,
|
||||
droop_pass = droop_pass,
|
||||
warnings = warnings,
|
||||
)
|
||||
|
||||
|
||||
def group_captures(data_dir: Path) -> dict[tuple[str, int], dict[str, Path]]:
|
||||
"""
|
||||
Scan data_dir and group CSV files by (timestamp, capture_number).
|
||||
@@ -325,7 +423,9 @@ def group_captures(data_dir: Path) -> dict[tuple[str, int], dict[str, Path]]:
|
||||
Example key: ("20260408_111448", 1)
|
||||
Example value: {"sig_clk": Path(...), "sig_dat": ..., "proto_clk": ..., "proto_dat": ...}
|
||||
"""
|
||||
pattern = re.compile(r"(\d{8}_\d{6})_(sig|proto|lp)_(\d+)_(clk|dat)\.csv", re.IGNORECASE)
|
||||
pattern = re.compile(
|
||||
r"(\d{8}_\d{6})_(sig|proto|lp|pwr)_(\d+)_(clk|dat|1v8)\.csv", re.IGNORECASE
|
||||
)
|
||||
groups: dict[tuple[str, int], dict[str, Path]] = {}
|
||||
for f in sorted(data_dir.glob("*.csv")):
|
||||
m = pattern.match(f.name)
|
||||
|
||||
Reference in New Issue
Block a user