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.
Files changed (465) hide show
  1. oscura/__init__.py +813 -8
  2. oscura/__main__.py +392 -0
  3. oscura/analyzers/__init__.py +37 -0
  4. oscura/analyzers/digital/__init__.py +177 -0
  5. oscura/analyzers/digital/bus.py +691 -0
  6. oscura/analyzers/digital/clock.py +805 -0
  7. oscura/analyzers/digital/correlation.py +720 -0
  8. oscura/analyzers/digital/edges.py +632 -0
  9. oscura/analyzers/digital/extraction.py +413 -0
  10. oscura/analyzers/digital/quality.py +878 -0
  11. oscura/analyzers/digital/signal_quality.py +877 -0
  12. oscura/analyzers/digital/thresholds.py +708 -0
  13. oscura/analyzers/digital/timing.py +1104 -0
  14. oscura/analyzers/eye/__init__.py +46 -0
  15. oscura/analyzers/eye/diagram.py +434 -0
  16. oscura/analyzers/eye/metrics.py +555 -0
  17. oscura/analyzers/jitter/__init__.py +83 -0
  18. oscura/analyzers/jitter/ber.py +333 -0
  19. oscura/analyzers/jitter/decomposition.py +759 -0
  20. oscura/analyzers/jitter/measurements.py +413 -0
  21. oscura/analyzers/jitter/spectrum.py +220 -0
  22. oscura/analyzers/measurements.py +40 -0
  23. oscura/analyzers/packet/__init__.py +171 -0
  24. oscura/analyzers/packet/daq.py +1077 -0
  25. oscura/analyzers/packet/metrics.py +437 -0
  26. oscura/analyzers/packet/parser.py +327 -0
  27. oscura/analyzers/packet/payload.py +2156 -0
  28. oscura/analyzers/packet/payload_analysis.py +1312 -0
  29. oscura/analyzers/packet/payload_extraction.py +236 -0
  30. oscura/analyzers/packet/payload_patterns.py +670 -0
  31. oscura/analyzers/packet/stream.py +359 -0
  32. oscura/analyzers/patterns/__init__.py +266 -0
  33. oscura/analyzers/patterns/clustering.py +1036 -0
  34. oscura/analyzers/patterns/discovery.py +539 -0
  35. oscura/analyzers/patterns/learning.py +797 -0
  36. oscura/analyzers/patterns/matching.py +1091 -0
  37. oscura/analyzers/patterns/periodic.py +650 -0
  38. oscura/analyzers/patterns/sequences.py +767 -0
  39. oscura/analyzers/power/__init__.py +116 -0
  40. oscura/analyzers/power/ac_power.py +391 -0
  41. oscura/analyzers/power/basic.py +383 -0
  42. oscura/analyzers/power/conduction.py +314 -0
  43. oscura/analyzers/power/efficiency.py +297 -0
  44. oscura/analyzers/power/ripple.py +356 -0
  45. oscura/analyzers/power/soa.py +372 -0
  46. oscura/analyzers/power/switching.py +479 -0
  47. oscura/analyzers/protocol/__init__.py +150 -0
  48. oscura/analyzers/protocols/__init__.py +150 -0
  49. oscura/analyzers/protocols/base.py +500 -0
  50. oscura/analyzers/protocols/can.py +620 -0
  51. oscura/analyzers/protocols/can_fd.py +448 -0
  52. oscura/analyzers/protocols/flexray.py +405 -0
  53. oscura/analyzers/protocols/hdlc.py +399 -0
  54. oscura/analyzers/protocols/i2c.py +368 -0
  55. oscura/analyzers/protocols/i2s.py +296 -0
  56. oscura/analyzers/protocols/jtag.py +393 -0
  57. oscura/analyzers/protocols/lin.py +445 -0
  58. oscura/analyzers/protocols/manchester.py +333 -0
  59. oscura/analyzers/protocols/onewire.py +501 -0
  60. oscura/analyzers/protocols/spi.py +334 -0
  61. oscura/analyzers/protocols/swd.py +325 -0
  62. oscura/analyzers/protocols/uart.py +393 -0
  63. oscura/analyzers/protocols/usb.py +495 -0
  64. oscura/analyzers/signal_integrity/__init__.py +63 -0
  65. oscura/analyzers/signal_integrity/embedding.py +294 -0
  66. oscura/analyzers/signal_integrity/equalization.py +370 -0
  67. oscura/analyzers/signal_integrity/sparams.py +484 -0
  68. oscura/analyzers/spectral/__init__.py +53 -0
  69. oscura/analyzers/spectral/chunked.py +273 -0
  70. oscura/analyzers/spectral/chunked_fft.py +571 -0
  71. oscura/analyzers/spectral/chunked_wavelet.py +391 -0
  72. oscura/analyzers/spectral/fft.py +92 -0
  73. oscura/analyzers/statistical/__init__.py +250 -0
  74. oscura/analyzers/statistical/checksum.py +923 -0
  75. oscura/analyzers/statistical/chunked_corr.py +228 -0
  76. oscura/analyzers/statistical/classification.py +778 -0
  77. oscura/analyzers/statistical/entropy.py +1113 -0
  78. oscura/analyzers/statistical/ngrams.py +614 -0
  79. oscura/analyzers/statistics/__init__.py +119 -0
  80. oscura/analyzers/statistics/advanced.py +885 -0
  81. oscura/analyzers/statistics/basic.py +263 -0
  82. oscura/analyzers/statistics/correlation.py +630 -0
  83. oscura/analyzers/statistics/distribution.py +298 -0
  84. oscura/analyzers/statistics/outliers.py +463 -0
  85. oscura/analyzers/statistics/streaming.py +93 -0
  86. oscura/analyzers/statistics/trend.py +520 -0
  87. oscura/analyzers/validation.py +598 -0
  88. oscura/analyzers/waveform/__init__.py +36 -0
  89. oscura/analyzers/waveform/measurements.py +943 -0
  90. oscura/analyzers/waveform/measurements_with_uncertainty.py +371 -0
  91. oscura/analyzers/waveform/spectral.py +1689 -0
  92. oscura/analyzers/waveform/wavelets.py +298 -0
  93. oscura/api/__init__.py +62 -0
  94. oscura/api/dsl.py +538 -0
  95. oscura/api/fluent.py +571 -0
  96. oscura/api/operators.py +498 -0
  97. oscura/api/optimization.py +392 -0
  98. oscura/api/profiling.py +396 -0
  99. oscura/automotive/__init__.py +73 -0
  100. oscura/automotive/can/__init__.py +52 -0
  101. oscura/automotive/can/analysis.py +356 -0
  102. oscura/automotive/can/checksum.py +250 -0
  103. oscura/automotive/can/correlation.py +212 -0
  104. oscura/automotive/can/discovery.py +355 -0
  105. oscura/automotive/can/message_wrapper.py +375 -0
  106. oscura/automotive/can/models.py +385 -0
  107. oscura/automotive/can/patterns.py +381 -0
  108. oscura/automotive/can/session.py +452 -0
  109. oscura/automotive/can/state_machine.py +300 -0
  110. oscura/automotive/can/stimulus_response.py +461 -0
  111. oscura/automotive/dbc/__init__.py +15 -0
  112. oscura/automotive/dbc/generator.py +156 -0
  113. oscura/automotive/dbc/parser.py +146 -0
  114. oscura/automotive/dtc/__init__.py +30 -0
  115. oscura/automotive/dtc/database.py +3036 -0
  116. oscura/automotive/j1939/__init__.py +14 -0
  117. oscura/automotive/j1939/decoder.py +745 -0
  118. oscura/automotive/loaders/__init__.py +35 -0
  119. oscura/automotive/loaders/asc.py +98 -0
  120. oscura/automotive/loaders/blf.py +77 -0
  121. oscura/automotive/loaders/csv_can.py +136 -0
  122. oscura/automotive/loaders/dispatcher.py +136 -0
  123. oscura/automotive/loaders/mdf.py +331 -0
  124. oscura/automotive/loaders/pcap.py +132 -0
  125. oscura/automotive/obd/__init__.py +14 -0
  126. oscura/automotive/obd/decoder.py +707 -0
  127. oscura/automotive/uds/__init__.py +48 -0
  128. oscura/automotive/uds/decoder.py +265 -0
  129. oscura/automotive/uds/models.py +64 -0
  130. oscura/automotive/visualization.py +369 -0
  131. oscura/batch/__init__.py +55 -0
  132. oscura/batch/advanced.py +627 -0
  133. oscura/batch/aggregate.py +300 -0
  134. oscura/batch/analyze.py +139 -0
  135. oscura/batch/logging.py +487 -0
  136. oscura/batch/metrics.py +556 -0
  137. oscura/builders/__init__.py +41 -0
  138. oscura/builders/signal_builder.py +1131 -0
  139. oscura/cli/__init__.py +14 -0
  140. oscura/cli/batch.py +339 -0
  141. oscura/cli/characterize.py +273 -0
  142. oscura/cli/compare.py +775 -0
  143. oscura/cli/decode.py +551 -0
  144. oscura/cli/main.py +247 -0
  145. oscura/cli/shell.py +350 -0
  146. oscura/comparison/__init__.py +66 -0
  147. oscura/comparison/compare.py +397 -0
  148. oscura/comparison/golden.py +487 -0
  149. oscura/comparison/limits.py +391 -0
  150. oscura/comparison/mask.py +434 -0
  151. oscura/comparison/trace_diff.py +30 -0
  152. oscura/comparison/visualization.py +481 -0
  153. oscura/compliance/__init__.py +70 -0
  154. oscura/compliance/advanced.py +756 -0
  155. oscura/compliance/masks.py +363 -0
  156. oscura/compliance/reporting.py +483 -0
  157. oscura/compliance/testing.py +298 -0
  158. oscura/component/__init__.py +38 -0
  159. oscura/component/impedance.py +365 -0
  160. oscura/component/reactive.py +598 -0
  161. oscura/component/transmission_line.py +312 -0
  162. oscura/config/__init__.py +191 -0
  163. oscura/config/defaults.py +254 -0
  164. oscura/config/loader.py +348 -0
  165. oscura/config/memory.py +271 -0
  166. oscura/config/migration.py +458 -0
  167. oscura/config/pipeline.py +1077 -0
  168. oscura/config/preferences.py +530 -0
  169. oscura/config/protocol.py +875 -0
  170. oscura/config/schema.py +713 -0
  171. oscura/config/settings.py +420 -0
  172. oscura/config/thresholds.py +599 -0
  173. oscura/convenience.py +457 -0
  174. oscura/core/__init__.py +299 -0
  175. oscura/core/audit.py +457 -0
  176. oscura/core/backend_selector.py +405 -0
  177. oscura/core/cache.py +590 -0
  178. oscura/core/cancellation.py +439 -0
  179. oscura/core/confidence.py +225 -0
  180. oscura/core/config.py +506 -0
  181. oscura/core/correlation.py +216 -0
  182. oscura/core/cross_domain.py +422 -0
  183. oscura/core/debug.py +301 -0
  184. oscura/core/edge_cases.py +541 -0
  185. oscura/core/exceptions.py +535 -0
  186. oscura/core/gpu_backend.py +523 -0
  187. oscura/core/lazy.py +832 -0
  188. oscura/core/log_query.py +540 -0
  189. oscura/core/logging.py +931 -0
  190. oscura/core/logging_advanced.py +952 -0
  191. oscura/core/memoize.py +171 -0
  192. oscura/core/memory_check.py +274 -0
  193. oscura/core/memory_guard.py +290 -0
  194. oscura/core/memory_limits.py +336 -0
  195. oscura/core/memory_monitor.py +453 -0
  196. oscura/core/memory_progress.py +465 -0
  197. oscura/core/memory_warnings.py +315 -0
  198. oscura/core/numba_backend.py +362 -0
  199. oscura/core/performance.py +352 -0
  200. oscura/core/progress.py +524 -0
  201. oscura/core/provenance.py +358 -0
  202. oscura/core/results.py +331 -0
  203. oscura/core/types.py +504 -0
  204. oscura/core/uncertainty.py +383 -0
  205. oscura/discovery/__init__.py +52 -0
  206. oscura/discovery/anomaly_detector.py +672 -0
  207. oscura/discovery/auto_decoder.py +415 -0
  208. oscura/discovery/comparison.py +497 -0
  209. oscura/discovery/quality_validator.py +528 -0
  210. oscura/discovery/signal_detector.py +769 -0
  211. oscura/dsl/__init__.py +73 -0
  212. oscura/dsl/commands.py +246 -0
  213. oscura/dsl/interpreter.py +455 -0
  214. oscura/dsl/parser.py +689 -0
  215. oscura/dsl/repl.py +172 -0
  216. oscura/exceptions.py +59 -0
  217. oscura/exploratory/__init__.py +111 -0
  218. oscura/exploratory/error_recovery.py +642 -0
  219. oscura/exploratory/fuzzy.py +513 -0
  220. oscura/exploratory/fuzzy_advanced.py +786 -0
  221. oscura/exploratory/legacy.py +831 -0
  222. oscura/exploratory/parse.py +358 -0
  223. oscura/exploratory/recovery.py +275 -0
  224. oscura/exploratory/sync.py +382 -0
  225. oscura/exploratory/unknown.py +707 -0
  226. oscura/export/__init__.py +25 -0
  227. oscura/export/wireshark/README.md +265 -0
  228. oscura/export/wireshark/__init__.py +47 -0
  229. oscura/export/wireshark/generator.py +312 -0
  230. oscura/export/wireshark/lua_builder.py +159 -0
  231. oscura/export/wireshark/templates/dissector.lua.j2 +92 -0
  232. oscura/export/wireshark/type_mapping.py +165 -0
  233. oscura/export/wireshark/validator.py +105 -0
  234. oscura/exporters/__init__.py +94 -0
  235. oscura/exporters/csv.py +303 -0
  236. oscura/exporters/exporters.py +44 -0
  237. oscura/exporters/hdf5.py +219 -0
  238. oscura/exporters/html_export.py +701 -0
  239. oscura/exporters/json_export.py +291 -0
  240. oscura/exporters/markdown_export.py +367 -0
  241. oscura/exporters/matlab_export.py +354 -0
  242. oscura/exporters/npz_export.py +219 -0
  243. oscura/exporters/spice_export.py +210 -0
  244. oscura/extensibility/__init__.py +131 -0
  245. oscura/extensibility/docs.py +752 -0
  246. oscura/extensibility/extensions.py +1125 -0
  247. oscura/extensibility/logging.py +259 -0
  248. oscura/extensibility/measurements.py +485 -0
  249. oscura/extensibility/plugins.py +414 -0
  250. oscura/extensibility/registry.py +346 -0
  251. oscura/extensibility/templates.py +913 -0
  252. oscura/extensibility/validation.py +651 -0
  253. oscura/filtering/__init__.py +89 -0
  254. oscura/filtering/base.py +563 -0
  255. oscura/filtering/convenience.py +564 -0
  256. oscura/filtering/design.py +725 -0
  257. oscura/filtering/filters.py +32 -0
  258. oscura/filtering/introspection.py +605 -0
  259. oscura/guidance/__init__.py +24 -0
  260. oscura/guidance/recommender.py +429 -0
  261. oscura/guidance/wizard.py +518 -0
  262. oscura/inference/__init__.py +251 -0
  263. oscura/inference/active_learning/README.md +153 -0
  264. oscura/inference/active_learning/__init__.py +38 -0
  265. oscura/inference/active_learning/lstar.py +257 -0
  266. oscura/inference/active_learning/observation_table.py +230 -0
  267. oscura/inference/active_learning/oracle.py +78 -0
  268. oscura/inference/active_learning/teachers/__init__.py +15 -0
  269. oscura/inference/active_learning/teachers/simulator.py +192 -0
  270. oscura/inference/adaptive_tuning.py +453 -0
  271. oscura/inference/alignment.py +653 -0
  272. oscura/inference/bayesian.py +943 -0
  273. oscura/inference/binary.py +1016 -0
  274. oscura/inference/crc_reverse.py +711 -0
  275. oscura/inference/logic.py +288 -0
  276. oscura/inference/message_format.py +1305 -0
  277. oscura/inference/protocol.py +417 -0
  278. oscura/inference/protocol_dsl.py +1084 -0
  279. oscura/inference/protocol_library.py +1230 -0
  280. oscura/inference/sequences.py +809 -0
  281. oscura/inference/signal_intelligence.py +1509 -0
  282. oscura/inference/spectral.py +215 -0
  283. oscura/inference/state_machine.py +634 -0
  284. oscura/inference/stream.py +918 -0
  285. oscura/integrations/__init__.py +59 -0
  286. oscura/integrations/llm.py +1827 -0
  287. oscura/jupyter/__init__.py +32 -0
  288. oscura/jupyter/display.py +268 -0
  289. oscura/jupyter/magic.py +334 -0
  290. oscura/loaders/__init__.py +526 -0
  291. oscura/loaders/binary.py +69 -0
  292. oscura/loaders/configurable.py +1255 -0
  293. oscura/loaders/csv.py +26 -0
  294. oscura/loaders/csv_loader.py +473 -0
  295. oscura/loaders/hdf5.py +9 -0
  296. oscura/loaders/hdf5_loader.py +510 -0
  297. oscura/loaders/lazy.py +370 -0
  298. oscura/loaders/mmap_loader.py +583 -0
  299. oscura/loaders/numpy_loader.py +436 -0
  300. oscura/loaders/pcap.py +432 -0
  301. oscura/loaders/preprocessing.py +368 -0
  302. oscura/loaders/rigol.py +287 -0
  303. oscura/loaders/sigrok.py +321 -0
  304. oscura/loaders/tdms.py +367 -0
  305. oscura/loaders/tektronix.py +711 -0
  306. oscura/loaders/validation.py +584 -0
  307. oscura/loaders/vcd.py +464 -0
  308. oscura/loaders/wav.py +233 -0
  309. oscura/math/__init__.py +45 -0
  310. oscura/math/arithmetic.py +824 -0
  311. oscura/math/interpolation.py +413 -0
  312. oscura/onboarding/__init__.py +39 -0
  313. oscura/onboarding/help.py +498 -0
  314. oscura/onboarding/tutorials.py +405 -0
  315. oscura/onboarding/wizard.py +466 -0
  316. oscura/optimization/__init__.py +19 -0
  317. oscura/optimization/parallel.py +440 -0
  318. oscura/optimization/search.py +532 -0
  319. oscura/pipeline/__init__.py +43 -0
  320. oscura/pipeline/base.py +338 -0
  321. oscura/pipeline/composition.py +242 -0
  322. oscura/pipeline/parallel.py +448 -0
  323. oscura/pipeline/pipeline.py +375 -0
  324. oscura/pipeline/reverse_engineering.py +1119 -0
  325. oscura/plugins/__init__.py +122 -0
  326. oscura/plugins/base.py +272 -0
  327. oscura/plugins/cli.py +497 -0
  328. oscura/plugins/discovery.py +411 -0
  329. oscura/plugins/isolation.py +418 -0
  330. oscura/plugins/lifecycle.py +959 -0
  331. oscura/plugins/manager.py +493 -0
  332. oscura/plugins/registry.py +421 -0
  333. oscura/plugins/versioning.py +372 -0
  334. oscura/py.typed +0 -0
  335. oscura/quality/__init__.py +65 -0
  336. oscura/quality/ensemble.py +740 -0
  337. oscura/quality/explainer.py +338 -0
  338. oscura/quality/scoring.py +616 -0
  339. oscura/quality/warnings.py +456 -0
  340. oscura/reporting/__init__.py +248 -0
  341. oscura/reporting/advanced.py +1234 -0
  342. oscura/reporting/analyze.py +448 -0
  343. oscura/reporting/argument_preparer.py +596 -0
  344. oscura/reporting/auto_report.py +507 -0
  345. oscura/reporting/batch.py +615 -0
  346. oscura/reporting/chart_selection.py +223 -0
  347. oscura/reporting/comparison.py +330 -0
  348. oscura/reporting/config.py +615 -0
  349. oscura/reporting/content/__init__.py +39 -0
  350. oscura/reporting/content/executive.py +127 -0
  351. oscura/reporting/content/filtering.py +191 -0
  352. oscura/reporting/content/minimal.py +257 -0
  353. oscura/reporting/content/verbosity.py +162 -0
  354. oscura/reporting/core.py +508 -0
  355. oscura/reporting/core_formats/__init__.py +17 -0
  356. oscura/reporting/core_formats/multi_format.py +210 -0
  357. oscura/reporting/engine.py +836 -0
  358. oscura/reporting/export.py +366 -0
  359. oscura/reporting/formatting/__init__.py +129 -0
  360. oscura/reporting/formatting/emphasis.py +81 -0
  361. oscura/reporting/formatting/numbers.py +403 -0
  362. oscura/reporting/formatting/standards.py +55 -0
  363. oscura/reporting/formatting.py +466 -0
  364. oscura/reporting/html.py +578 -0
  365. oscura/reporting/index.py +590 -0
  366. oscura/reporting/multichannel.py +296 -0
  367. oscura/reporting/output.py +379 -0
  368. oscura/reporting/pdf.py +373 -0
  369. oscura/reporting/plots.py +731 -0
  370. oscura/reporting/pptx_export.py +360 -0
  371. oscura/reporting/renderers/__init__.py +11 -0
  372. oscura/reporting/renderers/pdf.py +94 -0
  373. oscura/reporting/sections.py +471 -0
  374. oscura/reporting/standards.py +680 -0
  375. oscura/reporting/summary_generator.py +368 -0
  376. oscura/reporting/tables.py +397 -0
  377. oscura/reporting/template_system.py +724 -0
  378. oscura/reporting/templates/__init__.py +15 -0
  379. oscura/reporting/templates/definition.py +205 -0
  380. oscura/reporting/templates/index.html +649 -0
  381. oscura/reporting/templates/index.md +173 -0
  382. oscura/schemas/__init__.py +158 -0
  383. oscura/schemas/bus_configuration.json +322 -0
  384. oscura/schemas/device_mapping.json +182 -0
  385. oscura/schemas/packet_format.json +418 -0
  386. oscura/schemas/protocol_definition.json +363 -0
  387. oscura/search/__init__.py +16 -0
  388. oscura/search/anomaly.py +292 -0
  389. oscura/search/context.py +149 -0
  390. oscura/search/pattern.py +160 -0
  391. oscura/session/__init__.py +34 -0
  392. oscura/session/annotations.py +289 -0
  393. oscura/session/history.py +313 -0
  394. oscura/session/session.py +445 -0
  395. oscura/streaming/__init__.py +43 -0
  396. oscura/streaming/chunked.py +611 -0
  397. oscura/streaming/progressive.py +393 -0
  398. oscura/streaming/realtime.py +622 -0
  399. oscura/testing/__init__.py +54 -0
  400. oscura/testing/synthetic.py +808 -0
  401. oscura/triggering/__init__.py +68 -0
  402. oscura/triggering/base.py +229 -0
  403. oscura/triggering/edge.py +353 -0
  404. oscura/triggering/pattern.py +344 -0
  405. oscura/triggering/pulse.py +581 -0
  406. oscura/triggering/window.py +453 -0
  407. oscura/ui/__init__.py +48 -0
  408. oscura/ui/formatters.py +526 -0
  409. oscura/ui/progressive_display.py +340 -0
  410. oscura/utils/__init__.py +99 -0
  411. oscura/utils/autodetect.py +338 -0
  412. oscura/utils/buffer.py +389 -0
  413. oscura/utils/lazy.py +407 -0
  414. oscura/utils/lazy_imports.py +147 -0
  415. oscura/utils/memory.py +836 -0
  416. oscura/utils/memory_advanced.py +1326 -0
  417. oscura/utils/memory_extensions.py +465 -0
  418. oscura/utils/progressive.py +352 -0
  419. oscura/utils/windowing.py +362 -0
  420. oscura/visualization/__init__.py +321 -0
  421. oscura/visualization/accessibility.py +526 -0
  422. oscura/visualization/annotations.py +374 -0
  423. oscura/visualization/axis_scaling.py +305 -0
  424. oscura/visualization/colors.py +453 -0
  425. oscura/visualization/digital.py +337 -0
  426. oscura/visualization/eye.py +420 -0
  427. oscura/visualization/histogram.py +281 -0
  428. oscura/visualization/interactive.py +858 -0
  429. oscura/visualization/jitter.py +702 -0
  430. oscura/visualization/keyboard.py +394 -0
  431. oscura/visualization/layout.py +365 -0
  432. oscura/visualization/optimization.py +1028 -0
  433. oscura/visualization/palettes.py +446 -0
  434. oscura/visualization/plot.py +92 -0
  435. oscura/visualization/power.py +290 -0
  436. oscura/visualization/power_extended.py +626 -0
  437. oscura/visualization/presets.py +467 -0
  438. oscura/visualization/protocols.py +932 -0
  439. oscura/visualization/render.py +207 -0
  440. oscura/visualization/rendering.py +444 -0
  441. oscura/visualization/reverse_engineering.py +791 -0
  442. oscura/visualization/signal_integrity.py +808 -0
  443. oscura/visualization/specialized.py +553 -0
  444. oscura/visualization/spectral.py +811 -0
  445. oscura/visualization/styles.py +381 -0
  446. oscura/visualization/thumbnails.py +311 -0
  447. oscura/visualization/time_axis.py +351 -0
  448. oscura/visualization/waveform.py +367 -0
  449. oscura/workflow/__init__.py +13 -0
  450. oscura/workflow/dag.py +377 -0
  451. oscura/workflows/__init__.py +58 -0
  452. oscura/workflows/compliance.py +280 -0
  453. oscura/workflows/digital.py +272 -0
  454. oscura/workflows/multi_trace.py +502 -0
  455. oscura/workflows/power.py +178 -0
  456. oscura/workflows/protocol.py +492 -0
  457. oscura/workflows/reverse_engineering.py +639 -0
  458. oscura/workflows/signal_integrity.py +227 -0
  459. oscura-0.1.1.dist-info/METADATA +300 -0
  460. oscura-0.1.1.dist-info/RECORD +463 -0
  461. oscura-0.1.1.dist-info/entry_points.txt +2 -0
  462. {oscura-0.0.1.dist-info → oscura-0.1.1.dist-info}/licenses/LICENSE +1 -1
  463. oscura-0.0.1.dist-info/METADATA +0 -63
  464. oscura-0.0.1.dist-info/RECORD +0 -5
  465. {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"]