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,831 @@
|
|
|
1
|
+
"""Legacy system signal analysis.
|
|
2
|
+
|
|
3
|
+
This module provides analysis tools for legacy RTL/TTL systems with
|
|
4
|
+
mixed logic families and multi-voltage domains.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.exploratory.legacy import detect_logic_families_multi_channel
|
|
9
|
+
>>> families = detect_logic_families_multi_channel(channels)
|
|
10
|
+
>>> for ch, result in families.items():
|
|
11
|
+
... print(f"Channel {ch}: {result['family']} (confidence={result['confidence']:.2f})")
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from typing import TYPE_CHECKING, Any, Literal
|
|
18
|
+
|
|
19
|
+
import numpy as np
|
|
20
|
+
|
|
21
|
+
if TYPE_CHECKING:
|
|
22
|
+
from numpy.typing import NDArray
|
|
23
|
+
|
|
24
|
+
from oscura.core.types import WaveformTrace
|
|
25
|
+
|
|
26
|
+
# Logic family specifications per IEEE/JEDEC standards
|
|
27
|
+
LOGIC_FAMILY_SPECS = {
|
|
28
|
+
"TTL": {
|
|
29
|
+
"vil_max": 0.8,
|
|
30
|
+
"vih_min": 2.0,
|
|
31
|
+
"vol_max": 0.4,
|
|
32
|
+
"voh_min": 2.4,
|
|
33
|
+
"vcc": 5.0,
|
|
34
|
+
},
|
|
35
|
+
"CMOS_5V": {
|
|
36
|
+
"vil_max": 1.5,
|
|
37
|
+
"vih_min": 3.5,
|
|
38
|
+
"vol_max": 0.5,
|
|
39
|
+
"voh_min": 4.5,
|
|
40
|
+
"vcc": 5.0,
|
|
41
|
+
},
|
|
42
|
+
"LVTTL": {
|
|
43
|
+
"vil_max": 0.8,
|
|
44
|
+
"vih_min": 2.0,
|
|
45
|
+
"vol_max": 0.4,
|
|
46
|
+
"voh_min": 2.4,
|
|
47
|
+
"vcc": 3.3,
|
|
48
|
+
},
|
|
49
|
+
"LVCMOS_3V3": {
|
|
50
|
+
"vil_max": 0.8,
|
|
51
|
+
"vih_min": 2.0,
|
|
52
|
+
"vol_max": 0.4,
|
|
53
|
+
"voh_min": 2.4,
|
|
54
|
+
"vcc": 3.3,
|
|
55
|
+
},
|
|
56
|
+
"LVCMOS_2V5": {
|
|
57
|
+
"vil_max": 0.7,
|
|
58
|
+
"vih_min": 1.7,
|
|
59
|
+
"vol_max": 0.4,
|
|
60
|
+
"voh_min": 2.0,
|
|
61
|
+
"vcc": 2.5,
|
|
62
|
+
},
|
|
63
|
+
"LVCMOS_1V8": {
|
|
64
|
+
"vil_max": 0.35 * 1.8,
|
|
65
|
+
"vih_min": 0.65 * 1.8,
|
|
66
|
+
"vol_max": 0.4,
|
|
67
|
+
"voh_min": 1.4,
|
|
68
|
+
"vcc": 1.8,
|
|
69
|
+
},
|
|
70
|
+
"ECL": {
|
|
71
|
+
"vil_max": -1.475,
|
|
72
|
+
"vih_min": -1.105,
|
|
73
|
+
"vol_max": -1.65,
|
|
74
|
+
"voh_min": -0.98,
|
|
75
|
+
"vcc": -5.2,
|
|
76
|
+
},
|
|
77
|
+
"PECL": {
|
|
78
|
+
"vil_max": 3.4,
|
|
79
|
+
"vih_min": 4.0,
|
|
80
|
+
"vol_max": 3.2,
|
|
81
|
+
"voh_min": 4.4,
|
|
82
|
+
"vcc": 5.0,
|
|
83
|
+
},
|
|
84
|
+
"OPEN_COLLECTOR": {
|
|
85
|
+
"vil_max": 0.8,
|
|
86
|
+
"vih_min": 2.0,
|
|
87
|
+
"vol_max": 0.4,
|
|
88
|
+
"voh_min": None, # Depends on pullup
|
|
89
|
+
"vcc": 5.0,
|
|
90
|
+
},
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
@dataclass
|
|
95
|
+
class LogicFamilyResult:
|
|
96
|
+
"""Result of logic family detection.
|
|
97
|
+
|
|
98
|
+
Attributes:
|
|
99
|
+
family: Detected logic family name.
|
|
100
|
+
confidence: Confidence score (0.0 to 1.0).
|
|
101
|
+
v_low: Measured low voltage level.
|
|
102
|
+
v_high: Measured high voltage level.
|
|
103
|
+
alternatives: List of alternative candidates with confidence.
|
|
104
|
+
degradation_warning: Optional warning about signal degradation.
|
|
105
|
+
deviation_pct: Deviation from spec as percentage.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
family: str
|
|
109
|
+
confidence: float
|
|
110
|
+
v_low: float
|
|
111
|
+
v_high: float
|
|
112
|
+
alternatives: list[tuple[str, float]]
|
|
113
|
+
degradation_warning: str | None = None
|
|
114
|
+
deviation_pct: float = 0.0
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def detect_logic_families_multi_channel(
|
|
118
|
+
channels: list[WaveformTrace] | dict[int, WaveformTrace],
|
|
119
|
+
*,
|
|
120
|
+
confidence_thresholds: dict[str, float] | None = None,
|
|
121
|
+
warn_on_degradation: bool = True,
|
|
122
|
+
voltage_tolerance: float = 0.20,
|
|
123
|
+
min_edges_for_detection: int = 10,
|
|
124
|
+
) -> dict[int, LogicFamilyResult]:
|
|
125
|
+
"""Detect logic family for each channel independently.
|
|
126
|
+
|
|
127
|
+
Analyzes voltage distribution per channel and maps to logic family specs.
|
|
128
|
+
|
|
129
|
+
Args:
|
|
130
|
+
channels: List or dict of WaveformTrace objects.
|
|
131
|
+
confidence_thresholds: Thresholds for high/medium confidence.
|
|
132
|
+
Default: {'high': 0.9, 'medium': 0.7}
|
|
133
|
+
warn_on_degradation: If True, warn on degraded signals.
|
|
134
|
+
voltage_tolerance: Tolerance for spec matching (default 20%).
|
|
135
|
+
min_edges_for_detection: Minimum edges required per channel.
|
|
136
|
+
|
|
137
|
+
Returns:
|
|
138
|
+
Dictionary mapping channel ID to LogicFamilyResult.
|
|
139
|
+
|
|
140
|
+
Example:
|
|
141
|
+
>>> channels = [trace.get_channel(i) for i in range(8)]
|
|
142
|
+
>>> families = detect_logic_families_multi_channel(channels)
|
|
143
|
+
>>> for ch_id, result in families.items():
|
|
144
|
+
... print(f"Channel {ch_id}: {result.family} (confidence={result.confidence:.2f})")
|
|
145
|
+
|
|
146
|
+
References:
|
|
147
|
+
LEGACY-001: Multi-Channel Logic Family Auto-Detection
|
|
148
|
+
IEEE 1164: Standard for Logic Families
|
|
149
|
+
JEDEC: Logic Family Specifications
|
|
150
|
+
"""
|
|
151
|
+
if confidence_thresholds is None:
|
|
152
|
+
confidence_thresholds = {"high": 0.9, "medium": 0.7}
|
|
153
|
+
|
|
154
|
+
# Convert list to dict if needed
|
|
155
|
+
if isinstance(channels, list):
|
|
156
|
+
channels = dict(enumerate(channels))
|
|
157
|
+
|
|
158
|
+
results = {}
|
|
159
|
+
|
|
160
|
+
for ch_id, trace in channels.items():
|
|
161
|
+
data = trace.data
|
|
162
|
+
|
|
163
|
+
# Extract voltage percentiles
|
|
164
|
+
p10 = np.percentile(data, 10)
|
|
165
|
+
np.percentile(data, 50)
|
|
166
|
+
p90 = np.percentile(data, 90)
|
|
167
|
+
|
|
168
|
+
# Estimate low and high levels
|
|
169
|
+
v_low = p10
|
|
170
|
+
v_high = p90
|
|
171
|
+
v_high - v_low
|
|
172
|
+
|
|
173
|
+
# Count edges for confidence
|
|
174
|
+
threshold = (v_low + v_high) / 2
|
|
175
|
+
edges = np.sum(np.abs(np.diff(data > threshold)))
|
|
176
|
+
|
|
177
|
+
# Score each logic family
|
|
178
|
+
candidates = []
|
|
179
|
+
|
|
180
|
+
for family_name, specs in LOGIC_FAMILY_SPECS.items():
|
|
181
|
+
score = _score_logic_family(v_low, v_high, specs, voltage_tolerance) # type: ignore[arg-type]
|
|
182
|
+
if score > 0:
|
|
183
|
+
candidates.append((family_name, score))
|
|
184
|
+
|
|
185
|
+
# Sort by score descending
|
|
186
|
+
candidates.sort(key=lambda x: x[1], reverse=True)
|
|
187
|
+
|
|
188
|
+
if not candidates:
|
|
189
|
+
# No match found
|
|
190
|
+
result = LogicFamilyResult(
|
|
191
|
+
family="UNKNOWN",
|
|
192
|
+
confidence=0.0,
|
|
193
|
+
v_low=v_low,
|
|
194
|
+
v_high=v_high,
|
|
195
|
+
alternatives=[],
|
|
196
|
+
degradation_warning="No matching logic family found",
|
|
197
|
+
)
|
|
198
|
+
else:
|
|
199
|
+
best_family, best_score = candidates[0]
|
|
200
|
+
confidence = min(1.0, best_score)
|
|
201
|
+
|
|
202
|
+
# Reduce confidence if insufficient edges
|
|
203
|
+
if edges < min_edges_for_detection:
|
|
204
|
+
confidence *= 0.5
|
|
205
|
+
|
|
206
|
+
# Check for ambiguity (multiple families close in score)
|
|
207
|
+
alternatives = [
|
|
208
|
+
(name, score) for name, score in candidates[1:4] if best_score - score < 0.2
|
|
209
|
+
]
|
|
210
|
+
|
|
211
|
+
# Check for degradation
|
|
212
|
+
degradation_warning = None
|
|
213
|
+
deviation_pct = 0.0
|
|
214
|
+
|
|
215
|
+
if warn_on_degradation:
|
|
216
|
+
specs = LOGIC_FAMILY_SPECS[best_family]
|
|
217
|
+
if specs["voh_min"] is not None: # type: ignore[index]
|
|
218
|
+
expected_voh = specs["voh_min"] # type: ignore[index]
|
|
219
|
+
if v_high < expected_voh:
|
|
220
|
+
deviation_pct = 100 * (expected_voh - v_high) / expected_voh
|
|
221
|
+
if deviation_pct > 10:
|
|
222
|
+
degradation_warning = (
|
|
223
|
+
f"V_high below spec (expected >= {expected_voh:.3f}V)"
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
result = LogicFamilyResult(
|
|
227
|
+
family=best_family,
|
|
228
|
+
confidence=confidence,
|
|
229
|
+
v_low=v_low,
|
|
230
|
+
v_high=v_high,
|
|
231
|
+
alternatives=alternatives,
|
|
232
|
+
degradation_warning=degradation_warning,
|
|
233
|
+
deviation_pct=deviation_pct,
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
results[ch_id] = result
|
|
237
|
+
|
|
238
|
+
return results
|
|
239
|
+
|
|
240
|
+
|
|
241
|
+
def _score_logic_family(
|
|
242
|
+
v_low: float,
|
|
243
|
+
v_high: float,
|
|
244
|
+
specs: dict[str, float | None],
|
|
245
|
+
tolerance: float,
|
|
246
|
+
) -> float:
|
|
247
|
+
"""Score how well voltage levels match a logic family.
|
|
248
|
+
|
|
249
|
+
Args:
|
|
250
|
+
v_low: Measured low voltage.
|
|
251
|
+
v_high: Measured high voltage.
|
|
252
|
+
specs: Logic family specifications.
|
|
253
|
+
tolerance: Tolerance for matching.
|
|
254
|
+
|
|
255
|
+
Returns:
|
|
256
|
+
Score from 0.0 to 1.0.
|
|
257
|
+
"""
|
|
258
|
+
score = 1.0
|
|
259
|
+
|
|
260
|
+
# Check VOL (output low)
|
|
261
|
+
vol_max = specs["vol_max"]
|
|
262
|
+
if vol_max is not None:
|
|
263
|
+
if v_low <= vol_max:
|
|
264
|
+
score *= 1.0 # Exact match
|
|
265
|
+
elif v_low <= vol_max * (1 + tolerance):
|
|
266
|
+
score *= 0.85 # Within tolerance
|
|
267
|
+
else:
|
|
268
|
+
score *= 0.0 # Outside tolerance
|
|
269
|
+
|
|
270
|
+
# Check VOH (output high)
|
|
271
|
+
voh_min = specs["voh_min"]
|
|
272
|
+
if voh_min is not None:
|
|
273
|
+
if v_high >= voh_min:
|
|
274
|
+
score *= 1.0
|
|
275
|
+
elif v_high >= voh_min * (1 - tolerance):
|
|
276
|
+
score *= 0.85
|
|
277
|
+
else:
|
|
278
|
+
score *= 0.0
|
|
279
|
+
|
|
280
|
+
return score
|
|
281
|
+
|
|
282
|
+
|
|
283
|
+
@dataclass
|
|
284
|
+
class CrossCorrelationResult:
|
|
285
|
+
"""Result of multi-reference cross-correlation.
|
|
286
|
+
|
|
287
|
+
Attributes:
|
|
288
|
+
correlation: Pearson correlation coefficient.
|
|
289
|
+
confidence: Overall confidence in result.
|
|
290
|
+
ref_offset_mv: Reference voltage offset in mV.
|
|
291
|
+
offset_uncertainty_mv: Uncertainty in offset measurement.
|
|
292
|
+
lag_samples: Time lag in samples.
|
|
293
|
+
lag_ns: Time lag in nanoseconds.
|
|
294
|
+
drift_detected: True if reference drift detected.
|
|
295
|
+
drift_rate: Drift rate in V/ms if detected.
|
|
296
|
+
normalized_signal1: Normalized first signal.
|
|
297
|
+
normalized_signal2: Normalized second signal.
|
|
298
|
+
"""
|
|
299
|
+
|
|
300
|
+
correlation: float
|
|
301
|
+
confidence: float
|
|
302
|
+
ref_offset_mv: float
|
|
303
|
+
offset_uncertainty_mv: float
|
|
304
|
+
lag_samples: int
|
|
305
|
+
lag_ns: float
|
|
306
|
+
drift_detected: bool = False
|
|
307
|
+
drift_rate: float | None = None
|
|
308
|
+
normalized_signal1: NDArray[np.float64] | None = None
|
|
309
|
+
normalized_signal2: NDArray[np.float64] | None = None
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def cross_correlate_multi_reference(
|
|
313
|
+
signal1: WaveformTrace,
|
|
314
|
+
signal2: WaveformTrace,
|
|
315
|
+
*,
|
|
316
|
+
detect_drift: bool = False,
|
|
317
|
+
drift_window_ms: float = 10.0,
|
|
318
|
+
) -> CrossCorrelationResult:
|
|
319
|
+
"""Correlate signals with different voltage references.
|
|
320
|
+
|
|
321
|
+
Normalizes signals to [0, 1] using per-signal logic levels before
|
|
322
|
+
computing correlation, enabling comparison of signals with different
|
|
323
|
+
ground references.
|
|
324
|
+
|
|
325
|
+
Args:
|
|
326
|
+
signal1: First signal trace.
|
|
327
|
+
signal2: Second signal trace.
|
|
328
|
+
detect_drift: If True, detect time-varying reference drift.
|
|
329
|
+
drift_window_ms: Window size for drift detection in ms.
|
|
330
|
+
|
|
331
|
+
Returns:
|
|
332
|
+
CrossCorrelationResult with correlation and offset information.
|
|
333
|
+
|
|
334
|
+
Example:
|
|
335
|
+
>>> ttl = trace.get_channel(0) # 5V TTL
|
|
336
|
+
>>> cmos = trace.get_channel(1) # 3.3V CMOS
|
|
337
|
+
>>> result = cross_correlate_multi_reference(ttl, cmos)
|
|
338
|
+
>>> print(f"Correlation: {result.correlation:.3f}")
|
|
339
|
+
>>> print(f"Reference offset: {result.ref_offset_mv:.1f} mV")
|
|
340
|
+
|
|
341
|
+
References:
|
|
342
|
+
LEGACY-002: Multi-Reference Voltage Signal Correlation
|
|
343
|
+
"""
|
|
344
|
+
data1 = signal1.data
|
|
345
|
+
data2 = signal2.data
|
|
346
|
+
|
|
347
|
+
# Normalize each signal to [0, 1]
|
|
348
|
+
norm1 = _normalize_to_logic_levels(data1)
|
|
349
|
+
norm2 = _normalize_to_logic_levels(data2)
|
|
350
|
+
|
|
351
|
+
# Estimate DC offset between normalized signals
|
|
352
|
+
dc_offset = np.mean(norm1) - np.mean(norm2)
|
|
353
|
+
|
|
354
|
+
# Apply offset correction
|
|
355
|
+
norm2_corrected = norm2 + dc_offset
|
|
356
|
+
|
|
357
|
+
# Compute cross-correlation
|
|
358
|
+
min_len = min(len(norm1), len(norm2_corrected))
|
|
359
|
+
norm1 = norm1[:min_len]
|
|
360
|
+
norm2_corrected = norm2_corrected[:min_len]
|
|
361
|
+
|
|
362
|
+
correlation = np.corrcoef(norm1, norm2_corrected)[0, 1]
|
|
363
|
+
|
|
364
|
+
# Find lag using cross-correlation
|
|
365
|
+
xcorr = np.correlate(
|
|
366
|
+
norm1 - np.mean(norm1), norm2_corrected - np.mean(norm2_corrected), mode="full"
|
|
367
|
+
)
|
|
368
|
+
lag_samples = xcorr.argmax() - (len(norm1) - 1)
|
|
369
|
+
|
|
370
|
+
# Convert lag to nanoseconds
|
|
371
|
+
sample_rate = signal1.metadata.sample_rate
|
|
372
|
+
lag_ns = lag_samples / sample_rate * 1e9
|
|
373
|
+
|
|
374
|
+
# Estimate reference voltage offset
|
|
375
|
+
# Reference offset is how much signal2's ground differs from signal1's ground
|
|
376
|
+
v1_min = np.min(data1)
|
|
377
|
+
v2_min = np.min(data2)
|
|
378
|
+
|
|
379
|
+
# Reference offset is difference in ground levels (signal2 relative to signal1)
|
|
380
|
+
ref_offset_mv = (v2_min - v1_min) * 1000
|
|
381
|
+
|
|
382
|
+
# Confidence calculation
|
|
383
|
+
offset_uncertainty_mv = abs(ref_offset_mv) * 0.1 # 10% uncertainty
|
|
384
|
+
confidence = abs(correlation) * (1 - min(abs(ref_offset_mv) / 1000, 1.0))
|
|
385
|
+
|
|
386
|
+
# Drift detection
|
|
387
|
+
drift_detected = False
|
|
388
|
+
drift_rate = None
|
|
389
|
+
|
|
390
|
+
if detect_drift:
|
|
391
|
+
# Calculate offset in windows
|
|
392
|
+
window_samples = int(drift_window_ms * 1e-3 * sample_rate)
|
|
393
|
+
n_windows = min_len // window_samples
|
|
394
|
+
|
|
395
|
+
if n_windows >= 2:
|
|
396
|
+
offsets = []
|
|
397
|
+
for i in range(n_windows):
|
|
398
|
+
start = i * window_samples
|
|
399
|
+
end = start + window_samples
|
|
400
|
+
win_offset = np.mean(data1[start:end]) - np.mean(data2[start:end])
|
|
401
|
+
offsets.append(win_offset)
|
|
402
|
+
|
|
403
|
+
# Check for drift
|
|
404
|
+
offset_change = abs(offsets[-1] - offsets[0])
|
|
405
|
+
drift_rate_val = offset_change / (n_windows * drift_window_ms)
|
|
406
|
+
|
|
407
|
+
if drift_rate_val > 0.1: # V/ms threshold
|
|
408
|
+
drift_detected = True
|
|
409
|
+
drift_rate = drift_rate_val
|
|
410
|
+
|
|
411
|
+
return CrossCorrelationResult(
|
|
412
|
+
correlation=float(correlation),
|
|
413
|
+
confidence=float(confidence),
|
|
414
|
+
ref_offset_mv=float(ref_offset_mv),
|
|
415
|
+
offset_uncertainty_mv=float(offset_uncertainty_mv),
|
|
416
|
+
lag_samples=int(lag_samples),
|
|
417
|
+
lag_ns=float(lag_ns),
|
|
418
|
+
drift_detected=drift_detected,
|
|
419
|
+
drift_rate=drift_rate,
|
|
420
|
+
normalized_signal1=norm1,
|
|
421
|
+
normalized_signal2=norm2_corrected,
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
|
|
425
|
+
def _normalize_to_logic_levels(data: NDArray[np.float64]) -> NDArray[np.float64]:
|
|
426
|
+
"""Normalize signal to [0, 1] based on logic levels.
|
|
427
|
+
|
|
428
|
+
Args:
|
|
429
|
+
data: Signal data.
|
|
430
|
+
|
|
431
|
+
Returns:
|
|
432
|
+
Normalized signal.
|
|
433
|
+
"""
|
|
434
|
+
v_min = float(np.percentile(data, 5))
|
|
435
|
+
v_max = float(np.percentile(data, 95))
|
|
436
|
+
v_range = v_max - v_min
|
|
437
|
+
|
|
438
|
+
if v_range < 1e-6:
|
|
439
|
+
return np.zeros_like(data)
|
|
440
|
+
|
|
441
|
+
return (data - v_min) / v_range
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
@dataclass
|
|
445
|
+
class SignalQualityResult:
|
|
446
|
+
"""Result of signal quality assessment.
|
|
447
|
+
|
|
448
|
+
Attributes:
|
|
449
|
+
status: 'OK', 'WARNING', or 'CRITICAL'.
|
|
450
|
+
violation_count: Number of spec violations.
|
|
451
|
+
total_samples: Total samples analyzed.
|
|
452
|
+
min_margin_mv: Minimum margin to spec in mV.
|
|
453
|
+
violations: List of violation details.
|
|
454
|
+
vil_violations: Count of VIL violations.
|
|
455
|
+
vih_violations: Count of VIH violations.
|
|
456
|
+
vol_violations: Count of VOL violations.
|
|
457
|
+
voh_violations: Count of VOH violations.
|
|
458
|
+
failure_diagnosis: Suggested failure mode.
|
|
459
|
+
time_to_failure_s: Estimated time to failure.
|
|
460
|
+
drift_rate_mv_per_s: Voltage drift rate.
|
|
461
|
+
"""
|
|
462
|
+
|
|
463
|
+
status: Literal["OK", "WARNING", "CRITICAL"]
|
|
464
|
+
violation_count: int
|
|
465
|
+
total_samples: int
|
|
466
|
+
min_margin_mv: float
|
|
467
|
+
violations: list[dict[str, Any]]
|
|
468
|
+
vil_violations: int = 0
|
|
469
|
+
vih_violations: int = 0
|
|
470
|
+
vol_violations: int = 0
|
|
471
|
+
voh_violations: int = 0
|
|
472
|
+
vil_rate: float = 0.0
|
|
473
|
+
vih_rate: float = 0.0
|
|
474
|
+
vol_rate: float = 0.0
|
|
475
|
+
voh_rate: float = 0.0
|
|
476
|
+
failure_diagnosis: str | None = None
|
|
477
|
+
time_to_failure_s: float | None = None
|
|
478
|
+
drift_rate_mv_per_s: float | None = None
|
|
479
|
+
|
|
480
|
+
|
|
481
|
+
def assess_signal_quality(
|
|
482
|
+
signal: WaveformTrace,
|
|
483
|
+
logic_family: str,
|
|
484
|
+
*,
|
|
485
|
+
check_aging: bool = False,
|
|
486
|
+
time_window_s: float = 1.0,
|
|
487
|
+
) -> SignalQualityResult:
|
|
488
|
+
"""Assess signal quality against logic family specs.
|
|
489
|
+
|
|
490
|
+
Checks voltage compliance with specifications and detects degraded
|
|
491
|
+
signal levels that may indicate aging or failing components.
|
|
492
|
+
|
|
493
|
+
Args:
|
|
494
|
+
signal: Signal trace to assess.
|
|
495
|
+
logic_family: Logic family name (e.g., 'TTL', 'CMOS_5V').
|
|
496
|
+
check_aging: If True, analyze for aging/degradation.
|
|
497
|
+
time_window_s: Window for drift analysis.
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
SignalQualityResult with compliance status and violations.
|
|
501
|
+
|
|
502
|
+
Example:
|
|
503
|
+
>>> result = assess_signal_quality(signal, logic_family='TTL')
|
|
504
|
+
>>> print(f"Status: {result.status}")
|
|
505
|
+
>>> print(f"Violations: {result.violation_count}")
|
|
506
|
+
|
|
507
|
+
References:
|
|
508
|
+
LEGACY-003: Logic Level Compliance Checking
|
|
509
|
+
JEDEC Standard No. 8C
|
|
510
|
+
"""
|
|
511
|
+
if logic_family not in LOGIC_FAMILY_SPECS:
|
|
512
|
+
logic_family = "TTL" # Default fallback
|
|
513
|
+
|
|
514
|
+
specs = LOGIC_FAMILY_SPECS[logic_family]
|
|
515
|
+
data = signal.data
|
|
516
|
+
sample_rate = signal.metadata.sample_rate
|
|
517
|
+
n_samples = len(data)
|
|
518
|
+
|
|
519
|
+
# Threshold for high/low classification
|
|
520
|
+
threshold = (specs["vil_max"] + specs["vih_min"]) / 2 # type: ignore[index]
|
|
521
|
+
|
|
522
|
+
# Classify samples
|
|
523
|
+
is_high = data > threshold
|
|
524
|
+
is_low = ~is_high
|
|
525
|
+
|
|
526
|
+
# Count violations
|
|
527
|
+
high_samples = data[is_high]
|
|
528
|
+
low_samples = data[is_low]
|
|
529
|
+
|
|
530
|
+
voh_min = specs["voh_min"] # type: ignore[index]
|
|
531
|
+
vol_max = specs["vol_max"] # type: ignore[index]
|
|
532
|
+
|
|
533
|
+
voh_violations = 0
|
|
534
|
+
vol_violations = 0
|
|
535
|
+
violations = []
|
|
536
|
+
|
|
537
|
+
# Check VOH violations (high samples below spec)
|
|
538
|
+
if voh_min is not None and len(high_samples) > 0:
|
|
539
|
+
voh_mask = high_samples < voh_min
|
|
540
|
+
voh_violations = np.sum(voh_mask)
|
|
541
|
+
if voh_violations > 0:
|
|
542
|
+
violation_indices = np.where(is_high)[0][voh_mask]
|
|
543
|
+
for idx in violation_indices[:10]: # First 10 violations
|
|
544
|
+
violations.append(
|
|
545
|
+
{
|
|
546
|
+
"timestamp_us": idx / sample_rate * 1e6,
|
|
547
|
+
"type": "VOH",
|
|
548
|
+
"voltage": data[idx],
|
|
549
|
+
"spec_limit": voh_min,
|
|
550
|
+
}
|
|
551
|
+
)
|
|
552
|
+
|
|
553
|
+
# Check VOL violations (low samples above spec)
|
|
554
|
+
if vol_max is not None and len(low_samples) > 0:
|
|
555
|
+
vol_mask = low_samples > vol_max
|
|
556
|
+
vol_violations = np.sum(vol_mask)
|
|
557
|
+
if vol_violations > 0:
|
|
558
|
+
violation_indices = np.where(is_low)[0][vol_mask]
|
|
559
|
+
for idx in violation_indices[:10]:
|
|
560
|
+
violations.append(
|
|
561
|
+
{
|
|
562
|
+
"timestamp_us": idx / sample_rate * 1e6,
|
|
563
|
+
"type": "VOL",
|
|
564
|
+
"voltage": data[idx],
|
|
565
|
+
"spec_limit": vol_max,
|
|
566
|
+
}
|
|
567
|
+
)
|
|
568
|
+
|
|
569
|
+
total_violations = voh_violations + vol_violations
|
|
570
|
+
|
|
571
|
+
# Calculate margins
|
|
572
|
+
margins = []
|
|
573
|
+
if len(high_samples) > 0 and voh_min is not None:
|
|
574
|
+
margins.extend((high_samples - voh_min) * 1000) # Convert to mV
|
|
575
|
+
if len(low_samples) > 0 and vol_max is not None:
|
|
576
|
+
margins.extend((vol_max - low_samples) * 1000)
|
|
577
|
+
|
|
578
|
+
min_margin_mv = min(margins) if margins else 0.0
|
|
579
|
+
|
|
580
|
+
# Determine status
|
|
581
|
+
if min_margin_mv < 100:
|
|
582
|
+
status: Literal["OK", "WARNING", "CRITICAL"] = "CRITICAL"
|
|
583
|
+
elif min_margin_mv < 200:
|
|
584
|
+
status = "WARNING"
|
|
585
|
+
else:
|
|
586
|
+
status = "OK"
|
|
587
|
+
|
|
588
|
+
# Calculate rates
|
|
589
|
+
n_high = len(high_samples)
|
|
590
|
+
n_low = len(low_samples)
|
|
591
|
+
voh_rate = voh_violations / n_high if n_high > 0 else 0.0
|
|
592
|
+
vol_rate = vol_violations / n_low if n_low > 0 else 0.0
|
|
593
|
+
|
|
594
|
+
# Aging analysis
|
|
595
|
+
failure_diagnosis = None
|
|
596
|
+
time_to_failure_s = None
|
|
597
|
+
drift_rate_mv_per_s = None
|
|
598
|
+
|
|
599
|
+
if check_aging and n_samples > 1000:
|
|
600
|
+
# Calculate drift over time
|
|
601
|
+
window_samples = int(time_window_s * sample_rate)
|
|
602
|
+
n_windows = n_samples // window_samples
|
|
603
|
+
|
|
604
|
+
if n_windows >= 2:
|
|
605
|
+
window_means = [
|
|
606
|
+
np.mean(data[i * window_samples : (i + 1) * window_samples])
|
|
607
|
+
for i in range(n_windows)
|
|
608
|
+
]
|
|
609
|
+
|
|
610
|
+
drift = window_means[-1] - window_means[0]
|
|
611
|
+
drift_rate_mv_per_s = drift * 1000 / (n_windows * time_window_s)
|
|
612
|
+
|
|
613
|
+
if abs(drift_rate_mv_per_s) > 0.1: # Significant drift
|
|
614
|
+
# Estimate time to failure
|
|
615
|
+
if voh_min is not None and drift_rate_mv_per_s < 0:
|
|
616
|
+
current_margin = np.mean(high_samples) - voh_min
|
|
617
|
+
if current_margin > 0:
|
|
618
|
+
time_to_failure_s = current_margin * 1000 / abs(drift_rate_mv_per_s)
|
|
619
|
+
|
|
620
|
+
# Diagnose failure mode
|
|
621
|
+
if voh_violations > vol_violations:
|
|
622
|
+
failure_diagnosis = "Degraded output driver (weak high)"
|
|
623
|
+
elif vol_violations > voh_violations:
|
|
624
|
+
failure_diagnosis = "Degraded output driver (weak low)"
|
|
625
|
+
else:
|
|
626
|
+
failure_diagnosis = "General signal degradation"
|
|
627
|
+
|
|
628
|
+
return SignalQualityResult(
|
|
629
|
+
status=status,
|
|
630
|
+
violation_count=total_violations,
|
|
631
|
+
total_samples=n_samples,
|
|
632
|
+
min_margin_mv=min_margin_mv,
|
|
633
|
+
violations=violations,
|
|
634
|
+
voh_violations=voh_violations,
|
|
635
|
+
vol_violations=vol_violations,
|
|
636
|
+
voh_rate=voh_rate,
|
|
637
|
+
vol_rate=vol_rate,
|
|
638
|
+
failure_diagnosis=failure_diagnosis,
|
|
639
|
+
time_to_failure_s=time_to_failure_s,
|
|
640
|
+
drift_rate_mv_per_s=drift_rate_mv_per_s,
|
|
641
|
+
)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
@dataclass
|
|
645
|
+
class TestPointCharacterization:
|
|
646
|
+
"""Characterization of a single test point.
|
|
647
|
+
|
|
648
|
+
Attributes:
|
|
649
|
+
channel_id: Channel identifier.
|
|
650
|
+
v_low: Low voltage level.
|
|
651
|
+
v_high: High voltage level.
|
|
652
|
+
v_swing: Voltage swing.
|
|
653
|
+
logic_family: Detected logic family.
|
|
654
|
+
confidence: Detection confidence.
|
|
655
|
+
is_digital: True if signal appears digital.
|
|
656
|
+
is_clock: True if signal appears to be a clock.
|
|
657
|
+
frequency: Estimated frequency if periodic.
|
|
658
|
+
"""
|
|
659
|
+
|
|
660
|
+
channel_id: int
|
|
661
|
+
v_low: float
|
|
662
|
+
v_high: float
|
|
663
|
+
v_swing: float
|
|
664
|
+
logic_family: str
|
|
665
|
+
confidence: float
|
|
666
|
+
is_digital: bool
|
|
667
|
+
is_clock: bool
|
|
668
|
+
frequency: float | None
|
|
669
|
+
|
|
670
|
+
|
|
671
|
+
def characterize_test_points(
|
|
672
|
+
channels: list[WaveformTrace] | dict[int, WaveformTrace],
|
|
673
|
+
*,
|
|
674
|
+
sample_rate: float | None = None,
|
|
675
|
+
) -> dict[int, TestPointCharacterization]:
|
|
676
|
+
"""Batch characterize multiple test points.
|
|
677
|
+
|
|
678
|
+
Analyzes 8-16 test points to build a voltage level map of an
|
|
679
|
+
unknown board.
|
|
680
|
+
|
|
681
|
+
Args:
|
|
682
|
+
channels: List or dict of WaveformTrace objects.
|
|
683
|
+
sample_rate: Sample rate in Hz (uses metadata if not specified).
|
|
684
|
+
|
|
685
|
+
Returns:
|
|
686
|
+
Dictionary mapping channel ID to TestPointCharacterization.
|
|
687
|
+
|
|
688
|
+
Example:
|
|
689
|
+
>>> channels = [trace.get_channel(i) for i in range(8)]
|
|
690
|
+
>>> chars = characterize_test_points(channels)
|
|
691
|
+
>>> for ch_id, char in chars.items():
|
|
692
|
+
... print(f"CH{ch_id}: {char.logic_family} ({char.v_low:.2f}V - {char.v_high:.2f}V)")
|
|
693
|
+
|
|
694
|
+
References:
|
|
695
|
+
LEGACY-004: Multi-Channel Voltage Characterization
|
|
696
|
+
"""
|
|
697
|
+
if isinstance(channels, list):
|
|
698
|
+
channels = dict(enumerate(channels))
|
|
699
|
+
|
|
700
|
+
# First detect logic families
|
|
701
|
+
families = detect_logic_families_multi_channel(channels)
|
|
702
|
+
|
|
703
|
+
results = {}
|
|
704
|
+
|
|
705
|
+
for ch_id, trace in channels.items():
|
|
706
|
+
data = trace.data
|
|
707
|
+
sr = sample_rate or trace.metadata.sample_rate
|
|
708
|
+
|
|
709
|
+
# Voltage statistics
|
|
710
|
+
v_low = float(np.percentile(data, 10))
|
|
711
|
+
v_high = float(np.percentile(data, 90))
|
|
712
|
+
v_swing = v_high - v_low
|
|
713
|
+
|
|
714
|
+
# Get logic family result
|
|
715
|
+
family_result = families.get(
|
|
716
|
+
ch_id,
|
|
717
|
+
LogicFamilyResult(
|
|
718
|
+
family="UNKNOWN",
|
|
719
|
+
confidence=0.0,
|
|
720
|
+
v_low=v_low,
|
|
721
|
+
v_high=v_high,
|
|
722
|
+
alternatives=[],
|
|
723
|
+
),
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
# Determine if digital (bimodal distribution)
|
|
727
|
+
is_digital = v_swing > 0.5 and _is_bimodal(data)
|
|
728
|
+
|
|
729
|
+
# Check for clock signal
|
|
730
|
+
is_clock = False
|
|
731
|
+
frequency = None
|
|
732
|
+
|
|
733
|
+
if is_digital and sr is not None:
|
|
734
|
+
# Check for periodic signal via FFT
|
|
735
|
+
from scipy import signal as sp_signal
|
|
736
|
+
|
|
737
|
+
f, psd = sp_signal.welch(data, fs=sr, nperseg=min(1024, len(data)))
|
|
738
|
+
peak_idx = np.argmax(psd[1:]) + 1 # Skip DC
|
|
739
|
+
if psd[peak_idx] > 10 * np.mean(psd): # Strong peak
|
|
740
|
+
frequency = f[peak_idx]
|
|
741
|
+
# Check duty cycle for clock
|
|
742
|
+
threshold = (v_low + v_high) / 2
|
|
743
|
+
high_ratio = np.mean(data > threshold)
|
|
744
|
+
if 0.4 <= high_ratio <= 0.6:
|
|
745
|
+
is_clock = True
|
|
746
|
+
|
|
747
|
+
results[ch_id] = TestPointCharacterization(
|
|
748
|
+
channel_id=ch_id,
|
|
749
|
+
v_low=v_low,
|
|
750
|
+
v_high=v_high,
|
|
751
|
+
v_swing=v_swing,
|
|
752
|
+
logic_family=family_result.family,
|
|
753
|
+
confidence=family_result.confidence,
|
|
754
|
+
is_digital=is_digital,
|
|
755
|
+
is_clock=is_clock,
|
|
756
|
+
frequency=frequency,
|
|
757
|
+
)
|
|
758
|
+
|
|
759
|
+
return results
|
|
760
|
+
|
|
761
|
+
|
|
762
|
+
def _is_bimodal(data: NDArray[np.float64], bins: int = 50) -> bool:
|
|
763
|
+
"""Check if data has bimodal distribution.
|
|
764
|
+
|
|
765
|
+
Args:
|
|
766
|
+
data: Signal data.
|
|
767
|
+
bins: Number of histogram bins.
|
|
768
|
+
|
|
769
|
+
Returns:
|
|
770
|
+
True if distribution appears bimodal (digital signal).
|
|
771
|
+
False for analog signals (sine waves have many peaks).
|
|
772
|
+
"""
|
|
773
|
+
hist, bin_edges = np.histogram(data, bins=bins)
|
|
774
|
+
centers = (bin_edges[:-1] + bin_edges[1:]) / 2
|
|
775
|
+
|
|
776
|
+
# Find peaks (including edge bins for perfect bimodal signals)
|
|
777
|
+
threshold = 0.1 * np.max(hist)
|
|
778
|
+
peaks = []
|
|
779
|
+
|
|
780
|
+
# Check first bin (only needs to be > right neighbor)
|
|
781
|
+
if len(hist) > 1 and hist[0] > hist[1] and hist[0] > threshold:
|
|
782
|
+
peaks.append((0, hist[0], centers[0]))
|
|
783
|
+
|
|
784
|
+
# Check middle bins (need to be > both neighbors)
|
|
785
|
+
for i in range(1, len(hist) - 1):
|
|
786
|
+
if hist[i] > hist[i - 1] and hist[i] > hist[i + 1] and hist[i] > threshold:
|
|
787
|
+
peaks.append((i, hist[i], centers[i]))
|
|
788
|
+
|
|
789
|
+
# Check last bin (only needs to be > left neighbor)
|
|
790
|
+
if len(hist) > 1 and hist[-1] > hist[-2] and hist[-1] > threshold:
|
|
791
|
+
peaks.append((len(hist) - 1, hist[-1], centers[-1]))
|
|
792
|
+
|
|
793
|
+
# Too many peaks suggests analog signal (e.g., sine wave)
|
|
794
|
+
if len(peaks) >= 4:
|
|
795
|
+
return False
|
|
796
|
+
|
|
797
|
+
# Bimodal if exactly 2-3 significant peaks that are well-separated
|
|
798
|
+
if len(peaks) == 2 or len(peaks) == 3:
|
|
799
|
+
peaks.sort(key=lambda x: x[1], reverse=True)
|
|
800
|
+
|
|
801
|
+
# Check if peaks are well-separated (digital signals have peaks at extremes)
|
|
802
|
+
v_min, v_max = np.min(data), np.max(data)
|
|
803
|
+
v_range = v_max - v_min
|
|
804
|
+
if v_range == 0:
|
|
805
|
+
return False
|
|
806
|
+
|
|
807
|
+
# Normalize peak positions
|
|
808
|
+
peak_positions = [(p[2] - v_min) / v_range for p in peaks[:2]]
|
|
809
|
+
|
|
810
|
+
# Digital signals have one peak < 0.4 and one peak > 0.6
|
|
811
|
+
has_low_peak = any(p < 0.4 for p in peak_positions)
|
|
812
|
+
has_high_peak = any(p > 0.6 for p in peak_positions)
|
|
813
|
+
|
|
814
|
+
# Second peak should be significant
|
|
815
|
+
if has_low_peak and has_high_peak and peaks[1][1] > 0.3 * peaks[0][1]:
|
|
816
|
+
return True
|
|
817
|
+
|
|
818
|
+
return False
|
|
819
|
+
|
|
820
|
+
|
|
821
|
+
__all__ = [
|
|
822
|
+
"LOGIC_FAMILY_SPECS",
|
|
823
|
+
"CrossCorrelationResult",
|
|
824
|
+
"LogicFamilyResult",
|
|
825
|
+
"SignalQualityResult",
|
|
826
|
+
"TestPointCharacterization",
|
|
827
|
+
"assess_signal_quality",
|
|
828
|
+
"characterize_test_points",
|
|
829
|
+
"cross_correlate_multi_reference",
|
|
830
|
+
"detect_logic_families_multi_channel",
|
|
831
|
+
]
|