Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🚧 Add more types, experimental functionality, measurement analysis #112

Merged
merged 3 commits into from
Aug 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 5 additions & 28 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,22 +1,10 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: ca84e500209b3757213759d4d522f8ed307cd638
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: "v0.5.5"
hooks:
- id: check-yaml
exclude: ^(conda\.recipe/meta\.yaml|conda_build/templates/.*\.yaml|docs/click/meta\.yaml|conda/meta\.yaml|conda/construct.yaml|.*\.pic\.yml|conda/constructor/Miniforge3/construct.yaml)
- id: end-of-file-fixer
- id: trailing-whitespace

- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black

- repo: https://github.com/asottile/blacken-docs
rev: 7b71075ceb458be255e24da587c0275818b51faa
hooks:
- id: blacken-docs
additional_dependencies: [black==23.3.0]
- id: ruff
args: [ --fix ]
- id: ruff-format

- repo: https://github.com/codespell-project/codespell
rev: ad3ff374e97e29ca87c94b5dc7eccdd29adc6296
Expand All @@ -25,14 +13,3 @@ repos:
args: ["-L TE,TE/TM,te,ba,FPR,fpr_spacing,ro,nd,donot,schem,Synopsys,ket,inout,astroid" ]
additional_dependencies:
- tomli

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: v0.4.4
hooks:
- id: ruff

- repo: https://github.com/macisamuele/language-formatters-pre-commit-hooks
rev: d2425a62376c2197448cce2f825d5a0c3926b862
hooks:
- id: pretty-format-toml
args: [--autofix]
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,31 @@


# Now we can create our custom `PCB` definition.
#
# ```
# S6-S7 Load 50 pcb 3
# S1-S2 Through
# RES1 Short to GND
# S8 OPEN
# ```


# +
def pcb_smp_connector(name):
def pcb_smp_connector(name, pcb_name):
"""
This is our PCB SMP Port definition factory.
"""
return piel.types.PhysicalPort(name=name, connector="smp_plug", domain="RF")
return piel.types.PhysicalPort(
name=name, connector="smp_plug", domain="RF", parent_component_name=pcb_name
)


# These are the measurements we want to check
measurement_connections = {
"load_through": ("SIG6", "SIG7"),
"throguh": ("SIG1", "SIG2"),
"short": ("RES1", "GND"),
}


rf_calibration_pcb = piel.models.physical.electrical.create_pcb(
Expand All @@ -61,77 +78,106 @@ def pcb_smp_connector(name):
"RES4",
"SIG8",
"L50",
"GND",
],
connection_tuple_list=[],
port_factory=pcb_smp_connector,
name="rf_calibration_pcb",
pcb_name="PCB3",
environment=room_temperature_environment,
components=[],
)

rf_calibration_pcb
# -

# ```python
# PCB(name='cryo_rf_eic_interposer_v1', ports=[PhysicalPort(name='SIG14', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='RES1', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG1', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG2', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='RES2', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG3', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='OPEN', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SHORT', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG5', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='RES3', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG6', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG7', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='RES4', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG8', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='L50', domain='RF', connector='smp_plug', manifold=None)], connections=[], environment=Environment(temperature_K=273.0, region=None))
# PCB(name='PCB3', ports=[PhysicalPort(name='SIG14', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='RES1', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG1', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG2', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='RES2', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG3', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='OPEN', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SHORT', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG5', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='RES3', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG6', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG7', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='RES4', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG8', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='L50', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='GND', domain='RF', connector='smp_plug', manifold=None)], connections=[PhysicalConnection(connections=[Connection(name=None, ports=(PhysicalPort(name='SIG6', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG7', domain='RF', connector='smp_plug', manifold=None)))], components=None), PhysicalConnection(connections=[Connection(name=None, ports=(PhysicalPort(name='SIG1', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='SIG2', domain='RF', connector='smp_plug', manifold=None)))], components=None), PhysicalConnection(connections=[Connection(name=None, ports=(PhysicalPort(name='RES1', domain='RF', connector='smp_plug', manifold=None), PhysicalPort(name='GND', domain='RF', connector='smp_plug', manifold=None)))], components=None)], components=[], environment=Environment(temperature_K=273.0, region=None), manufacturer=None, model=None)
# ```

# A `Component` might contain subcomponents and there are parameters like `Environment`. In any case, we have all the flexibility of `python` of composing all the `ExperimentInstances` we want. As long as we create an `Experiment` with multiple `ExperimentInstances`, then it is pretty straightforward to just export that to a JSON model file. Using `pydantic`, we can also reinstantiate the model back into `python` which is quite powerful for this type of experiment management.

