oscura 0.0.1__py3-none-any.whl → 0.1.1__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.1.dist-info/METADATA +300 -0
- oscura-0.1.1.dist-info/RECORD +463 -0
- oscura-0.1.1.dist-info/entry_points.txt +2 -0
- {oscura-0.0.1.dist-info → oscura-0.1.1.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.1.dist-info}/WHEEL +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"""SPI protocol decoder.
|
|
2
|
+
|
|
3
|
+
This module provides SPI (Serial Peripheral Interface) protocol
|
|
4
|
+
decoding with configurable CPOL/CPHA modes and word sizes.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.protocols.spi import SPIDecoder
|
|
9
|
+
>>> decoder = SPIDecoder(cpol=0, cpha=0, word_size=8)
|
|
10
|
+
>>> for packet in decoder.decode(clk=clock, mosi=mosi, miso=miso, cs=cs):
|
|
11
|
+
... print(f"TX: {packet.annotations['mosi'].hex()}")
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
SPI Specification (Motorola)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from typing import TYPE_CHECKING, Literal
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
|
|
23
|
+
from oscura.analyzers.protocols.base import (
|
|
24
|
+
AnnotationLevel,
|
|
25
|
+
ChannelDef,
|
|
26
|
+
OptionDef,
|
|
27
|
+
SyncDecoder,
|
|
28
|
+
)
|
|
29
|
+
from oscura.core.types import DigitalTrace, ProtocolPacket
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import Iterator
|
|
33
|
+
|
|
34
|
+
from numpy.typing import NDArray
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SPIDecoder(SyncDecoder):
|
|
38
|
+
"""SPI protocol decoder.
|
|
39
|
+
|
|
40
|
+
Decodes SPI bus transactions with configurable clock polarity,
|
|
41
|
+
clock phase, and word size.
|
|
42
|
+
|
|
43
|
+
Mode mapping:
|
|
44
|
+
- Mode 0: CPOL=0, CPHA=0 (sample on rising, shift on falling)
|
|
45
|
+
- Mode 1: CPOL=0, CPHA=1 (sample on falling, shift on rising)
|
|
46
|
+
- Mode 2: CPOL=1, CPHA=0 (sample on falling, shift on rising)
|
|
47
|
+
- Mode 3: CPOL=1, CPHA=1 (sample on rising, shift on falling)
|
|
48
|
+
|
|
49
|
+
Example:
|
|
50
|
+
>>> decoder = SPIDecoder(cpol=0, cpha=0, word_size=8)
|
|
51
|
+
>>> for packet in decoder.decode(trace, clk=clk, mosi=mosi, miso=miso):
|
|
52
|
+
... print(f"MOSI: {packet.annotations['mosi'].hex()}")
|
|
53
|
+
"""
|
|
54
|
+
|
|
55
|
+
id = "spi"
|
|
56
|
+
name = "SPI"
|
|
57
|
+
longname = "Serial Peripheral Interface"
|
|
58
|
+
desc = "SPI bus protocol decoder"
|
|
59
|
+
|
|
60
|
+
channels = [ # noqa: RUF012
|
|
61
|
+
ChannelDef("clk", "CLK", "Clock signal", required=True),
|
|
62
|
+
ChannelDef("mosi", "MOSI", "Master Out Slave In", required=True),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
optional_channels = [ # noqa: RUF012
|
|
66
|
+
ChannelDef("miso", "MISO", "Master In Slave Out", required=False),
|
|
67
|
+
ChannelDef("cs", "CS#", "Chip Select (active low)", required=False),
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
options = [ # noqa: RUF012
|
|
71
|
+
OptionDef("cpol", "Clock Polarity", "Clock idle state", default=0, values=[0, 1]),
|
|
72
|
+
OptionDef("cpha", "Clock Phase", "Sample edge", default=0, values=[0, 1]),
|
|
73
|
+
OptionDef(
|
|
74
|
+
"word_size",
|
|
75
|
+
"Word size",
|
|
76
|
+
"Bits per word",
|
|
77
|
+
default=8,
|
|
78
|
+
values=[4, 8, 16, 24, 32],
|
|
79
|
+
),
|
|
80
|
+
OptionDef("bit_order", "Bit order", "Bit order", default="msb", values=["msb", "lsb"]),
|
|
81
|
+
OptionDef(
|
|
82
|
+
"cs_polarity",
|
|
83
|
+
"CS polarity",
|
|
84
|
+
"Chip select polarity",
|
|
85
|
+
default=0,
|
|
86
|
+
values=[0, 1],
|
|
87
|
+
),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
annotations = [ # noqa: RUF012
|
|
91
|
+
("bit", "Bit value"),
|
|
92
|
+
("byte", "Decoded byte"),
|
|
93
|
+
("word", "Decoded word"),
|
|
94
|
+
("transfer", "Complete transfer"),
|
|
95
|
+
]
|
|
96
|
+
|
|
97
|
+
def __init__(
|
|
98
|
+
self,
|
|
99
|
+
cpol: Literal[0, 1] = 0,
|
|
100
|
+
cpha: Literal[0, 1] = 0,
|
|
101
|
+
word_size: int = 8,
|
|
102
|
+
bit_order: Literal["msb", "lsb"] = "msb",
|
|
103
|
+
cs_polarity: Literal[0, 1] = 0,
|
|
104
|
+
) -> None:
|
|
105
|
+
"""Initialize SPI decoder.
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
cpol: Clock polarity (0=idle low, 1=idle high).
|
|
109
|
+
cpha: Clock phase (0=sample on first edge, 1=sample on second edge).
|
|
110
|
+
word_size: Bits per word.
|
|
111
|
+
bit_order: Bit order ("msb" or "lsb").
|
|
112
|
+
cs_polarity: CS active level (0=active low, 1=active high).
|
|
113
|
+
"""
|
|
114
|
+
super().__init__(
|
|
115
|
+
cpol=cpol,
|
|
116
|
+
cpha=cpha,
|
|
117
|
+
word_size=word_size,
|
|
118
|
+
bit_order=bit_order,
|
|
119
|
+
cs_polarity=cs_polarity,
|
|
120
|
+
)
|
|
121
|
+
self._cpol = cpol
|
|
122
|
+
self._cpha = cpha
|
|
123
|
+
self._word_size = word_size
|
|
124
|
+
self._bit_order = bit_order
|
|
125
|
+
self._cs_polarity = cs_polarity
|
|
126
|
+
|
|
127
|
+
def decode( # type: ignore[override]
|
|
128
|
+
self,
|
|
129
|
+
trace: DigitalTrace | None = None,
|
|
130
|
+
*,
|
|
131
|
+
clk: NDArray[np.bool_] | None = None,
|
|
132
|
+
mosi: NDArray[np.bool_] | None = None,
|
|
133
|
+
miso: NDArray[np.bool_] | None = None,
|
|
134
|
+
cs: NDArray[np.bool_] | None = None,
|
|
135
|
+
sample_rate: float = 1.0,
|
|
136
|
+
) -> Iterator[ProtocolPacket]:
|
|
137
|
+
"""Decode SPI transactions.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
trace: Optional primary trace (uses clk if provided).
|
|
141
|
+
clk: Clock signal.
|
|
142
|
+
mosi: Master Out Slave In data.
|
|
143
|
+
miso: Master In Slave Out data (optional).
|
|
144
|
+
cs: Chip Select signal (optional).
|
|
145
|
+
sample_rate: Sample rate in Hz.
|
|
146
|
+
|
|
147
|
+
Yields:
|
|
148
|
+
Decoded SPI words as ProtocolPacket objects.
|
|
149
|
+
|
|
150
|
+
Example:
|
|
151
|
+
>>> decoder = SPIDecoder(cpol=0, cpha=0)
|
|
152
|
+
>>> for pkt in decoder.decode(clk=clk, mosi=mosi, miso=miso, sample_rate=1e9):
|
|
153
|
+
... print(f"Word: 0x{pkt.annotations['mosi_value']:04X}")
|
|
154
|
+
"""
|
|
155
|
+
if trace is not None:
|
|
156
|
+
clk = trace.data
|
|
157
|
+
sample_rate = trace.metadata.sample_rate
|
|
158
|
+
|
|
159
|
+
if clk is None or mosi is None:
|
|
160
|
+
return
|
|
161
|
+
|
|
162
|
+
n_samples = min(len(clk), len(mosi))
|
|
163
|
+
if miso is not None:
|
|
164
|
+
n_samples = min(n_samples, len(miso))
|
|
165
|
+
if cs is not None:
|
|
166
|
+
n_samples = min(n_samples, len(cs))
|
|
167
|
+
|
|
168
|
+
clk = clk[:n_samples]
|
|
169
|
+
mosi = mosi[:n_samples]
|
|
170
|
+
if miso is not None:
|
|
171
|
+
miso = miso[:n_samples]
|
|
172
|
+
if cs is not None:
|
|
173
|
+
cs = cs[:n_samples]
|
|
174
|
+
|
|
175
|
+
# Determine sampling edge based on CPOL and CPHA
|
|
176
|
+
# CPOL=0: idle low, first edge is rising
|
|
177
|
+
# CPOL=1: idle high, first edge is falling
|
|
178
|
+
# CPHA=0: sample on first edge
|
|
179
|
+
# CPHA=1: sample on second edge
|
|
180
|
+
if self._cpol == 0:
|
|
181
|
+
sample_edge = "rising" if self._cpha == 0 else "falling"
|
|
182
|
+
elif self._cpha == 0:
|
|
183
|
+
sample_edge = "falling"
|
|
184
|
+
else:
|
|
185
|
+
sample_edge = "rising"
|
|
186
|
+
|
|
187
|
+
# Find clock edges
|
|
188
|
+
if sample_edge == "rising":
|
|
189
|
+
edges = np.where(~clk[:-1] & clk[1:])[0] + 1
|
|
190
|
+
else:
|
|
191
|
+
edges = np.where(clk[:-1] & ~clk[1:])[0] + 1
|
|
192
|
+
|
|
193
|
+
if len(edges) == 0:
|
|
194
|
+
return
|
|
195
|
+
|
|
196
|
+
# Collect bits into words
|
|
197
|
+
mosi_bits: list[int] = []
|
|
198
|
+
miso_bits: list[int] = []
|
|
199
|
+
word_start_idx = edges[0]
|
|
200
|
+
word_num = 0
|
|
201
|
+
|
|
202
|
+
for edge_idx in edges:
|
|
203
|
+
# Check if CS is active (if provided)
|
|
204
|
+
if cs is not None:
|
|
205
|
+
cs_active = cs[edge_idx] == (self._cs_polarity == 1)
|
|
206
|
+
if not cs_active:
|
|
207
|
+
# CS not active, reset and skip
|
|
208
|
+
if mosi_bits:
|
|
209
|
+
# Emit partial word if any
|
|
210
|
+
pass
|
|
211
|
+
mosi_bits = []
|
|
212
|
+
miso_bits = []
|
|
213
|
+
continue
|
|
214
|
+
|
|
215
|
+
# Sample MOSI
|
|
216
|
+
mosi_bit = 1 if mosi[edge_idx] else 0
|
|
217
|
+
mosi_bits.append(mosi_bit)
|
|
218
|
+
|
|
219
|
+
# Sample MISO if available
|
|
220
|
+
if miso is not None:
|
|
221
|
+
miso_bit = 1 if miso[edge_idx] else 0
|
|
222
|
+
miso_bits.append(miso_bit)
|
|
223
|
+
|
|
224
|
+
# Check if we have a complete word
|
|
225
|
+
if len(mosi_bits) >= self._word_size:
|
|
226
|
+
# Convert bits to value
|
|
227
|
+
mosi_value = self._bits_to_value(mosi_bits[: self._word_size])
|
|
228
|
+
miso_value = (
|
|
229
|
+
self._bits_to_value(miso_bits[: self._word_size]) if miso_bits else None
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
# Calculate timing
|
|
233
|
+
start_time = word_start_idx / sample_rate
|
|
234
|
+
end_time = edge_idx / sample_rate
|
|
235
|
+
|
|
236
|
+
# Encode as bytes
|
|
237
|
+
byte_count = (self._word_size + 7) // 8
|
|
238
|
+
mosi_bytes = mosi_value.to_bytes(byte_count, "big")
|
|
239
|
+
|
|
240
|
+
# Add annotations
|
|
241
|
+
self.put_annotation(
|
|
242
|
+
start_time,
|
|
243
|
+
end_time,
|
|
244
|
+
AnnotationLevel.WORDS,
|
|
245
|
+
f"MOSI: 0x{mosi_value:0{byte_count * 2}X}",
|
|
246
|
+
data=mosi_bytes,
|
|
247
|
+
)
|
|
248
|
+
|
|
249
|
+
annotations = {
|
|
250
|
+
"word_num": word_num,
|
|
251
|
+
"mosi_bits": mosi_bits[: self._word_size],
|
|
252
|
+
"mosi_value": mosi_value,
|
|
253
|
+
"word_size": self._word_size,
|
|
254
|
+
"mode": self._cpol * 2 + self._cpha,
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if miso_value is not None:
|
|
258
|
+
annotations["miso_bits"] = miso_bits[: self._word_size]
|
|
259
|
+
annotations["miso_value"] = miso_value
|
|
260
|
+
|
|
261
|
+
packet = ProtocolPacket(
|
|
262
|
+
timestamp=start_time,
|
|
263
|
+
protocol="spi",
|
|
264
|
+
data=mosi_bytes,
|
|
265
|
+
annotations=annotations,
|
|
266
|
+
errors=[],
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
yield packet
|
|
270
|
+
|
|
271
|
+
# Reset for next word
|
|
272
|
+
mosi_bits = mosi_bits[self._word_size :]
|
|
273
|
+
miso_bits = miso_bits[self._word_size :] if miso_bits else []
|
|
274
|
+
word_start_idx = edge_idx
|
|
275
|
+
word_num += 1
|
|
276
|
+
|
|
277
|
+
def _bits_to_value(self, bits: list[int]) -> int:
|
|
278
|
+
"""Convert bit list to integer value.
|
|
279
|
+
|
|
280
|
+
Args:
|
|
281
|
+
bits: List of bit values (0 or 1).
|
|
282
|
+
|
|
283
|
+
Returns:
|
|
284
|
+
Integer value.
|
|
285
|
+
"""
|
|
286
|
+
value = 0
|
|
287
|
+
|
|
288
|
+
if self._bit_order == "msb":
|
|
289
|
+
for bit in bits:
|
|
290
|
+
value = (value << 1) | bit
|
|
291
|
+
else:
|
|
292
|
+
for i, bit in enumerate(bits):
|
|
293
|
+
value |= bit << i
|
|
294
|
+
|
|
295
|
+
return value
|
|
296
|
+
|
|
297
|
+
|
|
298
|
+
def decode_spi(
|
|
299
|
+
clk: NDArray[np.bool_],
|
|
300
|
+
mosi: NDArray[np.bool_] | None = None,
|
|
301
|
+
miso: NDArray[np.bool_] | None = None,
|
|
302
|
+
cs: NDArray[np.bool_] | None = None,
|
|
303
|
+
sample_rate: float = 1.0,
|
|
304
|
+
cpol: Literal[0, 1] = 0,
|
|
305
|
+
cpha: Literal[0, 1] = 0,
|
|
306
|
+
word_size: int = 8,
|
|
307
|
+
bit_order: Literal["msb", "lsb"] = "msb",
|
|
308
|
+
) -> list[ProtocolPacket]:
|
|
309
|
+
"""Convenience function to decode SPI transactions.
|
|
310
|
+
|
|
311
|
+
Args:
|
|
312
|
+
clk: Clock signal.
|
|
313
|
+
mosi: Master Out Slave In signal (optional).
|
|
314
|
+
miso: Master In Slave Out signal (optional).
|
|
315
|
+
cs: Chip select signal (optional, active low).
|
|
316
|
+
sample_rate: Sample rate in Hz.
|
|
317
|
+
cpol: Clock polarity (0 or 1).
|
|
318
|
+
cpha: Clock phase (0 or 1).
|
|
319
|
+
word_size: Bits per word (default 8).
|
|
320
|
+
bit_order: Bit order ("msb" or "lsb").
|
|
321
|
+
|
|
322
|
+
Returns:
|
|
323
|
+
List of decoded SPI transactions.
|
|
324
|
+
|
|
325
|
+
Example:
|
|
326
|
+
>>> packets = decode_spi(clk, mosi=mosi, miso=miso, sample_rate=10e6)
|
|
327
|
+
>>> for pkt in packets:
|
|
328
|
+
... print(f"MOSI: {pkt.annotations['mosi'].hex()}")
|
|
329
|
+
"""
|
|
330
|
+
decoder = SPIDecoder(cpol=cpol, cpha=cpha, word_size=word_size, bit_order=bit_order)
|
|
331
|
+
return list(decoder.decode(clk=clk, mosi=mosi, miso=miso, cs=cs, sample_rate=sample_rate))
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
__all__ = ["SPIDecoder", "decode_spi"]
|
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"""SWD protocol decoder.
|
|
2
|
+
|
|
3
|
+
This module provides ARM Serial Wire Debug (SWD) protocol decoding
|
|
4
|
+
with DP/AP access detection and ACK/WAIT/FAULT response handling.
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
Example:
|
|
8
|
+
>>> from oscura.analyzers.protocols.swd import SWDDecoder
|
|
9
|
+
>>> decoder = SWDDecoder()
|
|
10
|
+
>>> for packet in decoder.decode(swclk=swclk, swdio=swdio):
|
|
11
|
+
... print(f"Request: {packet.annotations['request_type']}")
|
|
12
|
+
|
|
13
|
+
References:
|
|
14
|
+
ARM Debug Interface Architecture Specification ADIv5.0 to ADIv5.2
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from __future__ import annotations
|
|
18
|
+
|
|
19
|
+
from enum import Enum
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
|
|
24
|
+
from oscura.analyzers.protocols.base import (
|
|
25
|
+
AnnotationLevel,
|
|
26
|
+
ChannelDef,
|
|
27
|
+
SyncDecoder,
|
|
28
|
+
)
|
|
29
|
+
from oscura.core.types import DigitalTrace, ProtocolPacket
|
|
30
|
+
|
|
31
|
+
if TYPE_CHECKING:
|
|
32
|
+
from collections.abc import Iterator
|
|
33
|
+
|
|
34
|
+
from numpy.typing import NDArray
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class SWDResponse(Enum):
|
|
38
|
+
"""SWD ACK responses."""
|
|
39
|
+
|
|
40
|
+
OK = 0b001
|
|
41
|
+
WAIT = 0b010
|
|
42
|
+
FAULT = 0b100
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class SWDDecoder(SyncDecoder):
|
|
46
|
+
"""SWD protocol decoder.
|
|
47
|
+
|
|
48
|
+
Decodes ARM Serial Wire Debug transactions including read/write
|
|
49
|
+
operations to Debug Port (DP) and Access Port (AP) registers.
|
|
50
|
+
|
|
51
|
+
Attributes:
|
|
52
|
+
id: "swd"
|
|
53
|
+
name: "SWD"
|
|
54
|
+
channels: [swclk, swdio] (required)
|
|
55
|
+
|
|
56
|
+
Example:
|
|
57
|
+
>>> decoder = SWDDecoder()
|
|
58
|
+
>>> for packet in decoder.decode(swclk=swclk, swdio=swdio, sample_rate=10e6):
|
|
59
|
+
... print(f"ACK: {packet.annotations['ack']}")
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
id = "swd"
|
|
63
|
+
name = "SWD"
|
|
64
|
+
longname = "Serial Wire Debug"
|
|
65
|
+
desc = "ARM Serial Wire Debug protocol decoder"
|
|
66
|
+
|
|
67
|
+
channels = [ # noqa: RUF012
|
|
68
|
+
ChannelDef("swclk", "SWCLK", "Serial Wire Clock", required=True),
|
|
69
|
+
ChannelDef("swdio", "SWDIO", "Serial Wire Data I/O", required=True),
|
|
70
|
+
]
|
|
71
|
+
|
|
72
|
+
optional_channels = [] # noqa: RUF012
|
|
73
|
+
|
|
74
|
+
options = [] # noqa: RUF012
|
|
75
|
+
|
|
76
|
+
annotations = [ # noqa: RUF012
|
|
77
|
+
("request", "Request packet"),
|
|
78
|
+
("ack", "ACK response"),
|
|
79
|
+
("data", "Data phase"),
|
|
80
|
+
("parity", "Parity bit"),
|
|
81
|
+
("error", "Error"),
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
def __init__(self) -> None:
|
|
85
|
+
"""Initialize SWD decoder."""
|
|
86
|
+
super().__init__()
|
|
87
|
+
|
|
88
|
+
def decode( # type: ignore[override]
|
|
89
|
+
self,
|
|
90
|
+
trace: DigitalTrace | None = None,
|
|
91
|
+
*,
|
|
92
|
+
swclk: NDArray[np.bool_] | None = None,
|
|
93
|
+
swdio: NDArray[np.bool_] | None = None,
|
|
94
|
+
sample_rate: float = 1.0,
|
|
95
|
+
) -> Iterator[ProtocolPacket]:
|
|
96
|
+
"""Decode SWD transactions.
|
|
97
|
+
|
|
98
|
+
Args:
|
|
99
|
+
trace: Optional primary trace.
|
|
100
|
+
swclk: Serial Wire Clock signal.
|
|
101
|
+
swdio: Serial Wire Data I/O signal.
|
|
102
|
+
sample_rate: Sample rate in Hz.
|
|
103
|
+
|
|
104
|
+
Yields:
|
|
105
|
+
Decoded SWD transactions as ProtocolPacket objects.
|
|
106
|
+
|
|
107
|
+
Example:
|
|
108
|
+
>>> decoder = SWDDecoder()
|
|
109
|
+
>>> for pkt in decoder.decode(swclk=swclk, swdio=swdio, sample_rate=1e6):
|
|
110
|
+
... print(f"R/W: {'Read' if pkt.annotations['read'] else 'Write'}")
|
|
111
|
+
"""
|
|
112
|
+
if swclk is None or swdio is None:
|
|
113
|
+
return
|
|
114
|
+
|
|
115
|
+
n_samples = min(len(swclk), len(swdio))
|
|
116
|
+
swclk = swclk[:n_samples]
|
|
117
|
+
swdio = swdio[:n_samples]
|
|
118
|
+
|
|
119
|
+
# Find rising edges of SWCLK (data sampled on rising edge)
|
|
120
|
+
rising_edges = np.where(~swclk[:-1] & swclk[1:])[0] + 1
|
|
121
|
+
|
|
122
|
+
if len(rising_edges) == 0:
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
trans_num = 0
|
|
126
|
+
edge_idx = 0
|
|
127
|
+
|
|
128
|
+
while edge_idx < len(rising_edges):
|
|
129
|
+
# Look for start bit (should be 1)
|
|
130
|
+
start_idx = rising_edges[edge_idx]
|
|
131
|
+
if not swdio[start_idx]:
|
|
132
|
+
edge_idx += 1
|
|
133
|
+
continue
|
|
134
|
+
|
|
135
|
+
# Check if we have a low period before this start bit
|
|
136
|
+
# (to avoid decoding line reset sequences as transactions)
|
|
137
|
+
if start_idx > 0 and edge_idx > 0:
|
|
138
|
+
prev_edge_idx = rising_edges[edge_idx - 1]
|
|
139
|
+
# Check if there was a low period between previous and current edge
|
|
140
|
+
# If SWDIO stayed high between edges, this might be part of line reset
|
|
141
|
+
swdio_between = swdio[prev_edge_idx:start_idx]
|
|
142
|
+
if len(swdio_between) > 0 and np.all(swdio_between):
|
|
143
|
+
# SWDIO was high the entire time - likely line reset, skip
|
|
144
|
+
edge_idx += 1
|
|
145
|
+
continue
|
|
146
|
+
|
|
147
|
+
# Parse request packet (8 bits total)
|
|
148
|
+
# Bit 0: Start (1)
|
|
149
|
+
# Bit 1: APnDP (0=DP, 1=AP)
|
|
150
|
+
# Bit 2: RnW (0=Write, 1=Read)
|
|
151
|
+
# Bits 3-4: A[2:3] (register address bits)
|
|
152
|
+
# Bit 5: Parity (odd parity of bits 1-4)
|
|
153
|
+
# Bit 6: Stop (0)
|
|
154
|
+
# Bit 7: Park (1)
|
|
155
|
+
|
|
156
|
+
if edge_idx + 8 > len(rising_edges):
|
|
157
|
+
break
|
|
158
|
+
|
|
159
|
+
request_bits = []
|
|
160
|
+
for i in range(8):
|
|
161
|
+
bit_idx = rising_edges[edge_idx + i]
|
|
162
|
+
request_bits.append(1 if swdio[bit_idx] else 0)
|
|
163
|
+
|
|
164
|
+
# Extract fields
|
|
165
|
+
start_bit = request_bits[0]
|
|
166
|
+
apndp = request_bits[1]
|
|
167
|
+
rnw = request_bits[2]
|
|
168
|
+
addr_2 = request_bits[3]
|
|
169
|
+
addr_3 = request_bits[4]
|
|
170
|
+
parity = request_bits[5]
|
|
171
|
+
stop_bit = request_bits[6]
|
|
172
|
+
park_bit = request_bits[7]
|
|
173
|
+
|
|
174
|
+
# Validate request format
|
|
175
|
+
errors = []
|
|
176
|
+
if start_bit != 1:
|
|
177
|
+
errors.append("Invalid start bit")
|
|
178
|
+
if stop_bit != 0:
|
|
179
|
+
errors.append("Invalid stop bit")
|
|
180
|
+
if park_bit != 1:
|
|
181
|
+
errors.append("Invalid park bit")
|
|
182
|
+
|
|
183
|
+
# Check parity (odd parity of APnDP, RnW, A[2:3])
|
|
184
|
+
expected_parity = (apndp + rnw + addr_2 + addr_3) % 2
|
|
185
|
+
if parity != expected_parity:
|
|
186
|
+
errors.append("Request parity error")
|
|
187
|
+
|
|
188
|
+
# Construct register address
|
|
189
|
+
register_addr = (addr_3 << 3) | (addr_2 << 2)
|
|
190
|
+
|
|
191
|
+
edge_idx += 8
|
|
192
|
+
|
|
193
|
+
# Turnaround period (1 clock, host releases SWDIO)
|
|
194
|
+
edge_idx += 1
|
|
195
|
+
|
|
196
|
+
# ACK response (3 bits from target)
|
|
197
|
+
if edge_idx + 3 > len(rising_edges):
|
|
198
|
+
break
|
|
199
|
+
|
|
200
|
+
ack_bits = []
|
|
201
|
+
for i in range(3):
|
|
202
|
+
bit_idx = rising_edges[edge_idx + i]
|
|
203
|
+
ack_bits.append(1 if swdio[bit_idx] else 0)
|
|
204
|
+
|
|
205
|
+
ack_value = (ack_bits[2] << 2) | (ack_bits[1] << 1) | ack_bits[0]
|
|
206
|
+
|
|
207
|
+
# Decode ACK
|
|
208
|
+
if ack_value == SWDResponse.OK.value:
|
|
209
|
+
ack_str = "OK"
|
|
210
|
+
elif ack_value == SWDResponse.WAIT.value:
|
|
211
|
+
ack_str = "WAIT"
|
|
212
|
+
errors.append("Target responded with WAIT")
|
|
213
|
+
elif ack_value == SWDResponse.FAULT.value:
|
|
214
|
+
ack_str = "FAULT"
|
|
215
|
+
errors.append("Target responded with FAULT")
|
|
216
|
+
else:
|
|
217
|
+
ack_str = "INVALID"
|
|
218
|
+
errors.append(f"Invalid ACK: 0b{ack_value:03b}")
|
|
219
|
+
|
|
220
|
+
edge_idx += 3
|
|
221
|
+
|
|
222
|
+
# If ACK is OK, there's a data phase
|
|
223
|
+
data_value = 0
|
|
224
|
+
if ack_value == SWDResponse.OK.value:
|
|
225
|
+
# Turnaround (1 clock)
|
|
226
|
+
edge_idx += 1
|
|
227
|
+
|
|
228
|
+
# Data phase (32 bits + 1 parity)
|
|
229
|
+
if edge_idx + 33 > len(rising_edges):
|
|
230
|
+
break
|
|
231
|
+
|
|
232
|
+
data_bits = []
|
|
233
|
+
for i in range(32):
|
|
234
|
+
bit_idx = rising_edges[edge_idx + i]
|
|
235
|
+
data_bits.append(1 if swdio[bit_idx] else 0)
|
|
236
|
+
|
|
237
|
+
# Convert to value (LSB first)
|
|
238
|
+
for i, bit in enumerate(data_bits):
|
|
239
|
+
data_value |= bit << i
|
|
240
|
+
|
|
241
|
+
# Parity bit
|
|
242
|
+
parity_idx = rising_edges[edge_idx + 32]
|
|
243
|
+
data_parity = 1 if swdio[parity_idx] else 0
|
|
244
|
+
|
|
245
|
+
# Check data parity (odd parity)
|
|
246
|
+
expected_data_parity = sum(data_bits) % 2
|
|
247
|
+
if data_parity != expected_data_parity:
|
|
248
|
+
errors.append("Data parity error")
|
|
249
|
+
|
|
250
|
+
edge_idx += 33
|
|
251
|
+
|
|
252
|
+
# Calculate timing
|
|
253
|
+
start_time = start_idx / sample_rate
|
|
254
|
+
end_time = rising_edges[min(edge_idx - 1, len(rising_edges) - 1)] / sample_rate
|
|
255
|
+
|
|
256
|
+
# Add annotations
|
|
257
|
+
port_type = "AP" if apndp else "DP"
|
|
258
|
+
access_type = "Read" if rnw else "Write"
|
|
259
|
+
|
|
260
|
+
self.put_annotation(
|
|
261
|
+
start_time,
|
|
262
|
+
end_time,
|
|
263
|
+
AnnotationLevel.PACKETS,
|
|
264
|
+
f"{port_type} {access_type} @ 0x{register_addr:02X}: {ack_str}",
|
|
265
|
+
)
|
|
266
|
+
|
|
267
|
+
# Create packet
|
|
268
|
+
annotations = {
|
|
269
|
+
"transaction_num": trans_num,
|
|
270
|
+
"apndp": "AP" if apndp else "DP",
|
|
271
|
+
"read": bool(rnw),
|
|
272
|
+
"register_addr": register_addr,
|
|
273
|
+
"ack": ack_str,
|
|
274
|
+
"ack_value": ack_value,
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
if ack_value == SWDResponse.OK.value:
|
|
278
|
+
annotations["data"] = data_value
|
|
279
|
+
|
|
280
|
+
# Encode data as bytes (little-endian)
|
|
281
|
+
data_bytes = (
|
|
282
|
+
data_value.to_bytes(4, "little") if ack_value == SWDResponse.OK.value else b""
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
packet = ProtocolPacket(
|
|
286
|
+
timestamp=start_time,
|
|
287
|
+
protocol="swd",
|
|
288
|
+
data=data_bytes,
|
|
289
|
+
annotations=annotations,
|
|
290
|
+
errors=errors,
|
|
291
|
+
)
|
|
292
|
+
|
|
293
|
+
yield packet
|
|
294
|
+
|
|
295
|
+
trans_num += 1
|
|
296
|
+
|
|
297
|
+
# Turnaround and idle
|
|
298
|
+
edge_idx += 1
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
def decode_swd(
|
|
302
|
+
swclk: NDArray[np.bool_],
|
|
303
|
+
swdio: NDArray[np.bool_],
|
|
304
|
+
sample_rate: float = 1.0,
|
|
305
|
+
) -> list[ProtocolPacket]:
|
|
306
|
+
"""Convenience function to decode SWD transactions.
|
|
307
|
+
|
|
308
|
+
Args:
|
|
309
|
+
swclk: Serial Wire Clock signal.
|
|
310
|
+
swdio: Serial Wire Data I/O signal.
|
|
311
|
+
sample_rate: Sample rate in Hz.
|
|
312
|
+
|
|
313
|
+
Returns:
|
|
314
|
+
List of decoded SWD transactions.
|
|
315
|
+
|
|
316
|
+
Example:
|
|
317
|
+
>>> packets = decode_swd(swclk, swdio, sample_rate=10e6)
|
|
318
|
+
>>> for pkt in packets:
|
|
319
|
+
... print(f"ACK: {pkt.annotations['ack']}")
|
|
320
|
+
"""
|
|
321
|
+
decoder = SWDDecoder()
|
|
322
|
+
return list(decoder.decode(swclk=swclk, swdio=swdio, sample_rate=sample_rate))
|
|
323
|
+
|
|
324
|
+
|
|
325
|
+
__all__ = ["SWDDecoder", "SWDResponse", "decode_swd"]
|