Add noise, template catalog, DC/TF tools and workflow prompts (35 tools)
New tools: analyze_noise, get_spot_noise, get_total_noise, create_from_template, list_templates, get_operating_point, get_transfer_function, list_simulation_runs. Enhanced get_waveform with per-run extraction for stepped sims. Added 3 new workflow prompts: optimize_design, monte_carlo_analysis, circuit_from_scratch.
This commit is contained in:
parent
cfcd0ae221
commit
1afa4f112b
@ -61,6 +61,11 @@ from .netlist import (
|
||||
rc_lowpass,
|
||||
voltage_divider,
|
||||
)
|
||||
from .noise_analysis import (
|
||||
compute_noise_metrics,
|
||||
compute_spot_noise,
|
||||
compute_total_noise,
|
||||
)
|
||||
from .optimizer import (
|
||||
ComponentRange,
|
||||
OptimizationTarget,
|
||||
@ -103,6 +108,7 @@ mcp = FastMCP(
|
||||
- Compare schematics to see what changed
|
||||
- Export waveform data to CSV
|
||||
- Extract DC operating point (.op) and transfer function (.tf) data
|
||||
- Analyze noise: spectral density, spot noise, RMS, noise figure, 1/f corner
|
||||
- Measure stability (gain/phase margins from AC loop gain)
|
||||
- Compute power and efficiency from voltage/current waveforms
|
||||
- Evaluate waveform math expressions (V*I, gain, dB, etc.)
|
||||
@ -575,56 +581,160 @@ def analyze_stability(
|
||||
return compute_stability_metrics(freq.real, signal)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# NOISE ANALYSIS TOOLS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def analyze_noise(
|
||||
raw_file_path: str,
|
||||
noise_signal: str = "onoise",
|
||||
source_resistance: float = 50.0,
|
||||
) -> dict:
|
||||
"""Comprehensive noise analysis from a .noise simulation.
|
||||
|
||||
Returns spectral density, spot noise at standard frequencies (10Hz,
|
||||
100Hz, 1kHz, 10kHz, 100kHz), total integrated RMS noise, noise figure,
|
||||
and 1/f corner frequency estimate.
|
||||
|
||||
Run a simulation with .noise directive first, e.g.:
|
||||
.noise V(out) V1 dec 100 1 1meg
|
||||
|
||||
Args:
|
||||
raw_file_path: Path to .raw file from .noise simulation
|
||||
noise_signal: Which noise variable to analyze ("onoise" for
|
||||
output-referred or "inoise" for input-referred)
|
||||
source_resistance: Source impedance in ohms for noise figure (default 50)
|
||||
"""
|
||||
raw = parse_raw_file(raw_file_path)
|
||||
|
||||
freq = raw.get_frequency()
|
||||
signal = raw.get_variable(noise_signal)
|
||||
|
||||
if freq is None:
|
||||
return {"error": "No frequency data found. Is this a .noise simulation?"}
|
||||
if signal is None:
|
||||
available = [v.name for v in raw.variables]
|
||||
return {
|
||||
"error": f"Signal '{noise_signal}' not found. Available: {available}",
|
||||
}
|
||||
|
||||
return compute_noise_metrics(freq.real, signal, source_resistance)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_spot_noise(
|
||||
raw_file_path: str,
|
||||
target_freq: float,
|
||||
noise_signal: str = "onoise",
|
||||
) -> dict:
|
||||
"""Get noise spectral density at a specific frequency.
|
||||
|
||||
Interpolates between data points to estimate the noise density
|
||||
at the requested frequency.
|
||||
|
||||
Args:
|
||||
raw_file_path: Path to .raw file from .noise simulation
|
||||
target_freq: Frequency in Hz to measure noise at
|
||||
noise_signal: "onoise" (output-referred) or "inoise" (input-referred)
|
||||
"""
|
||||
raw = parse_raw_file(raw_file_path)
|
||||
|
||||
freq = raw.get_frequency()
|
||||
signal = raw.get_variable(noise_signal)
|
||||
|
||||
if freq is None:
|
||||
return {"error": "No frequency data found"}
|
||||
if signal is None:
|
||||
return {"error": f"Signal '{noise_signal}' not found"}
|
||||
|
||||
return compute_spot_noise(freq.real, signal, target_freq)
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_total_noise(
|
||||
raw_file_path: str,
|
||||
noise_signal: str = "onoise",
|
||||
f_low: float | None = None,
|
||||
f_high: float | None = None,
|
||||
) -> dict:
|
||||
"""Integrate noise over a frequency band to get total RMS noise.
|
||||
|
||||
Computes total_rms = sqrt(integral(|noise|^2 * df)) over the
|
||||
specified frequency range.
|
||||
|
||||
Args:
|
||||
raw_file_path: Path to .raw file from .noise simulation
|
||||
noise_signal: "onoise" or "inoise"
|
||||
f_low: Lower frequency bound in Hz (default: data minimum)
|
||||
f_high: Upper frequency bound in Hz (default: data maximum)
|
||||
"""
|
||||
raw = parse_raw_file(raw_file_path)
|
||||
|
||||
freq = raw.get_frequency()
|
||||
signal = raw.get_variable(noise_signal)
|
||||
|
||||
if freq is None:
|
||||
return {"error": "No frequency data found"}
|
||||
if signal is None:
|
||||
return {"error": f"Signal '{noise_signal}' not found"}
|
||||
|
||||
return compute_total_noise(freq.real, signal, f_low, f_high)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# DC OPERATING POINT & TRANSFER FUNCTION TOOLS
|
||||
# ============================================================================
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_operating_point(log_file_path: str) -> dict:
|
||||
"""Extract DC operating point results from a simulation log.
|
||||
def get_operating_point(raw_file_path: str) -> dict:
|
||||
"""Extract DC operating point results from a .raw file.
|
||||
|
||||
The .op analysis computes all node voltages and branch currents
|
||||
at the DC bias point. Results include device operating points
|
||||
(transistor Gm, Id, Vgs, etc.) when available.
|
||||
at the DC bias point, stored as a single data point in the .raw file.
|
||||
|
||||
Run a simulation with .op directive first, then pass the log file.
|
||||
Run a simulation with .op directive first, then pass the .raw file.
|
||||
|
||||
Args:
|
||||
log_file_path: Path to .log file from simulation
|
||||
raw_file_path: Path to .raw file from simulation
|
||||
"""
|
||||
log = parse_log(log_file_path)
|
||||
raw = parse_raw_file(raw_file_path)
|
||||
|
||||
if not log.operating_point:
|
||||
if raw.plotname and "operating point" not in raw.plotname.lower():
|
||||
return {
|
||||
"error": "No operating point data found in log. "
|
||||
"Ensure the simulation uses a .op directive.",
|
||||
"log_errors": log.errors,
|
||||
"error": f"Not an operating point analysis (plotname: '{raw.plotname}'). "
|
||||
"Ensure the simulation uses a .op directive."
|
||||
}
|
||||
|
||||
# Separate node voltages from branch currents/device params
|
||||
# Extract single-point values, separating voltages from currents
|
||||
voltages = {}
|
||||
currents = {}
|
||||
other = {}
|
||||
for name, value in log.operating_point.items():
|
||||
if name.startswith("V(") or name.startswith("v("):
|
||||
voltages[name] = value
|
||||
elif name.startswith("I(") or name.startswith("i(") or name.startswith("Ix("):
|
||||
currents[name] = value
|
||||
for v in raw.variables:
|
||||
data = raw.get_variable(v.name)
|
||||
if data is None or len(data) == 0:
|
||||
continue
|
||||
val = float(data[0].real) if hasattr(data[0], "real") else float(data[0])
|
||||
if v.name.lower().startswith("v("):
|
||||
voltages[v.name] = val
|
||||
elif v.name.lower().startswith("i(") or v.name.lower().startswith("ix("):
|
||||
currents[v.name] = val
|
||||
else:
|
||||
other[name] = value
|
||||
other[v.name] = val
|
||||
|
||||
return {
|
||||
"voltages": voltages,
|
||||
"currents": currents,
|
||||
"device_params": other,
|
||||
"total_entries": len(log.operating_point),
|
||||
"total_entries": len(voltages) + len(currents) + len(other),
|
||||
}
|
||||
|
||||
|
||||
@mcp.tool()
|
||||
def get_transfer_function(log_file_path: str) -> dict:
|
||||
"""Extract .tf (transfer function) results from a simulation log.
|
||||
def get_transfer_function(raw_file_path: str) -> dict:
|
||||
"""Extract .tf (transfer function) results from a .raw file.
|
||||
|
||||
The .tf analysis computes:
|
||||
- Transfer function (gain or transresistance)
|
||||
@ -632,32 +742,43 @@ def get_transfer_function(log_file_path: str) -> dict:
|
||||
- Output impedance at the output node
|
||||
|
||||
Run a simulation with .tf directive first (e.g., ".tf V(out) V1"),
|
||||
then pass the log file.
|
||||
then pass the .raw file.
|
||||
|
||||
Args:
|
||||
log_file_path: Path to .log file from simulation
|
||||
raw_file_path: Path to .raw file from simulation
|
||||
"""
|
||||
log = parse_log(log_file_path)
|
||||
raw = parse_raw_file(raw_file_path)
|
||||
|
||||
if not log.transfer_function:
|
||||
if raw.plotname and "transfer function" not in raw.plotname.lower():
|
||||
return {
|
||||
"error": "No transfer function data found in log. "
|
||||
"Ensure the simulation uses a .tf directive, "
|
||||
"e.g., '.tf V(out) V1'.",
|
||||
"log_errors": log.errors,
|
||||
"error": f"Not a transfer function analysis (plotname: '{raw.plotname}'). "
|
||||
"Ensure the simulation uses a .tf directive, e.g., '.tf V(out) V1'."
|
||||
}
|
||||
|
||||
# Identify the specific components
|
||||
result: dict = {"raw_data": log.transfer_function}
|
||||
result: dict = {}
|
||||
for v in raw.variables:
|
||||
data = raw.get_variable(v.name)
|
||||
if data is None or len(data) == 0:
|
||||
continue
|
||||
val = float(data[0].real) if hasattr(data[0], "real") else float(data[0])
|
||||
|
||||
for name, value in log.transfer_function.items():
|
||||
name_lower = name.lower()
|
||||
name_lower = v.name.lower()
|
||||
if "transfer_function" in name_lower:
|
||||
result["transfer_function"] = value
|
||||
result["transfer_function"] = val
|
||||
elif "output_impedance" in name_lower:
|
||||
result["output_impedance_ohms"] = value
|
||||
result["output_impedance_ohms"] = val
|
||||
elif "input_impedance" in name_lower:
|
||||
result["input_impedance_ohms"] = value
|
||||
result["input_impedance_ohms"] = val
|
||||
|
||||
# Always include raw data with original name
|
||||
result.setdefault("raw_data", {})[v.name] = val
|
||||
|
||||
if not result:
|
||||
return {
|
||||
"error": "No transfer function data found in .raw file.",
|
||||
"plotname": raw.plotname,
|
||||
"variables": [v.name for v in raw.variables],
|
||||
}
|
||||
|
||||
return result
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user