# For example, we might do multiple measurements with different relevant for example.

# ### Frequency-Domain Analysis

# Now, let's define an experiment accordingly:
# It is possible to extract the time-domain performance from frequency-domain measurements:
#
# - Time Domain Analysis Using a Network Analyzer, Keysight, Application Note
# - [scikit-rf Time Domain and Gating](https://scikit-rf.readthedocs.io/en/latest/examples/networktheory/Time%20Domain.html)
#
# Now, let's define an experiment accordingly. In this experiment, we will do s-parameter measurmeents of two shorted PCB traces directly from a reference plane.


def create_vna_measurements(
vna_configuration: list[pe.types.VNAConfiguration] = None, **kwargs
):
def create_calibration_vna_experiments(measurements: dict, **kwargs):
"""
This is a function that parametrizes multiple VNA measurement configurations and creates a set of VNA measurements
accordingly.
Simple two port measurement experiment.
"""
if vna_configuration is None:
vna_configuration = [None]

experiment_instances = list()
i = 0
for vna_configuration_i in vna_configuration:
# Simple two port measurement
vna = pe.types.VNA(
environment=room_temperature_environment,
name="DPO73304",
configuration=vna_configuration_i,
for measurement_i in measurements.items():
sparameter_measurement_configuration = (
pe.types.VNASParameterMeasurementConfiguration(
test_port_power_dBm=-17,
sweep_points=6401,
frequency_range_Hz=(45e6, 20e9),
)
)

# List of connections
experiment_connections = []

# Let's assume that we want to measure an open return loss between SIG14 and RES1
experiment_connections.extend(
piel.create_all_connections(
[vna.ports[0], rf_calibration_pcb.ports[0]],
)
# Define experimental components
vna_configuration = pe.types.VNAConfiguration(
measurement_configuration=sparameter_measurement_configuration
)

experiment_connections.extend(
piel.create_all_connections([vna.ports[1], rf_calibration_pcb.ports[1]])
vna = pe.models.vna.E8364A(configuration=vna_configuration)
blue_extension_cable = pe.models.cables.generic_sma(
name="blue_extension", model="1251C", length_m=0.025
)
experiment_components = [vna, blue_extension_cable, rf_calibration_pcb]

# Connect the iteration ports
measurement_name = measurement_i[0]
measurement_port1 = measurement_i[1][0]
measurement_port2 = measurement_i[1][1]

# Create the VNA connectivity
experiment_connections = piel.create_component_connections(
components=experiment_components,
connection_reference_str_list=[
[
f"{vna.name}.PORT1",
f"{blue_extension_cable.name}.IN",
],
[
f"{blue_extension_cable.name}.OUT",
f"{rf_calibration_pcb.name}.{measurement_port1}",
],
[
f"{rf_calibration_pcb.name}.{measurement_port2}",
f"{vna.name}.PORT2",
],
],
)

# Define experiment with connections
experiment = pe.types.ExperimentInstance(
components=[rf_calibration_pcb, vna],
experiment_measurement = pe.types.ExperimentInstance(
name=measurement_name,
components=experiment_components,
connections=experiment_connections,
index=i,
date_configured=datetime.now(),
date_configured=str(datetime.now()),
measurement_configuration_list=[sparameter_measurement_configuration],
)

experiment_instances.append(experiment)
experiment_instances.append(experiment_measurement)
i += 1

return pe.types.Experiment(experiment_instances=experiment_instances, **kwargs)


vna_pcb_experiment = create_vna_measurements(name="basic_vna_test")
vna_pcb_experiment
vna_pcb_experiment = create_calibration_vna_experiments(
name="pcb_rf_vna_measurement",
measurements=measurement_connections,
goal="Perform S-Parameter characterization of a PCB trace.",
)
# vna_pcb_experiment

# Now, let's create an experiment `data` directory in which to save the data accordingly:

Expand All @@ -141,8 +187,10 @@ def create_vna_measurements(
# Now, the beautiful thing that this can do, especially if you are allergic to repettitive experimental tasks like me, is that we can use this to identify all our experimental instances which correspond to specific dials and operating points we need to configure manually.


pe.construct_experiment_directories(
experiment=vna_pcb_experiment, parent_directory=experiment_data_directory
vna_pcb_experiment_directory = pe.construct_experiment_directories(
experiment=vna_pcb_experiment,
parent_directory=experiment_data_directory,
construct_directory=True,
)

# ```
Expand All @@ -153,6 +201,32 @@ def create_vna_measurements(
# Let's see how the structure of the project looks like:


# !pwd $vna_pcb_experiment_directory
# !ls $vna_pcb_experiment_directory

# Now, let's save the experimental data in there accordingly. Once we save the data, we can recompose the data into measurement containers based on the `MeasurementConfigurationTypes` we defined for each `ExperimantInstance`.


example_measurement = pe.compose_measurement_from_experiment_instance(
vna_pcb_experiment.experiment_instances[1],
instance_directory=vna_pcb_experiment_directory / "1",
)
example_measurement

# However, we might want to compose our measurements into a `MeasurementCollection`:

vna_pcb_experiment_collection = pe.compose_measurement_collection_from_experiment(
vna_pcb_experiment,
experiment_directory=vna_pcb_experiment_directory,
)
vna_pcb_experiment_collection

pe.extract_data_from_measurement_collection(
measurement_collection=vna_pcb_experiment_collection,
# measurement_to_data_map: dict = measurement_to_data_map,
# measurement_to_data_method_map: dict = measurement_to_data_method_map,
)

# ## Time-Domain Analysis
#
# Let's consider we want to measure the propagation velocity of a pulse through one of our coaxial cables. If you are doing a similar experiment, make sure to use ground ESD straps to avoid damage to the equipment. As there is frequency dispersion in the RF transmission lines, we also know the time-domain response is different according to the type of signal applied to the device. We can compare an analysis between the different pulse frequencies.
Expand Down Expand Up @@ -187,6 +261,7 @@ def calibration_propagation_delay_experiment_instance(
name=f"calibration_{square_wave_frequency_Hz}_Hz",
components=[oscilloscope, waveform_generator, splitter],
connections=experiment_connections,
parameters={"square_wave_frequency_Hz": square_wave_frequency_Hz},
)
return experiment_instance

Expand Down Expand Up @@ -229,7 +304,13 @@ def pcb_propagation_delay_experiment_instance(


def propagation_delay_experiment(square_wave_frequency_Hz_list: list[float] = None):
# Create reference iteration parameters
parameters_list = list()

# Create all the experiment instances
experiment_instance_list = list()

# Iterate through experimental parameters
for square_wave_frequency_Hz_i in square_wave_frequency_Hz_list:
pcb_experiment_instance_i = pcb_propagation_delay_experiment_instance(
square_wave_frequency_Hz=square_wave_frequency_Hz_i
Expand All @@ -242,11 +323,13 @@ def propagation_delay_experiment(square_wave_frequency_Hz_list: list[float] = No
)
)
experiment_instance_list.append(calibration_experiment_instance_i)
parameters_list.append({"square_wave_frequency_Hz": square_wave_frequency_Hz_i})

experiment = pe.types.Experiment(
name="multi_frequency_through_propagation_measurement",
experiment_instances=experiment_instance_list,
goal="Test the propagation response at multiple frequencies. Use a through connection to measure the approximate propagation delay through the calibration cables and PCB trace.",
parameters_list=parameters_list,
)
return experiment

Expand All @@ -260,6 +343,7 @@ def propagation_delay_experiment(square_wave_frequency_Hz_list: list[float] = No
propagation_delay_experiment_directory = pe.construct_experiment_directories(
experiment=basic_propagation_delay_experiment,
parent_directory=experiment_data_directory,
construct_directory=True,
)

# ```
Expand Down Expand Up @@ -339,10 +423,12 @@ def propagation_delay_experiment(square_wave_frequency_Hz_list: list[float] = No

# Now we need to write some functionality to extract the files stored in these files in a meaningful way. Fortunately, there's already some functionality using `piel` in this context:

calibration_propagation_delay_sweep_data = pe.types.PropagationDelayMeasurementSweep(
measurements=calibration_propagation_data,
calibration_propagation_delay_sweep_data = (
pe.types.PropagationDelayMeasurementCollection(
measurements=calibration_propagation_data,
)
)
pcb_propagation_delay_sweep_data = pe.types.PropagationDelayMeasurementSweep(
pcb_propagation_delay_sweep_data = pe.types.PropagationDelayMeasurementCollection(
measurements=pcb_propagation_data, name="frequency_sweep"
)

Expand Down Expand Up @@ -372,7 +458,11 @@ def propagation_delay_experiment(square_wave_frequency_Hz_list: list[float] = No
measurement_name="delay_ch1_ch2__s_1",
)

# TODO add graph showing this exact setup.
fig, ax = pe.visual.plot_signal_propagation_sweep_signals(
pcb_propagation_delay_sweep_data,
)

# ### TODO add graph showing this exact setup.
#
# In this setup, we will use a RF signal generator and a RF oscilloscope.
#
Expand Down

This file was deleted.

This file was deleted.

This file was deleted.

Loading
Loading