oscura 0.0.1__py3-none-any.whl → 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- oscura/__init__.py +813 -8
- oscura/__main__.py +392 -0
- oscura/analyzers/__init__.py +37 -0
- oscura/analyzers/digital/__init__.py +177 -0
- oscura/analyzers/digital/bus.py +691 -0
- oscura/analyzers/digital/clock.py +805 -0
- oscura/analyzers/digital/correlation.py +720 -0
- oscura/analyzers/digital/edges.py +632 -0
- oscura/analyzers/digital/extraction.py +413 -0
- oscura/analyzers/digital/quality.py +878 -0
- oscura/analyzers/digital/signal_quality.py +877 -0
- oscura/analyzers/digital/thresholds.py +708 -0
- oscura/analyzers/digital/timing.py +1104 -0
- oscura/analyzers/eye/__init__.py +46 -0
- oscura/analyzers/eye/diagram.py +434 -0
- oscura/analyzers/eye/metrics.py +555 -0
- oscura/analyzers/jitter/__init__.py +83 -0
- oscura/analyzers/jitter/ber.py +333 -0
- oscura/analyzers/jitter/decomposition.py +759 -0
- oscura/analyzers/jitter/measurements.py +413 -0
- oscura/analyzers/jitter/spectrum.py +220 -0
- oscura/analyzers/measurements.py +40 -0
- oscura/analyzers/packet/__init__.py +171 -0
- oscura/analyzers/packet/daq.py +1077 -0
- oscura/analyzers/packet/metrics.py +437 -0
- oscura/analyzers/packet/parser.py +327 -0
- oscura/analyzers/packet/payload.py +2156 -0
- oscura/analyzers/packet/payload_analysis.py +1312 -0
- oscura/analyzers/packet/payload_extraction.py +236 -0
- oscura/analyzers/packet/payload_patterns.py +670 -0
- oscura/analyzers/packet/stream.py +359 -0
- oscura/analyzers/patterns/__init__.py +266 -0
- oscura/analyzers/patterns/clustering.py +1036 -0
- oscura/analyzers/patterns/discovery.py +539 -0
- oscura/analyzers/patterns/learning.py +797 -0
- oscura/analyzers/patterns/matching.py +1091 -0
- oscura/analyzers/patterns/periodic.py +650 -0
- oscura/analyzers/patterns/sequences.py +767 -0
- oscura/analyzers/power/__init__.py +116 -0
- oscura/analyzers/power/ac_power.py +391 -0
- oscura/analyzers/power/basic.py +383 -0
- oscura/analyzers/power/conduction.py +314 -0
- oscura/analyzers/power/efficiency.py +297 -0
- oscura/analyzers/power/ripple.py +356 -0
- oscura/analyzers/power/soa.py +372 -0
- oscura/analyzers/power/switching.py +479 -0
- oscura/analyzers/protocol/__init__.py +150 -0
- oscura/analyzers/protocols/__init__.py +150 -0
- oscura/analyzers/protocols/base.py +500 -0
- oscura/analyzers/protocols/can.py +620 -0
- oscura/analyzers/protocols/can_fd.py +448 -0
- oscura/analyzers/protocols/flexray.py +405 -0
- oscura/analyzers/protocols/hdlc.py +399 -0
- oscura/analyzers/protocols/i2c.py +368 -0
- oscura/analyzers/protocols/i2s.py +296 -0
- oscura/analyzers/protocols/jtag.py +393 -0
- oscura/analyzers/protocols/lin.py +445 -0
- oscura/analyzers/protocols/manchester.py +333 -0
- oscura/analyzers/protocols/onewire.py +501 -0
- oscura/analyzers/protocols/spi.py +334 -0
- oscura/analyzers/protocols/swd.py +325 -0
- oscura/analyzers/protocols/uart.py +393 -0
- oscura/analyzers/protocols/usb.py +495 -0
- oscura/analyzers/signal_integrity/__init__.py +63 -0
- oscura/analyzers/signal_integrity/embedding.py +294 -0
- oscura/analyzers/signal_integrity/equalization.py +370 -0
- oscura/analyzers/signal_integrity/sparams.py +484 -0
- oscura/analyzers/spectral/__init__.py +53 -0
- oscura/analyzers/spectral/chunked.py +273 -0
- oscura/analyzers/spectral/chunked_fft.py +571 -0
- oscura/analyzers/spectral/chunked_wavelet.py +391 -0
- oscura/analyzers/spectral/fft.py +92 -0
- oscura/analyzers/statistical/__init__.py +250 -0
- oscura/analyzers/statistical/checksum.py +923 -0
- oscura/analyzers/statistical/chunked_corr.py +228 -0
- oscura/analyzers/statistical/classification.py +778 -0
- oscura/analyzers/statistical/entropy.py +1113 -0
- oscura/analyzers/statistical/ngrams.py +614 -0
- oscura/analyzers/statistics/__init__.py +119 -0
- oscura/analyzers/statistics/advanced.py +885 -0
- oscura/analyzers/statistics/basic.py +263 -0
- oscura/analyzers/statistics/correlation.py +630 -0
- oscura/analyzers/statistics/distribution.py +298 -0
- oscura/analyzers/statistics/outliers.py +463 -0
- oscura/analyzers/statistics/streaming.py +93 -0
- oscura/analyzers/statistics/trend.py +520 -0
- oscura/analyzers/validation.py +598 -0
- oscura/analyzers/waveform/__init__.py +36 -0
- oscura/analyzers/waveform/measurements.py +943 -0
- oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
- oscura/analyzers/waveform/spectral.py +1689 -0
- oscura/analyzers/waveform/wavelets.py +298 -0
- oscura/api/__init__.py +62 -0
- oscura/api/dsl.py +538 -0
- oscura/api/fluent.py +571 -0
- oscura/api/operators.py +498 -0
- oscura/api/optimization.py +392 -0
- oscura/api/profiling.py +396 -0
- oscura/automotive/__init__.py +73 -0
- oscura/automotive/can/__init__.py +52 -0
- oscura/automotive/can/analysis.py +356 -0
- oscura/automotive/can/checksum.py +250 -0
- oscura/automotive/can/correlation.py +212 -0
- oscura/automotive/can/discovery.py +355 -0
- oscura/automotive/can/message_wrapper.py +375 -0
- oscura/automotive/can/models.py +385 -0
- oscura/automotive/can/patterns.py +381 -0
- oscura/automotive/can/session.py +452 -0
- oscura/automotive/can/state_machine.py +300 -0
- oscura/automotive/can/stimulus_response.py +461 -0
- oscura/automotive/dbc/__init__.py +15 -0
- oscura/automotive/dbc/generator.py +156 -0
- oscura/automotive/dbc/parser.py +146 -0
- oscura/automotive/dtc/__init__.py +30 -0
- oscura/automotive/dtc/database.py +3036 -0
- oscura/automotive/j1939/__init__.py +14 -0
- oscura/automotive/j1939/decoder.py +745 -0
- oscura/automotive/loaders/__init__.py +35 -0
- oscura/automotive/loaders/asc.py +98 -0
- oscura/automotive/loaders/blf.py +77 -0
- oscura/automotive/loaders/csv_can.py +136 -0
- oscura/automotive/loaders/dispatcher.py +136 -0
- oscura/automotive/loaders/mdf.py +331 -0
- oscura/automotive/loaders/pcap.py +132 -0
- oscura/automotive/obd/__init__.py +14 -0
- oscura/automotive/obd/decoder.py +707 -0
- oscura/automotive/uds/__init__.py +48 -0
- oscura/automotive/uds/decoder.py +265 -0
- oscura/automotive/uds/models.py +64 -0
- oscura/automotive/visualization.py +369 -0
- oscura/batch/__init__.py +55 -0
- oscura/batch/advanced.py +627 -0
- oscura/batch/aggregate.py +300 -0
- oscura/batch/analyze.py +139 -0
- oscura/batch/logging.py +487 -0
- oscura/batch/metrics.py +556 -0
- oscura/builders/__init__.py +41 -0
- oscura/builders/signal_builder.py +1131 -0
- oscura/cli/__init__.py +14 -0
- oscura/cli/batch.py +339 -0
- oscura/cli/characterize.py +273 -0
- oscura/cli/compare.py +775 -0
- oscura/cli/decode.py +551 -0
- oscura/cli/main.py +247 -0
- oscura/cli/shell.py +350 -0
- oscura/comparison/__init__.py +66 -0
- oscura/comparison/compare.py +397 -0
- oscura/comparison/golden.py +487 -0
- oscura/comparison/limits.py +391 -0
- oscura/comparison/mask.py +434 -0
- oscura/comparison/trace_diff.py +30 -0
- oscura/comparison/visualization.py +481 -0
- oscura/compliance/__init__.py +70 -0
- oscura/compliance/advanced.py +756 -0
- oscura/compliance/masks.py +363 -0
- oscura/compliance/reporting.py +483 -0
- oscura/compliance/testing.py +298 -0
- oscura/component/__init__.py +38 -0
- oscura/component/impedance.py +365 -0
- oscura/component/reactive.py +598 -0
- oscura/component/transmission_line.py +312 -0
- oscura/config/__init__.py +191 -0
- oscura/config/defaults.py +254 -0
- oscura/config/loader.py +348 -0
- oscura/config/memory.py +271 -0
- oscura/config/migration.py +458 -0
- oscura/config/pipeline.py +1077 -0
- oscura/config/preferences.py +530 -0
- oscura/config/protocol.py +875 -0
- oscura/config/schema.py +713 -0
- oscura/config/settings.py +420 -0
- oscura/config/thresholds.py +599 -0
- oscura/convenience.py +457 -0
- oscura/core/__init__.py +299 -0
- oscura/core/audit.py +457 -0
- oscura/core/backend_selector.py +405 -0
- oscura/core/cache.py +590 -0
- oscura/core/cancellation.py +439 -0
- oscura/core/confidence.py +225 -0
- oscura/core/config.py +506 -0
- oscura/core/correlation.py +216 -0
- oscura/core/cross_domain.py +422 -0
- oscura/core/debug.py +301 -0
- oscura/core/edge_cases.py +541 -0
- oscura/core/exceptions.py +535 -0
- oscura/core/gpu_backend.py +523 -0
- oscura/core/lazy.py +832 -0
- oscura/core/log_query.py +540 -0
- oscura/core/logging.py +931 -0
- oscura/core/logging_advanced.py +952 -0
- oscura/core/memoize.py +171 -0
- oscura/core/memory_check.py +274 -0
- oscura/core/memory_guard.py +290 -0
- oscura/core/memory_limits.py +336 -0
- oscura/core/memory_monitor.py +453 -0
- oscura/core/memory_progress.py +465 -0
- oscura/core/memory_warnings.py +315 -0
- oscura/core/numba_backend.py +362 -0
- oscura/core/performance.py +352 -0
- oscura/core/progress.py +524 -0
- oscura/core/provenance.py +358 -0
- oscura/core/results.py +331 -0
- oscura/core/types.py +504 -0
- oscura/core/uncertainty.py +383 -0
- oscura/discovery/__init__.py +52 -0
- oscura/discovery/anomaly_detector.py +672 -0
- oscura/discovery/auto_decoder.py +415 -0
- oscura/discovery/comparison.py +497 -0
- oscura/discovery/quality_validator.py +528 -0
- oscura/discovery/signal_detector.py +769 -0
- oscura/dsl/__init__.py +73 -0
- oscura/dsl/commands.py +246 -0
- oscura/dsl/interpreter.py +455 -0
- oscura/dsl/parser.py +689 -0
- oscura/dsl/repl.py +172 -0
- oscura/exceptions.py +59 -0
- oscura/exploratory/__init__.py +111 -0
- oscura/exploratory/error_recovery.py +642 -0
- oscura/exploratory/fuzzy.py +513 -0
- oscura/exploratory/fuzzy_advanced.py +786 -0
- oscura/exploratory/legacy.py +831 -0
- oscura/exploratory/parse.py +358 -0
- oscura/exploratory/recovery.py +275 -0
- oscura/exploratory/sync.py +382 -0
- oscura/exploratory/unknown.py +707 -0
- oscura/export/__init__.py +25 -0
- oscura/export/wireshark/README.md +265 -0
- oscura/export/wireshark/__init__.py +47 -0
- oscura/export/wireshark/generator.py +312 -0
- oscura/export/wireshark/lua_builder.py +159 -0
- oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
- oscura/export/wireshark/type_mapping.py +165 -0
- oscura/export/wireshark/validator.py +105 -0
- oscura/exporters/__init__.py +94 -0
- oscura/exporters/csv.py +303 -0
- oscura/exporters/exporters.py +44 -0
- oscura/exporters/hdf5.py +219 -0
- oscura/exporters/html_export.py +701 -0
- oscura/exporters/json_export.py +291 -0
- oscura/exporters/markdown_export.py +367 -0
- oscura/exporters/matlab_export.py +354 -0
- oscura/exporters/npz_export.py +219 -0
- oscura/exporters/spice_export.py +210 -0
- oscura/extensibility/__init__.py +131 -0
- oscura/extensibility/docs.py +752 -0
- oscura/extensibility/extensions.py +1125 -0
- oscura/extensibility/logging.py +259 -0
- oscura/extensibility/measurements.py +485 -0
- oscura/extensibility/plugins.py +414 -0
- oscura/extensibility/registry.py +346 -0
- oscura/extensibility/templates.py +913 -0
- oscura/extensibility/validation.py +651 -0
- oscura/filtering/__init__.py +89 -0
- oscura/filtering/base.py +563 -0
- oscura/filtering/convenience.py +564 -0
- oscura/filtering/design.py +725 -0
- oscura/filtering/filters.py +32 -0
- oscura/filtering/introspection.py +605 -0
- oscura/guidance/__init__.py +24 -0
- oscura/guidance/recommender.py +429 -0
- oscura/guidance/wizard.py +518 -0
- oscura/inference/__init__.py +251 -0
- oscura/inference/active_learning/README.md +153 -0
- oscura/inference/active_learning/__init__.py +38 -0
- oscura/inference/active_learning/lstar.py +257 -0
- oscura/inference/active_learning/observation_table.py +230 -0
- oscura/inference/active_learning/oracle.py +78 -0
- oscura/inference/active_learning/teachers/__init__.py +15 -0
- oscura/inference/active_learning/teachers/simulator.py +192 -0
- oscura/inference/adaptive_tuning.py +453 -0
- oscura/inference/alignment.py +653 -0
- oscura/inference/bayesian.py +943 -0
- oscura/inference/binary.py +1016 -0
- oscura/inference/crc_reverse.py +711 -0
- oscura/inference/logic.py +288 -0
- oscura/inference/message_format.py +1305 -0
- oscura/inference/protocol.py +417 -0
- oscura/inference/protocol_dsl.py +1084 -0
- oscura/inference/protocol_library.py +1230 -0
- oscura/inference/sequences.py +809 -0
- oscura/inference/signal_intelligence.py +1509 -0
- oscura/inference/spectral.py +215 -0
- oscura/inference/state_machine.py +634 -0
- oscura/inference/stream.py +918 -0
- oscura/integrations/__init__.py +59 -0
- oscura/integrations/llm.py +1827 -0
- oscura/jupyter/__init__.py +32 -0
- oscura/jupyter/display.py +268 -0
- oscura/jupyter/magic.py +334 -0
- oscura/loaders/__init__.py +526 -0
- oscura/loaders/binary.py +69 -0
- oscura/loaders/configurable.py +1255 -0
- oscura/loaders/csv.py +26 -0
- oscura/loaders/csv_loader.py +473 -0
- oscura/loaders/hdf5.py +9 -0
- oscura/loaders/hdf5_loader.py +510 -0
- oscura/loaders/lazy.py +370 -0
- oscura/loaders/mmap_loader.py +583 -0
- oscura/loaders/numpy_loader.py +436 -0
- oscura/loaders/pcap.py +432 -0
- oscura/loaders/preprocessing.py +368 -0
- oscura/loaders/rigol.py +287 -0
- oscura/loaders/sigrok.py +321 -0
- oscura/loaders/tdms.py +367 -0
- oscura/loaders/tektronix.py +711 -0
- oscura/loaders/validation.py +584 -0
- oscura/loaders/vcd.py +464 -0
- oscura/loaders/wav.py +233 -0
- oscura/math/__init__.py +45 -0
- oscura/math/arithmetic.py +824 -0
- oscura/math/interpolation.py +413 -0
- oscura/onboarding/__init__.py +39 -0
- oscura/onboarding/help.py +498 -0
- oscura/onboarding/tutorials.py +405 -0
- oscura/onboarding/wizard.py +466 -0
- oscura/optimization/__init__.py +19 -0
- oscura/optimization/parallel.py +440 -0
- oscura/optimization/search.py +532 -0
- oscura/pipeline/__init__.py +43 -0
- oscura/pipeline/base.py +338 -0
- oscura/pipeline/composition.py +242 -0
- oscura/pipeline/parallel.py +448 -0
- oscura/pipeline/pipeline.py +375 -0
- oscura/pipeline/reverse_engineering.py +1119 -0
- oscura/plugins/__init__.py +122 -0
- oscura/plugins/base.py +272 -0
- oscura/plugins/cli.py +497 -0
- oscura/plugins/discovery.py +411 -0
- oscura/plugins/isolation.py +418 -0
- oscura/plugins/lifecycle.py +959 -0
- oscura/plugins/manager.py +493 -0
- oscura/plugins/registry.py +421 -0
- oscura/plugins/versioning.py +372 -0
- oscura/py.typed +0 -0
- oscura/quality/__init__.py +65 -0
- oscura/quality/ensemble.py +740 -0
- oscura/quality/explainer.py +338 -0
- oscura/quality/scoring.py +616 -0
- oscura/quality/warnings.py +456 -0
- oscura/reporting/__init__.py +248 -0
- oscura/reporting/advanced.py +1234 -0
- oscura/reporting/analyze.py +448 -0
- oscura/reporting/argument_preparer.py +596 -0
- oscura/reporting/auto_report.py +507 -0
- oscura/reporting/batch.py +615 -0
- oscura/reporting/chart_selection.py +223 -0
- oscura/reporting/comparison.py +330 -0
- oscura/reporting/config.py +615 -0
- oscura/reporting/content/__init__.py +39 -0
- oscura/reporting/content/executive.py +127 -0
- oscura/reporting/content/filtering.py +191 -0
- oscura/reporting/content/minimal.py +257 -0
- oscura/reporting/content/verbosity.py +162 -0
- oscura/reporting/core.py +508 -0
- oscura/reporting/core_formats/__init__.py +17 -0
- oscura/reporting/core_formats/multi_format.py +210 -0
- oscura/reporting/engine.py +836 -0
- oscura/reporting/export.py +366 -0
- oscura/reporting/formatting/__init__.py +129 -0
- oscura/reporting/formatting/emphasis.py +81 -0
- oscura/reporting/formatting/numbers.py +403 -0
- oscura/reporting/formatting/standards.py +55 -0
- oscura/reporting/formatting.py +466 -0
- oscura/reporting/html.py +578 -0
- oscura/reporting/index.py +590 -0
- oscura/reporting/multichannel.py +296 -0
- oscura/reporting/output.py +379 -0
- oscura/reporting/pdf.py +373 -0
- oscura/reporting/plots.py +731 -0
- oscura/reporting/pptx_export.py +360 -0
- oscura/reporting/renderers/__init__.py +11 -0
- oscura/reporting/renderers/pdf.py +94 -0
- oscura/reporting/sections.py +471 -0
- oscura/reporting/standards.py +680 -0
- oscura/reporting/summary_generator.py +368 -0
- oscura/reporting/tables.py +397 -0
- oscura/reporting/template_system.py +724 -0
- oscura/reporting/templates/__init__.py +15 -0
- oscura/reporting/templates/definition.py +205 -0
- oscura/reporting/templates/index.html +649 -0
- oscura/reporting/templates/index.md +173 -0
- oscura/schemas/__init__.py +158 -0
- oscura/schemas/bus_configuration.json +322 -0
- oscura/schemas/device_mapping.json +182 -0
- oscura/schemas/packet_format.json +418 -0
- oscura/schemas/protocol_definition.json +363 -0
- oscura/search/__init__.py +16 -0
- oscura/search/anomaly.py +292 -0
- oscura/search/context.py +149 -0
- oscura/search/pattern.py +160 -0
- oscura/session/__init__.py +34 -0
- oscura/session/annotations.py +289 -0
- oscura/session/history.py +313 -0
- oscura/session/session.py +445 -0
- oscura/streaming/__init__.py +43 -0
- oscura/streaming/chunked.py +611 -0
- oscura/streaming/progressive.py +393 -0
- oscura/streaming/realtime.py +622 -0
- oscura/testing/__init__.py +54 -0
- oscura/testing/synthetic.py +808 -0
- oscura/triggering/__init__.py +68 -0
- oscura/triggering/base.py +229 -0
- oscura/triggering/edge.py +353 -0
- oscura/triggering/pattern.py +344 -0
- oscura/triggering/pulse.py +581 -0
- oscura/triggering/window.py +453 -0
- oscura/ui/__init__.py +48 -0
- oscura/ui/formatters.py +526 -0
- oscura/ui/progressive_display.py +340 -0
- oscura/utils/__init__.py +99 -0
- oscura/utils/autodetect.py +338 -0
- oscura/utils/buffer.py +389 -0
- oscura/utils/lazy.py +407 -0
- oscura/utils/lazy_imports.py +147 -0
- oscura/utils/memory.py +836 -0
- oscura/utils/memory_advanced.py +1326 -0
- oscura/utils/memory_extensions.py +465 -0
- oscura/utils/progressive.py +352 -0
- oscura/utils/windowing.py +362 -0
- oscura/visualization/__init__.py +321 -0
- oscura/visualization/accessibility.py +526 -0
- oscura/visualization/annotations.py +374 -0
- oscura/visualization/axis_scaling.py +305 -0
- oscura/visualization/colors.py +453 -0
- oscura/visualization/digital.py +337 -0
- oscura/visualization/eye.py +420 -0
- oscura/visualization/histogram.py +281 -0
- oscura/visualization/interactive.py +858 -0
- oscura/visualization/jitter.py +702 -0
- oscura/visualization/keyboard.py +394 -0
- oscura/visualization/layout.py +365 -0
- oscura/visualization/optimization.py +1028 -0
- oscura/visualization/palettes.py +446 -0
- oscura/visualization/plot.py +92 -0
- oscura/visualization/power.py +290 -0
- oscura/visualization/power_extended.py +626 -0
- oscura/visualization/presets.py +467 -0
- oscura/visualization/protocols.py +932 -0
- oscura/visualization/render.py +207 -0
- oscura/visualization/rendering.py +444 -0
- oscura/visualization/reverse_engineering.py +791 -0
- oscura/visualization/signal_integrity.py +808 -0
- oscura/visualization/specialized.py +553 -0
- oscura/visualization/spectral.py +811 -0
- oscura/visualization/styles.py +381 -0
- oscura/visualization/thumbnails.py +311 -0
- oscura/visualization/time_axis.py +351 -0
- oscura/visualization/waveform.py +367 -0
- oscura/workflow/__init__.py +13 -0
- oscura/workflow/dag.py +377 -0
- oscura/workflows/__init__.py +58 -0
- oscura/workflows/compliance.py +280 -0
- oscura/workflows/digital.py +272 -0
- oscura/workflows/multi_trace.py +502 -0
- oscura/workflows/power.py +178 -0
- oscura/workflows/protocol.py +492 -0
- oscura/workflows/reverse_engineering.py +639 -0
- oscura/workflows/signal_integrity.py +227 -0
- oscura-0.1.0.dist-info/METADATA +300 -0
- oscura-0.1.0.dist-info/RECORD +463 -0
- oscura-0.1.0.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/licenses/LICENSE +1 -1
- oscura-0.0.1.dist-info/METADATA +0 -63
- oscura-0.0.1.dist-info/RECORD +0 -5
- {oscura-0.0.1.dist-info → oscura-0.1.0.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"""Eye diagram metrics and measurements.
|
|
2
|
+
|
|
3
|
+
This module provides measurements on eye diagrams including height,
|
|
4
|
+
width, Q-factor, and crossing percentage.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.eye.metrics import eye_height, eye_width, q_factor
|
|
9
|
+
>>> height = eye_height(eye_diagram)
|
|
10
|
+
>>> width = eye_width(eye_diagram)
|
|
11
|
+
>>> q = q_factor(eye_diagram)
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
IEEE 802.3: Ethernet Physical Layer Specifications
|
|
15
|
+
OIF CEI: Common Electrical I/O
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from typing import TYPE_CHECKING
|
|
22
|
+
|
|
23
|
+
import numpy as np
|
|
24
|
+
from scipy import special
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from numpy.typing import NDArray
|
|
28
|
+
|
|
29
|
+
from oscura.analyzers.eye.diagram import EyeDiagram
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class EyeMetrics:
|
|
34
|
+
"""Complete eye diagram measurement results.
|
|
35
|
+
|
|
36
|
+
Attributes:
|
|
37
|
+
height: Eye height in volts.
|
|
38
|
+
height_at_ber: Eye height at specified BER.
|
|
39
|
+
width: Eye width in UI.
|
|
40
|
+
width_at_ber: Eye width at specified BER.
|
|
41
|
+
q_factor: Signal quality factor.
|
|
42
|
+
crossing_percent: Crossing percentage (ideal = 50%).
|
|
43
|
+
mean_high: Mean logic high level.
|
|
44
|
+
mean_low: Mean logic low level.
|
|
45
|
+
sigma_high: Standard deviation of high level.
|
|
46
|
+
sigma_low: Standard deviation of low level.
|
|
47
|
+
snr: Signal-to-noise ratio in dB.
|
|
48
|
+
ber_estimate: Estimated BER from Q-factor.
|
|
49
|
+
"""
|
|
50
|
+
|
|
51
|
+
height: float
|
|
52
|
+
height_at_ber: float | None
|
|
53
|
+
width: float
|
|
54
|
+
width_at_ber: float | None
|
|
55
|
+
q_factor: float
|
|
56
|
+
crossing_percent: float
|
|
57
|
+
mean_high: float
|
|
58
|
+
mean_low: float
|
|
59
|
+
sigma_high: float
|
|
60
|
+
sigma_low: float
|
|
61
|
+
snr: float
|
|
62
|
+
ber_estimate: float
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def eye_height(
|
|
66
|
+
eye: EyeDiagram,
|
|
67
|
+
*,
|
|
68
|
+
position: float = 0.5,
|
|
69
|
+
ber: float | None = None,
|
|
70
|
+
) -> float:
|
|
71
|
+
"""Measure vertical eye opening (eye height).
|
|
72
|
+
|
|
73
|
+
Measures the vertical distance between logic levels at the
|
|
74
|
+
specified horizontal position within the unit interval.
|
|
75
|
+
|
|
76
|
+
Args:
|
|
77
|
+
eye: Eye diagram data.
|
|
78
|
+
position: Horizontal position in UI (0.0 to 1.0, default 0.5 = center).
|
|
79
|
+
ber: If specified, calculate height at this BER using Gaussian extrapolation.
|
|
80
|
+
|
|
81
|
+
Returns:
|
|
82
|
+
Eye height in volts (or input units).
|
|
83
|
+
|
|
84
|
+
Example:
|
|
85
|
+
>>> height = eye_height(eye)
|
|
86
|
+
>>> print(f"Eye height: {height * 1e3:.2f} mV")
|
|
87
|
+
|
|
88
|
+
References:
|
|
89
|
+
IEEE 802.3 Clause 68: 10GBASE-T PHY
|
|
90
|
+
"""
|
|
91
|
+
# Get samples at specified position
|
|
92
|
+
samples_per_ui = eye.samples_per_ui
|
|
93
|
+
position_idx = int(position * samples_per_ui) % len(eye.time_axis)
|
|
94
|
+
|
|
95
|
+
# Use global threshold to separate high from low logic levels
|
|
96
|
+
all_data = eye.data.flatten()
|
|
97
|
+
low_level = np.percentile(all_data, 10)
|
|
98
|
+
high_level = np.percentile(all_data, 90)
|
|
99
|
+
threshold = (low_level + high_level) / 2
|
|
100
|
+
|
|
101
|
+
# Extract voltage values at this position from all traces
|
|
102
|
+
voltages = eye.data[:, position_idx]
|
|
103
|
+
high_voltages = voltages[voltages > threshold]
|
|
104
|
+
low_voltages = voltages[voltages <= threshold]
|
|
105
|
+
|
|
106
|
+
# If no eye opening at this position, search for a better position
|
|
107
|
+
if len(high_voltages) == 0 or len(low_voltages) == 0:
|
|
108
|
+
# Search all positions for one with both high and low samples
|
|
109
|
+
for idx in range(len(eye.time_axis)):
|
|
110
|
+
v = eye.data[:, idx]
|
|
111
|
+
h_v = v[v > threshold]
|
|
112
|
+
l_v = v[v <= threshold]
|
|
113
|
+
if len(h_v) > 0 and len(l_v) > 0:
|
|
114
|
+
# Found a position with eye opening
|
|
115
|
+
high_voltages = h_v
|
|
116
|
+
low_voltages = l_v
|
|
117
|
+
break
|
|
118
|
+
else:
|
|
119
|
+
# No position found with eye opening
|
|
120
|
+
return np.nan # type: ignore[no-any-return]
|
|
121
|
+
|
|
122
|
+
if ber is None:
|
|
123
|
+
# Simple min-max eye height
|
|
124
|
+
min_high = np.min(high_voltages)
|
|
125
|
+
max_low = np.max(low_voltages)
|
|
126
|
+
return max(0.0, min_high - max_low) # type: ignore[no-any-return]
|
|
127
|
+
|
|
128
|
+
else:
|
|
129
|
+
# BER-extrapolated eye height
|
|
130
|
+
mu_high = np.mean(high_voltages)
|
|
131
|
+
mu_low = np.mean(low_voltages)
|
|
132
|
+
sigma_high = np.std(high_voltages)
|
|
133
|
+
sigma_low = np.std(low_voltages)
|
|
134
|
+
|
|
135
|
+
if sigma_high <= 0 or sigma_low <= 0:
|
|
136
|
+
return mu_high - mu_low # type: ignore[no-any-return]
|
|
137
|
+
|
|
138
|
+
# Q-factor for BER
|
|
139
|
+
q = np.sqrt(2) * special.erfcinv(2 * ber)
|
|
140
|
+
|
|
141
|
+
# Eye height at BER = (mu_high - q*sigma_high) - (mu_low + q*sigma_low)
|
|
142
|
+
height = (mu_high - q * sigma_high) - (mu_low + q * sigma_low)
|
|
143
|
+
|
|
144
|
+
return max(0.0, height) # type: ignore[no-any-return]
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def eye_width(
|
|
148
|
+
eye: EyeDiagram,
|
|
149
|
+
*,
|
|
150
|
+
level: float = 0.5,
|
|
151
|
+
ber: float | None = None,
|
|
152
|
+
) -> float:
|
|
153
|
+
"""Measure horizontal eye opening (eye width).
|
|
154
|
+
|
|
155
|
+
Measures the horizontal opening at the decision threshold level.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
eye: Eye diagram data.
|
|
159
|
+
level: Vertical level as fraction (0.0 = low, 1.0 = high, default 0.5).
|
|
160
|
+
ber: If specified, calculate width at this BER.
|
|
161
|
+
|
|
162
|
+
Returns:
|
|
163
|
+
Eye width in UI (0.0 to 1.0).
|
|
164
|
+
|
|
165
|
+
Example:
|
|
166
|
+
>>> width = eye_width(eye)
|
|
167
|
+
>>> print(f"Eye width: {width:.3f} UI")
|
|
168
|
+
|
|
169
|
+
References:
|
|
170
|
+
IEEE 802.3 Clause 68
|
|
171
|
+
"""
|
|
172
|
+
data = eye.data
|
|
173
|
+
|
|
174
|
+
# Calculate global threshold to separate logic levels
|
|
175
|
+
all_data = data.flatten()
|
|
176
|
+
low_level = np.percentile(all_data, 10)
|
|
177
|
+
high_level = np.percentile(all_data, 90)
|
|
178
|
+
global_threshold = (low_level + high_level) / 2
|
|
179
|
+
|
|
180
|
+
# For a 2-UI eye, we need to find the eye opening across all time positions
|
|
181
|
+
# We look for the widest region where all traces are separated
|
|
182
|
+
samples_per_ui = eye.samples_per_ui
|
|
183
|
+
|
|
184
|
+
# Calculate separation at each time point
|
|
185
|
+
separations = []
|
|
186
|
+
time_indices = []
|
|
187
|
+
|
|
188
|
+
for i in range(len(eye.time_axis)):
|
|
189
|
+
voltages = data[:, i]
|
|
190
|
+
high_v = voltages[voltages > global_threshold]
|
|
191
|
+
low_v = voltages[voltages <= global_threshold]
|
|
192
|
+
|
|
193
|
+
if len(high_v) > 0 and len(low_v) > 0:
|
|
194
|
+
# Measure separation between distributions
|
|
195
|
+
separation = np.min(high_v) - np.max(low_v)
|
|
196
|
+
if separation > 0:
|
|
197
|
+
separations.append(separation)
|
|
198
|
+
time_indices.append(i)
|
|
199
|
+
|
|
200
|
+
if len(separations) == 0:
|
|
201
|
+
return np.nan # type: ignore[no-any-return]
|
|
202
|
+
|
|
203
|
+
# Find contiguous region with good separation
|
|
204
|
+
if len(time_indices) < 2:
|
|
205
|
+
return float(len(time_indices)) / samples_per_ui
|
|
206
|
+
|
|
207
|
+
# Find the widest contiguous region
|
|
208
|
+
diffs = np.diff(time_indices)
|
|
209
|
+
gaps = np.where(diffs > 1)[0]
|
|
210
|
+
|
|
211
|
+
if len(gaps) == 0:
|
|
212
|
+
# All contiguous
|
|
213
|
+
width_samples = len(time_indices)
|
|
214
|
+
else:
|
|
215
|
+
# Find longest contiguous segment
|
|
216
|
+
segments = []
|
|
217
|
+
start = 0
|
|
218
|
+
for gap in gaps:
|
|
219
|
+
segments.append(gap + 1 - start)
|
|
220
|
+
start = gap + 1
|
|
221
|
+
segments.append(len(time_indices) - start)
|
|
222
|
+
width_samples = max(segments)
|
|
223
|
+
|
|
224
|
+
# Width in UI (can be > 1.0 for 2-UI eyes)
|
|
225
|
+
width_ui = width_samples / samples_per_ui
|
|
226
|
+
# Clamp to 1.0 for single UI measurement
|
|
227
|
+
width_ui = min(1.0, width_ui)
|
|
228
|
+
|
|
229
|
+
# Apply BER margin if requested
|
|
230
|
+
if ber is not None and width_ui > 0:
|
|
231
|
+
q = np.sqrt(2) * special.erfcinv(2 * ber)
|
|
232
|
+
# Reduce width by jitter margin (rough approximation)
|
|
233
|
+
jitter_reduction = 0.1 * q / 7.0 # Scale by Q/7 (Q=7 is ~1e-12 BER)
|
|
234
|
+
width_ui = max(0.0, width_ui - jitter_reduction)
|
|
235
|
+
|
|
236
|
+
return max(0.0, min(1.0, width_ui))
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def q_factor(eye: EyeDiagram, *, position: float = 0.5) -> float:
|
|
240
|
+
"""Calculate Q-factor from eye diagram.
|
|
241
|
+
|
|
242
|
+
Q-factor measures signal quality:
|
|
243
|
+
Q = (mu_high - mu_low) / (sigma_high + sigma_low)
|
|
244
|
+
|
|
245
|
+
Higher Q indicates cleaner eye with better BER margin.
|
|
246
|
+
|
|
247
|
+
Args:
|
|
248
|
+
eye: Eye diagram data.
|
|
249
|
+
position: Horizontal position in UI for measurement.
|
|
250
|
+
|
|
251
|
+
Returns:
|
|
252
|
+
Q-factor value.
|
|
253
|
+
|
|
254
|
+
Example:
|
|
255
|
+
>>> q = q_factor(eye)
|
|
256
|
+
>>> print(f"Q-factor: {q:.2f}")
|
|
257
|
+
|
|
258
|
+
References:
|
|
259
|
+
IEEE 802.3 Clause 52
|
|
260
|
+
"""
|
|
261
|
+
samples_per_ui = eye.samples_per_ui
|
|
262
|
+
position_idx = int(position * samples_per_ui) % len(eye.time_axis)
|
|
263
|
+
|
|
264
|
+
# Use global threshold to separate high from low logic levels
|
|
265
|
+
all_data = eye.data.flatten()
|
|
266
|
+
low_level = np.percentile(all_data, 10)
|
|
267
|
+
high_level = np.percentile(all_data, 90)
|
|
268
|
+
threshold = (low_level + high_level) / 2
|
|
269
|
+
|
|
270
|
+
voltages = eye.data[:, position_idx]
|
|
271
|
+
high_voltages = voltages[voltages > threshold]
|
|
272
|
+
low_voltages = voltages[voltages <= threshold]
|
|
273
|
+
|
|
274
|
+
# If no eye opening at this position, search for a better position
|
|
275
|
+
if len(high_voltages) < 2 or len(low_voltages) < 2:
|
|
276
|
+
# Search all positions for one with both high and low samples
|
|
277
|
+
for idx in range(len(eye.time_axis)):
|
|
278
|
+
v = eye.data[:, idx]
|
|
279
|
+
h_v = v[v > threshold]
|
|
280
|
+
l_v = v[v <= threshold]
|
|
281
|
+
if len(h_v) >= 2 and len(l_v) >= 2:
|
|
282
|
+
# Found a position with eye opening
|
|
283
|
+
high_voltages = h_v
|
|
284
|
+
low_voltages = l_v
|
|
285
|
+
break
|
|
286
|
+
else:
|
|
287
|
+
# No position found with eye opening
|
|
288
|
+
return np.nan # type: ignore[no-any-return]
|
|
289
|
+
|
|
290
|
+
mu_high = np.mean(high_voltages)
|
|
291
|
+
mu_low = np.mean(low_voltages)
|
|
292
|
+
sigma_high = np.std(high_voltages)
|
|
293
|
+
sigma_low = np.std(low_voltages)
|
|
294
|
+
|
|
295
|
+
denominator = sigma_high + sigma_low
|
|
296
|
+
|
|
297
|
+
if denominator <= 0:
|
|
298
|
+
return np.inf if mu_high > mu_low else np.nan # type: ignore[no-any-return]
|
|
299
|
+
|
|
300
|
+
q = (mu_high - mu_low) / denominator
|
|
301
|
+
|
|
302
|
+
return q # type: ignore[no-any-return]
|
|
303
|
+
|
|
304
|
+
|
|
305
|
+
def crossing_percentage(eye: EyeDiagram) -> float:
|
|
306
|
+
"""Measure eye crossing percentage.
|
|
307
|
+
|
|
308
|
+
The crossing percentage indicates where the eye crosses
|
|
309
|
+
vertically. Ideal is 50% (equal rise/fall times).
|
|
310
|
+
Deviation indicates duty cycle distortion.
|
|
311
|
+
|
|
312
|
+
Args:
|
|
313
|
+
eye: Eye diagram data.
|
|
314
|
+
|
|
315
|
+
Returns:
|
|
316
|
+
Crossing percentage (0.0 to 100.0).
|
|
317
|
+
|
|
318
|
+
Example:
|
|
319
|
+
>>> xing = crossing_percentage(eye)
|
|
320
|
+
>>> print(f"Crossing: {xing:.1f}%")
|
|
321
|
+
|
|
322
|
+
References:
|
|
323
|
+
OIF CEI 3.0 Section 5.3
|
|
324
|
+
"""
|
|
325
|
+
data = eye.data
|
|
326
|
+
samples_per_ui = eye.samples_per_ui
|
|
327
|
+
|
|
328
|
+
# Find voltage range
|
|
329
|
+
all_low = np.percentile(data, 5)
|
|
330
|
+
all_high = np.percentile(data, 95)
|
|
331
|
+
amplitude = all_high - all_low
|
|
332
|
+
|
|
333
|
+
if amplitude <= 0:
|
|
334
|
+
return np.nan # type: ignore[no-any-return]
|
|
335
|
+
|
|
336
|
+
# Find crossing points (where traces cross the center time)
|
|
337
|
+
# Look at the rising and falling edges
|
|
338
|
+
center_idx = samples_per_ui // 2
|
|
339
|
+
|
|
340
|
+
# Extract crossing voltages (at or near 0.5 UI and 1.5 UI)
|
|
341
|
+
crossing_voltages = []
|
|
342
|
+
|
|
343
|
+
for trace in data:
|
|
344
|
+
# Find zero-crossings in derivative (transitions)
|
|
345
|
+
diff = np.diff(trace)
|
|
346
|
+
|
|
347
|
+
# Find rising crossings
|
|
348
|
+
rising_mask = (diff[:-1] > 0) & (diff[1:] > 0)
|
|
349
|
+
rising_idx = np.where(rising_mask)[0]
|
|
350
|
+
|
|
351
|
+
for idx in rising_idx:
|
|
352
|
+
if abs(idx - center_idx) < samples_per_ui // 4:
|
|
353
|
+
crossing_voltages.append(trace[idx])
|
|
354
|
+
|
|
355
|
+
# Find falling crossings
|
|
356
|
+
falling_mask = (diff[:-1] < 0) & (diff[1:] < 0)
|
|
357
|
+
falling_idx = np.where(falling_mask)[0]
|
|
358
|
+
|
|
359
|
+
for idx in falling_idx:
|
|
360
|
+
if abs(idx - center_idx) < samples_per_ui // 4:
|
|
361
|
+
crossing_voltages.append(trace[idx])
|
|
362
|
+
|
|
363
|
+
if len(crossing_voltages) < 2:
|
|
364
|
+
# Fall back to simple median crossing level
|
|
365
|
+
np.percentile(data, 50)
|
|
366
|
+
return 50.0
|
|
367
|
+
|
|
368
|
+
crossing_voltage = np.mean(crossing_voltages)
|
|
369
|
+
|
|
370
|
+
# Calculate crossing percentage
|
|
371
|
+
crossing_percent = (crossing_voltage - all_low) / amplitude * 100
|
|
372
|
+
|
|
373
|
+
return crossing_percent # type: ignore[no-any-return]
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def eye_contour(
|
|
377
|
+
eye: EyeDiagram,
|
|
378
|
+
ber_levels: list[float] | None = None,
|
|
379
|
+
) -> dict[float, tuple[NDArray[np.float64], NDArray[np.float64]]]:
|
|
380
|
+
"""Generate eye contour polygons at various BER levels.
|
|
381
|
+
|
|
382
|
+
Creates nested contours showing the eye opening at different
|
|
383
|
+
BER levels, useful for margin analysis.
|
|
384
|
+
|
|
385
|
+
Args:
|
|
386
|
+
eye: Eye diagram data.
|
|
387
|
+
ber_levels: List of BER levels (default: [1e-3, 1e-6, 1e-9, 1e-12]).
|
|
388
|
+
|
|
389
|
+
Returns:
|
|
390
|
+
Dictionary mapping BER to (time_ui, voltage) contour arrays.
|
|
391
|
+
|
|
392
|
+
Example:
|
|
393
|
+
>>> contours = eye_contour(eye)
|
|
394
|
+
>>> for ber, (t, v) in contours.items():
|
|
395
|
+
... print(f"BER {ber:.0e}: {len(t)} points")
|
|
396
|
+
|
|
397
|
+
References:
|
|
398
|
+
OIF CEI: Eye Contour Methodology
|
|
399
|
+
"""
|
|
400
|
+
if ber_levels is None:
|
|
401
|
+
ber_levels = [1e-3, 1e-6, 1e-9, 1e-12]
|
|
402
|
+
|
|
403
|
+
contours: dict[float, tuple[NDArray[np.float64], NDArray[np.float64]]] = {}
|
|
404
|
+
|
|
405
|
+
# Use global threshold to separate high from low logic levels
|
|
406
|
+
# Use mean of 10th and 90th percentiles to handle skewed distributions
|
|
407
|
+
all_data = eye.data.flatten()
|
|
408
|
+
low_level = np.percentile(all_data, 10)
|
|
409
|
+
high_level = np.percentile(all_data, 90)
|
|
410
|
+
global_threshold = (low_level + high_level) / 2
|
|
411
|
+
|
|
412
|
+
for ber in ber_levels:
|
|
413
|
+
# Q-factor for this BER
|
|
414
|
+
q = np.sqrt(2) * special.erfcinv(2 * ber)
|
|
415
|
+
|
|
416
|
+
upper_times = []
|
|
417
|
+
upper_voltages = []
|
|
418
|
+
lower_times = []
|
|
419
|
+
lower_voltages = []
|
|
420
|
+
|
|
421
|
+
# Calculate eye opening at each time position across all UIs
|
|
422
|
+
for i in range(len(eye.time_axis)):
|
|
423
|
+
t_ui = eye.time_axis[i]
|
|
424
|
+
voltages = eye.data[:, i]
|
|
425
|
+
|
|
426
|
+
# Use global threshold
|
|
427
|
+
high_v = voltages[voltages > global_threshold]
|
|
428
|
+
low_v = voltages[voltages <= global_threshold]
|
|
429
|
+
|
|
430
|
+
# Need reasonable number of both high and low samples
|
|
431
|
+
if len(high_v) < 2 or len(low_v) < 2:
|
|
432
|
+
continue
|
|
433
|
+
|
|
434
|
+
# Skip if distribution is too skewed (likely transition region)
|
|
435
|
+
total = len(high_v) + len(low_v)
|
|
436
|
+
if len(high_v) < total * 0.2 or len(low_v) < total * 0.2:
|
|
437
|
+
continue
|
|
438
|
+
|
|
439
|
+
mu_high = np.mean(high_v)
|
|
440
|
+
sigma_high = np.std(high_v)
|
|
441
|
+
mu_low = np.mean(low_v)
|
|
442
|
+
sigma_low = np.std(low_v)
|
|
443
|
+
|
|
444
|
+
# Upper contour: mu_high - q * sigma_high
|
|
445
|
+
upper = mu_high - q * sigma_high
|
|
446
|
+
|
|
447
|
+
# Lower contour: mu_low + q * sigma_low
|
|
448
|
+
lower = mu_low + q * sigma_low
|
|
449
|
+
|
|
450
|
+
if upper > lower:
|
|
451
|
+
upper_times.append(t_ui)
|
|
452
|
+
upper_voltages.append(upper)
|
|
453
|
+
lower_times.append(t_ui)
|
|
454
|
+
lower_voltages.append(lower)
|
|
455
|
+
|
|
456
|
+
if len(upper_times) > 0:
|
|
457
|
+
# Create closed contour: upper trace forward, lower trace backward
|
|
458
|
+
contour_times = np.concatenate([np.array(upper_times), np.array(lower_times[::-1])])
|
|
459
|
+
contour_voltages = np.concatenate(
|
|
460
|
+
[np.array(upper_voltages), np.array(lower_voltages[::-1])]
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
contours[ber] = (contour_times, contour_voltages)
|
|
464
|
+
|
|
465
|
+
return contours
|
|
466
|
+
|
|
467
|
+
|
|
468
|
+
def measure_eye(
|
|
469
|
+
eye: EyeDiagram,
|
|
470
|
+
*,
|
|
471
|
+
ber: float = 1e-12,
|
|
472
|
+
) -> EyeMetrics:
|
|
473
|
+
"""Compute comprehensive eye diagram measurements.
|
|
474
|
+
|
|
475
|
+
Args:
|
|
476
|
+
eye: Eye diagram data.
|
|
477
|
+
ber: BER level for extrapolated measurements.
|
|
478
|
+
|
|
479
|
+
Returns:
|
|
480
|
+
EyeMetrics with all measurements.
|
|
481
|
+
|
|
482
|
+
Example:
|
|
483
|
+
>>> metrics = measure_eye(eye)
|
|
484
|
+
>>> print(f"Height: {metrics.height * 1e3:.2f} mV")
|
|
485
|
+
>>> print(f"Width: {metrics.width:.3f} UI")
|
|
486
|
+
>>> print(f"Q-factor: {metrics.q_factor:.2f}")
|
|
487
|
+
"""
|
|
488
|
+
# Get samples at center
|
|
489
|
+
samples_per_ui = eye.samples_per_ui
|
|
490
|
+
center_idx = samples_per_ui // 2
|
|
491
|
+
center_voltages = eye.data[:, center_idx]
|
|
492
|
+
|
|
493
|
+
# Use global threshold to separate logic levels
|
|
494
|
+
all_data = eye.data.flatten()
|
|
495
|
+
low_level = np.percentile(all_data, 10)
|
|
496
|
+
high_level = np.percentile(all_data, 90)
|
|
497
|
+
threshold = (low_level + high_level) / 2
|
|
498
|
+
|
|
499
|
+
high_v = center_voltages[center_voltages > threshold]
|
|
500
|
+
low_v = center_voltages[center_voltages <= threshold]
|
|
501
|
+
|
|
502
|
+
if len(high_v) < 2:
|
|
503
|
+
high_v = center_voltages[center_voltages >= np.percentile(center_voltages, 75)]
|
|
504
|
+
if len(low_v) < 2:
|
|
505
|
+
low_v = center_voltages[center_voltages <= np.percentile(center_voltages, 25)]
|
|
506
|
+
|
|
507
|
+
mean_high = float(np.mean(high_v)) if len(high_v) > 0 else np.nan
|
|
508
|
+
mean_low = float(np.mean(low_v)) if len(low_v) > 0 else np.nan
|
|
509
|
+
sigma_high = float(np.std(high_v)) if len(high_v) > 0 else np.nan
|
|
510
|
+
sigma_low = float(np.std(low_v)) if len(low_v) > 0 else np.nan
|
|
511
|
+
|
|
512
|
+
# Calculate metrics
|
|
513
|
+
height = eye_height(eye)
|
|
514
|
+
height_at_ber = eye_height(eye, ber=ber)
|
|
515
|
+
width = eye_width(eye)
|
|
516
|
+
width_at_ber = eye_width(eye, ber=ber)
|
|
517
|
+
q = q_factor(eye)
|
|
518
|
+
xing = crossing_percentage(eye)
|
|
519
|
+
|
|
520
|
+
# SNR
|
|
521
|
+
amplitude = mean_high - mean_low
|
|
522
|
+
noise_rms = np.sqrt((sigma_high**2 + sigma_low**2) / 2)
|
|
523
|
+
if noise_rms > 0 and amplitude > 0:
|
|
524
|
+
snr = 20 * np.log10(amplitude / noise_rms)
|
|
525
|
+
else:
|
|
526
|
+
snr = np.inf if amplitude > 0 else np.nan
|
|
527
|
+
|
|
528
|
+
# BER estimate from Q-factor
|
|
529
|
+
ber_estimate = 0.5 * special.erfc(q / np.sqrt(2)) if q > 0 and np.isfinite(q) else 0.5
|
|
530
|
+
|
|
531
|
+
return EyeMetrics(
|
|
532
|
+
height=height,
|
|
533
|
+
height_at_ber=height_at_ber,
|
|
534
|
+
width=width,
|
|
535
|
+
width_at_ber=width_at_ber,
|
|
536
|
+
q_factor=q,
|
|
537
|
+
crossing_percent=xing,
|
|
538
|
+
mean_high=mean_high,
|
|
539
|
+
mean_low=mean_low,
|
|
540
|
+
sigma_high=sigma_high,
|
|
541
|
+
sigma_low=sigma_low,
|
|
542
|
+
snr=snr,
|
|
543
|
+
ber_estimate=ber_estimate,
|
|
544
|
+
)
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
__all__ = [
|
|
548
|
+
"EyeMetrics",
|
|
549
|
+
"crossing_percentage",
|
|
550
|
+
"eye_contour",
|
|
551
|
+
"eye_height",
|
|
552
|
+
"eye_width",
|
|
553
|
+
"measure_eye",
|
|
554
|
+
"q_factor",
|
|
555
|
+
]
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"""Jitter analysis module for advanced timing characterization.
|
|
2
|
+
|
|
3
|
+
This module provides IEEE 2414-2020 compliant jitter analysis including
|
|
4
|
+
decomposition into random and deterministic components, bathtub curves,
|
|
5
|
+
and jitter spectrum analysis.
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
Example:
|
|
9
|
+
>>> from oscura.analyzers.jitter import extract_rj, tj_at_ber, bathtub_curve
|
|
10
|
+
>>> rj = extract_rj(tie_data)
|
|
11
|
+
>>> tj = tj_at_ber(rj_rms=rj.rj_rms, dj_pp=dj.dj_pp, ber=1e-12)
|
|
12
|
+
>>> positions, ber_values = bathtub_curve(tie_data, unit_interval=1e-9)
|
|
13
|
+
|
|
14
|
+
References:
|
|
15
|
+
IEEE 2414-2020: Standard for Jitter and Phase Noise
|
|
16
|
+
JEDEC JESD65C: Definition of Skew Specifications for Standard Logic Devices
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from oscura.analyzers.jitter.ber import (
|
|
20
|
+
BathtubCurveResult,
|
|
21
|
+
bathtub_curve,
|
|
22
|
+
ber_from_q_factor,
|
|
23
|
+
eye_opening_at_ber,
|
|
24
|
+
q_factor_from_ber,
|
|
25
|
+
tj_at_ber,
|
|
26
|
+
)
|
|
27
|
+
from oscura.analyzers.jitter.decomposition import (
|
|
28
|
+
DataDependentJitterResult,
|
|
29
|
+
DeterministicJitterResult,
|
|
30
|
+
JitterDecomposition,
|
|
31
|
+
PeriodicJitterResult,
|
|
32
|
+
RandomJitterResult,
|
|
33
|
+
decompose_jitter,
|
|
34
|
+
extract_ddj,
|
|
35
|
+
extract_dj,
|
|
36
|
+
extract_pj,
|
|
37
|
+
extract_rj,
|
|
38
|
+
)
|
|
39
|
+
from oscura.analyzers.jitter.measurements import (
|
|
40
|
+
CycleJitterResult,
|
|
41
|
+
DutyCycleDistortionResult,
|
|
42
|
+
cycle_to_cycle_jitter,
|
|
43
|
+
measure_dcd,
|
|
44
|
+
period_jitter,
|
|
45
|
+
tie_from_edges,
|
|
46
|
+
)
|
|
47
|
+
from oscura.analyzers.jitter.spectrum import (
|
|
48
|
+
JitterSpectrumResult,
|
|
49
|
+
identify_periodic_components,
|
|
50
|
+
jitter_spectrum,
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
__all__ = [
|
|
54
|
+
# BER
|
|
55
|
+
"BathtubCurveResult",
|
|
56
|
+
# Measurements
|
|
57
|
+
"CycleJitterResult",
|
|
58
|
+
"DataDependentJitterResult",
|
|
59
|
+
"DeterministicJitterResult",
|
|
60
|
+
"DutyCycleDistortionResult",
|
|
61
|
+
# Decomposition
|
|
62
|
+
"JitterDecomposition",
|
|
63
|
+
# Spectrum
|
|
64
|
+
"JitterSpectrumResult",
|
|
65
|
+
"PeriodicJitterResult",
|
|
66
|
+
"RandomJitterResult",
|
|
67
|
+
"bathtub_curve",
|
|
68
|
+
"ber_from_q_factor",
|
|
69
|
+
"cycle_to_cycle_jitter",
|
|
70
|
+
"decompose_jitter",
|
|
71
|
+
"extract_ddj",
|
|
72
|
+
"extract_dj",
|
|
73
|
+
"extract_pj",
|
|
74
|
+
"extract_rj",
|
|
75
|
+
"eye_opening_at_ber",
|
|
76
|
+
"identify_periodic_components",
|
|
77
|
+
"jitter_spectrum",
|
|
78
|
+
"measure_dcd",
|
|
79
|
+
"period_jitter",
|
|
80
|
+
"q_factor_from_ber",
|
|
81
|
+
"tie_from_edges",
|
|
82
|
+
"tj_at_ber",
|
|
83
|
+
]
|