"""
General IBIS-AMI utilities for PyBERT.
Original author: David Banas <capn.freako@gmail.com>
Original date: June 16, 2024
Copyright (c) 2024 David Banas; all rights reserved World wide.
A partial extraction of the old `pybert/utility.py`, as part of a refactoring.
"""
from numpy import array, convolve # type: ignore
from pyibisami.ami.model import AMIModel, AMIModelInitializer
from pyibisami.ami.parser import AMIParamConfigurator
from ..common import Rvec
# pylint: disable=too-many-arguments,too-many-locals,too-many-positional-arguments
[docs]
def run_ami_model(dll_fname: str, param_cfg: AMIParamConfigurator, use_getwave: bool,
ui: float, ts: float, chnl_h: Rvec, x: Rvec, bits_per_call: int = 0 # noqa: F405
) -> tuple[Rvec, Rvec, Rvec, Rvec, str, list[str]]: # noqa: F405
"""
Run a simulation of an IBIS-AMI model.
Args:
dll_fname: Filename of DLL/SO.
param_cfg: A pre-configured ``AMIParamConfigurator`` instance.
use_getwave: Use ``AMI_GetWave()`` when True, ``AMI_Init()`` when False.
ui: Unit interval (s).
ts: Sample interval (s).
chnl_h: Impulse response input to model (V/sample).
x: Input waveform.
Keyword Args:
bits_per_call: Number of bits per call of ``GetWave()``.
Default: 0 (Means "Use existing value.")
Returns:
A tuple consisting of
- the model output convolved w/ any channel impulse response given in ``chnl_h``,
- the model determined sampling instants (a.k.a. - "clock times"), if appropriate,
- the model's impulse response (V/sample),
- the impulse response of the model concatenated w/ the given channel (V/sample),
- input parameters, and any message returned by the model's ``AMI_Init()`` function, and
- any output parameters from ``GetWave()`` if apropos.
Raises:
IOError: if the given file name cannot be found/opened.
RuntimeError: if the given model doesn't support the requested mode.
"""
# Validate the model against the requested use mode.
if use_getwave:
if not param_cfg.fetch_param_val(["Reserved_Parameters", "GetWave_Exists"]):
raise RuntimeError(
"You've requested to use the `AMI_GetWave()` function of an IBIS-AMI model, which doesn't provide one!")
else:
if not param_cfg.fetch_param_val(["Reserved_Parameters", "Init_Returns_Impulse"]):
raise RuntimeError(
"You've requested to use the `AMI_Init()` function of an IBIS-AMI model, which doesn't return an impulse response!")
# Load and initialize the model.
model_init = AMIModelInitializer(param_cfg.input_ami_params, info_params=param_cfg.info_ami_params)
model_init.sample_interval = ts # Must be set, before 'channel_response'!
model_init.channel_response = chnl_h / ts
model_init.bit_time = ui
model = AMIModel(dll_fname)
model.initialize(model_init)
# Required when DLL returns a null pointer, instead of an empty string, in `ami_params_out`.
# Note: Having `AmiModel` trap this case doesn't work, since Python strings don't offer `decode()`.
try:
params_out_str = model.ami_params_out.decode('utf-8')
except AttributeError:
params_out_str = ""
msg = "\n".join([ # Python equivalent of Haskell's `unlines()`.
f"Input parameters: {model.ami_params_in.decode('utf-8')}",
f"Output parameters: {params_out_str}",
f"Message: {model.msg.decode('utf-8')}"])
# Capture model's responses.
resps = model.get_responses(bits_per_call=40)
if use_getwave:
h = resps["imp_resp_getw"]
out_h = resps["out_resp_getw"][1]
else:
h = resps["imp_resp_init"]
out_h = resps["out_resp_init"][1]
# Generate model's output.
if use_getwave:
y, clks, params_out = model.getWave(x, bits_per_call=bits_per_call)
return (y, clks, h, out_h, msg, params_out)
try:
y = convolve(x, out_h)[:len(x)]
except Exception:
print(f"x.shape: {x.shape}")
print(f"out_h shapes: {[h.shape for h in out_h]}")
raise
clks = array([])
return (y, clks, h, out_h, msg, [])